Merge branch 'release/v3.5.0'

This commit is contained in:
Ivan Kravets
2017-12-28 17:26:12 +02:00
49 changed files with 1519 additions and 813 deletions

View File

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

View File

@ -4,13 +4,82 @@ Release Notes
PlatformIO 3.0 PlatformIO 3.0
-------------- --------------
3.5.0 (2017-12-28)
~~~~~~~~~~~~~~~~~~
* `PlatformIO Home <http://docs.platformio.org/page/home/index.html>`__ -
interact with PlatformIO ecosystem using modern and cross-platform GUI:
- Library Manager:
* Search for new libraries in PlatformIO Registry
* "1-click" library installation, per-project libraries, extra storages
* List installed libraries in multiple storages
* List built-in libraries (by frameworks)
* Updates for installed libraries
* Multiple examples, trending libraries, and more.
- PlatformIO Projects
- PIO Account
- Development platforms, frameworks and board explorer
- Device Manager: serial, logical, and multicast DNS services
* Integration with `Jenkins CI <http://docs.platformio.org/page/ci/jenkins.html>`_
* New `include <http://docs.platformio.org/page/projectconf/section_platformio.html#include-dir>`__
folder for project's header files
(`issue #1107 <https://github.com/platformio/platformio-core/issues/1107>`_)
* Depend on development platform using VSC URL (Git, Mercurial and Subversion)
instead of a name in `Project Configuration File "platformio.ini" <http://docs.platformio.org/page/projectconf/section_env_general.html#platform>`__.
Drop support for ``*_stage`` dev/platform names (use VCS URL instead).
* Reinstall/redownload package with a new ``-f, --force`` option for
`platformio lib install <http://docs.platformio.org/page/userguide/lib/cmd_install.html>`__
and `platformio platform install <http://docs.platformio.org/page/userguide/platforms/cmd_install.html>`__
commands
(`issue #778 <https://github.com/platformio/platformio-core/issues/778>`_)
* Handle missed dependencies and provide a solution based on PlatformIO Library
Registry
(`issue #781 <https://github.com/platformio/platformio-core/issues/781>`_)
* New setting `projects_dir <http://docs.platformio.org/page/userguide/cmd_settings.html#projects-dir>`__
that allows to override a default PIO Home Projects location
(`issue #1161 <https://github.com/platformio/platformio-core/issues/1161>`_)
* `Library Dependency Finder (LDF) <http://docs.platformio.org/page/librarymanager/ldf.html>`__:
- Search for dependencies used in `PIO Unit Testing <http://docs.platformio.org/page/plus/unit-testing.html>`__
(`issue #953 <https://github.com/platformio/platformio-core/issues/953>`_)
- Parse library source file in pair with a header when they have the same name
(`issue #1175 <https://github.com/platformio/platformio-core/issues/1175>`_)
- Handle library dependencies defined as VCS or SemVer in
`Project Configuration File "platformio.ini" <http://docs.platformio.org/page/projectconf/section_env_general.html#platform>`__
(`issue #1155 <https://github.com/platformio/platformio-core/issues/1155>`_)
- Added option to configure library `Compatible Mode <http://docs.platformio.org/page/librarymanager/ldf.html#compatibility-mode>`__
using `library.json <http://docs.platformio.org/page/librarymanager/config.html>`__
* New options for `platformio device list <http://docs.platformio.org/page/userguide/cmd_device.html#platformio-device-list>`__
command:
- ``--serial`` list available serial ports (default)
- ``--logical`` list logical devices
- ``--mdns`` discover multicast DNS services
(`issue #463 <https://github.com/platformio/platformio-core/issues/463>`_)
* Fixed platforms, packages, and libraries updating behind proxy
(`issue #1061 <https://github.com/platformio/platformio-core/issues/1061>`_)
* Fixed missing toolchain include paths for project generator
(`issue #1154 <https://github.com/platformio/platformio-core/issues/1154>`_)
* Fixed "Super-Quick (Mac / Linux)" installation in "get-platformio.py" script
(`issue #1017 <https://github.com/platformio/platformio-core/issues/1017>`_)
* Fixed "get-platformio.py" script which hangs on Windows 10
(`issue #1118 <https://github.com/platformio/platformio-core/issues/1118>`_)
* Other bug fixes and performance improvements
3.4.1 (2017-08-02) 3.4.1 (2017-08-02)
~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~
* Pre/Post extra scripting for advanced control of PIO Build System * Pre/Post extra scripting for advanced control of PIO Build System
(`issue #891 <https://github.com/platformio/platformio-core/issues/891>`_) (`issue #891 <https://github.com/platformio/platformio-core/issues/891>`_)
* New `lib_archive <http://docs.platformio.org/page/projectconf/section_env_library.html#lib-archive>`_ * New `lib_archive <http://docs.platformio.org/page/projectconf/section_env_library.html#lib-archive>`_
option to control library archiving and linking behaviour option to control library archiving and linking behavior
(`issue #993 <https://github.com/platformio/platformio-core/issues/993>`_) (`issue #993 <https://github.com/platformio/platformio-core/issues/993>`_)
* Add "inc" folder automatically to CPPPATH when "src" is available (works for project and library) * Add "inc" folder automatically to CPPPATH when "src" is available (works for project and library)
(`issue #1003 <https://github.com/platformio/platformio-core/issues/1003>`_) (`issue #1003 <https://github.com/platformio/platformio-core/issues/1003>`_)
@ -104,7 +173,7 @@ PlatformIO 3.0
command command
(`issue #430 <https://github.com/platformio/platformio-core/issues/430>`_) (`issue #430 <https://github.com/platformio/platformio-core/issues/430>`_)
* List supported frameworks, SDKs with a new * List supported frameworks, SDKs with a new
`pio platform frameworks <http://docs.platformio.org/page/userguide/platforms/cmd_frameworks.htmll>`__ command `pio platform frameworks <http://docs.platformio.org/page/userguide/platforms/cmd_frameworks.html>`__ command
* Visual Studio Code extension for PlatformIO * Visual Studio Code extension for PlatformIO
(`issue #619 <https://github.com/platformio/platformio-core/issues/619>`_) (`issue #619 <https://github.com/platformio/platformio-core/issues/619>`_)
* Added new options ``--no-reset``, ``--monitor-rts`` and ``--monitor-dtr`` * Added new options ``--no-reset``, ``--monitor-rts`` and ``--monitor-dtr``
@ -222,7 +291,7 @@ PlatformIO 3.0
* `PlatformIO Plus <https://pioplus.com>`__ * `PlatformIO Plus <https://pioplus.com>`__
+ Local and Embedded `Unit Testing <http://docs.platformio.org/page/unit_testing.html>`__ + Local and Embedded `Unit Testing <http://docs.platformio.org/page/plus/unit-testing.html>`__
(`issue #408 <https://github.com/platformio/platformio-core/issues/408>`_, (`issue #408 <https://github.com/platformio/platformio-core/issues/408>`_,
`issue #519 <https://github.com/platformio/platformio-core/issues/519>`_) `issue #519 <https://github.com/platformio/platformio-core/issues/519>`_)
@ -893,7 +962,7 @@ PlatformIO 2.0
`windows_x86 <http://docs.platformio.org/page/platforms/windows_x86.html>`__ `windows_x86 <http://docs.platformio.org/page/platforms/windows_x86.html>`__
development platforms development platforms
(`issue #263 <https://github.com/platformio/platformio-core/issues/263>`_) (`issue #263 <https://github.com/platformio/platformio-core/issues/263>`_)
* Added `PlatformIO Demo <http://docs.platformio.org/page/demo.html>`_ * Added `PlatformIO Demo <http://docs.platformio.org/page/userguide/demo.html>`_
page to documentation page to documentation
* Simplified `installation <http://docs.platformio.org/page/installation.html>`__ * Simplified `installation <http://docs.platformio.org/page/installation.html>`__
process of PlatformIO process of PlatformIO
@ -1252,7 +1321,7 @@ PlatformIO 1.0
(`issue #83 <https://github.com/platformio/platformio-core/issues/83>`_) (`issue #83 <https://github.com/platformio/platformio-core/issues/83>`_)
* Added ``--json-output`` option to * Added ``--json-output`` option to
`platformio boards <http://docs.platformio.org/page/userguide/cmd_boards.html>`__ `platformio boards <http://docs.platformio.org/page/userguide/cmd_boards.html>`__
and `platformio search <http://docs.platformio.org/page/userguide/cmd_search.html>`__ and `platformio search <http://docs.platformio.org/page/userguide/platforms/cmd_search.html>`__
commands which allows to return the output in `JSON <http://en.wikipedia.org/wiki/JSON>`_ format commands which allows to return the output in `JSON <http://en.wikipedia.org/wiki/JSON>`_ format
(`issue #42 <https://github.com/platformio/platformio-core/issues/42>`_) (`issue #42 <https://github.com/platformio/platformio-core/issues/42>`_)
* Allowed to ignore some libs from *Library Dependency Finder* via * Allowed to ignore some libs from *Library Dependency Finder* via
@ -1293,7 +1362,7 @@ PlatformIO 0.0
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
* Added ``--json-output`` option to * Added ``--json-output`` option to
`platformio list <http://docs.platformio.org/page/userguide/cmd_list.html>`__, `platformio list <http://docs.platformio.org/page/userguide/platforms/cmd_list.htmll>`__,
`platformio serialports list <http://docs.platformio.org/page/userguide/cmd_serialports.html>`__ and `platformio serialports list <http://docs.platformio.org/page/userguide/cmd_serialports.html>`__ and
`platformio lib list <http://docs.platformio.org/page/userguide/lib/cmd_list.html>`__ `platformio lib list <http://docs.platformio.org/page/userguide/lib/cmd_list.html>`__
commands which allows to return the output in `JSON <http://en.wikipedia.org/wiki/JSON>`_ format commands which allows to return the output in `JSON <http://en.wikipedia.org/wiki/JSON>`_ format
@ -1337,14 +1406,14 @@ PlatformIO 0.0
* Ask user to install platform (when it hasn't been installed yet) within * Ask user to install platform (when it hasn't been installed yet) within
`platformio run <http://docs.platformio.org/page/userguide/cmd_run.html>`__ `platformio run <http://docs.platformio.org/page/userguide/cmd_run.html>`__
and `platformio show <http://docs.platformio.org/page/userguide/cmd_show.html>`_ commands and `platformio show <http://docs.platformio.org/page/userguide/platforms/cmd_show.html>`_ commands
* Improved main `documentation <http://docs.platformio.org>`_ * Improved main `documentation <http://docs.platformio.org>`_
* Fixed "*OSError: [Errno 2] No such file or directory*" within * Fixed "*OSError: [Errno 2] No such file or directory*" within
`platformio run <http://docs.platformio.org/page/userguide/cmd_run.html>`__ `platformio run <http://docs.platformio.org/page/userguide/cmd_run.html>`__
command when PlatformIO isn't installed properly command when PlatformIO isn't installed properly
* Fixed example for `Eclipse IDE with Tiva board <https://github.com/platformio/platformio-examples/tree/develop/ide/eclipse>`_ * Fixed example for Eclipse IDE with Tiva board
(`issue #32 <https://github.com/platformio/platformio-core/pull/32>`_) (`issue #32 <https://github.com/platformio/platformio-core/pull/32>`_)
* Upgraded `Eclipse Project Examples <https://github.com/platformio/platformio-examples/tree/develop/ide/eclipse>`_ * Upgraded Eclipse Project Examples
to latest *Luna* and *PlatformIO* releases to latest *Luna* and *PlatformIO* releases
0.9.0 (2014-12-01) 0.9.0 (2014-12-01)
@ -1433,7 +1502,7 @@ PlatformIO 0.0
* Implemented (especially for `SmartAnthill <http://docs.smartanthill.ikravets.com/>`_) * Implemented (especially for `SmartAnthill <http://docs.smartanthill.ikravets.com/>`_)
`platformio run -t uploadlazy <http://docs.platformio.org/page/userguide/cmd_run.html>`_ `platformio run -t uploadlazy <http://docs.platformio.org/page/userguide/cmd_run.html>`_
target (no dependencies to framework libs, ELF and etc.) target (no dependencies to framework libs, ELF and etc.)
* Allowed to skip default packages via `platformio install --skip-default-package <http://docs.platformio.org/page/userguide/cmd_install.html#cmdoption--skip-default>`_ * Allowed to skip default packages via `platformio install --skip-default-package <http://docs.platformio.org/page/userguide/platforms/cmd_install.html#cmdoption-platformio-platform-install-skip-default>`_
option option
* Added tools for *Raspberry Pi* platform * Added tools for *Raspberry Pi* platform
* Added support for *Microduino* and *Raspduino* boards in * Added support for *Microduino* and *Raspduino* boards in
@ -1442,7 +1511,7 @@ PlatformIO 0.0
0.3.1 (2014-06-21) 0.3.1 (2014-06-21)
~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~
* Fixed auto-installer for Windows OS (bug with %PATH% customisations) * Fixed auto-installer for Windows OS (bug with %PATH% custom installation)
0.3.0 (2014-06-21) 0.3.0 (2014-06-21)

View File

@ -9,7 +9,10 @@ isort:
yapf: yapf:
yapf --recursive --in-place platformio/ yapf --recursive --in-place platformio/
before-commit: isort yapf lint test:
py.test -v -s tests --ignore tests/test_examples.py --ignore tests/test_pkgmanifest.py
before-commit: isort yapf lint test
clean-docs: clean-docs:
rm -rf docs/_build rm -rf docs/_build

2
docs

Submodule docs updated: ebd68b4bac...c76ccaf337

View File

@ -14,7 +14,7 @@
import sys import sys
VERSION = (3, 4, 1) VERSION = (3, 5, 0)
__version__ = ".".join([str(s) for s in VERSION]) __version__ = ".".join([str(s) for s in VERSION])
__title__ = "platformio" __title__ = "platformio"

View File

@ -80,29 +80,45 @@ def process_result(ctx, result, force, caller): # pylint: disable=W0613
maintenance.on_platformio_end(ctx, result) maintenance.on_platformio_end(ctx, result)
def main(): def configure():
try: if "cygwin" in system().lower():
if "cygwin" in system().lower(): raise exception.CygwinEnvDetected()
raise exception.CygwinEnvDetected()
# https://urllib3.readthedocs.org # https://urllib3.readthedocs.org
# /en/latest/security.html#insecureplatformwarning # /en/latest/security.html#insecureplatformwarning
try:
import urllib3
urllib3.disable_warnings()
except (AttributeError, ImportError):
pass
# handle PLATFORMIO_FORCE_COLOR
if str(os.getenv("PLATFORMIO_FORCE_COLOR", "")).lower() == "true":
try: try:
import urllib3 # pylint: disable=protected-access
urllib3.disable_warnings() click._compat.isatty = lambda stream: True
except (AttributeError, ImportError): except: # pylint: disable=bare-except
pass pass
# handle PLATFORMIO_FORCE_COLOR # Handle IOError issue with VSCode's Terminal (Windows)
if str(os.getenv("PLATFORMIO_FORCE_COLOR", "")).lower() == "true": click_echo_origin = [click.echo, click.secho]
try:
# pylint: disable=protected-access
click._compat.isatty = lambda stream: True
except: # pylint: disable=bare-except
pass
def _safe_echo(origin, *args, **kwargs):
try:
click_echo_origin[origin](*args, **kwargs)
except IOError:
(sys.stderr.write if kwargs.get("err") else
sys.stdout.write)("%s\n" % (args[0] if args else ""))
click.echo = lambda *args, **kwargs: _safe_echo(0, *args, **kwargs)
click.secho = lambda *args, **kwargs: _safe_echo(1, *args, **kwargs)
def main():
try:
configure()
cli(None, None, None) cli(None, None, None)
except Exception as e: # pylint: disable=W0703 except Exception as e: # pylint: disable=broad-except
if not isinstance(e, exception.ReturnErrorCode): if not isinstance(e, exception.ReturnErrorCode):
maintenance.on_platformio_exception(e) maintenance.on_platformio_exception(e)
error_str = "Error: " error_str = "Error: "

View File

@ -18,16 +18,33 @@ import os
import uuid import uuid
from copy import deepcopy from copy import deepcopy
from os import environ, getenv, listdir, remove from os import environ, getenv, listdir, remove
from os.path import dirname, getmtime, isdir, isfile, join from os.path import abspath, dirname, expanduser, getmtime, isdir, isfile, join
from time import time from time import time
import requests import requests
from lockfile import LockFailed, LockFile from lockfile import LockFailed, LockFile
from platformio import __version__, exception, util from platformio import __version__, exception, util
from platformio.exception import InvalidSettingName, InvalidSettingValue
def projects_dir_validate(projects_dir):
assert isdir(projects_dir)
return abspath(projects_dir)
DEFAULT_SETTINGS = { DEFAULT_SETTINGS = {
"auto_update_libraries": {
"description": "Automatically update libraries (Yes/No)",
"value": False
},
"auto_update_platforms": {
"description": "Automatically update platforms (Yes/No)",
"value": False
},
"check_libraries_interval": {
"description": "Check for the library updates interval (days)",
"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
@ -36,37 +53,30 @@ DEFAULT_SETTINGS = {
"description": "Check for the platform updates interval (days)", "description": "Check for the platform updates interval (days)",
"value": 7 "value": 7
}, },
"check_libraries_interval": { "enable_cache": {
"description": "Check for the library updates interval (days)", "description": "Enable caching for API requests and Library Manager",
"value": 7 "value": True
},
"auto_update_platforms": {
"description": "Automatically update platforms (Yes/No)",
"value": False
},
"auto_update_libraries": {
"description": "Automatically update libraries (Yes/No)",
"value": False
},
"force_verbose": {
"description": "Force verbose output when processing environments",
"value": False
}, },
"enable_ssl": { "enable_ssl": {
"description": "Enable SSL for PlatformIO Services", "description": "Enable SSL for PlatformIO Services",
"value": False "value": False
}, },
"enable_cache": {
"description": "Enable caching for API requests and Library Manager",
"value": True
},
"enable_telemetry": { "enable_telemetry": {
"description": "description":
("Telemetry service <http://docs.platformio.org/page/" ("Telemetry service <http://docs.platformio.org/page/"
"userguide/cmd_settings.html?#enable-telemetry> (Yes/No)"), "userguide/cmd_settings.html?#enable-telemetry> (Yes/No)"),
"value": "value":
True True
} },
"force_verbose": {
"description": "Force verbose output when processing environments",
"value": False
},
"projects_dir": {
"description": "Default location for PlatformIO projects (PIO Home)",
"value": join(expanduser("~"), "Documents", "PlatformIO", "Projects"),
"validator": projects_dir_validate
},
} }
SESSION_VARS = {"command_ctx": None, "force_option": False, "caller_id": None} SESSION_VARS = {"command_ctx": None, "force_option": False, "caller_id": None}
@ -95,11 +105,14 @@ class State(object):
def __exit__(self, type_, value, traceback): def __exit__(self, type_, value, traceback):
if self._prev_state != self._state: if self._prev_state != self._state:
with open(self.path, "w") as fp: try:
if "dev" in __version__: with open(self.path, "w") as fp:
json.dump(self._state, fp, indent=4) if "dev" in __version__:
else: json.dump(self._state, fp, indent=4)
json.dump(self._state, fp) else:
json.dump(self._state, fp)
except IOError:
raise exception.HomeDirPermissionsError(util.get_home_dir())
self._unlock_state_file() self._unlock_state_file()
def _lock_state_file(self): def _lock_state_file(self):
@ -114,13 +127,7 @@ class State(object):
try: try:
self._lockfile.acquire() self._lockfile.acquire()
except LockFailed: except LockFailed:
raise exception.PlatformioException( raise exception.HomeDirPermissionsError(dirname(self.path))
"The directory `{0}` or its parent directory is not owned by "
"the current user and PlatformIO can not store configuration "
"data. \nPlease check the permissions and owner of that "
"directory. Otherwise, please remove manually `{0}` "
"directory and PlatformIO will create new from the current "
"user.".format(dirname(self.path)))
def _unlock_state_file(self): def _unlock_state_file(self):
if self._lockfile: if self._lockfile:
@ -134,16 +141,10 @@ class ContentCache(object):
self._db_path = None self._db_path = None
self._lockfile = None self._lockfile = None
if not get_setting("enable_cache"):
return
self.cache_dir = cache_dir or join(util.get_home_dir(), ".cache") self.cache_dir = cache_dir or join(util.get_home_dir(), ".cache")
self._db_path = join(self.cache_dir, "db.data") self._db_path = join(self.cache_dir, "db.data")
def __enter__(self): def __enter__(self):
if not self._db_path or not isfile(self._db_path):
return self
self.delete() self.delete()
return self return self
@ -155,6 +156,7 @@ class ContentCache(object):
os.makedirs(self.cache_dir) os.makedirs(self.cache_dir)
self._lockfile = LockFile(self.cache_dir) self._lockfile = LockFile(self.cache_dir)
if self._lockfile.is_locked() and \ if self._lockfile.is_locked() and \
isfile(self._lockfile.lock_file) and \
(time() - getmtime(self._lockfile.lock_file)) > 10: (time() - getmtime(self._lockfile.lock_file)) > 10:
self._lockfile.break_lock() self._lockfile.break_lock()
@ -192,11 +194,13 @@ class ContentCache(object):
return data return data
def set(self, key, data, valid): def set(self, key, data, valid):
if not get_setting("enable_cache"):
return False
cache_path = self.get_cache_path(key) cache_path = self.get_cache_path(key)
if isfile(cache_path): if isfile(cache_path):
self.delete(key) self.delete(key)
if not data: if not data:
return return False
if not isdir(self.cache_dir): if not isdir(self.cache_dir):
os.makedirs(self.cache_dir) os.makedirs(self.cache_dir)
tdmap = {"s": 1, "m": 60, "h": 3600, "d": 86400} tdmap = {"s": 1, "m": 60, "h": 3600, "d": 86400}
@ -220,6 +224,8 @@ class ContentCache(object):
def delete(self, keys=None): def delete(self, keys=None):
""" Keys=None, delete expired items """ """ Keys=None, delete expired items """
if not isfile(self._db_path):
return None
if not keys: if not keys:
keys = [] keys = []
if not isinstance(keys, list): if not isinstance(keys, list):
@ -266,19 +272,19 @@ def clean_cache():
def sanitize_setting(name, value): def sanitize_setting(name, value):
if name not in DEFAULT_SETTINGS: if name not in DEFAULT_SETTINGS:
raise InvalidSettingName(name) raise exception.InvalidSettingName(name)
defdata = DEFAULT_SETTINGS[name] defdata = DEFAULT_SETTINGS[name]
try: try:
if "validator" in defdata: if "validator" in defdata:
value = defdata['validator']() 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 InvalidSettingValue(value, name) raise exception.InvalidSettingValue(value, name)
return value return value
@ -354,7 +360,9 @@ def get_cid():
except: # pylint: disable=bare-except except: # pylint: disable=bare-except
pass pass
cid = str( cid = str(
uuid.UUID(bytes=hashlib.md5(str(_uid if _uid else uuid.getnode())) uuid.UUID(
.digest())) bytes=hashlib.md5(str(_uid if _uid else uuid.getnode()))
set_state_item("cid", cid) .digest()))
if "windows" in util.get_systype() or os.getuid() > 0:
set_state_item("cid", cid)
return cid return cid

View File

@ -16,7 +16,7 @@ import base64
import json import json
import sys import sys
from os import environ from os import environ
from os.path import join from os.path import expanduser, join
from time import time from time import time
from SCons.Script import (ARGUMENTS, COMMAND_LINE_TARGETS, DEFAULT_TARGETS, from SCons.Script import (ARGUMENTS, COMMAND_LINE_TARGETS, DEFAULT_TARGETS,
@ -87,6 +87,7 @@ DEFAULT_ENV_OPTIONS = dict(
UNIX_TIME=int(time()), UNIX_TIME=int(time()),
PIOHOME_DIR=util.get_home_dir(), PIOHOME_DIR=util.get_home_dir(),
PROJECT_DIR=util.get_project_dir(), PROJECT_DIR=util.get_project_dir(),
PROJECTINCLUDE_DIR=util.get_projectinclude_dir(),
PROJECTSRC_DIR=util.get_projectsrc_dir(), PROJECTSRC_DIR=util.get_projectsrc_dir(),
PROJECTTEST_DIR=util.get_projecttest_dir(), PROJECTTEST_DIR=util.get_projecttest_dir(),
PROJECTDATA_DIR=util.get_projectdata_dir(), PROJECTDATA_DIR=util.get_projectdata_dir(),
@ -138,9 +139,13 @@ for var in ("BUILD_FLAGS", "SRC_BUILD_FLAGS", "SRC_FILTER", "EXTRA_SCRIPTS",
# Configure extra library source directories for LDF # Configure extra library source directories for LDF
if util.get_project_optional_dir("lib_extra_dirs"): if util.get_project_optional_dir("lib_extra_dirs"):
env.Prepend(LIBSOURCE_DIRS=util.parse_conf_multi_values( env.Prepend(
util.get_project_optional_dir("lib_extra_dirs"))) LIBSOURCE_DIRS=util.parse_conf_multi_values(
util.get_project_optional_dir("lib_extra_dirs")))
env.Prepend(LIBSOURCE_DIRS=env.get("LIB_EXTRA_DIRS", [])) env.Prepend(LIBSOURCE_DIRS=env.get("LIB_EXTRA_DIRS", []))
env['LIBSOURCE_DIRS'] = [
expanduser(d) if d.startswith("~") else d for d in env['LIBSOURCE_DIRS']
]
env.LoadPioPlatform(commonvars) env.LoadPioPlatform(commonvars)
@ -167,7 +172,8 @@ if "envdump" in COMMAND_LINE_TARGETS:
if "idedata" in COMMAND_LINE_TARGETS: if "idedata" in COMMAND_LINE_TARGETS:
try: try:
print "\n%s\n" % json.dumps(env.DumpIDEData()) print "\n%s\n" % util.path_to_unicode(
json.dumps(env.DumpIDEData(), ensure_ascii=False))
env.Exit(0) env.Exit(0)
except UnicodeDecodeError: except UnicodeDecodeError:
sys.stderr.write( sys.stderr.write(

View File

@ -15,6 +15,7 @@
from __future__ import absolute_import from __future__ import absolute_import
from glob import glob from glob import glob
from os import environ
from os.path import join from os.path import join
from SCons.Defaults import processDefines from SCons.Defaults import processDefines
@ -23,7 +24,7 @@ from platformio import util
from platformio.managers.core import get_core_package_dir from platformio.managers.core import get_core_package_dir
def dump_includes(env): def _dump_includes(env):
includes = [] includes = []
for item in env.get("CPPPATH", []): for item in env.get("CPPPATH", []):
@ -31,7 +32,7 @@ def dump_includes(env):
# installed libs # installed libs
for lb in env.GetLibBuilders(): for lb in env.GetLibBuilders():
includes.extend(lb.get_inc_dirs()) includes.extend(lb.get_include_dirs())
# includes from toolchains # includes from toolchains
p = env.PioPlatform() p = env.PioPlatform()
@ -41,6 +42,8 @@ def dump_includes(env):
toolchain_dir = util.glob_escape(p.get_package_dir(name)) toolchain_dir = util.glob_escape(p.get_package_dir(name))
toolchain_incglobs = [ toolchain_incglobs = [
join(toolchain_dir, "*", "include*"), join(toolchain_dir, "*", "include*"),
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:
@ -53,7 +56,29 @@ def dump_includes(env):
return includes return includes
def dump_defines(env): def _get_gcc_defines(env):
items = []
try:
sysenv = environ.copy()
sysenv['PATH'] = str(env['ENV']['PATH'])
result = util.exec_command(
"echo | %s -dM -E -" % env.subst("$CC"), env=sysenv, shell=True)
except OSError:
return items
if result['returncode'] != 0:
return items
for line in result['out'].split("\n"):
tokens = line.strip().split(" ", 2)
if not tokens or tokens[0] != "#define":
continue
if len(tokens) > 2:
items.append("%s=%s" % (tokens[1], tokens[2]))
else:
items.append(tokens[1])
return items
def _dump_defines(env):
defines = [] defines = []
# global symbols # global symbols
for item in processDefines(env.get("CPPDEFINES", [])): for item in processDefines(env.get("CPPDEFINES", [])):
@ -61,9 +86,18 @@ def dump_defines(env):
# special symbol for Atmel AVR MCU # special symbol for Atmel AVR MCU
if env['PIOPLATFORM'] == "atmelavr": if env['PIOPLATFORM'] == "atmelavr":
defines.append( board_mcu = env.get("BOARD_MCU")
"__AVR_%s__" % env.BoardConfig().get("build.mcu").upper() if not board_mcu and "BOARD" in env:
.replace("ATMEGA", "ATmega").replace("ATTINY", "ATtiny")) board_mcu = env.BoardConfig().get("build.mcu")
if board_mcu:
defines.append(
str("__AVR_%s__" % board_mcu.upper()
.replace("ATMEGA", "ATmega").replace("ATTINY", "ATtiny")))
# built-in GCC marcos
if env.GetCompilerType() == "gcc":
defines.extend(_get_gcc_defines(env))
return defines return defines
@ -75,9 +109,9 @@ def DumpIDEData(env):
"libsource_dirs": "libsource_dirs":
[env.subst(l) for l in env.get("LIBSOURCE_DIRS", [])], [env.subst(l) for l in env.get("LIBSOURCE_DIRS", [])],
"defines": "defines":
dump_defines(env), _dump_defines(env),
"includes": "includes":
dump_includes(env), _dump_includes(env),
"cc_flags": "cc_flags":
env.subst(LINTCCOM), env.subst(LINTCCOM),
"cxx_flags": "cxx_flags":
@ -89,7 +123,9 @@ def DumpIDEData(env):
"gdb_path": "gdb_path":
util.where_is_program(env.subst("$GDB"), env.subst("${ENV['PATH']}")), util.where_is_program(env.subst("$GDB"), env.subst("${ENV['PATH']}")),
"prog_path": "prog_path":
env.subst("$PROG_PATH") env.subst("$PROG_PATH"),
"compiler_type":
env.GetCompilerType()
} }
env_ = env.Clone() env_ = env.Clone()

View File

@ -17,13 +17,15 @@
from __future__ import absolute_import from __future__ import absolute_import
import hashlib
import os import os
import sys import sys
from os.path import basename, commonprefix, isdir, isfile, join, realpath, sep from os.path import (basename, commonprefix, dirname, isdir, isfile, join,
realpath, sep)
from platform import system from platform import system
import SCons.Scanner import SCons.Scanner
from SCons.Script import ARGUMENTS, DefaultEnvironment from SCons.Script import ARGUMENTS, COMMAND_LINE_TARGETS, DefaultEnvironment
from platformio import util from platformio import util
from platformio.builder.tools import platformio as piotool from platformio.builder.tools import platformio as piotool
@ -82,9 +84,14 @@ class LibBuilderBase(object):
LDF_MODES = ["off", "chain", "deep", "chain+", "deep+"] LDF_MODES = ["off", "chain", "deep", "chain+", "deep+"]
LDF_MODE_DEFAULT = "chain" LDF_MODE_DEFAULT = "chain"
COMPAT_MODES = [0, 1, 2]
COMPAT_MODE_DEFAULT = 1
CLASSIC_SCANNER = SCons.Scanner.C.CScanner() CLASSIC_SCANNER = SCons.Scanner.C.CScanner()
ADVANCED_SCANNER = SCons.Scanner.C.CScanner(advanced=True) ADVANCED_SCANNER = SCons.Scanner.C.CScanner(advanced=True)
INC_DIRS_CACHE = None PARSE_SRC_BY_H_NAME = True
_INCLUDE_DIRS_CACHE = None
def __init__(self, env, path, manifest=None, verbose=False): def __init__(self, env, path, manifest=None, verbose=False):
self.env = env.Clone() self.env = env.Clone()
@ -93,13 +100,11 @@ class LibBuilderBase(object):
self.verbose = verbose self.verbose = verbose
self._manifest = manifest if manifest else self.load_manifest() self._manifest = manifest if manifest else self.load_manifest()
self._ldf_mode = self.validate_ldf_mode(
self.env.get("LIB_LDF_MODE", self.LDF_MODE_DEFAULT))
self._is_dependent = False self._is_dependent = False
self._is_built = False self._is_built = False
self._depbuilders = list() self._depbuilders = list()
self._circular_deps = list() self._circular_deps = list()
self._scanned_paths = 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'] = ""
@ -140,20 +145,29 @@ class LibBuilderBase(object):
"-<tests%s>" % os.sep "-<tests%s>" % os.sep
] ]
@property
def include_dir(self):
if not all([isdir(join(self.path, d)) for d in ("include", "src")]):
return None
return join(self.path, "include")
@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):
items = [self.src_dir]
include_dir = self.include_dir
if include_dir and include_dir not in items:
items.append(include_dir)
return items
@property @property
def build_dir(self): def build_dir(self):
return join("$BUILD_DIR", "lib", basename(self.path)) return join("$BUILD_DIR",
"lib%s" % hashlib.sha1(self.path).hexdigest()[:3],
def get_inc_dirs(self): basename(self.path))
items = [self.src_dir]
if all([isdir(join(self.path, d)) for d in ("inc", "src")]):
items.append(join(self.path, "inc"))
return items
@property @property
def build_flags(self): def build_flags(self):
@ -171,21 +185,15 @@ class LibBuilderBase(object):
def lib_archive(self): def lib_archive(self):
return self.env.get("LIB_ARCHIVE", "") != "false" return self.env.get("LIB_ARCHIVE", "") != "false"
@staticmethod
def validate_ldf_mode(mode):
if isinstance(mode, basestring):
mode = mode.strip().lower()
if mode in LibBuilderBase.LDF_MODES:
return mode
try:
return LibBuilderBase.LDF_MODES[int(mode)]
except (IndexError, ValueError):
pass
return LibBuilderBase.LDF_MODE_DEFAULT
@property @property
def lib_ldf_mode(self): def lib_ldf_mode(self):
return self._ldf_mode return self.validate_ldf_mode(
self.env.get("LIB_LDF_MODE", self.LDF_MODE_DEFAULT))
@property
def lib_compat_mode(self):
return self.validate_compat_mode(
self.env.get("LIB_COMPAT_MODE", self.COMPAT_MODE_DEFAULT))
@property @property
def depbuilders(self): def depbuilders(self):
@ -200,15 +208,35 @@ class LibBuilderBase(object):
return self._is_built return self._is_built
@staticmethod @staticmethod
def items_in_list(items, ilist): def validate_ldf_mode(mode):
if isinstance(mode, basestring):
mode = mode.strip().lower()
if mode in LibBuilderBase.LDF_MODES:
return mode
try:
return LibBuilderBase.LDF_MODES[int(mode)]
except (IndexError, ValueError):
pass
return LibBuilderBase.LDF_MODE_DEFAULT
def _items_to_list(items_): @staticmethod
if not isinstance(items_, list): def validate_compat_mode(mode):
items_ = [i.strip() for i in items_.split(",")] try:
return [i.lower() for i in items_ if i] mode = int(mode)
assert mode in LibBuilderBase.COMPAT_MODES
return mode
except (AssertionError, ValueError):
return LibBuilderBase.COMPAT_MODE_DEFAULT
items = _items_to_list(items) @staticmethod
ilist = _items_to_list(ilist) def items_to_list(items):
if not isinstance(items, list):
items = [i.strip() for i in items.split(",")]
return [i.lower() for i in items if i]
def items_in_list(self, items, ilist):
items = self.items_to_list(items)
ilist = self.items_to_list(ilist)
if "*" in items or "*" in ilist: if "*" in items or "*" in ilist:
return True return True
return set(items) & set(ilist) return set(items) & set(ilist)
@ -222,13 +250,6 @@ class LibBuilderBase(object):
def load_manifest(self): def load_manifest(self):
return {} return {}
def get_src_files(self):
return [
join(self.src_dir, item)
for item in self.env.MatchSourceFiles(self.src_dir,
self.src_filter)
]
def process_extra_options(self): def process_extra_options(self):
with util.cd(self.path): with util.cd(self.path):
self.env.ProcessUnFlags(self.build_unflags) self.env.ProcessUnFlags(self.build_unflags)
@ -237,10 +258,12 @@ class LibBuilderBase(object):
self.env.SConscriptChdir(1) self.env.SConscriptChdir(1)
self.env.SConscript( self.env.SConscript(
realpath(self.extra_script), realpath(self.extra_script),
exports={"env": self.env, exports={
"pio_lib_builder": self}) "env": self.env,
"pio_lib_builder": self
})
def _process_dependencies(self): def process_dependencies(self):
if not self.dependencies: if not self.dependencies:
return return
for item in self.dependencies: for item in self.dependencies:
@ -260,7 +283,7 @@ class LibBuilderBase(object):
continue continue
found = False found = False
for lb in self.envorigin.GetLibBuilders(): for lb in self.env.GetLibBuilders():
if item['name'] != lb.name: if item['name'] != lb.name:
continue continue
elif "frameworks" in item and \ elif "frameworks" in item and \
@ -279,56 +302,81 @@ class LibBuilderBase(object):
"library\n" % (item['name'], self.name)) "library\n" % (item['name'], self.name))
self.env.Exit(1) self.env.Exit(1)
def _validate_search_paths(self, search_paths=None): def get_search_files(self):
if not search_paths: items = [
search_paths = [] join(self.src_dir, item)
assert isinstance(search_paths, list) for item in self.env.MatchSourceFiles(self.src_dir,
self.src_filter)
]
include_dir = self.include_dir
if include_dir:
items.extend([
join(include_dir, item)
for item in self.env.MatchSourceFiles(include_dir)
])
return items
_search_paths = [] def _validate_search_files(self, search_files=None):
for path in search_paths: if not search_files:
if path not in self._scanned_paths: search_files = []
_search_paths.append(path) assert isinstance(search_files, list)
self._scanned_paths.append(path)
return _search_paths _search_files = []
for path in search_files:
if path not in self._processed_files:
_search_files.append(path)
self._processed_files.append(path)
def _get_found_includes(self, search_paths=None): return _search_files
def _get_found_includes(self, search_files=None):
# all include directories # all include directories
if not LibBuilderBase.INC_DIRS_CACHE: if not LibBuilderBase._INCLUDE_DIRS_CACHE:
inc_dirs = [] LibBuilderBase._INCLUDE_DIRS_CACHE = []
used_inc_dirs = [] for lb in self.env.GetLibBuilders():
for lb in self.envorigin.GetLibBuilders(): LibBuilderBase._INCLUDE_DIRS_CACHE.extend(
items = [self.env.Dir(d) for d in lb.get_inc_dirs()] [self.env.Dir(d) for d in lb.get_include_dirs()])
if lb.dependent:
used_inc_dirs.extend(items)
else:
inc_dirs.extend(items)
LibBuilderBase.INC_DIRS_CACHE = used_inc_dirs + inc_dirs
# append self include directories # append self include directories
inc_dirs = [self.env.Dir(d) for d in self.get_inc_dirs()] include_dirs = [self.env.Dir(d) for d in self.get_include_dirs()]
inc_dirs.extend(LibBuilderBase.INC_DIRS_CACHE) include_dirs.extend(LibBuilderBase._INCLUDE_DIRS_CACHE)
result = [] result = []
for path in self._validate_search_paths(search_paths): for path in self._validate_search_files(search_files):
try: try:
assert "+" in self.lib_ldf_mode assert "+" in self.lib_ldf_mode
incs = self.env.File(path).get_found_includes( incs = self.env.File(path).get_found_includes(
self.env, LibBuilderBase.ADVANCED_SCANNER, tuple(inc_dirs)) self.env, LibBuilderBase.ADVANCED_SCANNER,
tuple(include_dirs))
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))
incs = self.env.File(path).get_found_includes( _incs = self.env.File(path).get_found_includes(
self.env, LibBuilderBase.CLASSIC_SCANNER, tuple(inc_dirs)) self.env, LibBuilderBase.CLASSIC_SCANNER,
tuple(include_dirs))
incs = []
for inc in _incs:
incs.append(inc)
if not self.PARSE_SRC_BY_H_NAME:
continue
_h_path = inc.get_abspath()
if not self.env.IsFileWithExt(_h_path,
piotool.SRC_HEADER_EXT):
continue
_f_part = _h_path[:_h_path.rindex(".")]
for ext in piotool.SRC_C_EXT:
if isfile("%s.%s" % (_f_part, ext)):
incs.append(
self.env.File("%s.%s" % (_f_part, ext)))
# print path, map(lambda n: n.get_abspath(), incs) # print path, map(lambda n: n.get_abspath(), incs)
for inc in incs: for inc in incs:
if inc not in result: if inc not in result:
result.append(inc) result.append(inc)
return result return result
def depend_recursive(self, lb, search_paths=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:
@ -348,32 +396,32 @@ class LibBuilderBase(object):
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)
LibBuilderBase.INC_DIRS_CACHE = None LibBuilderBase._INCLUDE_DIRS_CACHE = None
lb.search_deps_recursive(search_paths) lb.search_deps_recursive(search_files)
def search_deps_recursive(self, search_paths=None): def search_deps_recursive(self, search_files=None):
if not self._is_dependent: if not self._is_dependent:
self._is_dependent = True self._is_dependent = True
self._process_dependencies() self.process_dependencies()
if self.lib_ldf_mode.startswith("deep"): if self.lib_ldf_mode.startswith("deep"):
search_paths = self.get_src_files() search_files = self.get_search_files()
# when LDF is disabled # when LDF is disabled
if self.lib_ldf_mode == "off": if self.lib_ldf_mode == "off":
return return
lib_inc_map = {} lib_inc_map = {}
for inc in self._get_found_includes(search_paths): for inc in self._get_found_includes(search_files):
for lb in self.envorigin.GetLibBuilders(): for lb in self.env.GetLibBuilders():
if inc.get_abspath() in lb: if inc.get_abspath() in lb:
if lb not in lib_inc_map: if lb not in lib_inc_map:
lib_inc_map[lb] = [] lib_inc_map[lb] = []
lib_inc_map[lb].append(inc.get_abspath()) lib_inc_map[lb].append(inc.get_abspath())
break break
for lb, lb_search_paths in lib_inc_map.items(): for lb, lb_search_files in lib_inc_map.items():
self.depend_recursive(lb, lb_search_paths) self.depend_recursive(lb, lb_search_files)
def build(self): def build(self):
libs = [] libs = []
@ -384,16 +432,16 @@ class LibBuilderBase(object):
self.env.AppendUnique(**{key: lb.env.get(key)}) self.env.AppendUnique(**{key: lb.env.get(key)})
for lb in self._circular_deps: for lb in self._circular_deps:
self.env.AppendUnique(CPPPATH=lb.get_inc_dirs()) self.env.AppendUnique(CPPPATH=lb.get_include_dirs())
if self._is_built: if self._is_built:
return libs return libs
self._is_built = True self._is_built = True
self.env.AppendUnique(CPPPATH=self.get_inc_dirs()) self.env.AppendUnique(CPPPATH=self.get_include_dirs())
if self.lib_ldf_mode == "off": if self.lib_ldf_mode == "off":
for lb in self.envorigin.GetLibBuilders(): for lb in self.env.GetLibBuilders():
if self == lb or not lb.is_built: if self == lb or not lb.is_built:
continue continue
for key in ("CPPPATH", "LIBPATH", "LIBS", "LINKFLAGS"): for key in ("CPPPATH", "LIBPATH", "LIBS", "LINKFLAGS"):
@ -427,13 +475,13 @@ class ArduinoLibBuilder(LibBuilderBase):
manifest[key.strip()] = value.strip() manifest[key.strip()] = value.strip()
return manifest return manifest
def get_inc_dirs(self): def get_include_dirs(self):
inc_dirs = LibBuilderBase.get_inc_dirs(self) include_dirs = LibBuilderBase.get_include_dirs(self)
if isdir(join(self.path, "src")): if isdir(join(self.path, "src")):
return inc_dirs return include_dirs
if isdir(join(self.path, "utility")): if isdir(join(self.path, "utility")):
inc_dirs.append(join(self.path, "utility")) include_dirs.append(join(self.path, "utility"))
return inc_dirs return include_dirs
@property @property
def src_filter(self): def src_filter(self):
@ -458,19 +506,25 @@ class MbedLibBuilder(LibBuilderBase):
return {} return {}
return util.load_json(join(self.path, "module.json")) return util.load_json(join(self.path, "module.json"))
@property
def include_dir(self):
if isdir(join(self.path, "include")):
return join(self.path, "include")
return None
@property @property
def src_dir(self): def src_dir(self):
if isdir(join(self.path, "source")): if isdir(join(self.path, "source")):
return join(self.path, "source") return join(self.path, "source")
return LibBuilderBase.src_dir.fget(self) return LibBuilderBase.src_dir.fget(self)
def get_inc_dirs(self): def get_include_dirs(self):
inc_dirs = LibBuilderBase.get_inc_dirs(self) include_dirs = LibBuilderBase.get_include_dirs(self)
if self.path not in inc_dirs: if self.path not in include_dirs:
inc_dirs.append(self.path) include_dirs.append(self.path)
for p in self._manifest.get("extraIncludes", []): for p in self._manifest.get("extraIncludes", []):
inc_dirs.append(join(self.path, p)) include_dirs.append(join(self.path, p))
return inc_dirs return include_dirs
def is_frameworks_compatible(self, frameworks): def is_frameworks_compatible(self, frameworks):
return self.items_in_list(frameworks, ["mbed"]) return self.items_in_list(frameworks, ["mbed"])
@ -482,6 +536,14 @@ class PlatformIOLibBuilder(LibBuilderBase):
assert isfile(join(self.path, "library.json")) assert isfile(join(self.path, "library.json"))
manifest = util.load_json(join(self.path, "library.json")) manifest = util.load_json(join(self.path, "library.json"))
assert "name" in manifest assert "name" in manifest
# replace "espressif" old name dev/platform with ESP8266
if "platforms" in manifest:
manifest['platforms'] = [
"espressif8266" if p == "espressif" else p
for p in self.items_to_list(manifest['platforms'])
]
return manifest return manifest
def _is_arduino_manifest(self): def _is_arduino_manifest(self):
@ -537,6 +599,13 @@ class PlatformIOLibBuilder(LibBuilderBase):
self._manifest.get("build").get("libLDFMode")) self._manifest.get("build").get("libLDFMode"))
return LibBuilderBase.lib_ldf_mode.fget(self) return LibBuilderBase.lib_ldf_mode.fget(self)
@property
def lib_compat_mode(self):
if "libCompatMode" in self._manifest.get("build", {}):
return self.validate_compat_mode(
self._manifest.get("build").get("libCompatMode"))
return 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")
if not items: if not items:
@ -549,27 +618,49 @@ class PlatformIOLibBuilder(LibBuilderBase):
return LibBuilderBase.is_frameworks_compatible(self, frameworks) return LibBuilderBase.is_frameworks_compatible(self, frameworks)
return self.items_in_list(frameworks, items) return self.items_in_list(frameworks, items)
def get_inc_dirs(self): def get_include_dirs(self):
inc_dirs = LibBuilderBase.get_inc_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 ("build" not in self._manifest and self._is_arduino_manifest()
and not isdir(join(self.path, "src")) and not isdir(join(self.path, "src"))
and isdir(join(self.path, "utility"))): and isdir(join(self.path, "utility"))):
inc_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", []):
if path not in self.envorigin.get("CPPPATH", []): if path not in self.envorigin.get("CPPPATH", []):
inc_dirs.append(self.env.subst(path)) include_dirs.append(self.env.subst(path))
return inc_dirs return include_dirs
class ProjectAsLibBuilder(LibBuilderBase): class ProjectAsLibBuilder(LibBuilderBase):
@property
def include_dir(self):
include_dir = self.env.subst("$PROJECTINCLUDE_DIR")
return include_dir if isdir(include_dir) else None
@property @property
def src_dir(self): def src_dir(self):
return self.env.subst("$PROJECTSRC_DIR") return self.env.subst("$PROJECTSRC_DIR")
def get_include_dirs(self):
include_dirs = LibBuilderBase.get_include_dirs(self)
include_dirs.append(self.env.subst("$PROJECTINCLUDE_DIR"))
return include_dirs
def get_search_files(self):
# project files
items = LibBuilderBase.get_search_files(self)
# test files
if "__test" in COMMAND_LINE_TARGETS:
items.extend([
join("$PROJECTTEST_DIR", item)
for item in self.env.MatchSourceFiles("$PROJECTTEST_DIR",
"$PIOTEST_SRC_FILTER")
])
return items
@property @property
def lib_ldf_mode(self): def lib_ldf_mode(self):
mode = LibBuilderBase.lib_ldf_mode.fget(self) mode = LibBuilderBase.lib_ldf_mode.fget(self)
@ -586,39 +677,63 @@ class ProjectAsLibBuilder(LibBuilderBase):
# skip for project, options are already processed # skip for project, options are already processed
pass pass
def search_deps_recursive(self, search_paths=None): def process_dependencies(self): # pylint: disable=too-many-branches
for dep in self.env.get("LIB_DEPS", []): uris = self.env.get("LIB_DEPS", [])
for token in ("@", "="): if not uris:
if token in dep: return
dep, _ = dep.split(token, 1) storage_dirs = []
for lb in self.envorigin.GetLibBuilders(): for lb in self.env.GetLibBuilders():
if lb.name == dep: if dirname(lb.path) not in storage_dirs:
storage_dirs.append(dirname(lb.path))
for uri in uris:
found = False
for storage_dir in storage_dirs:
if found:
break
lm = LibraryManager(storage_dir)
pkg_dir = lm.get_package_dir(*lm.parse_pkg_uri(uri))
if not pkg_dir:
continue
for lb in self.env.GetLibBuilders():
if lb.path != pkg_dir:
continue
if lb not in self.depbuilders:
self.depend_recursive(lb)
found = True
break
if not found:
for lb in self.env.GetLibBuilders():
if lb.name != uri:
continue
if lb not in self.depbuilders: if lb not in self.depbuilders:
self.depend_recursive(lb) self.depend_recursive(lb)
break break
return LibBuilderBase.search_deps_recursive(self, search_paths)
def build(self): def build(self):
self._is_built = True # do not build Project now self._is_built = True # do not build Project now
self.env.AppendUnique(CPPPATH=self.get_inc_dirs()) self.env.AppendUnique(CPPPATH=self.get_include_dirs())
return LibBuilderBase.build(self) return LibBuilderBase.build(self)
def GetLibBuilders(env): # pylint: disable=too-many-branches def GetLibBuilders(env): # pylint: disable=too-many-branches
if "__PIO_LIB_BUILDERS" in DefaultEnvironment(): if "__PIO_LIB_BUILDERS" in DefaultEnvironment():
return DefaultEnvironment()['__PIO_LIB_BUILDERS'] return sorted(
DefaultEnvironment()['__PIO_LIB_BUILDERS'],
key=lambda lb: 0 if lb.dependent else 1)
items = [] items = []
compat_mode = int(env.get("LIB_COMPAT_MODE", 1)) verbose = int(ARGUMENTS.get("PIOVERBOSE",
verbose = (int(ARGUMENTS.get("PIOVERBOSE", 0)) 0)) and not env.GetOption('clean')
and not env.GetOption('clean'))
def _check_lib_builder(lb): def _check_lib_builder(lb):
compat_mode = lb.lib_compat_mode
if lb.name in env.get("LIB_IGNORE", []): if lb.name in env.get("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 return None
if compat_mode > 1 and not lb.is_platforms_compatible( if compat_mode > 1 and not lb.is_platforms_compatible(
env['PIOPLATFORM']): env['PIOPLATFORM']):
if verbose: if verbose:
@ -678,7 +793,7 @@ def BuildProjectLibraries(env):
found_lbs = [lb for lb in lib_builders if lb.dependent] found_lbs = [lb for lb in lib_builders if lb.dependent]
for lb in lib_builders: for lb in lib_builders:
if lb in found_lbs: if lb in found_lbs:
lb.search_deps_recursive(lb.get_src_files()) lb.search_deps_recursive(lb.get_search_files())
for lb in lib_builders: for lb in lib_builders:
for deplb in lb.depbuilders[:]: for deplb in lb.depbuilders[:]:
if deplb not in found_lbs: if deplb not in found_lbs:
@ -690,9 +805,12 @@ def BuildProjectLibraries(env):
title = "<%s>" % lb.name title = "<%s>" % lb.name
if lb.version: if lb.version:
title += " v%s" % lb.version title += " v%s" % lb.version
sys.stdout.write("%s|-- %s" % (margin, title))
if int(ARGUMENTS.get("PIOVERBOSE", 0)): if int(ARGUMENTS.get("PIOVERBOSE", 0)):
title += " (%s)" % lb.path sys.stdout.write(" (")
print "%s|-- %s" % (margin, title) sys.stdout.write(lb.path)
sys.stdout.write(")")
sys.stdout.write("\n")
if lb.depbuilders: if lb.depbuilders:
print_deps_tree(lb, level + 1) print_deps_tree(lb, level + 1)
@ -709,10 +827,10 @@ def BuildProjectLibraries(env):
correct_found_libs() correct_found_libs()
if project.depbuilders: if project.depbuilders:
print "Library Dependency Graph" print "Library Dependency Graph ( http://bit.ly/configure-pio-ldf )"
print_deps_tree(project) print_deps_tree(project)
else: else:
print "Project does not have dependencies" print "No dependencies"
return project.build() return project.build()

View File

@ -50,7 +50,7 @@ class InoToCPPConverter(object):
def convert(self, nodes): def convert(self, nodes):
contents = self.merge(nodes) contents = self.merge(nodes)
if not contents: if not contents:
return return None
return self.process(contents) return self.process(contents)
def merge(self, nodes): def merge(self, nodes):
@ -199,7 +199,8 @@ def _delete_file(path):
pass pass
def GetCompilerType(env): @util.memoized
def _get_compiler_type(env):
try: try:
sysenv = environ.copy() sysenv = environ.copy()
sysenv['PATH'] = str(env['ENV']['PATH']) sysenv['PATH'] = str(env['ENV']['PATH'])
@ -216,6 +217,10 @@ def GetCompilerType(env):
return None return None
def GetCompilerType(env):
return _get_compiler_type(env)
def GetActualLDScript(env): def GetActualLDScript(env):
def _lookup_in_ldpath(script): def _lookup_in_ldpath(script):
@ -271,7 +276,7 @@ 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", "-ggdb"]) env.Replace(PIODEBUGFLAGS=["-Og", "-g3", "-ggdb3"])
env.Append( env.Append(
BUILD_FLAGS=env.get("PIODEBUGFLAGS", []), BUILD_FLAGS=env.get("PIODEBUGFLAGS", []),
BUILD_UNFLAGS=["-Os", "-O0", "-O1", "-O2", "-O3"]) BUILD_UNFLAGS=["-Os", "-O0", "-O1", "-O2", "-O3"])
@ -288,11 +293,12 @@ def ProcessTest(env):
src_filter = ["+<*.cpp>", "+<*.c>"] src_filter = ["+<*.cpp>", "+<*.c>"]
if "PIOTEST" in env: if "PIOTEST" in env:
src_filter.append("+<%s%s>" % (env['PIOTEST'], sep)) src_filter.append("+<%s%s>" % (env['PIOTEST'], sep))
env.Replace(PIOTEST_SRC_FILTER=src_filter)
return env.CollectBuildFiles( return env.CollectBuildFiles(
"$BUILDTEST_DIR", "$BUILDTEST_DIR",
"$PROJECTTEST_DIR", "$PROJECTTEST_DIR",
src_filter=src_filter, "$PIOTEST_SRC_FILTER",
duplicate=False) duplicate=False)

View File

@ -41,8 +41,9 @@ def PioPlatform(env):
def BoardConfig(env, board=None): def BoardConfig(env, board=None):
p = initPioPlatform(env['PLATFORM_MANIFEST']) p = initPioPlatform(env['PLATFORM_MANIFEST'])
try: try:
config = p.board_config(board if board else env['BOARD']) assert env.get("BOARD", board), "BoardConfig: Board is not defined"
except exception.UnknownBoard as e: config = p.board_config(board if board else env.get("BOARD"))
except (AssertionError, exception.UnknownBoard) as e:
sys.stderr.write("Error: %s\n" % str(e)) sys.stderr.write("Error: %s\n" % str(e))
env.Exit(1) env.Exit(1)
return config return config
@ -61,6 +62,9 @@ def LoadPioPlatform(env, variables):
p = env.PioPlatform() p = env.PioPlatform()
installed_packages = p.get_installed_packages() installed_packages = p.get_installed_packages()
# Ensure real platform name
env['PIOPLATFORM'] = p.name
# Add toolchains and uploaders to $PATH # Add toolchains and uploaders to $PATH
for name in installed_packages: for name in installed_packages:
type_ = p.get_package_type(name) type_ = p.get_package_type(name)
@ -80,9 +84,8 @@ def LoadPioPlatform(env, variables):
board_config = env.BoardConfig() board_config = env.BoardConfig()
for k in variables.keys(): for k in variables.keys():
if (k in env if k in env or \
or not any([k.startswith("BOARD_"), not any([k.startswith("BOARD_"), k.startswith("UPLOAD_")]):
k.startswith("UPLOAD_")])):
continue continue
_opt, _val = k.lower().split("_", 1) _opt, _val = k.lower().split("_", 1)
if _opt == "board": if _opt == "board":

View File

@ -58,7 +58,7 @@ def WaitForNewSerialPort(env, before):
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_serialports()] 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
@ -107,29 +107,33 @@ def AutodetectUploadPort(*args, **kwargs): # pylint: disable=unused-argument
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_logicaldisks(): for item in util.get_logical_devices():
if item['disk'].startswith( if item['path'].startswith("/net") or not _is_match_pattern(
"/net") or not _is_match_pattern(item['disk']): item['path']):
continue continue
mbed_pages = [ mbed_pages = [
join(item['disk'], 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['disk'] 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['disk'] return item['path']
return None return None
def _look_for_serial_port(): def _look_for_serial_port():
port = None port = None
board_hwids = [] board_hwids = []
upload_protocol = env.subst("$UPLOAD_PROTOCOL")
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_serialports(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") \
and "GDB" in item['description']:
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']:
@ -140,7 +144,8 @@ def AutodetectUploadPort(*args, **kwargs): # pylint: disable=unused-argument
print env.subst("Use manually specified: $UPLOAD_PORT") print env.subst("Use manually specified: $UPLOAD_PORT")
return return
if "mbed" in env.subst("$PIOFRAMEWORK"): if "mbed" in env.subst("$PIOFRAMEWORK") \
and not env.subst("$UPLOAD_PROTOCOL"):
env.Replace(UPLOAD_PORT=_look_for_mbed_disk()) env.Replace(UPLOAD_PORT=_look_for_mbed_disk())
else: else:
if (system() == "Linux" and not any([ if (system() == "Linux" and not any([

View File

@ -72,7 +72,7 @@ def exists(_):
def generate(env): def generate(env):
if system() != "Windows": if system() != "Windows":
return return None
env.Replace(_long_sources_hook=long_sources_hook) env.Replace(_long_sources_hook=long_sources_hook)
env.Replace(_long_incflags_hook=long_incflags_hook) env.Replace(_long_incflags_hook=long_incflags_hook)

View File

@ -27,18 +27,18 @@ from SCons.Util import case_sensitive_suffixes, is_Sequence
from platformio.util import glob_escape, pioversion_to_intstr from platformio.util import glob_escape, pioversion_to_intstr
SRC_BUILD_EXT = ["c", "cc", "cpp", "S", "spp", "SPP", "sx", "s", "asm", "ASM"]
SRC_HEADER_EXT = ["h", "hpp"] SRC_HEADER_EXT = ["h", "hpp"]
SRC_C_EXT = ["c", "cc", "cpp"]
SRC_BUILD_EXT = SRC_C_EXT + ["S", "spp", "SPP", "sx", "s", "asm", "ASM"]
SRC_FILTER_DEFAULT = ["+<*>", "-<.git%s>" % sep, "-<svn%s>" % sep] SRC_FILTER_DEFAULT = ["+<*>", "-<.git%s>" % sep, "-<svn%s>" % sep]
def BuildProgram(env): def BuildProgram(env):
def _append_pio_macros(): def _append_pio_macros():
env.AppendUnique(CPPDEFINES=[ env.AppendUnique(CPPDEFINES=[(
("PLATFORMIO", "PLATFORMIO",
int("{0:02d}{1:02d}{2:02d}".format(*pioversion_to_intstr()))) int("{0:02d}{1:02d}{2:02d}".format(*pioversion_to_intstr())))])
])
_append_pio_macros() _append_pio_macros()
@ -46,9 +46,6 @@ def BuildProgram(env):
if not case_sensitive_suffixes(".s", ".S"): if not case_sensitive_suffixes(".s", ".S"):
env.Replace(AS="$CC", ASCOM="$ASPPCOM") env.Replace(AS="$CC", ASCOM="$ASPPCOM")
if "__debug" in COMMAND_LINE_TARGETS:
env.ProcessDebug()
# process extra flags from board # process extra flags from board
if "BOARD" in env and "build.extra_flags" in env.BoardConfig(): if "BOARD" in env and "build.extra_flags" in env.BoardConfig():
env.ProcessFlags(env.BoardConfig().get("build.extra_flags")) env.ProcessFlags(env.BoardConfig().get("build.extra_flags"))
@ -57,13 +54,26 @@ def BuildProgram(env):
# apply user flags # apply user flags
env.ProcessFlags(env.get("BUILD_FLAGS")) env.ProcessFlags(env.get("BUILD_FLAGS"))
# process framework scripts
env.BuildFrameworks(env.get("PIOFRAMEWORK")) env.BuildFrameworks(env.get("PIOFRAMEWORK"))
# restore PIO macros if it was deleted by framework # restore PIO macros if it was deleted by framework
_append_pio_macros() _append_pio_macros()
# Search for project source files
env.Append(
LIBPATH=["$BUILD_DIR"],
PIOBUILDFILES=env.CollectBuildFiles(
"$BUILDSRC_DIR", "$PROJECTSRC_DIR", "$SRC_FILTER",
duplicate=False))
if "__debug" in COMMAND_LINE_TARGETS:
env.ProcessDebug()
if "__test" in COMMAND_LINE_TARGETS:
env.Append(PIOBUILDFILES=env.ProcessTest())
# build dependent libs # build dependent libs
deplibs = env.BuildProjectLibraries() env.Append(LIBS=env.BuildProjectLibraries())
# append specified LD_SCRIPT # append specified LD_SCRIPT
if ("LDSCRIPT_PATH" in env if ("LDSCRIPT_PATH" in env
@ -71,26 +81,14 @@ def BuildProgram(env):
env.Append(LINKFLAGS=['-Wl,-T"$LDSCRIPT_PATH"']) env.Append(LINKFLAGS=['-Wl,-T"$LDSCRIPT_PATH"'])
# enable "cyclic reference" for linker # enable "cyclic reference" for linker
if env.get("LIBS", deplibs) and env.GetCompilerType() == "gcc": if env.get("LIBS") and env.GetCompilerType() == "gcc":
env.Prepend(_LIBFLAGS="-Wl,--start-group ") env.Prepend(_LIBFLAGS="-Wl,--start-group ")
env.Append(_LIBFLAGS=" -Wl,--end-group") env.Append(_LIBFLAGS=" -Wl,--end-group")
# Handle SRC_BUILD_FLAGS # Handle SRC_BUILD_FLAGS
env.ProcessFlags(env.get("SRC_BUILD_FLAGS")) env.ProcessFlags(env.get("SRC_BUILD_FLAGS"))
env.Append( if not env.get("PIOBUILDFILES") and not COMMAND_LINE_TARGETS:
LIBS=deplibs,
LIBPATH=["$BUILD_DIR"],
PIOBUILDFILES=env.CollectBuildFiles(
"$BUILDSRC_DIR",
"$PROJECTSRC_DIR",
src_filter=env.get("SRC_FILTER"),
duplicate=False))
if "__test" in COMMAND_LINE_TARGETS:
env.Append(PIOBUILDFILES=env.ProcessTest())
if not env['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"))
@ -185,6 +183,7 @@ def MatchSourceFiles(env, src_dir, src_filter=None):
items.add(item.replace(src_dir + sep, "")) items.add(item.replace(src_dir + sep, ""))
src_dir = env.subst(src_dir) src_dir = env.subst(src_dir)
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
if isinstance(src_filter, (list, tuple)): if isinstance(src_filter, (list, tuple)):
src_filter = " ".join(src_filter) src_filter = " ".join(src_filter)
@ -269,12 +268,12 @@ def BuildLibrary(env, variant_dir, src_dir, src_filter=None):
lib = env.Clone() lib = env.Clone()
return lib.StaticLibrary( return lib.StaticLibrary(
lib.subst(variant_dir), lib.subst(variant_dir),
lib.CollectBuildFiles(variant_dir, src_dir, src_filter=src_filter)) lib.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):
DefaultEnvironment().Append(PIOBUILDFILES=env.Clone().CollectBuildFiles( DefaultEnvironment().Append(PIOBUILDFILES=env.Clone().CollectBuildFiles(
variant_dir, src_dir, src_filter=src_filter)) variant_dir, src_dir, src_filter))
def exists(_): def exists(_):

View File

@ -16,7 +16,6 @@ import json
import click import click
from platformio.exception import APIRequestError, InternetIsOffline
from platformio.managers.platform import PlatformManager from platformio.managers.platform import PlatformManager
@ -43,6 +42,7 @@ def cli(query, installed, json_output): # pylint: disable=R0912
click.secho(platform, bold=True) click.secho(platform, bold=True)
click.echo("-" * terminal_width) click.echo("-" * terminal_width)
print_boards(boards) print_boards(boards)
return True
def print_boards(boards): def print_boards(boards):
@ -80,27 +80,13 @@ def print_boards(boards):
def _get_boards(installed=False): def _get_boards(installed=False):
boards = PlatformManager().get_installed_boards() pm = PlatformManager()
if not installed: return pm.get_installed_boards() if installed else pm.get_all_boards()
know_boards = ["%s:%s" % (b['platform'], b['id']) for b in boards]
try:
for board in PlatformManager().get_registered_boards():
key = "%s:%s" % (board['platform'], board['id'])
if key not in know_boards:
boards.append(board)
except InternetIsOffline:
pass
return sorted(boards, key=lambda b: b['name'])
def _print_boards_json(query, installed=False): def _print_boards_json(query, installed=False):
result = [] result = []
try: for board in _get_boards(installed):
boards = _get_boards(installed)
except APIRequestError:
if not installed:
boards = _get_boards(True)
for board in boards:
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():

View File

@ -28,19 +28,71 @@ def cli():
@cli.command("list", short_help="List devices") @cli.command("list", short_help="List devices")
@click.option("--serial", is_flag=True, help="List serial ports, default")
@click.option("--logical", is_flag=True, help="List logical devices")
@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(json_output): def device_list( # pylint: disable=too-many-branches
serial, logical, mdns, json_output):
if not logical and not mdns:
serial = True
data = {}
if serial:
data['serial'] = util.get_serial_ports()
if logical:
data['logical'] = util.get_logical_devices()
if mdns:
data['mdns'] = util.get_mdns_services()
single_key = data.keys()[0] if len(data.keys()) == 1 else None
if json_output: if json_output:
click.echo(json.dumps(util.get_serialports())) return click.echo(json.dumps(data[single_key] if single_key else data))
return
for item in util.get_serialports(): titles = {
click.secho(item['port'], fg="cyan") "serial": "Serial Ports",
click.echo("-" * len(item['port'])) "logical": "Logical Devices",
click.echo("Hardware ID: %s" % item['hwid']) "mdns": "Multicast DNS Services"
click.echo("Description: %s" % item['description']) }
click.echo("")
for key, value in data.iteritems():
if not single_key:
click.secho(titles[key], bold=True)
click.echo("=" * len(titles[key]))
if key == "serial":
for item in value:
click.secho(item['port'], fg="cyan")
click.echo("-" * len(item['port']))
click.echo("Hardware ID: %s" % item['hwid'])
click.echo("Description: %s" % item['description'])
click.echo("")
if key == "logical":
for item in value:
click.secho(item['path'], fg="cyan")
click.echo("-" * len(item['path']))
click.echo("Name: %s" % item['name'])
click.echo("")
if key == "mdns":
for item in value:
click.secho(item['name'], fg="cyan")
click.echo("-" * len(item['name']))
click.echo("Type: %s" % item['type'])
click.echo("IP: %s" % item['ip'])
click.echo("Port: %s" % item['port'])
if item['properties']:
click.echo("Properties: %s" % ("; ".join([
"%s=%s" % (k, v)
for k, v in item['properties'].iteritems()
])))
click.echo("")
if single_key:
click.echo("")
return True
@cli.command("monitor", short_help="Monitor device (Serial)") @cli.command("monitor", short_help="Monitor device (Serial)")
@ -123,7 +175,7 @@ def device_monitor(**kwargs): # pylint: disable=too-many-branches
pass pass
if not kwargs['port']: if not kwargs['port']:
ports = util.get_serialports(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']
@ -154,7 +206,7 @@ def device_monitor(**kwargs): # pylint: disable=too-many-branches
def get_project_options(project_dir, environment): def get_project_options(project_dir, environment):
config = util.load_project_config(project_dir) config = util.load_project_config(project_dir)
if not config.sections(): if not config.sections():
return return None
known_envs = [s[4:] for s in config.sections() if s.startswith("env:")] known_envs = [s[4:] for s in config.sections() if s.startswith("env:")]
if environment: if environment:
@ -163,7 +215,7 @@ def get_project_options(project_dir, environment):
raise exception.UnknownEnvNames(environment, ", ".join(known_envs)) raise exception.UnknownEnvNames(environment, ", ".join(known_envs))
if not known_envs: if not known_envs:
return return None
if config.has_option("platformio", "env_default"): if config.has_option("platformio", "env_default"):
env_default = config.get("platformio", env_default = config.get("platformio",

View File

@ -0,0 +1,42 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import sys
import click
import requests
from platformio.managers.core import pioplus_call
@click.command("home", short_help="PIO Home")
@click.option("--port", type=int, default=8008, help="HTTP port, default=8008")
@click.option(
"--host",
default="127.0.0.1",
help="HTTP host, default=127.0.0.1. "
"You can open PIO Home for inbound connections with --host=0.0.0.0")
@click.option("--no-open", is_flag=True)
def cli(*args, **kwargs): # pylint: disable=unused-argument
pioplus_call(sys.argv[1:])
def shutdown_servers():
port = 8010
while port < 9000:
try:
requests.get("http://127.0.0.1:%d?__shutdown__=1" % port)
port += 1
except: # pylint: disable=bare-except
return

View File

@ -231,11 +231,6 @@ def init_ci_conf(project_dir):
# python: # python:
# - "2.7" # - "2.7"
# #
# sudo: false
# cache:
# directories:
# - "~/.platformio"
#
# install: # install:
# - pip install -U platformio # - pip install -U platformio
# #
@ -251,11 +246,6 @@ def init_ci_conf(project_dir):
# python: # python:
# - "2.7" # - "2.7"
# #
# sudo: false
# cache:
# directories:
# - "~/.platformio"
#
# env: # env:
# - PLATFORMIO_CI_SRC=path/to/test/file.c # - PLATFORMIO_CI_SRC=path/to/test/file.c
# - PLATFORMIO_CI_SRC=examples/file.ino # - PLATFORMIO_CI_SRC=examples/file.ino

View File

@ -33,8 +33,7 @@ from platformio.util import get_api_result
"-g", "-g",
"--global", "--global",
is_flag=True, is_flag=True,
help="Manage global PlatformIO" help="Manage global PlatformIO library storage")
" library storage `%s`" % join(util.get_home_dir(), "lib"))
@click.option( @click.option(
"-d", "-d",
"--storage-dir", "--storage-dir",
@ -93,11 +92,17 @@ def cli(ctx, **options):
"--interactive", "--interactive",
is_flag=True, is_flag=True,
help="Allow to make a choice for all prompts") help="Allow to make a choice for all prompts")
@click.option(
"-f",
"--force",
is_flag=True,
help="Reinstall/redownload library if exists")
@click.pass_obj @click.pass_obj
def lib_install(lm, libraries, silent, interactive): def lib_install(lm, libraries, silent, interactive, force):
# @TODO "save" option # @TODO "save" option
for library in libraries: for library in libraries:
lm.install(library, silent=silent, interactive=interactive) lm.install(
library, silent=silent, interactive=interactive, force=force)
@cli.command("uninstall", short_help="Uninstall libraries") @cli.command("uninstall", short_help="Uninstall libraries")
@ -128,7 +133,7 @@ def lib_update(lm, libraries, only_check, json_output):
requirements = None requirements = None
url = None url = None
if not pkg_dir: if not pkg_dir:
name, requirements, url = lm.parse_pkg_input(library) name, requirements, url = lm.parse_pkg_uri(library)
pkg_dir = lm.get_package_dir(name, requirements, url) pkg_dir = lm.get_package_dir(name, requirements, url)
if not pkg_dir: if not pkg_dir:
continue continue
@ -143,6 +148,8 @@ def lib_update(lm, libraries, only_check, json_output):
for library in libraries: for library in libraries:
lm.update(library, only_check=only_check) lm.update(library, only_check=only_check)
return True
def print_lib_item(item): def print_lib_item(item):
click.secho(item['name'], fg="cyan") click.secho(item['name'], fg="cyan")
@ -204,7 +211,7 @@ def lib_search(query, json_output, page, noninteractive, **filters):
result = get_api_result( result = get_api_result(
"/v2/lib/search", "/v2/lib/search",
dict(query=" ".join(query), page=page), dict(query=" ".join(query), page=page),
cache_valid="3d") cache_valid="1d")
if json_output: if json_output:
click.echo(json.dumps(result)) click.echo(json.dumps(result))
@ -234,8 +241,8 @@ def lib_search(query, json_output, page, noninteractive, **filters):
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']) >= if (int(result['page']) * int(result['perpage']) >= int(
int(result['total'])): result['total'])):
break break
if noninteractive: if noninteractive:
@ -252,7 +259,7 @@ def lib_search(query, json_output, page, noninteractive, **filters):
"/v2/lib/search", "/v2/lib/search",
{"query": " ".join(query), {"query": " ".join(query),
"page": int(result['page']) + 1}, "page": int(result['page']) + 1},
cache_valid="3d") cache_valid="1d")
@cli.command("list", short_help="List installed libraries") @cli.command("list", short_help="List installed libraries")
@ -265,11 +272,13 @@ def lib_list(lm, json_output):
return click.echo(json.dumps(items)) return click.echo(json.dumps(items))
if not items: if not items:
return return None
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)
return True
@util.memoized @util.memoized
def get_builtin_libs(storage_names=None): def get_builtin_libs(storage_names=None):
@ -308,13 +317,15 @@ def lib_builtin(storage, json_output):
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
@cli.command("show", short_help="Show detailed info about a library") @cli.command("show", short_help="Show detailed info about a library")
@click.argument("library", metavar="[LIBRARY]") @click.argument("library", metavar="[LIBRARY]")
@click.option("--json-output", is_flag=True) @click.option("--json-output", is_flag=True)
def lib_show(library, json_output): def lib_show(library, json_output):
lm = LibraryManager() lm = LibraryManager()
name, requirements, _ = lm.parse_pkg_input(library) name, requirements, _ = lm.parse_pkg_uri(library)
lib_id = lm.get_pkg_id_by_name( lib_id = lm.get_pkg_id_by_name(
name, requirements, silent=json_output, interactive=not json_output) name, requirements, silent=json_output, interactive=not json_output)
lib = get_api_result("/lib/info/%d" % lib_id, cache_valid="1d") lib = get_api_result("/lib/info/%d" % lib_id, cache_valid="1d")
@ -381,6 +392,8 @@ def lib_show(library, json_output):
for row in rows: for row in rows:
click.echo(row) click.echo(row)
return True
@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")
@ -438,8 +451,8 @@ def lib_stats(json_output):
printitem_tpl.format( printitem_tpl.format(
name=click.style(name, fg="cyan"), name=click.style(name, fg="cyan"),
url=click.style( url=click.style(
"http://platformio.org/lib/search?query=" + quote( "http://platformio.org/lib/search?query=" +
"keyword:%s" % name), quote("keyword:%s" % name),
fg="blue"))) fg="blue")))
for key in ("updated", "added"): for key in ("updated", "added"):
@ -468,3 +481,5 @@ def lib_stats(json_output):
for item in result.get(key, []): for item in result.get(key, []):
_print_lib_item(item) _print_lib_item(item)
click.echo() click.echo()
return True

View File

@ -47,7 +47,7 @@ def _print_platforms(platforms):
def _get_registry_platforms(): def _get_registry_platforms():
platforms = util.get_api_result("/platforms", cache_valid="30d") 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'])
@ -188,7 +188,7 @@ def platform_search(query, json_output):
@click.option("--json-output", is_flag=True) @click.option("--json-output", is_flag=True)
def platform_frameworks(query, json_output): def platform_frameworks(query, json_output):
frameworks = [] frameworks = []
for framework in util.get_api_result("/frameworks", cache_valid="30d"): for framework in util.get_api_result("/frameworks", cache_valid="7d"):
if query == "all": if query == "all":
query = "" query = ""
search_data = json.dumps(framework) search_data = json.dumps(framework)
@ -257,7 +257,7 @@ def platform_show(platform, json_output): # pylint: disable=too-many-branches
click.echo("Frameworks: %s" % ", ".join(data['frameworks'])) click.echo("Frameworks: %s" % ", ".join(data['frameworks']))
if not data['packages']: if not data['packages']:
return 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']))
@ -287,21 +287,29 @@ def platform_show(platform, json_output): # pylint: disable=too-many-branches
click.echo("------") click.echo("------")
print_boards(data['boards']) print_boards(data['boards'])
return True
@cli.command("install", short_help="Install new development platform") @cli.command("install", short_help="Install new development platform")
@click.argument("platforms", nargs=-1, required=True, metavar="[PLATFORM...]") @click.argument("platforms", nargs=-1, required=True, metavar="[PLATFORM...]")
@click.option("--with-package", multiple=True) @click.option("--with-package", multiple=True)
@click.option("--without-package", multiple=True) @click.option("--without-package", multiple=True)
@click.option("--skip-default-package", is_flag=True) @click.option("--skip-default-package", is_flag=True)
@click.option(
"-f",
"--force",
is_flag=True,
help="Reinstall/redownload dev/platform and its packages if exist")
def platform_install(platforms, with_package, without_package, def platform_install(platforms, with_package, without_package,
skip_default_package): skip_default_package, force):
pm = PlatformManager() pm = PlatformManager()
for platform in platforms: for platform in platforms:
if pm.install( if pm.install(
name=platform, name=platform,
with_packages=with_package, with_packages=with_package,
without_packages=without_package, without_packages=without_package,
skip_default_package=skip_default_package): skip_default_package=skip_default_package,
force=force):
click.secho( click.secho(
"The platform '%s' has been successfully installed!\n" "The platform '%s' has been successfully installed!\n"
"The rest of packages will be installed automatically " "The rest of packages will be installed automatically "
@ -351,7 +359,7 @@ def platform_update(platforms, only_packages, only_check, json_output):
requirements = None requirements = None
url = None url = None
if not pkg_dir: if not pkg_dir:
name, requirements, url = pm.parse_pkg_input(platform) name, requirements, url = pm.parse_pkg_uri(platform)
pkg_dir = pm.get_package_dir(name, requirements, url) pkg_dir = pm.get_package_dir(name, requirements, url)
if not pkg_dir: if not pkg_dir:
continue continue
@ -375,3 +383,5 @@ def platform_update(platforms, only_packages, only_check, json_output):
pm.update( pm.update(
platform, only_packages=only_packages, only_check=only_check) platform, only_packages=only_packages, only_check=only_check)
click.echo() click.echo()
return True

View File

@ -132,8 +132,8 @@ class EnvironmentProcessor(object):
"upload_protocol", "upload_speed", "upload_flags", "upload_protocol", "upload_speed", "upload_flags",
"upload_resetmethod", "lib_deps", "lib_ignore", "upload_resetmethod", "lib_deps", "lib_ignore",
"lib_extra_dirs", "lib_ldf_mode", "lib_compat_mode", "lib_extra_dirs", "lib_ldf_mode", "lib_compat_mode",
"lib_archive", "piotest", "test_transport", "test_ignore", "lib_archive", "piotest", "test_transport", "test_filter",
"test_port", "debug_tool", "debug_port", "test_ignore", "test_port", "debug_tool", "debug_port",
"debug_init_cmds", "debug_extra_cmds", "debug_server", "debug_init_cmds", "debug_extra_cmds", "debug_server",
"debug_init_break", "debug_load_cmd", "monitor_port", "debug_init_break", "debug_load_cmd", "monitor_port",
"monitor_baud", "monitor_rts", "monitor_dtr") "monitor_baud", "monitor_rts", "monitor_dtr")
@ -180,13 +180,14 @@ class EnvironmentProcessor(object):
self.options[k] = self.options[k].strip() self.options[k] = self.options[k].strip()
if not self.silent: if not self.silent:
click.echo( click.echo("[%s] Processing %s (%s)" %
"[%s] Processing %s (%s)" % (datetime.now().strftime("%c"),
(datetime.now().strftime("%c"), click.style(self.name, fg="cyan", bold=True),
click.style(self.name, fg="cyan", bold=True), "; ".join([ "; ".join([
"%s: %s" % (k, ", ".join(util.parse_conf_multi_values(v))) "%s: %s" %
for k, v in self.options.items() (k, ", ".join(util.parse_conf_multi_values(v)))
]))) for k, v in self.options.items()
])))
click.secho("-" * terminal_width, bold=True) click.secho("-" * terminal_width, bold=True)
self.options = self._validate_options(self.options) self.options = self._validate_options(self.options)
@ -227,7 +228,7 @@ class EnvironmentProcessor(object):
v = self.RENAMED_PLATFORMS[v] v = self.RENAMED_PLATFORMS[v]
# warn about unknown options # warn about unknown options
if k not in self.KNOWN_OPTIONS: if k not in self.KNOWN_OPTIONS and not k.startswith("custom_"):
click.secho( click.secho(
"Detected non-PlatformIO `%s` option in `[env:%s]` section" "Detected non-PlatformIO `%s` option in `[env:%s]` section"
% (k, self.name), % (k, self.name),
@ -278,10 +279,10 @@ class EnvironmentProcessor(object):
if d.strip() if d.strip()
], self.verbose) ], self.verbose)
if "lib_deps" in self.options: if "lib_deps" in self.options:
_autoinstall_libdeps( _autoinstall_libdeps(self.cmd_ctx,
self.cmd_ctx, util.parse_conf_multi_values(
util.parse_conf_multi_values(self.options['lib_deps']), self.options['lib_deps']),
self.verbose) self.verbose)
try: try:
p = PlatformFactory.newPlatform(self.options['platform']) p = PlatformFactory.newPlatform(self.options['platform'])
@ -323,8 +324,8 @@ def _clean_pioenvs_dir(pioenvs_dir):
# if project's config is modified # if project's config is modified
if (isdir(pioenvs_dir) if (isdir(pioenvs_dir)
and getmtime(join(util.get_project_dir(), "platformio.ini")) > and getmtime(join(util.get_project_dir(),
getmtime(pioenvs_dir)): "platformio.ini")) > getmtime(pioenvs_dir)):
util.rmtree_(pioenvs_dir) util.rmtree_(pioenvs_dir)
# check project structure # check project structure

View File

@ -32,8 +32,8 @@ def settings_get(name):
click.echo( click.echo(
list_tpl.format( list_tpl.format(
name=click.style("Name", fg="cyan"), name=click.style("Name", fg="cyan"),
value=(click.style("Value", fg="green") + click.style( value=(click.style("Value", fg="green") +
" [Default]", fg="yellow")), click.style(" [Default]", fg="yellow")),
description="Description")) description="Description"))
click.echo("-" * terminal_width) click.echo("-" * terminal_width)

View File

@ -18,62 +18,61 @@ import click
import requests import requests
from platformio import VERSION, __version__, exception, util from platformio import VERSION, __version__, exception, util
from platformio.managers.core import update_core_packages from platformio.commands.home import shutdown_servers
@click.command( @click.command(
"upgrade", short_help="Upgrade PlatformIO to the latest version") "upgrade", short_help="Upgrade PlatformIO to the latest version")
def cli(): @click.option("--dev", is_flag=True, help="Use development branch")
# Update PlatformIO's Core packages def cli(dev):
update_core_packages(silent=True) if not dev and __version__ == get_latest_version():
latest = get_latest_version()
if __version__ == latest:
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")
else:
click.secho("Please wait while upgrading PlatformIO ...", fg="yellow")
to_develop = not all([c.isdigit() for c in latest if c != "."]) click.secho("Please wait while upgrading PlatformIO ...", fg="yellow")
cmds = ([
"pip", "install", "--upgrade",
"https://github.com/platformio/platformio-core/archive/develop.zip"
if to_develop else "platformio"
], ["platformio", "--version"])
cmd = None # kill all PIO Home servers, they block `pioplus` binary
r = None shutdown_servers()
try:
for cmd in cmds: to_develop = dev or not all([c.isdigit() for c in __version__ if c != "."])
cmd = [util.get_pythonexe_path(), "-m"] + cmd cmds = ([
r = None "pip", "install", "--upgrade",
"https://github.com/platformio/platformio-core/archive/develop.zip"
if to_develop else "platformio"
], ["platformio", "--version"])
cmd = None
r = None
try:
for cmd in cmds:
cmd = [util.get_pythonexe_path(), "-m"] + cmd
r = None
r = util.exec_command(cmd)
# try pip with disabled cache
if r['returncode'] != 0 and cmd[2] == "pip":
cmd.insert(3, "--no-cache-dir")
r = util.exec_command(cmd) r = util.exec_command(cmd)
# try pip with disabled cache assert r['returncode'] == 0
if r['returncode'] != 0 and cmd[2] == "pip": assert "version" in r['out']
cmd.insert(3, "--no-cache-dir") actual_version = r['out'].strip().split("version", 1)[1].strip()
r = util.exec_command(cmd) click.secho(
"PlatformIO has been successfully upgraded to %s" % actual_version,
assert r['returncode'] == 0 fg="green")
assert "version" in r['out'] click.echo("Release notes: ", nl=False)
actual_version = r['out'].strip().split("version", 1)[1].strip() click.secho(
"http://docs.platformio.org/en/latest/history.html", fg="cyan")
except Exception as e: # pylint: disable=broad-except
if not r:
raise exception.UpgradeError("\n".join([str(cmd), str(e)]))
permission_errors = ("permission denied", "not permitted")
if (any([m in r['err'].lower() for m in permission_errors])
and "windows" not in util.get_systype()):
click.secho( click.secho(
"PlatformIO has been successfully upgraded to %s" % """
actual_version,
fg="green")
click.echo("Release notes: ", nl=False)
click.secho(
"http://docs.platformio.org/en/latest/history.html", fg="cyan")
except Exception as e: # pylint: disable=W0703
if not r:
raise exception.UpgradeError("\n".join([str(cmd), str(e)]))
permission_errors = ("permission denied", "not permitted")
if (any([m in r['err'].lower() for m in permission_errors])
and "windows" not in util.get_systype()):
click.secho(
"""
----------------- -----------------
Permission denied Permission denied
----------------- -----------------
@ -83,12 +82,14 @@ 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)
else: else:
raise exception.UpgradeError( raise exception.UpgradeError("\n".join(
"\n".join([str(cmd), r['out'], r['err']])) [str(cmd), r['out'], r['err']]))
return True
def get_latest_version(): def get_latest_version():

View File

@ -15,6 +15,7 @@
from email.utils import parsedate_tz from email.utils import parsedate_tz
from math import ceil from math import ceil
from os.path import getsize, join from os.path import getsize, join
from sys import getfilesystemencoding, version_info
from time import mktime from time import mktime
import click import click
@ -30,9 +31,13 @@ class FileDownloader(object):
CHUNK_SIZE = 1024 CHUNK_SIZE = 1024
def __init__(self, url, dest_dir=None): def __init__(self, url, dest_dir=None):
self._request = None
# make connection # make connection
self._request = requests.get( self._request = requests.get(
url, stream=True, headers=util.get_request_defheaders()) url,
stream=True,
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)
@ -48,7 +53,8 @@ class FileDownloader(object):
self._progressbar = None self._progressbar = None
self._destination = self._fname self._destination = self._fname
if dest_dir: if dest_dir:
self.set_destination(join(dest_dir, self._fname)) self.set_destination(
join(dest_dir.decode(getfilesystemencoding()), self._fname))
def set_destination(self, destination): def set_destination(self, destination):
self._destination = destination self._destination = destination
@ -65,21 +71,29 @@ class FileDownloader(object):
return int(self._request.headers['content-length']) return int(self._request.headers['content-length'])
def start(self): def start(self):
label = "Downloading"
itercontent = self._request.iter_content(chunk_size=self.CHUNK_SIZE) itercontent = self._request.iter_content(chunk_size=self.CHUNK_SIZE)
f = open(self._destination, "wb") f = open(self._destination, "wb")
try:
if app.is_disabled_progressbar() or self.get_size() == -1: if app.is_disabled_progressbar() or self.get_size() == -1:
click.echo("Downloading...") click.echo("%s..." % label)
for chunk in itercontent: for chunk in itercontent:
if chunk: if chunk:
f.write(chunk) f.write(chunk)
else: else:
chunks = int(ceil(self.get_size() / float(self.CHUNK_SIZE))) chunks = int(ceil(self.get_size() / float(self.CHUNK_SIZE)))
with click.progressbar(length=chunks, label="Downloading") as pb: with click.progressbar(length=chunks, label=label) as pb:
for _ in pb: for _ in pb:
f.write(next(itercontent)) f.write(next(itercontent))
f.close() except IOError as e:
self._request.close() click.secho(
"Error: Please read http://bit.ly/package-manager-ioerror",
fg="red",
err=True)
raise e
finally:
f.close()
self._request.close()
if self.get_lmtime(): if self.get_lmtime():
self._preserve_filemtime(self.get_lmtime()) self._preserve_filemtime(self.get_lmtime())

View File

@ -53,15 +53,15 @@ class IncompatiblePlatform(PlatformioException):
class PlatformNotInstalledYet(PlatformioException): class PlatformNotInstalledYet(PlatformioException):
MESSAGE = "The platform '{0}' has not been installed yet. "\ MESSAGE = ("The platform '{0}' has not been installed yet. "
"Use `platformio platform install {0}` command" "Use `platformio platform install {0}` command")
class BoardNotDefined(PlatformioException): class BoardNotDefined(PlatformioException):
MESSAGE = "You need to specify board ID using `-b` or `--board` "\ MESSAGE = (
"option. Supported boards list is available via "\ "You need to specify board ID using `-b` or `--board` option. "
"`platformio boards` command" "Supported boards list is available via `platformio boards` command")
class UnknownBoard(PlatformioException): class UnknownBoard(PlatformioException):
@ -91,16 +91,16 @@ class MissingPackageManifest(PlatformioException):
class UndefinedPackageVersion(PlatformioException): class UndefinedPackageVersion(PlatformioException):
MESSAGE = "Could not find a version that satisfies the requirement '{0}'"\ MESSAGE = ("Could not find a version that satisfies the requirement '{0}'"
" for your system '{1}'" " for your system '{1}'")
class PackageInstallError(PlatformioException): class PackageInstallError(PlatformioException):
MESSAGE = "Could not install '{0}' with version requirements '{1}' "\ MESSAGE = (
"for your system '{2}'.\n"\ "Could not install '{0}' with version requirements '{1}' for your "
"If you use Antivirus, it can block PlatformIO Package "\ "system '{2}'.\n If you use Antivirus, it can block PlatformIO "
"Manager. Try to disable it for a while." "Package Manager. Try to disable it for a while.")
class FDUnrecognizedStatusCode(PlatformioException): class FDUnrecognizedStatusCode(PlatformioException):
@ -110,21 +110,22 @@ class FDUnrecognizedStatusCode(PlatformioException):
class FDSizeMismatch(PlatformioException): class FDSizeMismatch(PlatformioException):
MESSAGE = "The size ({0:d} bytes) of downloaded file '{1}' "\ MESSAGE = ("The size ({0:d} bytes) of downloaded file '{1}' "
"is not equal to remote size ({2:d} bytes)" "is not equal to remote size ({2:d} bytes)")
class FDSHASumMismatch(PlatformioException): class FDSHASumMismatch(PlatformioException):
MESSAGE = "The 'sha1' sum '{0}' of downloaded file '{1}' "\ MESSAGE = ("The 'sha1' sum '{0}' of downloaded file '{1}' "
"is not equal to remote '{2}'" "is not equal to remote '{2}'")
class NotPlatformIOProject(PlatformioException): class NotPlatformIOProject(PlatformioException):
MESSAGE = "Not a PlatformIO project. `platformio.ini` file has not been "\ MESSAGE = (
"found in current working directory ({0}). To initialize new project "\ "Not a PlatformIO project. `platformio.ini` file has not been "
"please use `platformio init` command" "found in current working directory ({0}). To initialize new project "
"please use `platformio init` command")
class UndefinedEnvPlatform(PlatformioException): class UndefinedEnvPlatform(PlatformioException):
@ -164,24 +165,27 @@ class APIRequestError(PlatformioException):
class InternetIsOffline(PlatformioException): class InternetIsOffline(PlatformioException):
MESSAGE = "You are not connected to the Internet" MESSAGE = (
"You are not connected to the Internet.\n"
"If you build a project first time, we need Internet connection "
"to install all dependencies and toolchain.")
class LibNotFound(PlatformioException): class LibNotFound(PlatformioException):
MESSAGE = "Library `{0}` has not been found in PlatformIO Registry.\n"\ MESSAGE = ("Library `{0}` has not been found in PlatformIO Registry.\n"
"You can ignore this message, if `{0}` is a built-in library "\ "You can ignore this message, if `{0}` is a built-in library "
"(included in framework, SDK). E.g., SPI, Wire, etc." "(included in framework, SDK). E.g., SPI, Wire, etc.")
class NotGlobalLibDir(PlatformioException): class NotGlobalLibDir(PlatformioException):
MESSAGE = "The `{0}` is not a PlatformIO project.\n\n"\ MESSAGE = (
"To manage libraries "\ "The `{0}` is not a PlatformIO project.\n\n"
"in global storage `{1}`,\n"\ "To manage libraries in global storage `{1}`,\n"
"please use `platformio lib --global {2}` or specify custom "\ "please use `platformio lib --global {2}` or specify custom storage "
"storage `platformio lib --storage-dir /path/to/storage/ {2}`."\ "`platformio lib --storage-dir /path/to/storage/ {2}`.\n"
"\nCheck `platformio lib --help` for details." "Check `platformio lib --help` for details.")
class InvalidLibConfURL(PlatformioException): class InvalidLibConfURL(PlatformioException):
@ -206,9 +210,9 @@ class InvalidSettingValue(PlatformioException):
class CIBuildEnvsEmpty(PlatformioException): class CIBuildEnvsEmpty(PlatformioException):
MESSAGE = "Can't find PlatformIO build environments.\n"\ MESSAGE = ("Can't find PlatformIO build environments.\n"
"Please specify `--board` or path to `platformio.ini` with "\ "Please specify `--board` or path to `platformio.ini` with "
"predefined environments using `--project-conf` option" "predefined environments using `--project-conf` option")
class UpgradeError(PlatformioException): class UpgradeError(PlatformioException):
@ -221,7 +225,17 @@ class UpgradeError(PlatformioException):
""" """
class HomeDirPermissionsError(PlatformioException):
MESSAGE = (
"The directory `{0}` or its parent directory is not owned by the "
"current user and PlatformIO can not store configuration data.\n"
"Please check the permissions and owner of that directory.\n"
"Otherwise, please remove manually `{0}` directory and PlatformIO "
"will create new from the current user.")
class CygwinEnvDetected(PlatformioException): class CygwinEnvDetected(PlatformioException):
MESSAGE = "PlatformIO does not work within Cygwin environment. "\ MESSAGE = ("PlatformIO does not work within Cygwin environment. "
"Use native Terminal instead." "Use native Terminal instead.")

View File

@ -15,11 +15,10 @@
import json import json
import os import os
import re import re
from cStringIO import StringIO
from os.path import abspath, basename, expanduser, isdir, isfile, join, relpath from os.path import abspath, basename, expanduser, isdir, isfile, join, relpath
import bottle import bottle
import click from click.testing import CliRunner
from platformio import exception, util from platformio import exception, util
from platformio.commands.run import cli as cmd_run from platformio.commands.run import cli as cmd_run
@ -60,24 +59,28 @@ class ProjectGenerator(object):
@util.memoized @util.memoized
def get_project_build_data(self): def get_project_build_data(self):
data = {"defines": [], "includes": [], "cxx_path": None} data = {
"defines": [],
"includes": [],
"cxx_path": None,
"prog_path": None
}
envdata = self.get_project_env() envdata = self.get_project_env()
if not envdata: if not envdata:
return data return data
out = StringIO() result = CliRunner().invoke(cmd_run, [
with util.capture_stdout(out): "--project-dir", self.project_dir, "--environment",
click.get_current_context().invoke( envdata['env_name'], "--target", "idedata"
cmd_run, ])
project_dir=self.project_dir,
environment=[envdata['env_name']],
target=["idedata"])
result = out.getvalue()
if '"includes":' not in result: if result.exit_code != 0 and not isinstance(result.exception,
raise exception.PlatformioException(result) exception.ReturnErrorCode):
raise result.exception
if '"includes":' not in result.output:
raise exception.PlatformioException(result.output)
for line in result.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("}"):
data = json.loads(line) data = json.loads(line)

View File

@ -1,3 +1,3 @@
.pioenvs .pioenvs
.piolibdeps .piolibdeps
.vscode .vscode/c_cpp_properties.json

View File

@ -31,7 +31,8 @@
"{{!define.replace('"', '\\"')}}", "{{!define.replace('"', '\\"')}}",
% end % end
"" ""
] ],
"intelliSenseMode": "clang-x64"
} }
] ]
} }

View File

@ -34,33 +34,10 @@ from platformio.managers.lib import LibraryManager
from platformio.managers.platform import PlatformFactory, PlatformManager from platformio.managers.platform import PlatformFactory, PlatformManager
def in_silence(ctx=None):
ctx = ctx or app.get_session_var("command_ctx")
assert ctx
ctx_args = ctx.args or []
return ctx_args and any([
ctx.args[0] == "upgrade", "--json-output" in ctx_args,
"--version" in ctx_args
])
def on_platformio_start(ctx, force, caller): def on_platformio_start(ctx, force, caller):
if not caller:
if getenv("PLATFORMIO_CALLER"):
caller = getenv("PLATFORMIO_CALLER")
elif getenv("VSCODE_PID") or getenv("VSCODE_NLS_CONFIG"):
caller = "vscode"
elif util.is_container():
if getenv("C9_UID"):
caller = "C9"
elif getenv("USER") == "cabox":
caller = "CA"
elif getenv("CHE_API", getenv("CHE_API_ENDPOINT")):
caller = "Che"
app.set_session_var("command_ctx", ctx) app.set_session_var("command_ctx", ctx)
app.set_session_var("force_option", force) app.set_session_var("force_option", force)
app.set_session_var("caller_id", caller) set_caller(caller)
telemetry.on_command() telemetry.on_command()
if not in_silence(ctx): if not in_silence(ctx):
@ -75,7 +52,8 @@ def on_platformio_end(ctx, result): # pylint: disable=W0613
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.GetLatestVersionError, exception.APIRequestError): except (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.",
@ -86,6 +64,32 @@ def on_platformio_exception(e):
telemetry.on_exception(e) telemetry.on_exception(e)
def in_silence(ctx=None):
ctx = ctx or app.get_session_var("command_ctx")
assert ctx
ctx_args = ctx.args or []
return ctx_args and any([
ctx.args[0] == "upgrade", "--json-output" in ctx_args,
"--version" in ctx_args
])
def set_caller(caller=None):
if not caller:
if getenv("PLATFORMIO_CALLER"):
caller = getenv("PLATFORMIO_CALLER")
elif getenv("VSCODE_PID") or getenv("VSCODE_NLS_CONFIG"):
caller = "vscode"
elif util.is_container():
if getenv("C9_UID"):
caller = "C9"
elif getenv("USER") == "cabox":
caller = "CA"
elif getenv("CHE_API", getenv("CHE_API_ENDPOINT")):
caller = "Che"
app.set_session_var("caller_id", caller)
class Upgrader(object): class Upgrader(object):
def __init__(self, from_version, to_version): def __init__(self, from_version, to_version):
@ -98,7 +102,7 @@ class Upgrader(object):
self._upgrade_to_3_0_0), self._upgrade_to_3_0_0),
(semantic_version.Version("3.0.0-b.11"), (semantic_version.Version("3.0.0-b.11"),
self._upgrade_to_3_0_0b11), self._upgrade_to_3_0_0b11),
(semantic_version.Version("3.4.0-a.9"), (semantic_version.Version("3.5.0-a.2"),
self._update_dev_platforms)] self._update_dev_platforms)]
def run(self, ctx): def run(self, ctx):
@ -234,12 +238,14 @@ def check_platformio_upgrade():
if (time() - interval) < last_check.get("platformio_upgrade", 0): if (time() - interval) < last_check.get("platformio_upgrade", 0):
return return
# Update PlatformIO's Core packages
update_core_packages(silent=True)
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)
# Update PlatformIO's Core packages
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(util.pepver_to_semver(
latest_version)) <= semantic_version.Version.coerce( latest_version)) <= semantic_version.Version.coerce(
@ -282,6 +288,8 @@ def check_internal_updates(ctx, what):
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)
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():

View File

@ -15,14 +15,15 @@
import os import os
import subprocess import subprocess
import sys import sys
from os.path import join from os.path import dirname, join
from platformio import __version__, exception, util from platformio import __version__, exception, util
from platformio.managers.package import PackageManager from platformio.managers.package import PackageManager
CORE_PACKAGES = { CORE_PACKAGES = {
"pysite-pioplus": ">=0.3.0,<2", "contrib-piohome": ">=0.6.0,<2",
"tool-pioplus": ">=0.9.1,<2", "contrib-pysite": ">=0.1.2,<2",
"tool-pioplus": ">=0.12.1,<2",
"tool-unity": "~1.20302.1", "tool-unity": "~1.20302.1",
"tool-scons": "~3.20501.2" "tool-scons": "~3.20501.2"
} }
@ -35,15 +36,18 @@ PIOPLUS_AUTO_UPDATES_MAX = 100
class CorePackageManager(PackageManager): class CorePackageManager(PackageManager):
def __init__(self): def __init__(self):
PackageManager.__init__( PackageManager.__init__(self, join(util.get_home_dir(), "packages"), [
self, "https://dl.bintray.com/platformio/dl-packages/manifest.json",
join(util.get_home_dir(), "packages"), [ "http%s://dl.platformio.org/packages/manifest.json" %
"https://dl.bintray.com/platformio/dl-packages/manifest.json", ("" if sys.version_info < (2, 7, 9) else "s")
"http%s://dl.platformio.org/packages/manifest.json" % ])
("" if sys.version_info < (2, 7, 9) else "s")
])
def install(self, name, requirements=None, *args, **kwargs): def install( # pylint: disable=keyword-arg-before-vararg
self,
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)
@ -71,7 +75,8 @@ class CorePackageManager(PackageManager):
def get_core_package_dir(name): def get_core_package_dir(name):
assert name in CORE_PACKAGES if name not in CORE_PACKAGES:
raise exception.PlatformioException("Please upgrade PIO Core")
requirements = CORE_PACKAGES[name] requirements = CORE_PACKAGES[name]
pm = CorePackageManager() pm = CorePackageManager()
pkg_dir = pm.get_package_dir(name, requirements) pkg_dir = pm.get_package_dir(name, requirements)
@ -88,6 +93,7 @@ def update_core_packages(only_check=False, silent=False):
continue continue
if not silent or pm.outdated(pkg_dir, requirements): if not silent or pm.outdated(pkg_dir, requirements):
pm.update(name, requirements, only_check=only_check) pm.update(name, requirements, only_check=only_check)
return True
def pioplus_call(args, **kwargs): def pioplus_call(args, **kwargs):
@ -99,8 +105,11 @@ def pioplus_call(args, **kwargs):
sys.version.split()[0])) sys.version.split()[0]))
pioplus_path = join(get_core_package_dir("tool-pioplus"), "pioplus") pioplus_path = join(get_core_package_dir("tool-pioplus"), "pioplus")
os.environ['PYTHONEXEPATH'] = util.get_pythonexe_path() pythonexe_path = util.get_pythonexe_path()
os.environ['PYTHONPYSITEDIR'] = get_core_package_dir("pysite-pioplus") os.environ['PYTHONEXEPATH'] = pythonexe_path
os.environ['PYTHONPYSITEDIR'] = get_core_package_dir("contrib-pysite")
os.environ['PATH'] = (os.pathsep).join(
[dirname(pythonexe_path), os.environ['PATH']])
util.copy_pythonpath_to_osenv() util.copy_pythonpath_to_osenv()
code = subprocess.call([pioplus_path] + args, **kwargs) code = subprocess.call([pioplus_path] + args, **kwargs)
@ -124,3 +133,5 @@ def pioplus_call(args, **kwargs):
if code != 0: if code != 0:
raise exception.ReturnErrorCode(1) raise exception.ReturnErrorCode(1)
return True

View File

@ -21,7 +21,6 @@ from os.path import isdir, join
import arrow import arrow
import click import click
import semantic_version
from platformio import app, commands, exception, util from platformio import app, commands, exception, util
from platformio.managers.package import BasePkgManager from platformio.managers.package import BasePkgManager
@ -71,7 +70,10 @@ class LibraryManager(BasePkgManager):
del manifest['sentence'] del manifest['sentence']
if "author" in manifest: if "author" in manifest:
manifest['authors'] = [{"name": manifest['author']}] if isinstance(manifest['author'], dict):
manifest['authors'] = [manifest['author']]
else:
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):
@ -101,6 +103,7 @@ class LibraryManager(BasePkgManager):
"sam": "atmelsam", "sam": "atmelsam",
"samd": "atmelsam", "samd": "atmelsam",
"esp8266": "espressif8266", "esp8266": "espressif8266",
"esp32": "espressif32",
"arc32": "intel_arc32" "arc32": "intel_arc32"
} }
for arch in manifest['architectures'].split(","): for arch in manifest['architectures'].split(","):
@ -149,8 +152,7 @@ class LibraryManager(BasePkgManager):
] ]
return items return items
@staticmethod def max_satisfying_repo_version(self, versions, requirements=None):
def max_satisfying_repo_version(versions, requirements=None):
def _cmp_dates(datestr1, datestr2): def _cmp_dates(datestr1, datestr2):
date1 = arrow.get(datestr1) date1 = arrow.get(datestr1)
@ -159,29 +161,22 @@ class LibraryManager(BasePkgManager):
return 0 return 0
return -1 if date1 < date2 else 1 return -1 if date1 < date2 else 1
semver_spec = self.parse_semver_spec(
requirements) if requirements else None
item = None item = None
reqspec = None
if requirements:
try:
reqspec = semantic_version.Spec(requirements)
except ValueError:
pass
for v in versions:
specver = None
try:
specver = semantic_version.Version(v['name'], partial=True)
except ValueError:
pass
if reqspec: for v in versions:
if not specver or specver not in reqspec: semver_new = self.parse_semver_version(v['name'])
if semver_spec:
if not semver_new or semver_new not in semver_spec:
continue continue
if not item or semantic_version.Version( if not item or self.parse_semver_version(
item['name'], partial=True) < specver: 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:
@ -193,7 +188,7 @@ class LibraryManager(BasePkgManager):
util.get_api_result( util.get_api_result(
"/lib/info/%d" % self.get_pkg_id_by_name( "/lib/info/%d" % self.get_pkg_id_by_name(
name, requirements, silent=silent), name, requirements, silent=silent),
cache_valid="1d")['versions'], requirements) cache_valid="1h")['versions'], requirements)
return item['name'] if item else None return item['name'] if item else None
def get_pkg_id_by_name(self, def get_pkg_id_by_name(self,
@ -236,11 +231,11 @@ class LibraryManager(BasePkgManager):
requirements=None, requirements=None,
silent=False, silent=False,
trigger_event=True, trigger_event=True,
interactive=False): interactive=False,
force=False):
pkg_dir = None pkg_dir = None
try: try:
_name, _requirements, _url = self.parse_pkg_input( _name, _requirements, _url = self.parse_pkg_uri(name, requirements)
name, requirements)
if not _url: if not _url:
name = "id=%d" % self.get_pkg_id_by_name( name = "id=%d" % self.get_pkg_id_by_name(
_name, _name,
@ -248,15 +243,20 @@ class LibraryManager(BasePkgManager):
silent=silent, silent=silent,
interactive=interactive) interactive=interactive)
requirements = _requirements requirements = _requirements
pkg_dir = BasePkgManager.install(self, name, requirements, silent, pkg_dir = BasePkgManager.install(
trigger_event) self,
name,
requirements,
silent=silent,
trigger_event=trigger_event,
force=force)
except exception.InternetIsOffline as e: except exception.InternetIsOffline as e:
if not silent: if not silent:
click.secho(str(e), fg="yellow") click.secho(str(e), fg="yellow")
return return None
if not pkg_dir: if not pkg_dir:
return return None
manifest = self.load_manifest(pkg_dir) manifest = self.load_manifest(pkg_dir)
if "dependencies" not in manifest: if "dependencies" not in manifest:
@ -268,7 +268,12 @@ class LibraryManager(BasePkgManager):
for filters in self.normalize_dependencies(manifest['dependencies']): for filters in self.normalize_dependencies(manifest['dependencies']):
assert "name" in filters assert "name" in filters
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(
"{name}={version}".format(**filters),
silent=silent,
trigger_event=trigger_event,
interactive=interactive,
force=force)
else: else:
try: try:
lib_info = self.search_for_library(filters, silent, lib_info = self.search_for_library(filters, silent,
@ -281,14 +286,18 @@ class LibraryManager(BasePkgManager):
if filters.get("version"): if filters.get("version"):
self.install( self.install(
lib_info['id'], lib_info['id'],
requirements=filters.get("version"), filters.get("version"),
silent=silent, silent=silent,
trigger_event=trigger_event) trigger_event=trigger_event,
interactive=interactive,
force=force)
else: else:
self.install( self.install(
lib_info['id'], lib_info['id'],
silent=silent, silent=silent,
trigger_event=trigger_event) trigger_event=trigger_event,
interactive=interactive,
force=force)
return pkg_dir return pkg_dir
@staticmethod @staticmethod
@ -315,7 +324,7 @@ class LibraryManager(BasePkgManager):
lib_info = None lib_info = None
result = util.get_api_result( result = util.get_api_result(
"/v2/lib/search", dict(query=" ".join(query)), cache_valid="3d") "/v2/lib/search", dict(query=" ".join(query)), 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:

View File

@ -30,7 +30,7 @@ from platformio.downloader import FileDownloader
from platformio.unpacker import FileUnpacker from platformio.unpacker import FileUnpacker
from platformio.vcsclient import VCSClientFactory from platformio.vcsclient import VCSClientFactory
# pylint: disable=too-many-arguments # pylint: disable=too-many-arguments, too-many-return-statements
class PackageRepoIterator(object): class PackageRepoIterator(object):
@ -78,9 +78,15 @@ class PkgRepoMixin(object):
PIO_VERSION = semantic_version.Version(util.pepver_to_semver(__version__)) PIO_VERSION = semantic_version.Version(util.pepver_to_semver(__version__))
@staticmethod @staticmethod
def max_satisfying_repo_version(versions, requirements=None): def is_system_compatible(valid_systems):
if valid_systems in (None, "all", "*"):
return True
if not isinstance(valid_systems, list):
valid_systems = list([valid_systems])
return util.get_systype() in valid_systems
def max_satisfying_repo_version(self, versions, requirements=None):
item = None item = None
systype = util.get_systype()
reqspec = None reqspec = None
if requirements: if requirements:
try: try:
@ -89,8 +95,7 @@ class PkgRepoMixin(object):
pass pass
for v in versions: for v in versions:
if "system" in v and v['system'] not in ("all", "*") and \ if not self.is_system_compatible(v.get("system")):
systype not in v['system']:
continue continue
if "platformio" in v.get("engines", {}): if "platformio" in v.get("engines", {}):
if PkgRepoMixin.PIO_VERSION not in semantic_version.Spec( if PkgRepoMixin.PIO_VERSION not in semantic_version.Spec(
@ -121,8 +126,9 @@ class PkgRepoMixin(object):
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([v['version'] for v in versions]) result.extend(
return sorted(set(result)) [semantic_version.Version(v['version']) for v in versions])
return [str(v) for v in sorted(set(result))]
class PkgInstallerMixin(object): class PkgInstallerMixin(object):
@ -187,15 +193,36 @@ class PkgInstallerMixin(object):
@staticmethod @staticmethod
def unpack(source_path, dest_dir): def unpack(source_path, dest_dir):
fu = FileUnpacker(source_path, dest_dir) with FileUnpacker(source_path) as fu:
return fu.start() return fu.unpack(dest_dir)
@staticmethod
def parse_semver_spec(value, raise_exception=False):
try:
return semantic_version.Spec(value)
except ValueError as e:
if raise_exception:
raise e
return None
@staticmethod
def parse_semver_version(value, raise_exception=False):
try:
try:
return semantic_version.Version(value)
except ValueError:
return semantic_version.Version.coerce(value)
except ValueError as e:
if raise_exception:
raise e
return None
@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 name return str(name)
def get_src_manifest_path(self, pkg_dir): def get_src_manifest_path(self, pkg_dir):
if not isdir(pkg_dir): if not isdir(pkg_dir):
@ -258,7 +285,7 @@ class PkgInstallerMixin(object):
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'] = util.path_to_unicode(pkg_dir)
self.cache_set(cache_key, manifest) self.cache_set(cache_key, manifest)
return manifest return manifest
@ -283,21 +310,23 @@ class PkgInstallerMixin(object):
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")):
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.Spec( if requirements and not self.parse_semver_spec(
requirements).match( requirements, raise_exception=True).match(
semantic_version.Version( self.parse_semver_version(
manifest['version'], partial=True)): manifest['version'], raise_exception=True)):
continue continue
elif not best or (semantic_version.Version( elif not best or (self.parse_semver_version(
manifest['version'], partial=True) > manifest['version'], raise_exception=True) >
semantic_version.Version( self.parse_semver_version(
best['version'], partial=True)): best['version'], raise_exception=True)):
best = manifest best = manifest
except ValueError: except ValueError:
pass pass
@ -383,7 +412,7 @@ class PkgInstallerMixin(object):
finally: finally:
if isdir(tmp_dir): if isdir(tmp_dir):
util.rmtree_(tmp_dir) util.rmtree_(tmp_dir)
return return None
def _update_src_manifest(self, data, src_dir): def _update_src_manifest(self, data, src_dir):
if not isdir(src_dir): if not isdir(src_dir):
@ -405,16 +434,10 @@ 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 = None tmp_semver = self.parse_semver_version(tmp_manifest['version'])
cur_semver = None cur_semver = None
try: if cur_manifest:
tmp_semver = semantic_version.Version( cur_semver = self.parse_semver_version(cur_manifest['version'])
tmp_manifest['version'], partial=True)
if cur_manifest:
cur_semver = semantic_version.Version(
cur_manifest['version'], partial=True)
except ValueError:
pass
# package should satisfy requirements # package should satisfy requirements
if requirements: if requirements:
@ -490,51 +513,57 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin):
click.echo("%s: %s" % (self.__class__.__name__, message), nl=nl) click.echo("%s: %s" % (self.__class__.__name__, message), nl=nl)
@staticmethod @staticmethod
def parse_pkg_input( # pylint: disable=too-many-branches def parse_pkg_uri( # pylint: disable=too-many-branches
text, requirements=None): text, requirements=None):
text = str(text) text = str(text)
# git@github.com:user/package.git name, url = None, None
url_marker = text[:4]
if url_marker not in ("git@", "git+") or ":" not in text:
url_marker = "://"
# Parse requirements
req_conditions = [ req_conditions = [
not requirements, "@" in text, not requirements, ":" not in text
"@" in text, or text.rfind("/") < text.rfind("@")
not url_marker.startswith("git") ]
] # yapf: disable
if all(req_conditions): if all(req_conditions):
text, requirements = text.rsplit("@", 1) text, requirements = text.rsplit("@", 1)
# Handle PIO Library Registry ID
if text.isdigit(): if text.isdigit():
text = "id=" + text text = "id=" + text
# Parse custom name
elif "=" in text and not text.startswith("id="):
name, text = text.split("=", 1)
name, url = (None, text) # Parse URL
if "=" in text and not text.startswith("id="): # if valid URL with scheme vcs+protocol://
name, url = text.split("=", 1) if "+" in text and text.find("+") < text.find("://"):
url = text
elif "/" in text or "\\" in text:
git_conditions = [
# Handle GitHub URL (https://github.com/user/package)
text.startswith("https://github.com/") and not text.endswith(
(".zip", ".tar.gz")),
(text.split("#", 1)[0]
if "#" in text else text).endswith(".git")
]
hg_conditions = [
# Handle Developer Mbed URL
# (https://developer.mbed.org/users/user/code/package/)
text.startswith("https://developer.mbed.org")
]
if any(git_conditions):
url = "git+" + text
elif any(hg_conditions):
url = "hg+" + text
elif "://" not in text and (isfile(text) or isdir(text)):
url = "file://" + text
elif "://" in text:
url = text
# Handle short version of GitHub URL
elif text.count("/") == 1:
url = "git+https://github.com/" + text
git_conditions = [ # Parse name from URL
# Handle GitHub URL (https://github.com/user/package) if url and not name:
url.startswith("https://github.com/") and not url.endswith(
(".zip", ".tar.gz")),
url.startswith("http")
and (url.split("#", 1)[0] if "#" in url else url).endswith(".git")
]
if any(git_conditions):
url = "git+" + url
# Handle Developer Mbed URL
# (https://developer.mbed.org/users/user/code/package/)
if url.startswith("https://developer.mbed.org"):
url = "hg+" + url
if any([s in url for s in ("\\", "/")]) and url_marker not in url:
if isfile(url) or isdir(url):
url = "file://" + url
elif url.count("/") == 1 and "git" not in url_marker:
url = "git+https://github.com/" + url
# determine name
if url_marker in url and not name:
_url = url.split("#", 1)[0] if "#" in url else url _url = url.split("#", 1)[0] if "#" in url else url
if _url.endswith(("\\", "/")): if _url.endswith(("\\", "/")):
_url = _url[:-1] _url = _url[:-1]
@ -542,8 +571,6 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin):
if "." in name and not name.startswith("."): if "." in name and not name.startswith("."):
name = name.rsplit(".", 1)[0] name = name.rsplit(".", 1)[0]
if url_marker not in url:
url = None
return (name or text, requirements, url) return (name or text, requirements, url)
def outdated(self, pkg_dir, requirements=None): def outdated(self, pkg_dir, requirements=None):
@ -553,11 +580,12 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin):
`False` - package is up-to-date `False` - package is up-to-date
`String` - a found latest version `String` - a found latest version
""" """
assert isdir(pkg_dir) if not isdir(pkg_dir):
return None
latest = None latest = None
manifest = self.load_manifest(pkg_dir) manifest = self.load_manifest(pkg_dir)
# skip a fixed package to a specific version # skip fixed package to a specific version
if "@" in pkg_dir and "__src_url" not in manifest: if "@" in pkg_dir and "__src_url" not in manifest and not requirements:
return None return None
if "__src_url" in manifest: if "__src_url" in manifest:
@ -585,8 +613,10 @@ 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 = (semantic_version.Version.coerce(manifest['version']) up_to_date = (self.parse_semver_version(
>= semantic_version.Version.coerce(latest)) manifest['version'], raise_exception=True) >=
self.parse_semver_version(
latest, raise_exception=True))
except (AssertionError, ValueError): except (AssertionError, ValueError):
up_to_date = latest == manifest['version'] up_to_date = latest == manifest['version']
@ -596,18 +626,22 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin):
name, name,
requirements=None, requirements=None,
silent=False, silent=False,
trigger_event=True): trigger_event=True,
force=False):
name, requirements, url = self.parse_pkg_uri(name, requirements)
package_dir = self.get_package_dir(name, requirements, url)
# avoid circle dependencies # avoid circle dependencies
if not self.INSTALL_HISTORY: if not self.INSTALL_HISTORY:
self.INSTALL_HISTORY = [] self.INSTALL_HISTORY = []
history_key = "%s-%s" % (name, requirements) if requirements else name history_key = "%s-%s-%s" % (name, requirements or "", url or "")
if history_key in self.INSTALL_HISTORY: if history_key in self.INSTALL_HISTORY:
return return package_dir
self.INSTALL_HISTORY.append(history_key) self.INSTALL_HISTORY.append(history_key)
name, requirements, url = self.parse_pkg_input(name, requirements) if package_dir and force:
package_dir = self.get_package_dir(name, requirements, url) self.uninstall(package_dir)
package_dir = None
if not package_dir or not silent: if not package_dir or not silent:
msg = "Installing " + click.style(name, fg="cyan") msg = "Installing " + click.style(name, fg="cyan")
@ -652,8 +686,7 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin):
if isdir(package): if isdir(package):
pkg_dir = package pkg_dir = package
else: else:
name, requirements, url = self.parse_pkg_input( 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:
@ -689,15 +722,11 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin):
label=manifest['name']) label=manifest['name'])
return True return True
def update( # pylint: disable=too-many-return-statements def update(self, package, requirements=None, only_check=False):
self,
package,
requirements=None,
only_check=False):
if isdir(package): if isdir(package):
pkg_dir = package pkg_dir = package
else: else:
pkg_dir = self.get_package_dir(*self.parse_pkg_input(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" % (package, raise exception.UnknownPackage("%s @ %s" % (package,
@ -713,7 +742,7 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin):
nl=False) 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 return None
latest = self.outdated(pkg_dir, requirements) latest = self.outdated(pkg_dir, requirements)
if latest: if latest:
@ -721,10 +750,10 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin):
elif latest is False: elif latest is False:
click.echo("[%s]" % (click.style("Up-to-date", fg="green"))) click.echo("[%s]" % (click.style("Up-to-date", fg="green")))
else: else:
click.echo("[%s]" % (click.style("Skip", fg="yellow"))) click.echo("[%s]" % (click.style("Fixed", fg="yellow")))
if only_check or not latest: if only_check or not latest:
return 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'])

View File

@ -18,6 +18,7 @@ import re
from imp import load_source from imp import load_source
from multiprocessing import cpu_count from multiprocessing import cpu_count
from os.path import basename, dirname, isdir, isfile, join from os.path import basename, dirname, isdir, isfile, join
from urllib import quote
import click import click
import semantic_version import semantic_version
@ -63,9 +64,10 @@ class PlatformManager(BasePkgManager):
skip_default_package=False, skip_default_package=False,
trigger_event=True, trigger_event=True,
silent=False, silent=False,
force=False,
**_): # pylint: disable=too-many-arguments, arguments-differ **_): # pylint: disable=too-many-arguments, arguments-differ
platform_dir = BasePkgManager.install( platform_dir = BasePkgManager.install(
self, name, requirements, silent=silent) self, name, requirements, silent=silent, force=force)
p = PlatformFactory.newPlatform(platform_dir) p = PlatformFactory.newPlatform(platform_dir)
# @Hook: when 'update' operation (trigger_event is False), # @Hook: when 'update' operation (trigger_event is False),
@ -76,7 +78,8 @@ class PlatformManager(BasePkgManager):
with_packages, with_packages,
without_packages, without_packages,
skip_default_package, skip_default_package,
silent=silent) silent=silent,
force=force)
self.cleanup_packages(p.packages.keys()) self.cleanup_packages(p.packages.keys())
return True return True
@ -84,10 +87,12 @@ class PlatformManager(BasePkgManager):
if isdir(package): if isdir(package):
pkg_dir = package pkg_dir = package
else: else:
name, requirements, url = self.parse_pkg_input( 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:
raise exception.UnknownPlatform(package)
p = PlatformFactory.newPlatform(pkg_dir) p = PlatformFactory.newPlatform(pkg_dir)
BasePkgManager.uninstall(self, pkg_dir, requirements) BasePkgManager.uninstall(self, pkg_dir, requirements)
@ -108,25 +113,28 @@ class PlatformManager(BasePkgManager):
if isdir(package): if isdir(package):
pkg_dir = package pkg_dir = package
else: else:
name, requirements, url = self.parse_pkg_input( 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)
p = PlatformFactory.newPlatform(pkg_dir) if not pkg_dir:
pkgs_before = pkgs_after = p.get_installed_packages().keys() raise exception.UnknownPlatform(package)
p = PlatformFactory.newPlatform(pkg_dir)
pkgs_before = p.get_installed_packages().keys()
missed_pkgs = set()
if not only_packages: if not only_packages:
BasePkgManager.update(self, pkg_dir, requirements, only_check) BasePkgManager.update(self, pkg_dir, requirements, only_check)
p = PlatformFactory.newPlatform(pkg_dir) p = PlatformFactory.newPlatform(pkg_dir)
pkgs_after = p.get_installed_packages().keys() missed_pkgs = set(pkgs_before) & set(p.packages.keys())
missed_pkgs -= set(p.get_installed_packages().keys())
p.update_packages(only_check) p.update_packages(only_check)
self.cleanup_packages(p.packages.keys()) self.cleanup_packages(p.packages.keys())
pkgs_missed = set(pkgs_before) - set(pkgs_after) if missed_pkgs:
if pkgs_missed:
p.install_packages( p.install_packages(
with_packages=pkgs_missed, skip_default_package=True) with_packages=list(missed_pkgs), skip_default_package=True)
return True return True
@ -164,7 +172,19 @@ class PlatformManager(BasePkgManager):
@staticmethod @staticmethod
@util.memoized @util.memoized
def get_registered_boards(): def get_registered_boards():
return util.get_api_result("/boards", cache_valid="30d") return util.get_api_result("/boards", cache_valid="7d")
def get_all_boards(self):
boards = self.get_installed_boards()
know_boards = ["%s:%s" % (b['platform'], b['id']) for b in boards]
try:
for board in self.get_registered_boards():
key = "%s:%s" % (board['platform'], board['id'])
if key not in know_boards:
boards.append(board)
except (exception.APIRequestError, exception.InternetIsOffline):
pass
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():
@ -197,18 +217,19 @@ class PlatformFactory(object):
@classmethod @classmethod
def newPlatform(cls, name, requirements=None): def newPlatform(cls, name, requirements=None):
pm = PlatformManager()
platform_dir = None platform_dir = None
if isdir(name): if isdir(name):
platform_dir = name platform_dir = name
name = PlatformManager().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 = util.load_json(name)['name'] name = util.load_json(name)['name']
else: else:
if not requirements and "@" in name: name, requirements, url = pm.parse_pkg_uri(name, requirements)
name, requirements = name.rsplit("@", 1) platform_dir = pm.get_package_dir(name, requirements, url)
platform_dir = PlatformManager().get_package_dir( if platform_dir:
name, requirements) name = pm.load_manifest(platform_dir)['name']
if not platform_dir: if not platform_dir:
raise exception.UnknownPlatform(name if not requirements else raise exception.UnknownPlatform(name if not requirements else
@ -230,11 +251,13 @@ class PlatformFactory(object):
class PlatformPackagesMixin(object): class PlatformPackagesMixin(object):
def install_packages(self, def install_packages( # pylint: disable=too-many-arguments
with_packages=None, self,
without_packages=None, with_packages=None,
skip_default_package=False, without_packages=None,
silent=False): skip_default_package=False,
silent=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 []))
@ -249,14 +272,11 @@ class PlatformPackagesMixin(object):
continue continue
elif (name in with_packages or elif (name in with_packages or
not (skip_default_package or opts.get("optional", False))): not (skip_default_package or opts.get("optional", False))):
if self.is_valid_requirements(version): if ":" in version:
self.pm.install(name, version, silent=silent)
else:
requirements = None
if "@" in version:
version, requirements = version.rsplit("@", 1)
self.pm.install( self.pm.install(
"%s=%s" % (name, version), requirements, silent=silent) "%s=%s" % (name, version), silent=silent, force=force)
else:
self.pm.install(name, version, silent=silent, force=force)
return True return True
@ -279,10 +299,10 @@ class PlatformPackagesMixin(object):
def update_packages(self, only_check=False): def update_packages(self, only_check=False):
for name, manifest in self.get_installed_packages().items(): for name, manifest in self.get_installed_packages().items():
version = self.packages[name].get("version", "") requirements = self.packages[name].get("version", "")
if "@" in version: if ":" in requirements:
_, version = version.rsplit("@", 1) _, requirements, __ = self.pm.parse_pkg_uri(requirements)
self.pm.update(manifest['__pkg_dir'], version, only_check) self.pm.update(manifest['__pkg_dir'], requirements, only_check)
def get_installed_packages(self): def get_installed_packages(self):
items = {} items = {}
@ -294,18 +314,19 @@ class PlatformPackagesMixin(object):
def are_outdated_packages(self): def are_outdated_packages(self):
for name, manifest in self.get_installed_packages().items(): for name, manifest in self.get_installed_packages().items():
version = self.packages[name].get("version", "") requirements = self.packages[name].get("version", "")
if "@" in version: if ":" in requirements:
_, version = version.rsplit("@", 1) _, requirements, __ = self.pm.parse_pkg_uri(requirements)
if self.pm.outdated(manifest['__pkg_dir'], version): if self.pm.outdated(manifest['__pkg_dir'], requirements):
return True return True
return False return False
def get_package_dir(self, name): def get_package_dir(self, name):
version = self.packages[name].get("version", "") version = self.packages[name].get("version", "")
if self.is_valid_requirements(version): if ":" in version:
return self.pm.get_package_dir(name, version) return self.pm.get_package_dir(*self.pm.parse_pkg_uri(
return self.pm.get_package_dir(*self._parse_pkg_input(name, version)) "%s=%s" % (name, version)))
return self.pm.get_package_dir(name, version)
def get_package_version(self, name): def get_package_version(self, name):
pkg_dir = self.get_package_dir(name) pkg_dir = self.get_package_dir(name)
@ -313,16 +334,6 @@ class PlatformPackagesMixin(object):
return None return None
return self.pm.load_manifest(pkg_dir).get("version") return self.pm.load_manifest(pkg_dir).get("version")
@staticmethod
def is_valid_requirements(requirements):
return requirements and "://" not in requirements
def _parse_pkg_input(self, name, version):
requirements = None
if "@" in version:
version, requirements = version.rsplit("@", 1)
return self.pm.parse_pkg_input("%s=%s" % (name, version), requirements)
class PlatformRunMixin(object): class PlatformRunMixin(object):
@ -384,6 +395,12 @@ class PlatformRunMixin(object):
is_error = self.LINE_ERROR_RE.search(line) is not None is_error = self.LINE_ERROR_RE.search(line) is not None
self._echo_line(line, level=3 if is_error else 2) self._echo_line(line, level=3 if is_error else 2)
a_pos = line.find("fatal error:")
b_pos = line.rfind(": No such file or directory")
if a_pos == -1 or b_pos == -1:
return
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: "):
line = line[7:] line = line[7:]
@ -395,6 +412,27 @@ class PlatformRunMixin(object):
fg = "green" fg = "green"
click.secho(line, fg=fg, err=level > 1) click.secho(line, fg=fg, err=level > 1)
@staticmethod
def _echo_missed_dependency(filename):
if "/" in filename or not filename.endswith((".h", ".hpp")):
return
banner = """
{dots}
* Looking for {filename_styled} dependency? Check our library registry!
*
* CLI > platformio lib search "header:{filename}"
* Web > {link}
*
{dots}
""".format(filename=filename,
filename_styled=click.style(filename, fg="cyan"),
link=click.style(
"http://platformio.org/lib/search?query=header:%s" % quote(
filename, safe=""),
fg="blue"),
dots="*" * (55 + len(filename)))
click.echo(banner, err=True)
@staticmethod @staticmethod
def get_job_nums(): def get_job_nums():
try: try:
@ -498,8 +536,8 @@ class PlatformBase( # pylint: disable=too-many-public-methods
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
elif ("platforms" in config elif "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
@ -637,24 +675,37 @@ class PlatformBoardConfig(object):
def get_brief_data(self): def get_brief_data(self):
return { return {
"id": self.id, "id":
"name": self._manifest['name'], self.id,
"platform": self._manifest.get("platform"), "name":
"mcu": self._manifest.get("build", {}).get("mcu", "").upper(), self._manifest['name'],
"platform":
self._manifest.get("platform"),
"mcu":
self._manifest.get("build", {}).get("mcu", "").upper(),
"fcpu": "fcpu":
int(self._manifest.get("build", {}).get("f_cpu", "0L")[:-1]), int(
"ram": self._manifest.get("upload", {}).get("maximum_ram_size", 0), re.sub(r"[^\d]+", "",
"rom": self._manifest.get("upload", {}).get("maximum_size", 0), self._manifest.get("build", {}).get("f_cpu", "0L"))),
"connectivity": self._manifest.get("connectivity"), "ram":
"frameworks": self._manifest.get("frameworks"), self._manifest.get("upload", {}).get("maximum_ram_size", 0),
"debug": self.get_debug_data(), "rom":
"vendor": self._manifest['vendor'], self._manifest.get("upload", {}).get("maximum_size", 0),
"url": self._manifest['url'] "connectivity":
self._manifest.get("connectivity"),
"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 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] = {}

View File

@ -15,10 +15,11 @@
import atexit import atexit
import platform import platform
import Queue import Queue
import sys import re
import threading import threading
from collections import deque from collections import deque
from os import getenv from os import getenv, sep
from os.path import join
from time import sleep, time from time import sleep, time
from traceback import format_exc from traceback import format_exc
@ -109,7 +110,7 @@ class MeasurementProtocol(TelemetryBase):
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() self['cd4'] = 1 if (not util.is_ci()
and (caller_id or not util.is_container())) else 0 and (caller_id or not util.is_container())) else 0
if caller_id: if caller_id:
@ -314,14 +315,29 @@ def on_event(category, action, label=None, value=None, screen_name=None):
def on_exception(e): def on_exception(e):
def _cleanup_description(text):
text = text.replace("Traceback (most recent call last):", "")
text = re.sub(
r'File "([^"]+)"',
lambda m: join(*m.group(1).split(sep)[-2:]),
text,
flags=re.M)
text = re.sub(r"\s+", " ", text, flags=re.M)
return text.strip()
skip_conditions = [ skip_conditions = [
isinstance(e, cls) isinstance(e, cls)
for cls in (IOError, exception.AbortedByUser, for cls in (IOError, exception.ReturnErrorCode,
exception.NotGlobalLibDir, exception.InternetIsOffline, exception.AbortedByUser, exception.NotGlobalLibDir,
exception.InternetIsOffline,
exception.NotPlatformIOProject, exception.NotPlatformIOProject,
exception.UserSideException) exception.UserSideException)
] ]
skip_conditions.append("[API] Account: " in str(e)) try:
skip_conditions.append("[API] Account: " in str(e))
except UnicodeEncodeError as ue:
e = ue
if any(skip_conditions): if any(skip_conditions):
return return
is_crash = any([ is_crash = any([
@ -329,8 +345,8 @@ def on_exception(e):
"Error" in e.__class__.__name__ "Error" in e.__class__.__name__
]) ])
mp = MeasurementProtocol() mp = MeasurementProtocol()
mp['exd'] = ("%s: %s" % (type(e).__name__, format_exc() description = _cleanup_description(format_exc() if is_crash else str(e))
if is_crash else e))[: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")
@ -391,3 +407,4 @@ def resend_backuped_reports():
# clean # clean
tm['backup'] = [] tm['backup'] = []
app.set_state_item("telemetry", tm) app.set_state_item("telemetry", tm)
return True

View File

@ -13,7 +13,7 @@
# limitations under the License. # limitations under the License.
from os import chmod from os import chmod
from os.path import join, splitext from os.path import join
from tarfile import open as tarfile_open from tarfile import open as tarfile_open
from time import mktime from time import mktime
from zipfile import ZipFile from zipfile import ZipFile
@ -39,6 +39,9 @@ class ArchiveBase(object):
def after_extract(self, item, dest_dir): def after_extract(self, item, dest_dir):
pass pass
def close(self):
self._afo.close()
class TARArchive(ArchiveBase): class TARArchive(ArchiveBase):
@ -76,28 +79,32 @@ class ZIPArchive(ArchiveBase):
class FileUnpacker(object): class FileUnpacker(object):
def __init__(self, archpath, dest_dir="."): def __init__(self, archpath):
self._archpath = archpath self.archpath = archpath
self._dest_dir = dest_dir
self._unpacker = None self._unpacker = None
_, archext = splitext(archpath.lower()) def __enter__(self):
if archext in (".gz", ".bz2"): if self.archpath.lower().endswith((".gz", ".bz2")):
self._unpacker = TARArchive(archpath) self._unpacker = TARArchive(self.archpath)
elif archext == ".zip": elif self.archpath.lower().endswith(".zip"):
self._unpacker = ZIPArchive(archpath) self._unpacker = ZIPArchive(self.archpath)
if not self._unpacker: if not self._unpacker:
raise UnsupportedArchiveType(archpath) raise UnsupportedArchiveType(self.archpath)
return self
def start(self): def __exit__(self, *args):
if self._unpacker:
self._unpacker.close()
def unpack(self, dest_dir="."):
assert self._unpacker
if app.is_disabled_progressbar(): if app.is_disabled_progressbar():
click.echo("Unpacking...") click.echo("Unpacking...")
for item in self._unpacker.get_items(): for item in self._unpacker.get_items():
self._unpacker.extract_item(item, self._dest_dir) self._unpacker.extract_item(item, dest_dir)
else: else:
items = self._unpacker.get_items() items = self._unpacker.get_items()
with click.progressbar(items, label="Unpacking") as pb: with click.progressbar(items, label="Unpacking") as pb:
for item in pb: for item in pb:
self._unpacker.extract_item(item, self._dest_dir) self._unpacker.extract_item(item, dest_dir)
return True return True

View File

@ -22,13 +22,13 @@ import socket
import stat import stat
import subprocess import subprocess
import sys import sys
from contextlib import contextmanager from functools import wraps
from glob import glob from glob import glob
from os.path import (abspath, basename, dirname, expanduser, isdir, isfile, from os.path import (abspath, basename, dirname, expanduser, isdir, isfile,
join, normpath, splitdrive) join, normpath, splitdrive)
from shutil import rmtree from shutil import rmtree
from threading import Thread from threading import Thread
from time import sleep from time import sleep, time
import click import click
import requests import requests
@ -149,6 +149,25 @@ class memoized(object):
self.cache = {} self.cache = {}
class throttle(object):
def __init__(self, threshhold):
self.threshhold = threshhold # milliseconds
self.last = 0
def __call__(self, fn):
@wraps(fn)
def wrapper(*args, **kwargs):
diff = int(round((time() - self.last) * 1000))
if diff < self.threshhold:
sleep((self.threshhold - diff) * 0.001)
self.last = time()
return fn(*args, **kwargs)
return wrapper
def singleton(cls): def singleton(cls):
""" From PEP-318 http://www.python.org/dev/peps/pep-0318/#examples """ """ From PEP-318 http://www.python.org/dev/peps/pep-0318/#examples """
_instances = {} _instances = {}
@ -161,12 +180,8 @@ def singleton(cls):
return get_instance return get_instance
@contextmanager def path_to_unicode(path):
def capture_stdout(output): return path.decode(sys.getfilesystemencoding()).encode("utf-8")
stdout = sys.stdout
sys.stdout = output
yield
sys.stdout = stdout
def load_json(file_path): def load_json(file_path):
@ -281,6 +296,11 @@ def get_projectsrc_dir():
return get_project_optional_dir("src_dir", join(get_project_dir(), "src")) return get_project_optional_dir("src_dir", join(get_project_dir(), "src"))
def get_projectinclude_dir():
return get_project_optional_dir("include_dir",
join(get_project_dir(), "include"))
def get_projecttest_dir(): def get_projecttest_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"))
@ -317,11 +337,10 @@ def get_projectdata_dir():
def load_project_config(path=None): def load_project_config(path=None):
if not path or isdir(path): if not path or isdir(path):
project_dir = path or get_project_dir() path = join(path or get_project_dir(), "platformio.ini")
if not is_platformio_project(project_dir): if not isfile(path):
raise exception.NotPlatformIOProject(project_dir) raise exception.NotPlatformIOProject(
path = join(project_dir, "platformio.ini") dirname(path) if path.endswith("platformio.ini") else path)
assert isfile(path)
cp = ProjectConfig() cp = ProjectConfig()
cp.read(path) cp.read(path)
return cp return cp
@ -336,8 +355,8 @@ def parse_conf_multi_values(items):
] ]
def change_filemtime(path, time): def change_filemtime(path, mtime):
os.utime(path, (time, time)) os.utime(path, (mtime, mtime))
def is_ci(): def is_ci():
@ -398,7 +417,7 @@ def copy_pythonpath_to_osenv():
os.environ['PYTHONPATH'] = os.pathsep.join(_PYTHONPATH) os.environ['PYTHONPATH'] = os.pathsep.join(_PYTHONPATH)
def get_serialports(filter_hwid=False): def get_serial_ports(filter_hwid=False):
try: try:
from serial.tools.list_ports import comports from serial.tools.list_ports import comports
except ImportError: except ImportError:
@ -426,29 +445,117 @@ def get_serialports(filter_hwid=False):
return result return result
def get_logicaldisks(): def get_logical_devices():
disks = [] items = []
if platform.system() == "Windows": if platform.system() == "Windows":
result = exec_command( try:
["wmic", "logicaldisk", "get", "name,VolumeName"]).get("out", "") result = exec_command(
disknamere = re.compile(r"^([A-Z]{1}\:)\s*(\S+)?") ["wmic", "logicaldisk", "get", "name,VolumeName"]).get(
for line in result.split("\n"): "out", "")
match = disknamere.match(line.strip()) devicenamere = re.compile(r"^([A-Z]{1}\:)\s*(\S+)?")
if not match: for line in result.split("\n"):
continue match = devicenamere.match(line.strip())
disks.append({"disk": match.group(1), "name": match.group(2)}) if not match:
continue
items.append({
"path": match.group(1) + "\\",
"name": match.group(2)
})
return items
except WindowsError: # pylint: disable=undefined-variable
pass
# try "fsutil"
result = exec_command(["fsutil", "fsinfo", "drives"]).get("out", "")
for device in re.findall(r"[A-Z]:\\", result):
items.append({"path": device, "name": None})
return items
else: else:
result = exec_command(["df"]).get("out") result = exec_command(["df"]).get("out")
disknamere = re.compile(r"\d+\%\s+([a-z\d\-_/]+)$", flags=re.I) devicenamere = re.compile(r"^/.+\d+\%\s+([a-z\d\-_/]+)$", flags=re.I)
for line in result.split("\n"): for line in result.split("\n"):
match = disknamere.search(line.strip()) match = devicenamere.match(line.strip())
if not match: if not match:
continue continue
disks.append({ items.append({
"disk": match.group(1), "path": match.group(1),
"name": basename(match.group(1)) "name": basename(match.group(1))
}) })
return disks return items
### Backward compatibility for PIO Core <3.5
get_serialports = get_serial_ports
get_logicaldisks = lambda: [{
"disk": d['path'],
"name": d['name']
} for d in get_logical_devices()]
def get_mdns_services():
try:
import zeroconf
except ImportError:
from site import addsitedir
from platformio.managers.core import get_core_package_dir
contrib_pysite_dir = get_core_package_dir("contrib-pysite")
addsitedir(contrib_pysite_dir)
sys.path.insert(0, contrib_pysite_dir)
import zeroconf
class mDNSListener(object):
def __init__(self):
self._zc = zeroconf.Zeroconf(
interfaces=zeroconf.InterfaceChoice.All)
self._found_types = []
self._found_services = []
def __enter__(self):
zeroconf.ServiceBrowser(self._zc, "_services._dns-sd._udp.local.",
self)
return self
def __exit__(self, etype, value, traceback):
self._zc.close()
def remove_service(self, zc, type_, name):
pass
def add_service(self, zc, type_, name):
try:
assert zeroconf.service_type_name(name)
assert str(name)
except (AssertionError, UnicodeError,
zeroconf.BadTypeInNameException):
return
if name not in self._found_types:
self._found_types.append(name)
zeroconf.ServiceBrowser(self._zc, name, self)
if type_ in self._found_types:
s = zc.get_service_info(type_, name)
if s:
self._found_services.append(s)
def get_services(self):
return self._found_services
items = []
with mDNSListener() as mdns:
sleep(3)
for service in mdns.get_services():
items.append({
"type":
service.type,
"name":
service.name,
"ip":
".".join([str(ord(c)) for c in service.address]),
"port":
service.port,
"properties":
service.properties
})
return items
def get_request_defheaders(): def get_request_defheaders():
@ -461,6 +568,7 @@ def _api_request_session():
return requests.Session() return requests.Session()
@throttle(500)
def _get_api_result( def _get_api_result(
url, # pylint: disable=too-many-branches url, # pylint: disable=too-many-branches
params=None, params=None,
@ -470,7 +578,7 @@ def _get_api_result(
result = None result = None
r = None r = None
disable_ssl_check = sys.version_info < (2, 7, 9) verify_ssl = sys.version_info >= (2, 7, 9)
headers = get_request_defheaders() headers = get_request_defheaders()
if not url.startswith("http"): if not url.startswith("http"):
@ -486,14 +594,14 @@ def _get_api_result(
data=data, data=data,
headers=headers, headers=headers,
auth=auth, auth=auth,
verify=not disable_ssl_check) verify=verify_ssl)
else: else:
r = _api_request_session().get( r = _api_request_session().get(
url, url,
params=params, params=params,
headers=headers, headers=headers,
auth=auth, auth=auth,
verify=not disable_ssl_check) verify=verify_ssl)
result = r.json() result = r.json()
r.raise_for_status() r.raise_for_status()
except requests.exceptions.HTTPError as e: except requests.exceptions.HTTPError as e:
@ -513,6 +621,7 @@ 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):
internet_on(raise_exception=True)
from platformio.app import ContentCache from platformio.app import ContentCache
total = 0 total = 0
max_retries = 5 max_retries = 5
@ -532,8 +641,6 @@ def get_api_result(url, params=None, data=None, auth=None, cache_valid=None):
return result return result
except (requests.exceptions.ConnectionError, except (requests.exceptions.ConnectionError,
requests.exceptions.Timeout) as e: requests.exceptions.Timeout) as e:
if not internet_on():
raise exception.InternetIsOffline()
from platformio.maintenance import in_silence from platformio.maintenance import in_silence
total += 1 total += 1
if not in_silence(): if not in_silence():
@ -548,18 +655,38 @@ def get_api_result(url, params=None, data=None, auth=None, cache_valid=None):
"Please try later.") "Please try later.")
def internet_on(timeout=3): PING_INTERNET_IPS = [
"192.30.253.113", # github.com
"159.122.18.156", # dl.bintray.com
"193.222.52.25" # dl.platformio.org
]
@memoized
def _internet_on():
timeout = 2
socket.setdefaulttimeout(timeout) socket.setdefaulttimeout(timeout)
for host in ("dl.bintray.com", "dl.platformio.org"): for ip in PING_INTERNET_IPS:
try: try:
socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect((host, if os.getenv("HTTP_PROXY", os.getenv("HTTPS_PROXY")):
80)) requests.get(
"http://%s" % ip, allow_redirects=False, timeout=timeout)
else:
socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect((ip,
80))
return True return True
except: # pylint: disable=bare-except except: # pylint: disable=bare-except
pass pass
return False return False
def internet_on(raise_exception=False):
result = _internet_on()
if raise_exception and not result:
raise exception.InternetIsOffline()
return result
def get_pythonexe_path(): def get_pythonexe_path():
return os.environ.get("PYTHONEXEPATH", normpath(sys.executable)) return os.environ.get("PYTHONEXEPATH", normpath(sys.executable))
@ -596,8 +723,13 @@ def pepver_to_semver(pepver):
def rmtree_(path): def rmtree_(path):
def _onerror(_, name, __): def _onerror(_, name, __):
os.chmod(name, stat.S_IWRITE) try:
os.remove(name) os.chmod(name, stat.S_IWRITE)
os.remove(name)
except Exception as e: # pylint: disable=broad-except
click.secho(
"Please manually remove file `%s`" % name, fg="red", err=True)
raise e
return rmtree(path, onerror=_onerror) return rmtree(path, onerror=_onerror)

View File

@ -14,7 +14,7 @@
import re import re
from os.path import join from os.path import join
from subprocess import check_call from subprocess import CalledProcessError, check_call
from sys import modules from sys import modules
from urlparse import urlparse from urlparse import urlparse
@ -29,8 +29,9 @@ class VCSClientFactory(object):
result = urlparse(remote_url) result = urlparse(remote_url)
type_ = result.scheme type_ = result.scheme
tag = None tag = None
if not type_ and remote_url.startswith("git@"): if not type_ and remote_url.startswith("git+"):
type_ = "git" type_ = "git"
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:]
@ -93,7 +94,12 @@ class VCSClientBase(object):
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
return check_call(args, **kwargs) == 0 try:
check_call(args, **kwargs)
return True
except CalledProcessError as e:
raise PlatformioException(
"VCS: Could not process command %s" % e.cmd)
def get_cmd_output(self, args, **kwargs): def get_cmd_output(self, args, **kwargs):
args = [self.command] + args args = [self.command] + args
@ -111,6 +117,13 @@ class GitClient(VCSClientBase):
command = "git" command = "git"
def check_client(self):
try:
return VCSClientBase.check_client(self)
except UserSideException:
raise UserSideException(
"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"])
output = output.replace("*", "") # fix active branch output = output.replace("*", "") # fix active branch

View File

@ -91,3 +91,7 @@ SUBSYSTEMS=="usb", ATTRS{idVendor}=="0451", ATTRS{idProduct}=="f432", MODE="0666
# CMSIS-DAP compatible adapters # CMSIS-DAP compatible adapters
ATTRS{product}=="*CMSIS-DAP*", MODE="664", GROUP="plugdev" ATTRS{product}=="*CMSIS-DAP*", MODE="664", GROUP="plugdev"
# Black Magic Probe
SUBSYSTEM=="tty", ATTRS{interface}=="Black Magic GDB Server"
SUBSYSTEM=="tty", ATTRS{interface}=="Black Magic UART Port"

View File

@ -335,7 +335,7 @@ Boards
vendors = {} vendors = {}
for data in BOARDS: for data in BOARDS:
frameworks = data['frameworks'] frameworks = data['frameworks'] or []
vendor = data['vendor'] vendor = data['vendor']
if type_ in frameworks: if type_ in frameworks:
if vendor in vendors: if vendor in vendors:

View File

@ -14,6 +14,7 @@
import os import os
import subprocess import subprocess
import site
import sys import sys
from platform import system from platform import system
from tempfile import NamedTemporaryFile from tempfile import NamedTemporaryFile
@ -26,39 +27,34 @@ def fix_winpython_pathenv():
""" """
Add Python & Python Scripts to the search path on Windows Add Python & Python Scripts to the search path on Windows
""" """
import ctypes
from ctypes.wintypes import HWND, UINT, WPARAM, LPARAM, LPVOID
try: try:
import _winreg as winreg import _winreg as winreg
except ImportError: except ImportError:
import winreg import winreg
# took these lines from the native "win_add2path.py" # took these lines from the native "win_add2path.py"
pythonpath = os.path.dirname(CURINTERPRETER_PATH) pythonpath = os.path.dirname(os.path.normpath(sys.executable))
scripts = os.path.join(pythonpath, "Scripts") scripts = os.path.join(pythonpath, "Scripts")
if not os.path.isdir(scripts): appdata = os.environ["APPDATA"]
os.makedirs(scripts) if hasattr(site, "USER_SITE"):
userpath = site.USER_SITE.replace(appdata, "%APPDATA%")
userscripts = os.path.join(userpath, "Scripts")
else:
userscripts = None
with winreg.CreateKey(winreg.HKEY_CURRENT_USER, u"Environment") as key: with winreg.CreateKey(winreg.HKEY_CURRENT_USER, "Environment") as key:
try: try:
envpath = winreg.QueryValueEx(key, u"PATH")[0] envpath = winreg.QueryValueEx(key, "PATH")[0]
except WindowsError: except WindowsError:
envpath = u"%PATH%" envpath = u"%PATH%"
paths = [envpath] paths = [envpath]
for path in (pythonpath, scripts): for path in (pythonpath, scripts, userscripts):
if path and path not in envpath and os.path.isdir(path): if path and path not in envpath and os.path.isdir(path):
paths.append(path) paths.append(path)
envpath = os.pathsep.join(paths) envpath = os.pathsep.join(paths)
winreg.SetValueEx(key, u"PATH", 0, winreg.REG_EXPAND_SZ, envpath) winreg.SetValueEx(key, "PATH", 0, winreg.REG_EXPAND_SZ, envpath)
winreg.ExpandEnvironmentStrings(envpath)
# notify the system about the changes
SendMessage = ctypes.windll.user32.SendMessageW
SendMessage.argtypes = HWND, UINT, WPARAM, LPVOID
SendMessage.restype = LPARAM
SendMessage(0xFFFF, 0x1A, 0, u"Environment")
return True return True
@ -92,6 +88,10 @@ def exec_python_cmd(args):
def install_pip(): def install_pip():
r = exec_python_cmd(["-m", "pip", "--version"])
if r['returncode'] == 0:
print r['out']
return
try: try:
from urllib2 import urlopen from urllib2 import urlopen
except ImportError: except ImportError:
@ -112,16 +112,16 @@ def install_pip():
def install_platformio(): def install_platformio():
r = None r = None
cmd = ["pip", "install", "-U", "platformio"] cmd = ["-m", "pip", "install", "-U", "platformio"]
# cmd = [ # cmd = [
# "pip", "install", "-U", # "-m", "pip", "install", "-U",
# "https://github.com/platformio/platformio-core/archive/develop.zip" # "https://github.com/platformio/platformio-core/archive/develop.zip"
# ] # ]
try: try:
r = exec_python_cmd(cmd) r = exec_python_cmd(cmd)
assert r['returncode'] == 0 assert r['returncode'] == 0
except AssertionError: except AssertionError:
cmd.insert(1, "--no-cache-dir") cmd.insert(2, "--no-cache-dir")
r = exec_python_cmd(cmd) r = exec_python_cmd(cmd)
if r: if r:
print_exec_result(r) print_exec_result(r)

View File

@ -18,14 +18,14 @@ from platformio import (__author__, __description__, __email__, __license__,
__title__, __url__, __version__) __title__, __url__, __version__)
install_requires = [ install_requires = [
"arrow<1", "arrow>=0.10.0,!=0.11.0",
"bottle<0.13", "bottle<0.13",
"click>=5,<6", "click>=5,<6",
"colorama", "colorama",
"lockfile>=0.9.1,<0.13", "lockfile>=0.9.1,<0.13",
"pyserial>=3,<4,!=3.3", "pyserial>=3,<4,!=3.3",
"requests>=2.4.0,<3", "requests>=2.4.0,<3",
"semantic_version>=2.5.0" "semantic_version>=2.5.0,<3"
] ]
setup( setup(

View File

@ -14,7 +14,6 @@
import json import json
import re import re
from os.path import basename
from platformio import exception, util from platformio import exception, util
from platformio.commands.init import cli as cmd_init from platformio.commands.init import cli as cmd_init
@ -39,7 +38,7 @@ def test_global_install_registry(clirunner, validate_cliresult,
result = clirunner.invoke(cmd_lib, [ result = clirunner.invoke(cmd_lib, [
"-g", "install", "58", "547@2.2.4", "DallasTemperature", "-g", "install", "58", "547@2.2.4", "DallasTemperature",
"http://dl.platformio.org/libraries/archives/3/5174.tar.gz", "http://dl.platformio.org/libraries/archives/3/5174.tar.gz",
"ArduinoJson@5.6.7", "ArduinoJson@~5.7.0", "1089@fee16e880b" "ArduinoJson@5.6.7", "ArduinoJson@~5.7.0", "168@00589a3250"
]) ])
validate_cliresult(result) validate_cliresult(result)
@ -65,7 +64,7 @@ def test_global_install_registry(clirunner, validate_cliresult,
items2 = [ items2 = [
"ArduinoJson_ID64", "ArduinoJson_ID64@5.6.7", "DallasTemperature_ID54", "ArduinoJson_ID64", "ArduinoJson_ID64@5.6.7", "DallasTemperature_ID54",
"DHT22_ID58", "ESPAsyncTCP_ID305", "NeoPixelBus_ID547", "OneWire_ID1", "DHT22_ID58", "ESPAsyncTCP_ID305", "NeoPixelBus_ID547", "OneWire_ID1",
"IRremoteESP8266_ID1089" "EspSoftwareSerial_ID168"
] ]
assert set(items1) == set(items2) assert set(items1) == set(items2)
@ -134,7 +133,7 @@ def test_global_install_repository(clirunner, validate_cliresult,
assert "is already installed" in result.output assert "is already installed" in result.output
def test_global_lib_list(clirunner, validate_cliresult, isolated_pio_home): 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([n in result.output for n in ("OneWire", "DHT22", "64")]) assert all([n in result.output for n in ("OneWire", "DHT22", "64")])
@ -142,31 +141,30 @@ def test_global_lib_list(clirunner, validate_cliresult, isolated_pio_home):
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 n in result.output
for n in ("PJON", "git+https://github.com/knolleary/pubsubclient", for n in (
"https://github.com/bblanchon/ArduinoJson/archive/v5.8.2.zip" "PJON", "git+https://github.com/knolleary/pubsubclient",
) "https://github.com/bblanchon/ArduinoJson/archive/v5.8.2.zip")
]) ])
items1 = [i['name'] for i in json.loads(result.output)] items1 = [i['name'] for i in json.loads(result.output)]
items2 = [ items2 = [
"OneWire", "DHT22", "PJON", "ESPAsyncTCP", "ArduinoJson", "OneWire", "DHT22", "PJON", "ESPAsyncTCP", "ArduinoJson",
"PubSubClient", "rs485-nodeproto", "Adafruit ST7735 Library", "PubSubClient", "rs485-nodeproto", "Adafruit ST7735 Library",
"RadioHead-1.62", "DallasTemperature", "NeoPixelBus", "RadioHead-1.62", "DallasTemperature", "NeoPixelBus",
"IRremoteESP8266", "platformio-libmirror" "EspSoftwareSerial", "platformio-libmirror"
] ]
assert set(items1) == set(items2) assert set(items1) == set(items2)
def test_global_lib_update_check(clirunner, validate_cliresult, def test_global_lib_update_check(clirunner, validate_cliresult):
isolated_pio_home):
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(["ArduinoJson", "IRremoteESP8266", "NeoPixelBus"]) == set( assert set(["ArduinoJson", "EspSoftwareSerial",
[l['name'] for l in output]) "NeoPixelBus"]) == set([l['name'] for l in output])
def test_global_lib_update(clirunner, validate_cliresult, isolated_pio_home): 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,
@ -184,10 +182,10 @@ def test_global_lib_update(clirunner, validate_cliresult, isolated_pio_home):
result = clirunner.invoke(cmd_lib, ["-g", "update"]) result = clirunner.invoke(cmd_lib, ["-g", "update"])
validate_cliresult(result) validate_cliresult(result)
validate_cliresult(result) validate_cliresult(result)
assert result.output.count("[Skip]") == 5 assert result.output.count("[Fixed]") == 5
assert result.output.count("[Up-to-date]") == 10 assert result.output.count("[Up-to-date]") == 10
assert "Uninstalling ArduinoJson @ 5.7.3" in result.output assert "Uninstalling ArduinoJson @ 5.7.3" in result.output
assert "Uninstalling IRremoteESP8266 @ fee16e880b" in result.output assert "Uninstalling EspSoftwareSerial @ 00589a3250" in result.output
# update unknown library # update unknown library
result = clirunner.invoke(cmd_lib, ["-g", "update", "Unknown"]) result = clirunner.invoke(cmd_lib, ["-g", "update", "Unknown"])
@ -208,14 +206,14 @@ def test_global_lib_uninstall(clirunner, validate_cliresult,
# uninstall the rest libraries # uninstall the rest libraries
result = clirunner.invoke(cmd_lib, [ result = clirunner.invoke(cmd_lib, [
"-g", "uninstall", "1", "ArduinoJson@!=5.6.7", "-g", "uninstall", "1", "https://github.com/bblanchon/ArduinoJson.git",
"https://github.com/bblanchon/ArduinoJson.git", "IRremoteESP8266@>=0.2" "ArduinoJson@!=5.6.7", "EspSoftwareSerial@>=3.3.1"
]) ])
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 = [
"ArduinoJson", "ArduinoJson_ID64@5.6.7", "DallasTemperature_ID54", "ArduinoJson_ID64", "ArduinoJson_ID64@5.6.7", "DallasTemperature_ID54",
"DHT22_ID58", "ESPAsyncTCP_ID305", "NeoPixelBus_ID547", "PJON", "DHT22_ID58", "ESPAsyncTCP_ID305", "NeoPixelBus_ID547", "PJON",
"PJON@src-79de467ebe19de18287becff0a1fb42d", "PubSubClient", "PJON@src-79de467ebe19de18287becff0a1fb42d", "PubSubClient",
"RadioHead-1.62", "rs485-nodeproto", "platformio-libmirror" "RadioHead-1.62", "rs485-nodeproto", "platformio-libmirror"
@ -228,7 +226,7 @@ def test_global_lib_uninstall(clirunner, validate_cliresult,
assert isinstance(result.exception, exception.UnknownPackage) assert isinstance(result.exception, exception.UnknownPackage)
def test_lib_show(clirunner, validate_cliresult, isolated_pio_home): 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(
@ -238,14 +236,14 @@ def test_lib_show(clirunner, validate_cliresult, isolated_pio_home):
assert "OneWire" in result.output assert "OneWire" in result.output
def test_lib_builtin(clirunner, validate_cliresult, isolated_pio_home): def test_lib_builtin(clirunner, validate_cliresult):
result = clirunner.invoke(cmd_lib, ["builtin"]) result = clirunner.invoke(cmd_lib, ["builtin"])
validate_cliresult(result) validate_cliresult(result)
result = clirunner.invoke(cmd_lib, ["builtin", "--json-output"]) result = clirunner.invoke(cmd_lib, ["builtin", "--json-output"])
validate_cliresult(result) validate_cliresult(result)
def test_lib_stats(clirunner, validate_cliresult, isolated_pio_home): 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([

View File

@ -54,11 +54,11 @@ def test_install_unknown_from_registry(clirunner, validate_cliresult,
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(cli_platform.platform_install, [ result = clirunner.invoke(cli_platform.platform_install, [
"atmelavr@1.1.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.1.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
assert len(isolated_pio_home.join("packages").listdir()) == 1 assert len(isolated_pio_home.join("packages").listdir()) == 1
@ -69,7 +69,7 @@ def test_install_from_vcs(clirunner, validate_cliresult, isolated_pio_home):
"platform-espressif8266.git#feature/stage", "--skip-default-package" "platform-espressif8266.git#feature/stage", "--skip-default-package"
]) ])
validate_cliresult(result) validate_cliresult(result)
assert "espressif8266_stage" in result.output assert "espressif8266" in result.output
def test_list_json_output(clirunner, validate_cliresult, isolated_pio_home): def test_list_json_output(clirunner, validate_cliresult, isolated_pio_home):
@ -79,14 +79,14 @@ def test_list_json_output(clirunner, validate_cliresult, isolated_pio_home):
assert isinstance(list_result, list) assert isinstance(list_result, list)
assert len(list_result) assert len(list_result)
platforms = [item['name'] for item in list_result] platforms = [item['name'] for item in list_result]
assert set(["atmelavr", "espressif8266_stage"]) == set(platforms) assert set(["atmelavr", "espressif8266"]) == set(platforms)
def test_list_raw_output(clirunner, validate_cliresult, isolated_pio_home): def test_list_raw_output(clirunner, validate_cliresult, isolated_pio_home):
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_stage")]) [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):
@ -102,13 +102,13 @@ def test_update_check(clirunner, validate_cliresult, isolated_pio_home):
def test_update_raw(clirunner, validate_cliresult, isolated_pio_home): def test_update_raw(clirunner, validate_cliresult, isolated_pio_home):
result = clirunner.invoke(cli_platform.platform_update) result = clirunner.invoke(cli_platform.platform_update)
validate_cliresult(result) validate_cliresult(result)
assert "Uninstalling atmelavr @ 1.1.0:" in result.output assert "Uninstalling atmelavr @ 1.2.0:" in result.output
assert "PlatformManager: Installing atmelavr @" in result.output assert "PlatformManager: Installing atmelavr @" in result.output
assert len(isolated_pio_home.join("packages").listdir()) == 1 assert len(isolated_pio_home.join("packages").listdir()) == 1
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(cli_platform.platform_uninstall,
["atmelavr", "espressif8266_stage"]) ["atmelavr", "espressif8266"])
validate_cliresult(result) validate_cliresult(result)
assert len(isolated_pio_home.join("platforms").listdir()) == 0 assert len(isolated_pio_home.join("platforms").listdir()) == 0

View File

@ -25,8 +25,8 @@ def test_pkg_input_parser():
[("PkgName", "!=1.2.3,<2.0"), ("PkgName", "!=1.2.3,<2.0", None)], [("PkgName", "!=1.2.3,<2.0"), ("PkgName", "!=1.2.3,<2.0", None)],
["PkgName@1.2.3", ("PkgName", "1.2.3", None)], ["PkgName@1.2.3", ("PkgName", "1.2.3", None)],
[("PkgName@1.2.3", "1.2.5"), ("PkgName@1.2.3", "1.2.5", None)], [("PkgName@1.2.3", "1.2.5"), ("PkgName@1.2.3", "1.2.5", None)],
["id:13", ("id:13", None, None)], ["id=13", ("id=13", None, None)],
["id:13@~1.2.3", ("id:13", "~1.2.3", None)], ["id=13@~1.2.3", ("id=13", "~1.2.3", None)],
[ [
util.get_home_dir(), util.get_home_dir(),
(".platformio", None, "file://" + util.get_home_dir()) (".platformio", None, "file://" + util.get_home_dir())
@ -117,11 +117,15 @@ def test_pkg_input_parser():
], ],
[ [
"git@github.com:user/package.git", "git@github.com:user/package.git",
("package", None, "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@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", "~1.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",
@ -132,13 +136,19 @@ def test_pkg_input_parser():
"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, ("package", None,
"git+ssh://user@gitlab.private-server.com:1234/package#1.2.0") "git+ssh://user@gitlab.private-server.com:1234/package#1.2.0")
],
[
"LocalName=git+ssh://user@gitlab.private-server.com:1234"
"/package#1.2.0@!=13",
("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):
assert PackageManager.parse_pkg_input(*params) == result assert PackageManager.parse_pkg_uri(*params) == result
else: else:
assert PackageManager.parse_pkg_input(params) == result assert PackageManager.parse_pkg_uri(params) == result
def test_install_packages(isolated_pio_home, tmpdir): def test_install_packages(isolated_pio_home, tmpdir):
@ -146,7 +156,7 @@ def test_install_packages(isolated_pio_home, tmpdir):
dict(id=1, name="name_1", version="shasum"), dict(id=1, name="name_1", version="shasum"),
dict(id=1, name="name_1", version="2.0.0"), dict(id=1, name="name_1", version="2.0.0"),
dict(id=1, name="name_1", version="2.1.0"), dict(id=1, name="name_1", version="2.1.0"),
dict(id=1, name="name_1", version="1.2.0"), 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", version="2.0.0", dict(name="name_2", version="2.0.0",
@ -167,7 +177,7 @@ def test_install_packages(isolated_pio_home, tmpdir):
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.0', 'name_1_ID1', 'name_1_ID1@1.0.0', 'name_1_ID1@1.2',
'name_1_ID1@2.0.0', 'name_1_ID1@shasum', 'name_2', 'name_1_ID1@2.0.0', 'name_1_ID1@shasum', 'name_2',
'name_2@src-177cbce1f0705580d17790fda1cc2ef5', 'name_2@src-177cbce1f0705580d17790fda1cc2ef5',
'name_2@src-f863b537ab00f4c7b5011fc44b120e1f' 'name_2@src-f863b537ab00f4c7b5011fc44b120e1f'
@ -182,12 +192,11 @@ def test_get_package(isolated_pio_home):
[("1", ), None], [("1", ), None],
[("id=1", "shasum"), dict(id=1, name="name_1", version="shasum")], [("id=1", "shasum"), dict(id=1, name="name_1", version="shasum")],
[("id=1", "*"), dict(id=1, name="name_1", version="2.1.0")], [("id=1", "*"), dict(id=1, name="name_1", version="2.1.0")],
[("id=1", "^1"), dict(id=1, name="name_1", version="1.2.0")], [("id=1", "^1"), dict(id=1, name="name_1", version="1.2")],
[("id=1", "^1"), dict(id=1, name="name_1", version="1.2.0")], [("id=1", "^1"), dict(id=1, name="name_1", version="1.2")],
[("name_1", "<2"), dict(id=1, name="name_1", version="1.2.0")], [("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"), dict(id=1, name="name_1", version="2.1.0")], [("name_1", "2-0-0"), None],
[("name_1", "2-0-0"), dict(id=1, name="name_1", version="2.1.0")],
[("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"), [("url_has_higher_priority", None, "git+https://github.com"),
dict(name="name_2", version="2.0.0", dict(name="name_2", version="2.0.0",

22
tests/test_misc.py Normal file
View File

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

View File

@ -41,4 +41,4 @@ def test_packages():
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"), item assert item['sha1'] == r.headers.get("X-Checksum-Sha1")[0:40], item

View File

@ -20,8 +20,7 @@ basepython = python2.7
usedevelop = True usedevelop = True
deps = deps =
isort isort
flake8 yapf
yapf<0.17
pylint pylint
pytest pytest
commands = python --version commands = python --version
@ -47,10 +46,8 @@ commands =
[testenv:lint] [testenv:lint]
basepython = python2.7 basepython = python2.7
deps = deps =
flake8
pylint pylint
commands = commands =
flake8 ./platformio
pylint --rcfile=./.pylintrc ./platformio pylint --rcfile=./.pylintrc ./platformio
[testenv] [testenv]