Merge branch 'release/v3.3.0'

This commit is contained in:
Ivan Kravets
2017-03-27 14:39:43 +03:00
51 changed files with 2381 additions and 1362 deletions

View File

@ -7,7 +7,9 @@ What kind of issue is this?
- [ ] PlatformIO IDE. All issues related to PlatformIO IDE should be reported to appropriate repository - [ ] PlatformIO IDE. All issues related to PlatformIO IDE should be reported to appropriate repository
https://github.com/platformio/platformio-atom-ide/issues https://github.com/platformio/platformio-atom-ide/issues
- [ ] Development Platform. All issues related to Development Platform should be reported to appropriate repository. Search it using link below - [ ] Development Platform or Board. All issues related to Development Platforms or Embedded Boards
should be reported to appropriate repository.
See full list with repositories and search for "platform-xxx" repository related to your hardware
https://github.com/platformio?query=platform- https://github.com/platformio?query=platform-
- [ ] Feature Request. Start by telling us what problem youre trying to solve. Often a solution - [ ] Feature Request. Start by telling us what problem youre trying to solve. Often a solution

View File

@ -1,3 +1,3 @@
[settings] [settings]
line_length=79 line_length=79
known_third_party=bottle,click,lockfile,pytest,requests,semantic_version,serial,SCons known_third_party=arrow,bottle,click,lockfile,pytest,requests,SCons,semantic_version,serial

View File

@ -16,7 +16,7 @@ matrix:
env: TOX_ENV=py27 env: TOX_ENV=py27
- os: osx - os: osx
language: generic language: generic
env: TOX_ENV=py27 env: TOX_ENV=skipexamples
install: install:
- git submodule update --init --recursive - git submodule update --init --recursive

View File

@ -1,7 +1,7 @@
Contributing Contributing
------------ ------------
To get started, <a href="https://www.clahub.com/agreements/platformio/platformio">sign the Contributor License Agreement</a>. To get started, <a href="https://www.clahub.com/agreements/platformio/platformio-core">sign the Contributor License Agreement</a>.
1. Fork the repository on GitHub. 1. Fork the repository on GitHub.
2. Make a branch off of ``develop`` 2. Make a branch off of ``develop``

File diff suppressed because it is too large Load Diff

View File

@ -7,8 +7,8 @@ PlatformIO
.. image:: https://ci.appveyor.com/api/projects/status/unnpw0n3c5k14btn/branch/develop?svg=true .. image:: https://ci.appveyor.com/api/projects/status/unnpw0n3c5k14btn/branch/develop?svg=true
:target: https://ci.appveyor.com/project/ivankravets/platformio-core :target: https://ci.appveyor.com/project/ivankravets/platformio-core
:alt: AppVeyor.CI Build Status :alt: AppVeyor.CI Build Status
.. image:: https://requires.io/github/platformio/platformio/requirements.svg?branch=develop .. image:: https://requires.io/github/platformio/platformio-core/requirements.svg?branch=develop
:target: https://requires.io/github/platformio/platformio/requirements/?branch=develop :target: https://requires.io/github/platformio/platformio-core/requirements/?branch=develop
:alt: Requirements Status :alt: Requirements Status
.. image:: https://img.shields.io/pypi/v/platformio.svg .. image:: https://img.shields.io/pypi/v/platformio.svg
:target: https://pypi.python.org/pypi/platformio/ :target: https://pypi.python.org/pypi/platformio/
@ -26,7 +26,7 @@ PlatformIO
**Quick Links:** `Home Page <http://platformio.org>`_ | **Quick Links:** `Home Page <http://platformio.org>`_ |
`PlatformIO Plus <https://pioplus.com>`_ | `PlatformIO Plus <https://pioplus.com>`_ |
`PlatformIO IDE <http://platformio.org/platformio-ide>`_ | `PlatformIO IDE <http://platformio.org/platformio-ide>`_ |
`Project Examples <https://github.com/platformio/platformio-examples/tree/develop>`_ | `Project Examples <https://github.com/platformio/platformio-examples/>`_ |
`Docs <http://docs.platformio.org>`_ | `Docs <http://docs.platformio.org>`_ |
`Donate <http://platformio.org/donate>`_ | `Donate <http://platformio.org/donate>`_ |
`Contact Us <https://pioplus.com/contact.html>`_ `Contact Us <https://pioplus.com/contact.html>`_
@ -98,13 +98,13 @@ settings for most popular `Embedded Boards <http://platformio.org/boards>`_.
* Colourful `command-line output <https://raw.githubusercontent.com/platformio/platformio/develop/examples/platformio-examples.png>`_ * Colourful `command-line output <https://raw.githubusercontent.com/platformio/platformio/develop/examples/platformio-examples.png>`_
* `IDE Integration <http://docs.platformio.org/en/stable/ide.html>`_ with * `IDE Integration <http://docs.platformio.org/en/stable/ide.html>`_ with
*Arduino, Atom, Eclipse, Emacs, Energia, Qt Creator, Sublime Text, Vim, Visual Studio* *Cloud9, Codeanywhere, Eclipse Che, Atom, CLion, CodeBlocks, Eclipse, Emacs, NetBeans, Qt Creator, Sublime Text, Vim, Visual Studio*
* Cloud compiling and `Continuous Integration <http://docs.platformio.org/en/stable/ci/index.html>`_ * Cloud compiling and `Continuous Integration <http://docs.platformio.org/en/stable/ci/index.html>`_
with *AppVeyor, Circle CI, Drone, Shippable, Travis CI* with *AppVeyor, Circle CI, Drone, Shippable, Travis CI*
* Built-in `Serial Port Monitor <http://docs.platformio.org/en/stable/userguide/cmd_serialports.html#platformio-serialports-monitor>`_ and configurable * Built-in `Serial Port Monitor <http://docs.platformio.org/en/stable/userguide/cmd_serialports.html#platformio-serialports-monitor>`_ and configurable
`build -flags/-options <http://docs.platformio.org/en/stable/projectconf.html#build-flags>`_ `build -flags/-options <http://docs.platformio.org/en/stable/projectconf.html#build-flags>`_
* Automatic **firmware uploading** * Automatic **firmware uploading**
* Pre-built tool chains, frameworks for the popular `Hardware Platforms <http://platformio.org/platforms>`_ * Pre-built tool chains, frameworks for the popular `development platforms <http://platformio.org/platforms>`_
.. image:: https://raw.githubusercontent.com/platformio/platformio-web/develop/app/images/platformio-embedded-development.png .. image:: https://raw.githubusercontent.com/platformio/platformio-web/develop/app/images/platformio-embedded-development.png
:target: http://platformio.org :target: http://platformio.org
@ -116,12 +116,11 @@ The Missing Library Manager. *It's here!*
platforms which allows you to organize and have up-to-date external libraries. platforms which allows you to organize and have up-to-date external libraries.
* Friendly `Command-Line Interface <http://docs.platformio.org/en/stable/librarymanager/index.html>`_ * Friendly `Command-Line Interface <http://docs.platformio.org/en/stable/librarymanager/index.html>`_
* Modern `Web 2.0 Library Search <http://platformio.org/lib>`_ * Modern `Web 2.0 Library Portal <http://platformio.org/lib>`_
* Open Source `Library Registry API <https://github.com/platformio/platformio-api>`_ * Open Source `Library Registry API <https://github.com/platformio/platformio-api>`_
* Library Crawler based on `library.json <http://docs.platformio.org/en/stable/librarymanager/config.html>`_ * Library Crawler based on `library.json <http://docs.platformio.org/en/stable/librarymanager/config.html>`_
specification specification
* Library **dependency management** * Project Dependency Manager with `Semantic Versioning <http://docs.platformio.org/page/librarymanager/index.html>`_ requirements
* Automatic library updating
.. image:: https://raw.githubusercontent.com/platformio/platformio-web/develop/app/images/platformio-library-manager.png .. image:: https://raw.githubusercontent.com/platformio/platformio-web/develop/app/images/platformio-library-manager.png
:target: http://platformio.org :target: http://platformio.org
@ -158,7 +157,8 @@ It has support for the most popular embedded platforms:
* `Atmel AVR <http://platformio.org/platforms/atmelavr>`_ * `Atmel AVR <http://platformio.org/platforms/atmelavr>`_
* `Atmel SAM <http://platformio.org/platforms/atmelsam>`_ * `Atmel SAM <http://platformio.org/platforms/atmelsam>`_
* `Espressif <http://platformio.org/platforms/espressif>`_ * `Espressif 32 <http://platformio.org/platforms/espressif32>`_
* `Espressif 8266 <http://platformio.org/platforms/espressif8266>`_
* `Freescale Kinetis <http://platformio.org/platforms/freescalekinetis>`_ * `Freescale Kinetis <http://platformio.org/platforms/freescalekinetis>`_
* `Intel ARC32 <http://platformio.org/platforms/intel_arc32>`_ * `Intel ARC32 <http://platformio.org/platforms/intel_arc32>`_
* `Lattice iCE40 <http://platformio.org/platforms/lattice_ice40>`_ * `Lattice iCE40 <http://platformio.org/platforms/lattice_ice40>`_
@ -169,15 +169,18 @@ It has support for the most popular embedded platforms:
* `Silicon Labs EFM32 <http://platformio.org/platforms/siliconlabsefm32>`_ * `Silicon Labs EFM32 <http://platformio.org/platforms/siliconlabsefm32>`_
* `Teensy <http://platformio.org/platforms/teensy>`_ * `Teensy <http://platformio.org/platforms/teensy>`_
* `TI MSP430 <http://platformio.org/platforms/timsp430>`_ * `TI MSP430 <http://platformio.org/platforms/timsp430>`_
* `TI TIVA C <http://platformio.org/platforms/titiva>`_ * `TI TivaVA C <http://platformio.org/platforms/titiva>`_
Frameworks: Frameworks:
* `Arduino <http://platformio.org/frameworks/arduino>`_ * `Arduino <http://platformio.org/frameworks/arduino>`_
* `ARTIK SDK <http://platformio.org/frameworks/artik-sdk>`_
* `CMSIS <http://platformio.org/frameworks/cmsis>`_ * `CMSIS <http://platformio.org/frameworks/cmsis>`_
* `Energia <http://platformio.org/frameworks/energia>`_ * `Energia <http://platformio.org/frameworks/energia>`_
* `ESP-IDF <http://platformio.org/frameworks/espidf>`_
* `libOpenCM3 <http://platformio.org/frameworks/libopencm3>`_ * `libOpenCM3 <http://platformio.org/frameworks/libopencm3>`_
* `mbed <http://platformio.org/frameworks/mbed>`_ * `mbed <http://platformio.org/frameworks/mbed>`_
* `Pumbaa <http://platformio.org/frameworks/pumbaa>`_
* `Simba <http://platformio.org/frameworks/simba>`_ * `Simba <http://platformio.org/frameworks/simba>`_
* `SPL <http://platformio.org/frameworks/spl>`_ * `SPL <http://platformio.org/frameworks/spl>`_
* `WiringPi <http://platformio.org/frameworks/wiringpi>`_ * `WiringPi <http://platformio.org/frameworks/wiringpi>`_

2
docs

Submodule docs updated: fad663db0c...d20acf3631

View File

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

View File

@ -137,8 +137,6 @@ class ContentCache(object):
return 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")
if not self.cache_dir:
os.makedirs(self.cache_dir)
self._db_path = join(self.cache_dir, "db.data") self._db_path = join(self.cache_dir, "db.data")
def __enter__(self): def __enter__(self):
@ -152,7 +150,7 @@ class ContentCache(object):
continue continue
line = line.strip() line = line.strip()
expire, path = line.split("=") expire, path = line.split("=")
if time() < int(expire): if time() < int(expire) and isfile(path):
newlines.append(line) newlines.append(line)
continue continue
found = True found = True
@ -172,6 +170,8 @@ class ContentCache(object):
pass pass
def _lock_dbindex(self): def _lock_dbindex(self):
if not 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 \
(time() - getmtime(self._lockfile.lock_file)) > 10: (time() - getmtime(self._lockfile.lock_file)) > 10:
@ -200,19 +200,17 @@ class ContentCache(object):
return h.hexdigest() return h.hexdigest()
def get(self, key): def get(self, key):
if not self.cache_dir:
return None
cache_path = self.get_cache_path(key) cache_path = self.get_cache_path(key)
if not isfile(cache_path): if not isfile(cache_path):
return None return None
with open(cache_path, "rb") as fp: with open(cache_path, "rb") as fp:
data = fp.read() data = fp.read()
if data[0] in ("{", "["): if data and data[0] in ("{", "["):
return json.loads(data) return json.loads(data)
return data return data
def set(self, key, data, valid): def set(self, key, data, valid):
if not self.cache_dir or not data: if not data:
return return
if not isdir(self.cache_dir): if not isdir(self.cache_dir):
os.makedirs(self.cache_dir) os.makedirs(self.cache_dir)
@ -238,8 +236,14 @@ class ContentCache(object):
return True return True
def clean(self): def clean(self):
if self.cache_dir and isdir(self.cache_dir): if not self.cache_dir or not isdir(self.cache_dir):
util.rmtree_(self.cache_dir) return
util.rmtree_(self.cache_dir)
def clean_cache():
with ContentCache() as cc:
cc.clean()
def sanitize_setting(name, value): def sanitize_setting(name, value):
@ -325,7 +329,7 @@ def get_cid():
except: # pylint: disable=bare-except except: # pylint: disable=bare-except
pass pass
cid = str( cid = str(
uuid.UUID(bytes=hashlib.md5( uuid.UUID(bytes=hashlib.md5(str(_uid if _uid else uuid.getnode()))
str(_uid if _uid else uuid.getnode())).digest())) .digest()))
set_state_item("cid", cid) set_state_item("cid", cid)
return cid return cid

View File

@ -117,8 +117,13 @@ elif not int(ARGUMENTS.get("PIOVERBOSE", 0)):
for var in ("BUILD_FLAGS", "SRC_BUILD_FLAGS", "SRC_FILTER", "EXTRA_SCRIPT", for var in ("BUILD_FLAGS", "SRC_BUILD_FLAGS", "SRC_FILTER", "EXTRA_SCRIPT",
"UPLOAD_PORT", "UPLOAD_FLAGS", "LIB_EXTRA_DIRS"): "UPLOAD_PORT", "UPLOAD_FLAGS", "LIB_EXTRA_DIRS"):
k = "PLATFORMIO_%s" % var k = "PLATFORMIO_%s" % var
if environ.get(k): if k not in environ:
continue
if var in ("UPLOAD_PORT", "EXTRA_SCRIPT") or not env.get(var):
env[var] = environ.get(k) env[var] = environ.get(k)
else:
env[var] = "%s%s%s" % (environ.get(k), ", "
if var == "LIB_EXTRA_DIRS" else " ", env[var])
# Parse comma separated items # Parse comma separated items
for opt in ("PIOFRAMEWORK", "LIB_DEPS", "LIB_IGNORE", "LIB_EXTRA_DIRS"): for opt in ("PIOFRAMEWORK", "LIB_DEPS", "LIB_IGNORE", "LIB_EXTRA_DIRS"):

View File

@ -46,9 +46,8 @@ class LibBuilderFactory(object):
elif used_frameworks: elif used_frameworks:
clsname = "%sLibBuilder" % used_frameworks[0].title() clsname = "%sLibBuilder" % used_frameworks[0].title()
obj = getattr(sys.modules[__name__], clsname)(env, obj = getattr(sys.modules[__name__], clsname)(
path, env, path, verbose=verbose)
verbose=verbose)
assert isinstance(obj, LibBuilderBase) assert isinstance(obj, LibBuilderBase)
return obj return obj
@ -571,7 +570,7 @@ class PlatformIOLibBuilder(LibBuilderBase):
inc_dirs.append(join(self.path, "utility")) inc_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['CPPPATH']: if path not in self.envorigin.get("CPPPATH", []):
inc_dirs.append(self.env.subst(path)) inc_dirs.append(self.env.subst(path))
return inc_dirs return inc_dirs
@ -591,8 +590,8 @@ def GetLibBuilders(env): # pylint: disable=too-many-branches
if verbose: if verbose:
sys.stderr.write("Ignored library %s\n" % lb.path) sys.stderr.write("Ignored library %s\n" % lb.path)
return return
if compat_mode > 1 and not lb.is_platforms_compatible(env[ if compat_mode > 1 and not lb.is_platforms_compatible(
'PIOPLATFORM']): env['PIOPLATFORM']):
if verbose: if verbose:
sys.stderr.write("Platform incompatible library %s\n" % sys.stderr.write("Platform incompatible library %s\n" %
lb.path) lb.path)
@ -614,9 +613,8 @@ def GetLibBuilders(env): # pylint: disable=too-many-branches
if item == "__cores__" or not isdir(join(libs_dir, item)): if item == "__cores__" or not isdir(join(libs_dir, item)):
continue continue
try: try:
lb = LibBuilderFactory.new(env, lb = LibBuilderFactory.new(
join(libs_dir, item), env, join(libs_dir, item), verbose=verbose)
verbose=verbose)
except ValueError: except ValueError:
if verbose: if verbose:
sys.stderr.write("Skip library with broken manifest: %s\n" sys.stderr.write("Skip library with broken manifest: %s\n"

View File

@ -32,6 +32,7 @@ from platformio import util
class InoToCPPConverter(object): class InoToCPPConverter(object):
PROTOTYPE_RE = re.compile(r"""^( PROTOTYPE_RE = re.compile(r"""^(
(?:template\<.*\>\s*)? # template
([a-z_\d]+\*?\s+){1,2} # return type ([a-z_\d]+\*?\s+){1,2} # return type
([a-z_\d]+\s*) # name of prototype ([a-z_\d]+\s*) # name of prototype
\([a-z_,\.\*\&\[\]\s\d]*\) # arguments \([a-z_,\.\*\&\[\]\s\d]*\) # arguments
@ -180,8 +181,9 @@ class InoToCPPConverter(object):
def ConvertInoToCpp(env): def ConvertInoToCpp(env):
ino_nodes = (env.Glob(join("$PROJECTSRC_DIR", "*.ino")) + src_dir = util.glob_escape(env.subst("$PROJECTSRC_DIR"))
env.Glob(join("$PROJECTSRC_DIR", "*.pde"))) ino_nodes = (
env.Glob(join(src_dir, "*.ino")) + env.Glob(join(src_dir, "*.pde")))
if not ino_nodes: if not ino_nodes:
return return
c = InoToCPPConverter(env) c = InoToCPPConverter(env)
@ -215,7 +217,7 @@ def DumpIDEData(env):
for name in p.get_installed_packages(): for name in p.get_installed_packages():
if p.get_package_type(name) != "toolchain": if p.get_package_type(name) != "toolchain":
continue continue
toolchain_dir = 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, "lib", "gcc", "*", "*", "include*") join(toolchain_dir, "lib", "gcc", "*", "*", "include*")

View File

@ -16,14 +16,15 @@ from __future__ import absolute_import
from os.path import join, sep from os.path import join, sep
from platformio.managers.core import get_core_package_dir
def ProcessTest(env): def ProcessTest(env):
env.Append( env.Append(
CPPDEFINES=["UNIT_TEST", "UNITY_INCLUDE_CONFIG_H"], CPPDEFINES=["UNIT_TEST", "UNITY_INCLUDE_CONFIG_H"],
CPPPATH=[join("$BUILD_DIR", "UnityTestLib")]) CPPPATH=[join("$BUILD_DIR", "UnityTestLib")])
unitylib = env.BuildLibrary( unitylib = env.BuildLibrary(
join("$BUILD_DIR", "UnityTestLib"), join("$BUILD_DIR", "UnityTestLib"), get_core_package_dir("tool-unity"))
env.PioPlatform().get_package_dir("tool-unity"))
env.Prepend(LIBS=[unitylib]) env.Prepend(LIBS=[unitylib])
src_filter = None src_filter = None

View File

@ -25,7 +25,7 @@ from SCons.Script import (COMMAND_LINE_TARGETS, AlwaysBuild,
DefaultEnvironment, SConscript) DefaultEnvironment, SConscript)
from SCons.Util import case_sensitive_suffixes, is_Sequence from SCons.Util import case_sensitive_suffixes, is_Sequence
from platformio.util import pioversion_to_intstr from platformio.util import glob_escape, pioversion_to_intstr
SRC_BUILD_EXT = ["c", "cpp", "S", "spp", "SPP", "sx", "s", "asm", "ASM"] SRC_BUILD_EXT = ["c", "cpp", "S", "spp", "SPP", "sx", "s", "asm", "ASM"]
SRC_HEADER_EXT = ["h", "hpp"] SRC_HEADER_EXT = ["h", "hpp"]
@ -191,7 +191,7 @@ def MatchSourceFiles(env, src_dir, src_filter=None):
src_filter = src_filter.replace("/", sep).replace("\\", sep) src_filter = src_filter.replace("/", sep).replace("\\", sep)
for (action, pattern) in SRC_FILTER_PATTERNS_RE.findall(src_filter): for (action, pattern) in SRC_FILTER_PATTERNS_RE.findall(src_filter):
items = set() items = set()
for item in glob(join(src_dir, pattern)): for item in glob(join(glob_escape(src_dir), pattern)):
if isdir(item): if isdir(item):
for root, _, files in walk(item, followlinks=True): for root, _, files in walk(item, followlinks=True):
for f in files: for f in files:
@ -266,8 +266,7 @@ 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( lib.CollectBuildFiles(variant_dir, src_dir, src_filter=src_filter))
variant_dir, src_dir, src_filter=src_filter))
def BuildSources(env, variant_dir, src_dir, src_filter=None): def BuildSources(env, variant_dir, src_dir, src_filter=None):

View File

@ -18,7 +18,7 @@ import sys
import click import click
from platformio.pioplus import pioplus_call from platformio.managers.core import pioplus_call
@click.group("account", short_help="Manage PIO Account") @click.group("account", short_help="Manage PIO Account")

View File

@ -20,72 +20,63 @@ from platformio.exception import APIRequestError, InternetIsOffline
from platformio.managers.platform import PlatformManager from platformio.managers.platform import PlatformManager
@click.command("boards", short_help="Pre-configured Embedded Boards") @click.command("boards", short_help="Embedded Board Explorer")
@click.argument("query", required=False) @click.argument("query", required=False)
@click.option("--installed", is_flag=True) @click.option("--installed", is_flag=True)
@click.option("--json-output", is_flag=True) @click.option("--json-output", is_flag=True)
def cli(query, installed, json_output): # pylint: disable=R0912 def cli(query, installed, json_output): # pylint: disable=R0912
if json_output: if json_output:
return _ouput_boards_json(query, installed) return _print_boards_json(query, installed)
BOARDLIST_TPL = ("{type:<30} {mcu:<14} {frequency:<8} "
" {flash:<7} {ram:<6} {name}")
terminal_width, _ = click.get_terminal_size()
grpboards = {} grpboards = {}
for board in _get_boards(installed): for board in _get_boards(installed):
if query and query.lower() not in json.dumps(board).lower():
continue
if board['platform'] not in grpboards: if board['platform'] not in grpboards:
grpboards[board['platform']] = [] grpboards[board['platform']] = []
grpboards[board['platform']].append(board) grpboards[board['platform']].append(board)
for (platform, pboards) in sorted(grpboards.items()): terminal_width, _ = click.get_terminal_size()
if query: for (platform, boards) in sorted(grpboards.items()):
search_data = json.dumps(pboards).lower()
if query.lower() not in search_data.lower():
continue
click.echo("") click.echo("")
click.echo("Platform: ", nl=False) click.echo("Platform: ", nl=False)
click.secho(platform, bold=True) click.secho(platform, bold=True)
click.echo("-" * terminal_width) click.echo("-" * terminal_width)
print_boards(boards)
def print_boards(boards):
terminal_width, _ = click.get_terminal_size()
BOARDLIST_TPL = ("{type:<30} {mcu:<14} {frequency:<8} "
" {flash:<7} {ram:<6} {name}")
click.echo(
BOARDLIST_TPL.format(
type=click.style("ID", fg="cyan"),
mcu="MCU",
frequency="Frequency",
flash="Flash",
ram="RAM",
name="Name"))
click.echo("-" * terminal_width)
for board in boards:
ram_size = board['ram']
if ram_size >= 1024:
if ram_size % 1024:
ram_size = "%.1fkB" % (ram_size / 1024.0)
else:
ram_size = "%dkB" % (ram_size / 1024)
else:
ram_size = "%dB" % ram_size
click.echo( click.echo(
BOARDLIST_TPL.format( BOARDLIST_TPL.format(
type=click.style( type=click.style(board['id'], fg="cyan"),
"ID", fg="cyan"), mcu=board['mcu'],
mcu="MCU", frequency="%dMhz" % (board['fcpu'] / 1000000),
frequency="Frequency", flash="%dkB" % (board['rom'] / 1024),
flash="Flash", ram=ram_size,
ram="RAM", name=board['name']))
name="Name"))
click.echo("-" * terminal_width)
for board in sorted(pboards, key=lambda b: b['id']):
if query:
search_data = "%s %s" % (board['id'],
json.dumps(board).lower())
if query.lower() not in search_data.lower():
continue
flash_size = "%dkB" % (board['rom'] / 1024)
ram_size = board['ram']
if ram_size >= 1024:
if ram_size % 1024:
ram_size = "%.1fkB" % (ram_size / 1024.0)
else:
ram_size = "%dkB" % (ram_size / 1024)
else:
ram_size = "%dB" % ram_size
click.echo(
BOARDLIST_TPL.format(
type=click.style(
board['id'], fg="cyan"),
mcu=board['mcu'],
frequency="%dMhz" % (board['fcpu'] / 1000000),
flash=flash_size,
ram=ram_size,
name=board['name']))
def _get_boards(installed=False): def _get_boards(installed=False):
@ -99,10 +90,10 @@ def _get_boards(installed=False):
boards.append(board) boards.append(board)
except InternetIsOffline: except InternetIsOffline:
pass pass
return boards return sorted(boards, key=lambda b: b['name'])
def _ouput_boards_json(query, installed=False): def _print_boards_json(query, installed=False):
result = [] result = []
try: try:
boards = _get_boards(installed) boards = _get_boards(installed)

View File

@ -152,7 +152,7 @@ def _copy_contents(dst_dir, contents):
def _exclude_contents(dst_dir, patterns): def _exclude_contents(dst_dir, patterns):
contents = [] contents = []
for p in patterns: for p in patterns:
contents += glob(join(dst_dir, p)) contents += glob(join(util.glob_escape(dst_dir), p))
for path in contents: for path in contents:
path = abspath(path) path = abspath(path)
if isdir(path): if isdir(path):

View File

@ -61,12 +61,12 @@ def device_list(json_output):
@click.option( @click.option(
"--rts", "--rts",
default=None, default=None,
type=click.Choice(["0", "1"]), type=click.IntRange(0, 1),
help="Set initial RTS line state") help="Set initial RTS line state")
@click.option( @click.option(
"--dtr", "--dtr",
default=None, default=None,
type=click.Choice(["0", "1"]), type=click.IntRange(0, 1),
help="Set initial DTR line state") help="Set initial DTR line state")
@click.option("--echo", is_flag=True, help="Enable local echo, default=Off") @click.option("--echo", is_flag=True, help="Enable local echo, default=Off")
@click.option( @click.option(

View File

@ -57,6 +57,7 @@ def validate_boards(ctx, param, value): # pylint: disable=W0613
"--ide", type=click.Choice(ProjectGenerator.get_supported_ides())) "--ide", type=click.Choice(ProjectGenerator.get_supported_ides()))
@click.option("-O", "--project-option", multiple=True) @click.option("-O", "--project-option", multiple=True)
@click.option("--env-prefix", default="") @click.option("--env-prefix", default="")
@click.option("-s", "--silent", is_flag=True)
@click.pass_context @click.pass_context
def cli( def cli(
ctx, # pylint: disable=R0913 ctx, # pylint: disable=R0913
@ -64,28 +65,29 @@ def cli(
board, board,
ide, ide,
project_option, project_option,
env_prefix): env_prefix,
silent):
if project_dir == getcwd(): if not silent:
click.secho("\nThe current working directory", fg="yellow", nl=False) if project_dir == getcwd():
click.secho(" %s " % project_dir, fg="cyan", nl=False) click.secho(
click.secho( "\nThe current working directory", fg="yellow", nl=False)
"will be used for project.\n" click.secho(" %s " % project_dir, fg="cyan", nl=False)
"You can specify another project directory via\n" click.secho(
"`platformio init -d %PATH_TO_THE_PROJECT_DIR%` command.", "will be used for project.\n"
fg="yellow") "You can specify another project directory via\n"
click.echo("") "`platformio init -d %PATH_TO_THE_PROJECT_DIR%` command.",
fg="yellow")
click.echo("")
click.echo("The next files/directories have been created in %s" % click.echo("The next files/directories have been created in %s" %
click.style( click.style(project_dir, fg="cyan"))
project_dir, fg="cyan")) click.echo("%s - Project Configuration File" % click.style(
click.echo("%s - Project Configuration File" % click.style( "platformio.ini", fg="cyan"))
"platformio.ini", fg="cyan")) click.echo("%s - Put your source files here" % click.style(
click.echo("%s - Put your source files here" % click.style( "src", fg="cyan"))
"src", fg="cyan")) click.echo("%s - Put here project specific (private) libraries" %
click.echo("%s - Put here project specific (private) libraries" % click.style("lib", fg="cyan"))
click.style(
"lib", fg="cyan"))
init_base_project(project_dir) init_base_project(project_dir)
@ -111,16 +113,17 @@ def cli(
pg = ProjectGenerator(project_dir, ide, board[0]) pg = ProjectGenerator(project_dir, ide, board[0])
pg.generate() pg.generate()
click.secho( if not silent:
"\nProject has been successfully initialized!\nUseful commands:\n" click.secho(
"`platformio run` - process/build project from the current " "\nProject has been successfully initialized!\nUseful commands:\n"
"directory\n" "`platformio run` - process/build project from the current "
"`platformio run --target upload` or `platformio run -t upload` " "directory\n"
"- upload firmware to embedded board\n" "`platformio run --target upload` or `platformio run -t upload` "
"`platformio run --target clean` - clean project (remove compiled " "- upload firmware to embedded board\n"
"files)\n" "`platformio run --target clean` - clean project (remove compiled "
"`platformio run --help` - additional information", "files)\n"
fg="green") "`platformio run --help` - additional information",
fg="green")
def get_first_board(project_dir): def get_first_board(project_dir):

View File

@ -12,14 +12,19 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import json # pylint: disable=too-many-branches, too-many-locals
from os.path import join
from time import sleep
import json
from os.path import isdir, join
from time import sleep
from urllib import quote
import arrow
import click import click
from platformio import exception, util from platformio import exception, util
from platformio.managers.lib import LibraryManager from platformio.managers.lib import LibraryManager
from platformio.managers.platform import PlatformFactory, PlatformManager
from platformio.util import get_api_result from platformio.util import get_api_result
@ -43,8 +48,9 @@ from platformio.util import get_api_result
help="Manage custom library storage") help="Manage custom library storage")
@click.pass_context @click.pass_context
def cli(ctx, **options): def cli(ctx, **options):
non_storage_cmds = ("search", "show", "register", "stats", "builtin")
# skip commands that don't need storage folder # skip commands that don't need storage folder
if ctx.invoked_subcommand in ("search", "register") or \ if ctx.invoked_subcommand in non_storage_cmds or \
(len(ctx.args) == 2 and ctx.args[1] in ("-h", "--help")): (len(ctx.args) == 2 and ctx.args[1] in ("-h", "--help")):
return return
storage_dir = options['storage_dir'] storage_dir = options['storage_dir']
@ -106,57 +112,69 @@ def lib_uninstall(lm, libraries):
"--only-check", "--only-check",
is_flag=True, is_flag=True,
help="Do not update, only check for new version") help="Do not update, only check for new version")
@click.option("--json-output", is_flag=True)
@click.pass_obj @click.pass_obj
def lib_update(lm, libraries, only_check): def lib_update(lm, libraries, only_check, json_output):
if not libraries: if not libraries:
libraries = [str(m.get("id", m['name'])) for m in lm.get_installed()] libraries = [manifest['__pkg_dir'] for manifest in lm.get_installed()]
for library in libraries:
lm.update(library, only_check=only_check) if only_check and json_output:
result = []
for library in libraries:
pkg_dir = library if isdir(library) else None
requirements = None
url = None
if not pkg_dir:
name, requirements, url = lm.parse_pkg_input(library)
pkg_dir = lm.get_package_dir(name, requirements, url)
if not pkg_dir:
continue
latest = lm.outdated(pkg_dir, requirements)
if not latest:
continue
manifest = lm.load_manifest(pkg_dir)
manifest['versionLatest'] = latest
result.append(manifest)
return click.echo(json.dumps(result))
else:
for library in libraries:
lm.update(library, only_check=only_check)
####### def print_lib_item(item):
click.secho(item['name'], fg="cyan")
click.echo("=" * len(item['name']))
if "id" in item:
click.secho("#ID: %d" % item['id'], bold=True)
if "description" in item or "url" in item:
click.echo(item.get("description", item.get("url", "")))
click.echo()
LIBLIST_TPL = ("[{id:^14}] {name:<25} {compatibility:<30} " for key in ("version", "homepage", "license", "keywords"):
"\"{authornames}\": {description}") if key not in item or not item[key]:
continue
if isinstance(item[key], list):
click.echo("%s: %s" % (key.title(), ", ".join(item[key])))
else:
click.echo("%s: %s" % (key.title(), item[key]))
for key in ("frameworks", "platforms"):
if key not in item:
continue
click.echo("Compatible %s: %s" % (key, ", ".join(
[i['title'] if isinstance(i, dict) else i for i in item[key]])))
if "authors" in item or "authornames" in item:
click.echo("Authors: %s" % ", ".join(
item.get("authornames",
[a.get("name", "") for a in item.get("authors", [])])))
if "__src_url" in item:
click.secho("Source: %s" % item['__src_url'])
click.echo()
def echo_liblist_header(): @cli.command("search", short_help="Search for a library")
click.echo(
LIBLIST_TPL.format(
id=click.style(
"ID", fg="green"),
name=click.style(
"Name", fg="cyan"),
compatibility=click.style(
"Compatibility", fg="yellow"),
authornames="Authors",
description="Description"))
terminal_width, _ = click.get_terminal_size()
click.echo("-" * terminal_width)
def echo_liblist_item(item):
description = item.get("description", item.get("url", "")).encode("utf-8")
if "version" in item:
description += " | @" + click.style(item['version'], fg="yellow")
click.echo(
LIBLIST_TPL.format(
id=click.style(
str(item.get("id", "-")), fg="green"),
name=click.style(
item['name'], fg="cyan"),
compatibility=click.style(
", ".join(
item.get("frameworks", ["-"]) + item.get("platforms", [])),
fg="yellow"),
authornames=", ".join(item.get("authornames", ["Unknown"])).encode(
"utf-8"),
description=description))
@cli.command("search", short_help="Search for library")
@click.argument("query", required=False, nargs=-1) @click.argument("query", required=False, nargs=-1)
@click.option("--json-output", is_flag=True) @click.option("--json-output", is_flag=True)
@click.option("--page", type=click.INT, default=1) @click.option("--page", type=click.INT, default=1)
@ -181,9 +199,8 @@ def lib_search(query, json_output, page, noninteractive, **filters):
query.append('%s:"%s"' % (key, value)) query.append('%s:"%s"' % (key, value))
result = get_api_result( result = get_api_result(
"/lib/search", "/v2/lib/search",
dict( dict(query=" ".join(query), page=page),
query=" ".join(query), page=page),
cache_valid="3d") cache_valid="3d")
if json_output: if json_output:
@ -210,12 +227,9 @@ def lib_search(query, json_output, page, noninteractive, **filters):
"Found %d libraries:\n" % result['total'], "Found %d libraries:\n" % result['total'],
fg="green" if result['total'] else "yellow") fg="green" if result['total'] else "yellow")
if result['total']:
echo_liblist_header()
while True: while True:
for item in result['items']: for item in result['items']:
echo_liblist_item(item) print_lib_item(item)
if (int(result['page']) * int(result['perpage']) >= if (int(result['page']) * int(result['perpage']) >=
int(result['total'])): int(result['total'])):
@ -232,9 +246,9 @@ def lib_search(query, json_output, page, noninteractive, **filters):
elif not click.confirm("Show next libraries?"): elif not click.confirm("Show next libraries?"):
break break
result = get_api_result( result = get_api_result(
"/lib/search", "/v2/lib/search",
dict( {"query": " ".join(query),
query=" ".join(query), page=int(result['page']) + 1), "page": int(result['page']) + 1},
cache_valid="3d") cache_valid="3d")
@ -245,41 +259,87 @@ def lib_list(lm, json_output):
items = lm.get_installed() items = lm.get_installed()
if json_output: if json_output:
click.echo(json.dumps(items)) return click.echo(json.dumps(items))
return
if not items: if not items:
return return
echo_liblist_header()
for item in sorted(items, key=lambda i: i['name']): for item in sorted(items, key=lambda i: i['name']):
if "authors" in item: print_lib_item(item)
item['authornames'] = [i['name'] for i in item['authors']]
echo_liblist_item(item)
@cli.command("show", short_help="Show details about installed library") @util.memoized
@click.pass_obj def get_builtin_libs(storage_names=None):
items = []
storage_names = storage_names or []
pm = PlatformManager()
for manifest in pm.get_installed():
p = PlatformFactory.newPlatform(manifest['__pkg_dir'])
for storage in p.get_lib_storages():
if storage_names and storage['name'] not in storage_names:
continue
lm = LibraryManager(storage['path'])
items.append({
"name": storage['name'],
"path": storage['path'],
"items": lm.get_installed()
})
return items
@cli.command("builtin", short_help="List built-in libraries")
@click.option("--storage", multiple=True)
@click.option("--json-output", is_flag=True)
def lib_builtin(storage, json_output):
items = get_builtin_libs(storage)
if json_output:
return click.echo(json.dumps(items))
for storage in items:
if not storage['items']:
continue
click.secho(storage['name'], fg="green")
click.echo("*" * len(storage['name']))
click.echo()
for item in sorted(storage['items'], key=lambda i: i['name']):
print_lib_item(item)
@cli.command("show", short_help="Show detailed info about a library")
@click.argument("library", metavar="[LIBRARY]") @click.argument("library", metavar="[LIBRARY]")
def lib_show(lm, library): # pylint: disable=too-many-branches @click.option("--json-output", is_flag=True)
name, requirements, url = lm.parse_pkg_name(library) def lib_show(library, json_output):
package_dir = lm.get_package_dir(name, requirements, url) lm = LibraryManager()
if not package_dir: name, requirements, _ = lm.parse_pkg_input(library)
click.secho( lib_id = lm.get_pkg_id_by_name(
"%s @ %s is not installed" % (name, requirements or "*"), name, requirements, silent=json_output, interactive=not json_output)
fg="yellow") lib = get_api_result("/lib/info/%d" % lib_id, cache_valid="1d")
return if json_output:
return click.echo(json.dumps(lib))
manifest = lm.load_manifest(package_dir) click.secho(lib['name'], fg="cyan")
click.echo("=" * len(lib['name']))
click.secho(manifest['name'], fg="cyan") click.secho("#ID: %d" % lib['id'], bold=True)
click.echo("=" * len(manifest['name'])) click.echo(lib['description'])
if "description" in manifest:
click.echo(manifest['description'])
click.echo() click.echo()
click.echo("Version: %s, released %s" %
(lib['version']['name'],
arrow.get(lib['version']['released']).humanize()))
click.echo("Manifest: %s" % lib['confurl'])
for key in ("homepage", "repository", "license"):
if key not in lib or not lib[key]:
continue
if isinstance(lib[key], list):
click.echo("%s: %s" % (key.title(), ", ".join(lib[key])))
else:
click.echo("%s: %s" % (key.title(), lib[key]))
blocks = []
_authors = [] _authors = []
for author in manifest.get("authors", []): for author in lib.get("authors", []):
_data = [] _data = []
for key in ("name", "email", "url", "maintainer"): for key in ("name", "email", "url", "maintainer"):
if not author[key]: if not author[key]:
@ -292,19 +352,33 @@ def lib_show(lm, library): # pylint: disable=too-many-branches
_data.append(author[key]) _data.append(author[key])
_authors.append(" ".join(_data)) _authors.append(" ".join(_data))
if _authors: if _authors:
click.echo("Authors: %s" % ", ".join(_authors)) blocks.append(("Authors", _authors))
for key in ("keywords", "frameworks", "platforms", "license", "url", blocks.append(("Keywords", lib['keywords']))
"version"): for key in ("frameworks", "platforms"):
if key not in manifest: if key not in lib or not lib[key]:
continue continue
if isinstance(manifest[key], list): blocks.append(("Compatible %s" % key, [i['title'] for i in lib[key]]))
click.echo("%s: %s" % (key.title(), ", ".join(manifest[key]))) blocks.append(("Headers", lib['headers']))
else: blocks.append(("Examples", lib['examples']))
click.echo("%s: %s" % (key.title(), manifest[key])) blocks.append(("Versions", [
"%s, released %s" % (v['name'], arrow.get(v['released']).humanize())
for v in lib['versions']
]))
blocks.append(("Unique Downloads", [
"Today: %s" % lib['dlstats']['day'], "Week: %s" %
lib['dlstats']['week'], "Month: %s" % lib['dlstats']['month']
]))
for (title, rows) in blocks:
click.echo()
click.secho(title, bold=True)
click.echo("-" * len(title))
for row in rows:
click.echo(row)
@cli.command("register", short_help="Register new library") @cli.command("register", short_help="Register a new library")
@click.argument("config_url") @click.argument("config_url")
def lib_register(config_url): def lib_register(config_url):
if (not config_url.startswith("http://") and if (not config_url.startswith("http://") and
@ -317,3 +391,76 @@ def lib_register(config_url):
result['message'], result['message'],
fg="green" fg="green"
if "successed" in result and result['successed'] else "red") if "successed" in result and result['successed'] else "red")
@cli.command("stats", short_help="Library Registry Statistics")
@click.option("--json-output", is_flag=True)
def lib_stats(json_output):
result = get_api_result("/lib/stats", cache_valid="1h")
if json_output:
return click.echo(json.dumps(result))
printitem_tpl = "{name:<33} {url}"
printitemdate_tpl = "{name:<33} {date:23} {url}"
def _print_title(title):
click.secho(title.upper(), bold=True)
click.echo("*" * len(title))
def _print_header(with_date=False):
click.echo((printitemdate_tpl if with_date else printitem_tpl).format(
name=click.style("Name", fg="cyan"),
date="Date",
url=click.style("Url", fg="blue")))
terminal_width, _ = click.get_terminal_size()
click.echo("-" * terminal_width)
def _print_lib_item(item):
click.echo((
printitemdate_tpl if "date" in item else printitem_tpl
).format(
name=click.style(item['name'], fg="cyan"),
date=str(
arrow.get(item['date']).humanize() if "date" in item else ""),
url=click.style(
"http://platformio.org/lib/show/%s/%s" % (item['id'],
quote(item['name'])),
fg="blue")))
def _print_tag_item(name):
click.echo(
printitem_tpl.format(
name=click.style(name, fg="cyan"),
url=click.style(
"http://platformio.org/lib/search?query=" + quote(
"keyword:%s" % name),
fg="blue")))
for key in ("updated", "added"):
_print_title("Recently " + key)
_print_header(with_date=True)
for item in result.get(key, []):
_print_lib_item(item)
click.echo()
_print_title("Recent keywords")
_print_header(with_date=False)
for item in result.get("lastkeywords"):
_print_tag_item(item)
click.echo()
_print_title("Popular keywords")
_print_header(with_date=False)
for item in result.get("topkeywords"):
_print_tag_item(item)
click.echo()
for key, title in (("dlday", "Today"), ("dlweek", "Week"),
("dlmonth", "Month")):
_print_title("Featured: " + title)
_print_header(with_date=False)
for item in result.get(key, []):
_print_lib_item(item)
click.echo()

View File

@ -13,10 +13,12 @@
# limitations under the License. # limitations under the License.
import json import json
from os.path import dirname, isdir
import click import click
from platformio import exception, util from platformio import app, exception, util
from platformio.commands.boards import print_boards
from platformio.managers.platform import PlatformFactory, PlatformManager from platformio.managers.platform import PlatformFactory, PlatformManager
@ -28,40 +30,152 @@ def cli():
def _print_platforms(platforms): def _print_platforms(platforms):
for platform in platforms: for platform in platforms:
click.echo("{name} ~ {title}".format( click.echo("{name} ~ {title}".format(
name=click.style( name=click.style(platform['name'], fg="cyan"),
platform['name'], fg="cyan"),
title=platform['title'])) title=platform['title']))
click.echo("=" * (3 + len(platform['name'] + platform['title']))) click.echo("=" * (3 + len(platform['name'] + platform['title'])))
click.echo(platform['description']) click.echo(platform['description'])
click.echo() click.echo()
click.echo("Home: %s" % "http://platformio.org/platforms/" + platform[ if "homepage" in platform:
'name']) click.echo("Home: %s" % platform['homepage'])
if platform['packages']: if "frameworks" in platform and platform['frameworks']:
click.echo("Frameworks: %s" % ", ".join(platform['frameworks']))
if "packages" in platform:
click.echo("Packages: %s" % ", ".join(platform['packages'])) click.echo("Packages: %s" % ", ".join(platform['packages']))
if "version" in platform: if "version" in platform:
click.echo("Version: " + platform['version']) click.echo("Version: " + platform['version'])
click.echo() click.echo()
def _get_registry_platforms():
platforms = util.get_api_result("/platforms", cache_valid="30d")
pm = PlatformManager()
for platform in platforms or []:
platform['versions'] = pm.get_all_repo_versions(platform['name'])
return platforms
def _original_version(version):
if version.count(".") != 2:
return None
_, y = version.split(".")[:2]
if int(y) < 100:
return None
if len(y) % 2 != 0:
y = "0" + y
parts = [str(int(y[i * 2:i * 2 + 2])) for i in range(len(y) / 2)]
return ".".join(parts)
def _get_platform_data(*args, **kwargs):
try:
return _get_installed_platform_data(*args, **kwargs)
except exception.UnknownPlatform:
return _get_registry_platform_data(*args, **kwargs)
def _get_installed_platform_data(platform,
with_boards=True,
expose_packages=True):
p = PlatformFactory.newPlatform(platform)
data = dict(
name=p.name,
title=p.title,
description=p.description,
version=p.version, # comment before dump
homepage=p.homepage,
repository=p.repository_url,
url=p.vendor_url,
license=p.license,
forDesktop=not p.is_embedded(),
frameworks=sorted(p.frameworks.keys() if p.frameworks else []),
packages=p.packages.keys() if p.packages else [])
# if dump to API
# del data['version']
# return data
# overwrite VCS version and add extra fields
manifest = PlatformManager().load_manifest(dirname(p.manifest_path))
assert manifest
for key in manifest:
if key == "version" or key.startswith("__"):
data[key] = manifest[key]
if with_boards:
data['boards'] = [c.get_brief_data() for c in p.get_boards().values()]
if not data['packages'] or not expose_packages:
return data
data['packages'] = []
installed_pkgs = p.get_installed_packages()
for name, opts in p.packages.items():
item = dict(
name=name,
type=p.get_package_type(name),
requirements=opts.get("version"),
optional=opts.get("optional") is True)
if name in installed_pkgs:
for key, value in installed_pkgs[name].items():
if key not in ("url", "version", "description"):
continue
item[key] = value
if key == "version":
item["originalVersion"] = _original_version(value)
data['packages'].append(item)
return data
def _get_registry_platform_data( # pylint: disable=unused-argument
platform,
with_boards=True,
expose_packages=True):
_data = None
for p in _get_registry_platforms():
if p['name'] == platform:
_data = p
break
if not _data:
return None
data = dict(
name=_data['name'],
title=_data['title'],
description=_data['description'],
homepage=_data['homepage'],
repository=_data['repository'],
url=_data['url'],
license=_data['license'],
forDesktop=_data['forDesktop'],
frameworks=_data['frameworks'],
packages=_data['packages'],
versions=_data['versions'])
if with_boards:
data['boards'] = [
board for board in PlatformManager().get_registered_boards()
if board['platform'] == _data['name']
]
return data
@cli.command("search", short_help="Search for development platform") @cli.command("search", short_help="Search for development platform")
@click.argument("query", required=False) @click.argument("query", required=False)
@click.option("--json-output", is_flag=True) @click.option("--json-output", is_flag=True)
def platform_search(query, json_output): def platform_search(query, json_output):
platforms = [] platforms = []
for platform in util.get_api_result("/platforms", cache_valid="365d"): for platform in _get_registry_platforms():
if query == "all": if query == "all":
query = "" query = ""
search_data = json.dumps(platform) search_data = json.dumps(platform)
if query and query.lower() not in search_data.lower(): if query and query.lower() not in search_data.lower():
continue continue
platforms.append(
platforms.append({ _get_registry_platform_data(
"name": platform['name'], platform['name'], with_boards=False, expose_packages=False))
"title": platform['title'],
"description": platform['description'],
"packages": platform['packages']
})
if json_output: if json_output:
click.echo(json.dumps(platforms)) click.echo(json.dumps(platforms))
@ -69,6 +183,108 @@ def platform_search(query, json_output):
_print_platforms(platforms) _print_platforms(platforms)
@cli.command("frameworks", short_help="List supported frameworks, SDKs")
@click.argument("query", required=False)
@click.option("--json-output", is_flag=True)
def platform_frameworks(query, json_output):
frameworks = []
for framework in util.get_api_result("/frameworks", cache_valid="30d"):
if query == "all":
query = ""
search_data = json.dumps(framework)
if query and query.lower() not in search_data.lower():
continue
framework['homepage'] = (
"http://platformio.org/frameworks/" + framework['name'])
framework['platforms'] = [
platform['name'] for platform in _get_registry_platforms()
if framework['name'] in platform['frameworks']
]
frameworks.append(framework)
if json_output:
click.echo(json.dumps(frameworks))
else:
_print_platforms(frameworks)
@cli.command("list", short_help="List installed development platforms")
@click.option("--json-output", is_flag=True)
def platform_list(json_output):
platforms = []
pm = PlatformManager()
for manifest in pm.get_installed():
platforms.append(
_get_installed_platform_data(
manifest['__pkg_dir'],
with_boards=False,
expose_packages=False))
if json_output:
click.echo(json.dumps(platforms))
else:
_print_platforms(platforms)
@cli.command("show", short_help="Show details about development platform")
@click.argument("platform")
@click.option("--json-output", is_flag=True)
def platform_show(platform, json_output): # pylint: disable=too-many-branches
data = _get_platform_data(platform)
if not data:
raise exception.UnknownPlatform(platform)
if json_output:
return click.echo(json.dumps(data))
click.echo("{name} ~ {title}".format(
name=click.style(data['name'], fg="cyan"), title=data['title']))
click.echo("=" * (3 + len(data['name'] + data['title'])))
click.echo(data['description'])
click.echo()
if "version" in data:
click.echo("Version: %s" % data['version'])
if data['homepage']:
click.echo("Home: %s" % data['homepage'])
if data['repository']:
click.echo("Repository: %s" % data['repository'])
if data['url']:
click.echo("Vendor: %s" % data['url'])
if data['license']:
click.echo("License: %s" % data['license'])
if data['frameworks']:
click.echo("Frameworks: %s" % ", ".join(data['frameworks']))
if not data['packages']:
return
if not isinstance(data['packages'][0], dict):
click.echo("Packages: %s" % ", ".join(data['packages']))
else:
click.echo()
click.secho("Packages", bold=True)
click.echo("--------")
for item in data['packages']:
click.echo()
click.echo("Package %s" % click.style(item['name'], fg="yellow"))
click.echo("-" * (8 + len(item['name'])))
if item['type']:
click.echo("Type: %s" % item['type'])
click.echo("Requirements: %s" % item['requirements'])
click.echo("Installed: %s" % ("Yes" if item.get("version") else
"No (optional)"))
if "version" in item:
click.echo("Version: %s" % item['version'])
if "originalVersion" in item:
click.echo("Original version: %s" % item['originalVersion'])
if "description" in item:
click.echo("Description: %s" % item['description'])
if data['boards']:
click.echo()
click.secho("Boards", bold=True)
click.echo("------")
print_boards(data['boards'])
@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)
@ -108,99 +324,51 @@ def platform_uninstall(platforms):
"-p", "-p",
"--only-packages", "--only-packages",
is_flag=True, is_flag=True,
help="Update only platform packages") help="Update only the platform packages")
@click.option( @click.option(
"-c", "-c",
"--only-check", "--only-check",
is_flag=True, is_flag=True,
help="Do not update, only check for new version") help="Do not update, only check for a new version")
def platform_update(platforms, only_packages, only_check):
pm = PlatformManager()
if not platforms:
platforms = set([m['name'] for m in pm.get_installed()])
for platform in platforms:
click.echo("Platform %s" % click.style(platform, fg="cyan"))
click.echo("--------")
pm.update(platform, only_packages=only_packages, only_check=only_check)
click.echo()
@cli.command("list", short_help="List installed development platforms")
@click.option("--json-output", is_flag=True) @click.option("--json-output", is_flag=True)
def platform_list(json_output): def platform_update(platforms, only_packages, only_check, json_output):
platforms = []
pm = PlatformManager() pm = PlatformManager()
for manifest in pm.get_installed(): pkg_dir_to_name = {}
p = PlatformFactory.newPlatform( if not platforms:
pm.get_manifest_path(manifest['__pkg_dir'])) platforms = []
platforms.append({ for manifest in pm.get_installed():
"name": p.name, platforms.append(manifest['__pkg_dir'])
"title": p.title, pkg_dir_to_name[manifest['__pkg_dir']] = manifest.get(
"description": p.description, "title", manifest['name'])
"version": p.version,
"url": p.vendor_url,
"packages": p.get_installed_packages().keys(),
'forDesktop': any([
p.name.startswith(n) for n in ("native", "linux", "windows")
])
})
if json_output: if only_check and json_output:
click.echo(json.dumps(platforms)) result = []
for platform in platforms:
pkg_dir = platform if isdir(platform) else None
requirements = None
url = None
if not pkg_dir:
name, requirements, url = pm.parse_pkg_input(platform)
pkg_dir = pm.get_package_dir(name, requirements, url)
if not pkg_dir:
continue
latest = pm.outdated(pkg_dir, requirements)
if (not latest and not PlatformFactory.newPlatform(pkg_dir)
.are_outdated_packages()):
continue
data = _get_installed_platform_data(
pkg_dir, with_boards=False, expose_packages=False)
if latest:
data['versionLatest'] = latest
result.append(data)
return click.echo(json.dumps(result))
else: else:
_print_platforms(platforms) # cleanup cached board and platform lists
app.clean_cache()
for platform in platforms:
@cli.command("show", short_help="Show details about installed platform") click.echo("Platform %s" % click.style(
@click.argument("platform") pkg_dir_to_name.get(platform, platform), fg="cyan"))
def platform_show(platform): click.echo("--------")
pm.update(
def _detail_version(version): platform, only_packages=only_packages, only_check=only_check)
if version.count(".") != 2: click.echo()
return version
_, y = version.split(".")[:2]
if int(y) < 100:
return version
if len(y) % 2 != 0:
y = "0" + y
parts = [str(int(y[i * 2:i * 2 + 2])) for i in range(len(y) / 2)]
return "%s (%s)" % (version, ".".join(parts))
try:
p = PlatformFactory.newPlatform(platform)
except exception.UnknownPlatform:
raise exception.PlatformNotInstalledYet(platform)
click.echo("{name} ~ {title}".format(
name=click.style(
p.name, fg="cyan"), title=p.title))
click.echo("=" * (3 + len(p.name + p.title)))
click.echo(p.description)
click.echo()
click.echo("Version: %s" % p.version)
if p.homepage:
click.echo("Home: %s" % p.homepage)
if p.license:
click.echo("License: %s" % p.license)
if p.frameworks:
click.echo("Frameworks: %s" % ", ".join(p.frameworks.keys()))
if not p.packages:
return
installed_pkgs = p.get_installed_packages()
for name, opts in p.packages.items():
click.echo()
click.echo("Package %s" % click.style(name, fg="yellow"))
click.echo("-" * (8 + len(name)))
if p.get_package_type(name):
click.echo("Type: %s" % p.get_package_type(name))
click.echo("Requirements: %s" % opts.get("version"))
click.echo("Installed: %s" % ("Yes" if name in installed_pkgs else
"No (optional)"))
if name in installed_pkgs:
for key, value in installed_pkgs[name].items():
if key in ("url", "version", "description"):
if key == "version":
value = _detail_version(value)
click.echo("%s: %s" % (key.title(), value))

View File

@ -23,7 +23,7 @@ import click
from platformio import exception, util from platformio import exception, util
from platformio.commands.device import device_monitor as cmd_device_monitor from platformio.commands.device import device_monitor as cmd_device_monitor
from platformio.pioplus import pioplus_call from platformio.managers.core import pioplus_call
# pylint: disable=unused-argument # pylint: disable=unused-argument
@ -147,12 +147,12 @@ def device_list(json_output):
@click.option( @click.option(
"--rts", "--rts",
default=None, default=None,
type=click.Choice(["0", "1"]), type=click.IntRange(0, 1),
help="Set initial RTS line state") help="Set initial RTS line state")
@click.option( @click.option(
"--dtr", "--dtr",
default=None, default=None,
type=click.Choice(["0", "1"]), type=click.IntRange(0, 1),
help="Set initial DTR line state") help="Set initial DTR line state")
@click.option("--echo", is_flag=True, help="Enable local echo, default=Off") @click.option("--echo", is_flag=True, help="Enable local echo, default=Off")
@click.option( @click.option(

View File

@ -22,6 +22,7 @@ import click
from platformio import __version__, exception, telemetry, util from platformio import __version__, exception, telemetry, util
from platformio.commands.lib import lib_install as cmd_lib_install from platformio.commands.lib import lib_install as cmd_lib_install
from platformio.commands.lib import get_builtin_libs
from platformio.commands.platform import \ from platformio.commands.platform import \
platform_install as cmd_platform_install platform_install as cmd_platform_install
from platformio.managers.lib import LibraryManager from platformio.managers.lib import LibraryManager
@ -95,7 +96,7 @@ def cli(ctx, environment, target, upload_port, project_dir, silent, verbose,
results.append((envname, None)) results.append((envname, None))
continue continue
if results: if not silent and results:
click.echo() click.echo()
options = {} options = {}
@ -108,11 +109,13 @@ def cli(ctx, environment, target, upload_port, project_dir, silent, verbose,
upload_port, silent, verbose) upload_port, silent, verbose)
results.append((envname, ep.process())) results.append((envname, ep.process()))
if len(results) > 1: found_error = any([status is False for (_, status) in results])
if (found_error or not silent) and len(results) > 1:
click.echo() click.echo()
print_summary(results, start_time) print_summary(results, start_time)
if any([status is False for (_, status) in results]): if found_error:
raise exception.ReturnErrorCode(1) raise exception.ReturnErrorCode(1)
return True return True
@ -160,18 +163,20 @@ class EnvironmentProcessor(object):
if "\n" in v: if "\n" in v:
self.options[k] = self.options[k].strip().replace("\n", ", ") self.options[k] = self.options[k].strip().replace("\n", ", ")
click.echo("[%s] Processing %s (%s)" % ( if not self.silent:
datetime.now().strftime("%c"), click.style( click.echo("[%s] Processing %s (%s)" % (
self.name, fg="cyan", bold=True), datetime.now().strftime("%c"), click.style(
", ".join(["%s: %s" % (k, v) for k, v in self.options.items()]))) self.name, fg="cyan", bold=True), ", ".join(
click.secho("-" * terminal_width, bold=True) ["%s: %s" % (k, v) for k, v in self.options.items()])))
if self.silent: click.secho("-" * terminal_width, bold=True)
click.echo("Please wait...")
self.options = self._validate_options(self.options) self.options = self._validate_options(self.options)
result = self._run() result = self._run()
is_error = result['returncode'] != 0 is_error = result['returncode'] != 0
if self.silent and not is_error:
return True
if is_error or "piotest_processor" not in self.cmd_ctx.meta: if is_error or "piotest_processor" not in self.cmd_ctx.meta:
print_header( print_header(
"[%s] Took %.2f seconds" % ((click.style( "[%s] Took %.2f seconds" % ((click.style(
@ -275,7 +280,15 @@ def _autoinstall_libdeps(ctx, libraries, verbose=False):
try: try:
ctx.invoke(cmd_lib_install, libraries=[lib], silent=not verbose) ctx.invoke(cmd_lib_install, libraries=[lib], silent=not verbose)
except exception.LibNotFound as e: except exception.LibNotFound as e:
click.secho("Warning! %s" % e, fg="yellow") if not _is_builtin_lib(lib):
click.secho("Warning! %s" % e, fg="yellow")
def _is_builtin_lib(lib_name):
for storage in get_builtin_libs():
if any([l.get("name") == lib_name for l in storage['items']]):
return True
return False
def _clean_pioenvs_dir(pioenvs_dir): def _clean_pioenvs_dir(pioenvs_dir):
@ -329,9 +342,7 @@ def print_summary(results, start_time):
format_str = ( format_str = (
"Environment {0:<" + str(envname_max_len + 9) + "}\t[{1}]") "Environment {0:<" + str(envname_max_len + 9) + "}\t[{1}]")
click.echo( click.echo(
format_str.format( format_str.format(click.style(envname, fg="cyan"), status_str),
click.style(
envname, fg="cyan"), status_str),
err=status is False) err=status is False)
print_header( print_header(

View File

@ -31,11 +31,9 @@ def settings_get(name):
click.echo( click.echo(
list_tpl.format( list_tpl.format(
name=click.style( name=click.style("Name", fg="cyan"),
"Name", fg="cyan"), value=(click.style("Value", fg="green") + click.style(
value=(click.style( " [Default]", fg="yellow")),
"Value", fg="green") + click.style(
" [Default]", fg="yellow")),
description="Description")) description="Description"))
click.echo("-" * terminal_width) click.echo("-" * terminal_width)
@ -59,8 +57,7 @@ def settings_get(name):
click.echo( click.echo(
list_tpl.format( list_tpl.format(
name=click.style( name=click.style(_name, fg="cyan"),
_name, fg="cyan"),
value=_value_str, value=_value_str,
description=_data['description'])) description=_data['description']))

View File

@ -17,7 +17,7 @@ from os import getcwd
import click import click
from platformio.pioplus import pioplus_call from platformio.managers.core import pioplus_call
@click.command("test", short_help="Local Unit Testing") @click.command("test", short_help="Local Unit Testing")
@ -37,6 +37,20 @@ from platformio.pioplus import pioplus_call
resolve_path=True)) resolve_path=True))
@click.option("--without-building", is_flag=True) @click.option("--without-building", is_flag=True)
@click.option("--without-uploading", is_flag=True) @click.option("--without-uploading", is_flag=True)
@click.option(
"--no-reset",
is_flag=True,
help="Disable software reset via Serial.DTR/RST")
@click.option(
"--monitor-rts",
default=None,
type=click.IntRange(0, 1),
help="Set initial RTS line state for Serial Monitor")
@click.option(
"--monitor-dtr",
default=None,
type=click.IntRange(0, 1),
help="Set initial DTR line state for Serial Monitor")
@click.option("--verbose", "-v", is_flag=True) @click.option("--verbose", "-v", is_flag=True)
def cli(*args, **kwargs): # pylint: disable=unused-argument def cli(*args, **kwargs): # pylint: disable=unused-argument
pioplus_call(sys.argv[1:]) pioplus_call(sys.argv[1:])

View File

@ -14,25 +14,36 @@
import click import click
from platformio import app
from platformio.commands.lib import lib_update as cmd_lib_update from platformio.commands.lib import lib_update as cmd_lib_update
from platformio.commands.platform import platform_update as cmd_platform_update from platformio.commands.platform import platform_update as cmd_platform_update
from platformio.managers.core import update_core_packages
from platformio.managers.lib import LibraryManager from platformio.managers.lib import LibraryManager
from platformio.pioplus import pioplus_update
@click.command( @click.command(
"update", short_help="Update installed Platforms, Packages and Libraries") "update", short_help="Update installed platforms, packages and libraries")
@click.option(
"--core-packages", is_flag=True, help="Update only the core packages")
@click.option( @click.option(
"-c", "-c",
"--only-check", "--only-check",
is_flag=True, is_flag=True,
help="Do not update, only check for new version") help="Do not update, only check for new version")
@click.pass_context @click.pass_context
def cli(ctx, only_check): def cli(ctx, core_packages, only_check):
update_core_packages(only_check)
if core_packages:
return
# cleanup lib search results, cached board and platform lists
app.clean_cache()
click.echo()
click.echo("Platform Manager") click.echo("Platform Manager")
click.echo("================") click.echo("================")
ctx.invoke(cmd_platform_update, only_check=only_check) ctx.invoke(cmd_platform_update, only_check=only_check)
pioplus_update()
click.echo() click.echo()
click.echo("Library Manager") click.echo("Library Manager")

View File

@ -83,8 +83,8 @@ WARNING! Don't use `sudo` for the rest PlatformIO commands.
err=True) err=True)
raise exception.ReturnErrorCode(1) raise exception.ReturnErrorCode(1)
else: else:
raise exception.UpgradeError("\n".join( raise exception.UpgradeError(
[str(cmd), r['out'], r['err']])) "\n".join([str(cmd), r['out'], r['err']]))
def get_latest_version(): def get_latest_version():
@ -101,9 +101,10 @@ def get_latest_version():
def get_develop_latest_version(): def get_develop_latest_version():
version = None version = None
r = requests.get("https://raw.githubusercontent.com/platformio/platformio" r = requests.get(
"/develop/platformio/__init__.py", "https://raw.githubusercontent.com/platformio/platformio"
headers=util.get_request_defheaders()) "/develop/platformio/__init__.py",
headers=util.get_request_defheaders())
r.raise_for_status() r.raise_for_status()
for line in r.text.split("\n"): for line in r.text.split("\n"):
line = line.strip() line = line.strip()
@ -121,7 +122,8 @@ def get_develop_latest_version():
def get_pypi_latest_version(): def get_pypi_latest_version():
r = requests.get("https://pypi.python.org/pypi/platformio/json", r = requests.get(
headers=util.get_request_defheaders()) "https://pypi.python.org/pypi/platformio/json",
headers=util.get_request_defheaders())
r.raise_for_status() r.raise_for_status()
return r.json()['info']['version'] return r.json()['info']['version']

View File

@ -31,9 +31,8 @@ class FileDownloader(object):
def __init__(self, url, dest_dir=None): def __init__(self, url, dest_dir=None):
# make connection # make connection
self._request = requests.get(url, self._request = requests.get(
stream=True, url, stream=True, headers=util.get_request_defheaders())
headers=util.get_request_defheaders())
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)

View File

@ -40,7 +40,12 @@ class AbortedByUser(PlatformioException):
class UnknownPlatform(PlatformioException): class UnknownPlatform(PlatformioException):
MESSAGE = "Unknown platform '{0}'" MESSAGE = "Unknown development platform '{0}'"
class IncompatiblePlatform(PlatformioException):
MESSAGE = "Development platform '{0}' is not compatible with PIO Core v{1}"
class PlatformNotInstalledYet(PlatformioException): class PlatformNotInstalledYet(PlatformioException):
@ -53,7 +58,7 @@ class BoardNotDefined(PlatformioException):
MESSAGE = "You need to specify board ID using `-b` or `--board` "\ MESSAGE = "You need to specify board ID using `-b` or `--board` "\
"option. Supported boards list is available via "\ "option. Supported boards list is available via "\
" `platformio boards` command" "`platformio boards` command"
class UnknownBoard(PlatformioException): class UnknownBoard(PlatformioException):
@ -78,7 +83,7 @@ class UnknownPackage(PlatformioException):
class MissingPackageManifest(PlatformioException): class MissingPackageManifest(PlatformioException):
MESSAGE = "Could not find '{0}' manifest file in the package" MESSAGE = "Could not find one of '{0}' manifest files in the package"
class UndefinedPackageVersion(PlatformioException): class UndefinedPackageVersion(PlatformioException):
@ -89,8 +94,10 @@ class UndefinedPackageVersion(PlatformioException):
class PackageInstallError(PlatformioException): class PackageInstallError(PlatformioException):
MESSAGE = "Can not install '{0}' with version requirements '{1}' "\ MESSAGE = "Could not install '{0}' with version requirements '{1}' "\
"for your system '{2}'" "for your system '{2}'.\n"\
"If you use Antivirus, it can block PlatformIO Package "\
"Manager. Try to disable it for a while."
class FDUnrecognizedStatusCode(PlatformioException): class FDUnrecognizedStatusCode(PlatformioException):

View File

@ -69,8 +69,8 @@ class ProjectGenerator(object):
result = util.exec_command(cmd) result = util.exec_command(cmd)
if result['returncode'] != 0 or '"includes":' not in result['out']: if result['returncode'] != 0 or '"includes":' not in result['out']:
raise exception.PlatformioException("\n".join( raise exception.PlatformioException(
[result['out'], result['err']])) "\n".join([result['out'], result['err']]))
for line in result['out'].split("\n"): for line in result['out'].split("\n"):
line = line.strip() line = line.strip()

View File

@ -5,6 +5,7 @@ SET(CMAKE_C_COMPILER "{{cc_path.replace("\\", "/")}}")
SET(CMAKE_CXX_COMPILER "{{cxx_path.replace("\\", "/")}}") SET(CMAKE_CXX_COMPILER "{{cxx_path.replace("\\", "/")}}")
SET(CMAKE_CXX_FLAGS_DISTRIBUTION "{{cxx_flags}}") SET(CMAKE_CXX_FLAGS_DISTRIBUTION "{{cxx_flags}}")
SET(CMAKE_C_FLAGS_DISTRIBUTION "{{cc_flags}}") SET(CMAKE_C_FLAGS_DISTRIBUTION "{{cc_flags}}")
set(CMAKE_CXX_STANDARD 11)
% for define in defines: % for define in defines:
add_definitions(-D{{!define}}) add_definitions(-D{{!define}})

View File

@ -29,9 +29,9 @@ from platformio.commands.platform import \
platform_uninstall as cmd_platform_uninstall platform_uninstall as cmd_platform_uninstall
from platformio.commands.platform import platform_update as cmd_platform_update from platformio.commands.platform import platform_update as cmd_platform_update
from platformio.commands.upgrade import get_latest_version from platformio.commands.upgrade import get_latest_version
from platformio.managers.core import update_core_packages
from platformio.managers.lib import LibraryManager from platformio.managers.lib import LibraryManager
from platformio.managers.platform import PlatformManager from platformio.managers.platform import PlatformFactory, PlatformManager
from platformio.pioplus import pioplus_update
def in_silence(ctx=None): def in_silence(ctx=None):
@ -42,11 +42,6 @@ def in_silence(ctx=None):
(ctx.args[0] == "upgrade" or "--json-output" in ctx_args)) (ctx.args[0] == "upgrade" or "--json-output" in ctx_args))
def clean_cache():
with app.ContentCache() as cc:
cc.clean()
def on_platformio_start(ctx, force, caller): def on_platformio_start(ctx, force, caller):
if not caller: if not caller:
if getenv("PLATFORMIO_CALLER"): if getenv("PLATFORMIO_CALLER"):
@ -64,8 +59,6 @@ def on_platformio_start(ctx, force, caller):
app.set_session_var("caller_id", caller) app.set_session_var("caller_id", caller)
telemetry.on_command() telemetry.on_command()
if ctx.args and (ctx.args[0] == "upgrade" or "update" in ctx.args):
clean_cache()
if not in_silence(ctx): if not in_silence(ctx):
after_upgrade(ctx) after_upgrade(ctx)
@ -98,8 +91,8 @@ class Upgrader(object):
util.pepver_to_semver(to_version)) util.pepver_to_semver(to_version))
self._upgraders = [ self._upgraders = [
(semantic_version.Version("3.0.0-a1"), self._upgrade_to_3_0_0), (semantic_version.Version("3.0.0-a.1"), self._upgrade_to_3_0_0),
(semantic_version.Version("3.0.0-b11"), self._upgrade_to_3_0_0) (semantic_version.Version("3.0.0-b.11"), self._upgrade_to_3_0_0b11)
] ]
def run(self, ctx): def run(self, ctx):
@ -146,9 +139,10 @@ class Upgrader(object):
m['name'] for m in PlatformManager().get_installed() m['name'] for m in PlatformManager().get_installed()
] ]
if "espressif" not in current_platforms: if "espressif" not in current_platforms:
return return True
ctx.invoke(cmd_platform_install, platforms=["espressif8266"]) ctx.invoke(cmd_platform_install, platforms=["espressif8266"])
ctx.invoke(cmd_platform_uninstall, platforms=["espressif"]) ctx.invoke(cmd_platform_uninstall, platforms=["espressif"])
return True
def after_upgrade(ctx): def after_upgrade(ctx):
@ -159,26 +153,19 @@ def after_upgrade(ctx):
if last_version == "0.0.0": if last_version == "0.0.0":
app.set_state_item("last_version", __version__) app.set_state_item("last_version", __version__)
else: else:
click.secho("Please wait while upgrading PlatformIO ...", fg="yellow") click.secho("Please wait while upgrading PlatformIO...", fg="yellow")
clean_cache() app.clean_cache()
# Update PlatformIO's Core packages
update_core_packages(silent=True)
u = Upgrader(last_version, __version__) u = Upgrader(last_version, __version__)
if u.run(ctx): if u.run(ctx):
app.set_state_item("last_version", __version__) app.set_state_item("last_version", __version__)
# update development platforms
pm = PlatformManager()
for manifest in pm.get_installed():
# pm.update(manifest['name'], "^" + manifest['version'])
pm.update(manifest['name'])
# update PlatformIO Plus tool if installed
pioplus_update()
click.secho( click.secho(
"PlatformIO has been successfully upgraded to %s!\n" % "PlatformIO has been successfully upgraded to %s!\n" %
__version__, __version__,
fg="green") fg="green")
telemetry.on_event( telemetry.on_event(
category="Auto", category="Auto",
action="Upgrade", action="Upgrade",
@ -196,14 +183,13 @@ def after_upgrade(ctx):
"on the latest project news > %s" % (click.style( "on the latest project news > %s" % (click.style(
"follow", fg="cyan"), click.style( "follow", fg="cyan"), click.style(
"https://twitter.com/PlatformIO_Org", fg="cyan"))) "https://twitter.com/PlatformIO_Org", fg="cyan")))
click.echo("- %s it on GitHub > %s" % (click.style( click.echo("- %s it on GitHub > %s" %
"star", fg="cyan"), click.style( (click.style("star", fg="cyan"), click.style(
"https://github.com/platformio/platformio", fg="cyan"))) "https://github.com/platformio/platformio", fg="cyan")))
if not getenv("PLATFORMIO_IDE"): if not getenv("PLATFORMIO_IDE"):
click.echo("- %s PlatformIO IDE for IoT development > %s" % click.echo("- %s PlatformIO IDE for IoT development > %s" %
(click.style( (click.style("try", fg="cyan"), click.style(
"try", fg="cyan"), click.style( "http://platformio.org/platformio-ide", fg="cyan")))
"http://platformio.org/platformio-ide", fg="cyan")))
if not util.is_ci(): if not util.is_ci():
click.echo("- %s us with PlatformIO Plus > %s" % (click.style( click.echo("- %s us with PlatformIO Plus > %s" % (click.style(
"support", fg="cyan"), click.style( "support", fg="cyan"), click.style(
@ -267,8 +253,14 @@ def check_internal_updates(ctx, what):
pm = PlatformManager() if what == "platforms" else LibraryManager() pm = PlatformManager() if what == "platforms" else LibraryManager()
outdated_items = [] outdated_items = []
for manifest in pm.get_installed(): for manifest in pm.get_installed():
if manifest['name'] not in outdated_items and \ if manifest['name'] in outdated_items:
pm.is_outdated(manifest['name']): continue
conds = [
pm.outdated(manifest['__pkg_dir']), what == "platforms" and
PlatformFactory.newPlatform(
manifest['__pkg_dir']).are_outdated_packages()
]
if any(conds):
outdated_items.append(manifest['name']) outdated_items.append(manifest['name'])
if not outdated_items: if not outdated_items:

View File

@ -20,21 +20,17 @@ from os.path import join
from platformio import exception, util from platformio import exception, util
from platformio.managers.package import PackageManager from platformio.managers.package import PackageManager
PACKAGE_DEPS = { CORE_PACKAGES = {
"pysite": { "pysite-pioplus": ">=0.3.0,<2",
"name": "pysite-pioplus", "tool-pioplus": ">=0.6.10,<2",
"requirements": ">=0.3.0,<2" "tool-unity": "~1.20302.1",
}, "tool-scons": "~3.20501.2"
"tool": {
"name": "tool-pioplus",
"requirements": ">=0.6.6,<2"
}
} }
AUTO_UPDATES_MAX = 100 PIOPLUS_AUTO_UPDATES_MAX = 100
class PioPlusPackageManager(PackageManager): class CorePackageManager(PackageManager):
def __init__(self): def __init__(self):
PackageManager.__init__( PackageManager.__init__(
@ -46,29 +42,30 @@ class PioPlusPackageManager(PackageManager):
]) ])
def pioplus_install(): def get_core_package_dir(name):
pm = PioPlusPackageManager() assert name in CORE_PACKAGES
for item in PACKAGE_DEPS.values(): requirements = CORE_PACKAGES[name]
pm.install(item['name'], item['requirements'], silent=True) pm = CorePackageManager()
pkg_dir = pm.get_package_dir(name, requirements)
if pkg_dir:
return pkg_dir
return pm.install(name, requirements)
def pioplus_update(): def update_core_packages(only_check=False, silent=False):
pm = PioPlusPackageManager() pm = CorePackageManager()
for item in PACKAGE_DEPS.values(): for name, requirements in CORE_PACKAGES.items():
package_dir = pm.get_package_dir(item['name']) pkg_dir = pm.get_package_dir(name)
if package_dir: if not pkg_dir:
pm.update(item['name'], item['requirements']) continue
if not silent or pm.outdated(pkg_dir, requirements):
pm.update(name, requirements, only_check=only_check)
def pioplus_call(args, **kwargs): def pioplus_call(args, **kwargs):
pioplus_install() pioplus_path = join(get_core_package_dir("tool-pioplus"), "pioplus")
pm = PioPlusPackageManager()
pioplus_path = join(
pm.get_package_dir(PACKAGE_DEPS['tool']['name'],
PACKAGE_DEPS['tool']['requirements']), "pioplus")
os.environ['PYTHONEXEPATH'] = util.get_pythonexe_path() os.environ['PYTHONEXEPATH'] = util.get_pythonexe_path()
os.environ['PYTHONPYSITEDIR'] = pm.get_package_dir( os.environ['PYTHONPYSITEDIR'] = get_core_package_dir("pysite-pioplus")
PACKAGE_DEPS['pysite']['name'], PACKAGE_DEPS['pysite']['requirements'])
util.copy_pythonpath_to_osenv() util.copy_pythonpath_to_osenv()
code = subprocess.call([pioplus_path] + args, **kwargs) code = subprocess.call([pioplus_path] + args, **kwargs)
@ -82,8 +79,8 @@ def pioplus_call(args, **kwargs):
setattr(pioplus_call, count_attr, 1) setattr(pioplus_call, count_attr, 1)
count_value += 1 count_value += 1
setattr(pioplus_call, count_attr, count_value) setattr(pioplus_call, count_attr, count_value)
if count_value < AUTO_UPDATES_MAX: if count_value < PIOPLUS_AUTO_UPDATES_MAX:
pioplus_update() update_core_packages()
return pioplus_call(args, **kwargs) return pioplus_call(args, **kwargs)
# handle reload request # handle reload request

View File

@ -15,10 +15,11 @@
# pylint: disable=too-many-arguments, too-many-locals, too-many-branches # pylint: disable=too-many-arguments, too-many-locals, too-many-branches
import json import json
import os import re
from hashlib import md5 from glob import glob
from os.path import dirname, join from os.path import isdir, join
import arrow
import click import click
import semantic_version import semantic_version
@ -34,70 +35,93 @@ class LibraryManager(BasePkgManager):
BasePkgManager.__init__(self, package_dir) BasePkgManager.__init__(self, package_dir)
@property @property
def manifest_name(self): def manifest_names(self):
return ".library.json" return [
".library.json", "library.json", "library.properties",
"module.json"
]
def check_pkg_structure(self, pkg_dir): def get_manifest_path(self, pkg_dir):
try: path = BasePkgManager.get_manifest_path(self, pkg_dir)
return BasePkgManager.check_pkg_structure(self, pkg_dir) if path:
except exception.MissingPackageManifest: return path
# we will generate manifest automatically
pass
manifest = { # if library without manifest, returns first source file
"name": "Library_" + md5(pkg_dir).hexdigest()[:5], src_dir = join(util.glob_escape(pkg_dir))
"version": "0.0.0" if isdir(join(pkg_dir, "src")):
} src_dir = join(src_dir, "src")
manifest_path = self._find_any_manifest(pkg_dir) chs_files = glob(join(src_dir, "*.[chS]"))
if manifest_path: if chs_files:
_manifest = self._parse_manifest(manifest_path) return chs_files[0]
pkg_dir = dirname(manifest_path) cpp_files = glob(join(src_dir, "*.cpp"))
for key in ("name", "version"): if cpp_files:
if key not in _manifest: return cpp_files[0]
_manifest[key] = manifest[key]
manifest = _manifest
else:
for root, dirs, files in os.walk(pkg_dir):
if len(dirs) == 1 and not files:
manifest['name'] = dirs[0]
continue
if dirs or files:
pkg_dir = root
break
with open(join(pkg_dir, self.manifest_name), "w") as fp:
json.dump(manifest, fp)
return pkg_dir
@staticmethod
def _find_any_manifest(pkg_dir):
manifests = ("library.json", "library.properties", "module.json")
for root, _, files in os.walk(pkg_dir):
for manifest in manifests:
if manifest in files:
return join(root, manifest)
return None return None
@staticmethod def load_manifest(self, pkg_dir):
def _parse_manifest(path): manifest = BasePkgManager.load_manifest(self, pkg_dir)
manifest = {} if not manifest:
if path.endswith(".json"): return manifest
return util.load_json(path)
elif path.endswith("library.properties"): # if Arudino library.properties
with open(path) as fp: if "sentence" in manifest:
for line in fp.readlines():
if "=" not in line:
continue
key, value = line.split("=", 1)
manifest[key.strip()] = value.strip()
manifest['frameworks'] = ["arduino"] manifest['frameworks'] = ["arduino"]
if "author" in manifest: manifest['description'] = manifest['sentence']
manifest['authors'] = [{"name": manifest['author']}] del manifest['sentence']
del manifest['author']
if "sentence" in manifest: if "author" in manifest:
manifest['description'] = manifest['sentence'] manifest['authors'] = [{"name": manifest['author']}]
del manifest['sentence'] del manifest['author']
if "authors" in manifest and not isinstance(manifest['authors'], list):
manifest['authors'] = [manifest['authors']]
if "keywords" not in manifest:
keywords = []
for keyword in re.split(r"[\s/]+",
manifest.get("category", "Uncategorized")):
keyword = keyword.strip()
if not keyword:
continue
keywords.append(keyword.lower())
manifest['keywords'] = keywords
if "category" in manifest:
del manifest['category']
# don't replace VCS URL
if "url" in manifest and "description" in manifest:
manifest['homepage'] = manifest['url']
del manifest['url']
if "architectures" in manifest:
platforms = []
platforms_map = {
"avr": "atmelavr",
"sam": "atmelsam",
"samd": "atmelsam",
"esp8266": "espressif8266",
"arc32": "intel_arc32"
}
for arch in manifest['architectures'].split(","):
arch = arch.strip()
if arch == "*":
platforms = "*"
break
if arch in platforms_map:
platforms.append(platforms_map[arch])
manifest['platforms'] = platforms
del manifest['architectures']
# convert listed items via comma to array
for key in ("keywords", "frameworks", "platforms"):
if key not in manifest or \
not isinstance(manifest[key], basestring):
continue
manifest[key] = [
i.strip() for i in manifest[key].split(",") if i.strip()
]
return manifest return manifest
@staticmethod @staticmethod
@ -129,13 +153,8 @@ class LibraryManager(BasePkgManager):
def max_satisfying_repo_version(versions, requirements=None): def max_satisfying_repo_version(versions, requirements=None):
def _cmp_dates(datestr1, datestr2): def _cmp_dates(datestr1, datestr2):
from datetime import datetime date1 = arrow.get(datestr1)
assert "T" in datestr1 and "T" in datestr2 date2 = arrow.get(datestr2)
dateformat = "%Y-%m-%d %H:%M:%S"
date1 = datetime.strptime(datestr1[:-1].replace("T", " "),
dateformat)
date2 = datetime.strptime(datestr2[:-1].replace("T", " "),
dateformat)
if date1 == date2: if date1 == date2:
return 0 return 0
return -1 if date1 < date2 else 1 return -1 if date1 < date2 else 1
@ -150,7 +169,7 @@ class LibraryManager(BasePkgManager):
for v in versions: for v in versions:
specver = None specver = None
try: try:
specver = semantic_version.Version(v['version'], partial=True) specver = semantic_version.Version(v['name'], partial=True)
except ValueError: except ValueError:
pass pass
@ -158,30 +177,30 @@ class LibraryManager(BasePkgManager):
if not specver or specver not in reqspec: if not specver or specver not in reqspec:
continue continue
if not item or semantic_version.Version( if not item or semantic_version.Version(
item['version'], partial=True) < specver: item['name'], partial=True) < specver:
item = v item = v
elif requirements: elif requirements:
if requirements == v['version']: if requirements == v['name']:
return v return v
else: else:
if not item or _cmp_dates(item['date'], v['date']) == -1: if not item or _cmp_dates(item['released'],
v['released']) == -1:
item = v item = v
return item return item
def get_latest_repo_version(self, name, requirements): def get_latest_repo_version(self, name, requirements, silent=False):
item = self.max_satisfying_repo_version( item = self.max_satisfying_repo_version(
util.get_api_result( util.get_api_result(
"/lib/versions/%d" % self._get_pkg_id_by_name(name, "/lib/info/%d" % self.get_pkg_id_by_name(
requirements), name, requirements, silent=silent),
cache_valid="1h"), cache_valid="1d")['versions'], requirements)
requirements) return item['name'] if item else None
return item['version'] if item else None
def _get_pkg_id_by_name(self, def get_pkg_id_by_name(self,
name, name,
requirements, requirements,
silent=False, silent=False,
interactive=False): interactive=False):
if name.startswith("id="): if name.startswith("id="):
return int(name[3:]) return int(name[3:])
# try to find ID from installed packages # try to find ID from installed packages
@ -196,7 +215,7 @@ class LibraryManager(BasePkgManager):
}, silent, interactive)['id']) }, silent, interactive)['id'])
def _install_from_piorepo(self, name, requirements): def _install_from_piorepo(self, name, requirements):
assert name.startswith("id=") assert name.startswith("id="), name
version = self.get_latest_repo_version(name, requirements) version = self.get_latest_repo_version(name, requirements)
if not version: if not version:
raise exception.UndefinedPackageVersion(requirements or "latest", raise exception.UndefinedPackageVersion(requirements or "latest",
@ -211,32 +230,32 @@ class LibraryManager(BasePkgManager):
name, dl_data['url'].replace("http://", "https://") name, dl_data['url'].replace("http://", "https://")
if app.get_setting("enable_ssl") else dl_data['url'], requirements) if app.get_setting("enable_ssl") else dl_data['url'], requirements)
def install(self, def install( # pylint: disable=arguments-differ
name, self,
requirements=None, name,
silent=False, requirements=None,
trigger_event=True, silent=False,
interactive=False): trigger_event=True,
already_installed = False interactive=False):
_name, _requirements, _url = self.parse_pkg_name(name, requirements) pkg_dir = None
try: try:
_name, _requirements, _url = self.parse_pkg_input(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,
_requirements, _requirements,
silent=silent, silent=silent,
interactive=interactive) interactive=interactive)
already_installed = self.get_package(_name, _requirements, _url) requirements = _requirements
pkg_dir = BasePkgManager.install( pkg_dir = BasePkgManager.install(self, name, requirements, silent,
self, _name trigger_event)
if not _url else name, _requirements, silent, trigger_event)
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
if already_installed: if not pkg_dir:
return return
manifest = self.load_manifest(pkg_dir) manifest = self.load_manifest(pkg_dir)
@ -295,34 +314,37 @@ class LibraryManager(BasePkgManager):
lib_info = None lib_info = None
result = util.get_api_result( result = util.get_api_result(
"/lib/search", dict(query=" ".join(query)), cache_valid="3d") "/v2/lib/search", dict(query=" ".join(query)), cache_valid="3d")
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:
click.secho( if silent and not interactive:
"Conflict: More than one library has been found "
"by request %s:" % json.dumps(filters),
fg="red",
err=True)
commands.lib.echo_liblist_header()
for item in result['items']:
commands.lib.echo_liblist_item(item)
if not interactive:
click.secho(
"Automatically chose the first available library "
"(use `--interactive` option to make a choice)",
fg="yellow",
err=True)
lib_info = result['items'][0] lib_info = result['items'][0]
else: else:
deplib_id = click.prompt( click.secho(
"Please choose library ID", "Conflict: More than one library has been found "
type=click.Choice([str(i['id']) for i in result['items']])) "by request %s:" % json.dumps(filters),
fg="yellow",
err=True)
for item in result['items']: for item in result['items']:
if item['id'] == int(deplib_id): commands.lib.print_lib_item(item)
lib_info = item
break if not interactive:
click.secho(
"Automatically chose the first available library "
"(use `--interactive` option to make a choice)",
fg="yellow",
err=True)
lib_info = result['items'][0]
else:
deplib_id = click.prompt(
"Please choose library ID",
type=click.Choice(
[str(i['id']) for i in result['items']]))
for item in result['items']:
if item['id'] == int(deplib_id):
lib_info = item
break
if not lib_info: if not lib_info:
if filters.keys() == ["name"]: if filters.keys() == ["name"]:

View File

@ -12,17 +12,19 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import codecs
import hashlib
import json import json
import os import os
import shutil import shutil
from os.path import basename, dirname, getsize, isdir, isfile, islink, join from os.path import basename, getsize, isdir, isfile, islink, join
from tempfile import mkdtemp from tempfile import mkdtemp
import click import click
import requests import requests
import semantic_version import semantic_version
from platformio import app, exception, telemetry, util from platformio import __version__, app, exception, telemetry, util
from platformio.downloader import FileDownloader 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
@ -73,6 +75,8 @@ class PackageRepoIterator(object):
class PkgRepoMixin(object): class PkgRepoMixin(object):
PIO_VERSION = semantic_version.Version(util.pepver_to_semver(__version__))
@staticmethod @staticmethod
def max_satisfying_repo_version(versions, requirements=None): def max_satisfying_repo_version(versions, requirements=None):
item = None item = None
@ -85,9 +89,13 @@ 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 "system" in v and v['system'] not in ("all", "*") and \
systype not in v['system']): systype not in v['system']:
continue continue
if "platformio" in v.get("engines", {}):
if PkgRepoMixin.PIO_VERSION not in semantic_version.Spec(
v['engines']['platformio']):
continue
specver = semantic_version.Version(v['version']) specver = semantic_version.Version(v['version'])
if reqspec and specver not in reqspec: if reqspec and specver not in reqspec:
continue continue
@ -95,7 +103,11 @@ class PkgRepoMixin(object):
item = v item = v
return item return item
def get_latest_repo_version(self, name, requirements): def get_latest_repo_version( # pylint: disable=unused-argument
self,
name,
requirements,
silent=False):
version = None version = None
for versions in PackageRepoIterator(name, self.repositories): for versions in PackageRepoIterator(name, self.repositories):
pkgdata = self.max_satisfying_repo_version(versions, requirements) pkgdata = self.max_satisfying_repo_version(versions, requirements)
@ -106,54 +118,202 @@ class PkgRepoMixin(object):
version = pkgdata['version'] version = pkgdata['version']
return version return version
def get_all_repo_versions(self, name):
result = []
for versions in PackageRepoIterator(name, self.repositories):
result.extend([v['version'] for v in versions])
return sorted(set(result))
class PkgInstallerMixin(object): class PkgInstallerMixin(object):
VCS_MANIFEST_NAME = ".piopkgmanager.json" SRC_MANIFEST_NAME = ".piopkgmanager.json"
def get_vcs_manifest_path(self, pkg_dir): FILE_CACHE_VALID = "1m" # 1 month
FILE_CACHE_MAX_SIZE = 1024 * 1024
MEMORY_CACHE = {}
@staticmethod
def cache_get(key, default=None):
return PkgInstallerMixin.MEMORY_CACHE.get(key, default)
@staticmethod
def cache_set(key, value):
PkgInstallerMixin.MEMORY_CACHE[key] = value
@staticmethod
def cache_reset():
PkgInstallerMixin.MEMORY_CACHE = {}
def read_dirs(self, src_dir):
cache_key = "read_dirs-%s" % src_dir
result = self.cache_get(cache_key)
if result:
return result
result = [
join(src_dir, name) for name in sorted(os.listdir(src_dir))
if isdir(join(src_dir, name))
]
self.cache_set(cache_key, result)
return result
def download(self, url, dest_dir, sha1=None):
cache_key_fname = app.ContentCache.key_from_args(url, "fname")
cache_key_data = app.ContentCache.key_from_args(url, "data")
if self.FILE_CACHE_VALID:
with app.ContentCache() as cc:
fname = cc.get(cache_key_fname)
cache_path = cc.get_cache_path(cache_key_data)
if fname and isfile(cache_path):
dst_path = join(dest_dir, fname)
shutil.copy(cache_path, dst_path)
return dst_path
fd = FileDownloader(url, dest_dir)
fd.start()
if sha1:
fd.verify(sha1)
dst_path = fd.get_filepath()
if not self.FILE_CACHE_VALID or getsize(
dst_path) > PkgInstallerMixin.FILE_CACHE_MAX_SIZE:
return dst_path
with app.ContentCache() as cc:
cc.set(cache_key_fname, basename(dst_path), self.FILE_CACHE_VALID)
cc.set(cache_key_data, "DUMMY", self.FILE_CACHE_VALID)
shutil.copy(dst_path, cc.get_cache_path(cache_key_data))
return dst_path
@staticmethod
def unpack(source_path, dest_dir):
fu = FileUnpacker(source_path, dest_dir)
return fu.start()
@staticmethod
def get_install_dirname(manifest):
name = manifest['name']
if "id" in manifest:
name += "_ID%d" % manifest['id']
return name
def get_src_manifest_path(self, pkg_dir):
for item in os.listdir(pkg_dir): for item in os.listdir(pkg_dir):
if not isdir(join(pkg_dir, item)): if not isdir(join(pkg_dir, item)):
continue continue
if isfile(join(pkg_dir, item, self.VCS_MANIFEST_NAME)): if isfile(join(pkg_dir, item, self.SRC_MANIFEST_NAME)):
return join(pkg_dir, item, self.VCS_MANIFEST_NAME) return join(pkg_dir, item, self.SRC_MANIFEST_NAME)
return None return None
def get_manifest_path(self, pkg_dir): def get_manifest_path(self, pkg_dir):
if not isdir(pkg_dir): if not isdir(pkg_dir):
return None return None
manifest_path = join(pkg_dir, self.manifest_name) for name in self.manifest_names:
if isfile(manifest_path): manifest_path = join(pkg_dir, name)
return manifest_path if isfile(manifest_path):
return self.get_vcs_manifest_path(pkg_dir) return manifest_path
def manifest_exists(self, pkg_dir):
return self.get_manifest_path(pkg_dir) is not None
def load_manifest(self, path):
assert path
pkg_dir = path
if isdir(path):
path = self.get_manifest_path(path)
else:
pkg_dir = dirname(pkg_dir)
if path:
if isfile(path) and path.endswith(self.VCS_MANIFEST_NAME):
pkg_dir = dirname(dirname(path))
manifest = util.load_json(path)
manifest['__pkg_dir'] = pkg_dir
return manifest
return None return None
def check_pkg_structure(self, pkg_dir): def manifest_exists(self, pkg_dir):
if self.manifest_exists(pkg_dir): return self.get_manifest_path(pkg_dir) or \
return pkg_dir self.get_src_manifest_path(pkg_dir)
for root, _, _ in os.walk(pkg_dir): def load_manifest(self, pkg_dir):
cache_key = "load_manifest-%s" % pkg_dir
result = self.cache_get(cache_key)
if result:
return result
manifest_path = self.get_manifest_path(pkg_dir)
if not manifest_path:
return None
# if non-registry packages: VCS or archive
src_manifest_path = self.get_src_manifest_path(pkg_dir)
src_manifest = None
if src_manifest_path:
src_manifest = util.load_json(src_manifest_path)
manifest = {}
if manifest_path.endswith(".json"):
manifest = util.load_json(manifest_path)
elif manifest_path.endswith(".properties"):
with codecs.open(manifest_path, encoding="utf-8") as fp:
for line in fp.readlines():
if "=" not in line:
continue
key, value = line.split("=", 1)
manifest[key.strip()] = value.strip()
if src_manifest:
if "name" not in manifest:
manifest['name'] = src_manifest['name']
if "version" in src_manifest:
manifest['version'] = src_manifest['version']
manifest['__src_url'] = src_manifest['url']
if "name" not in manifest:
manifest['name'] = basename(pkg_dir)
if "version" not in manifest:
manifest['version'] = "0.0.0"
manifest['__pkg_dir'] = pkg_dir
self.cache_set(cache_key, manifest)
return manifest
def get_installed(self):
items = []
for pkg_dir in self.read_dirs(self.package_dir):
manifest = self.load_manifest(pkg_dir)
if not manifest:
continue
assert "name" in manifest
items.append(manifest)
return items
def get_package(self, name, requirements=None, url=None):
pkg_id = int(name[3:]) if name.startswith("id=") else 0
best = None
for manifest in self.get_installed():
if url:
if manifest.get("__src_url") != url:
continue
elif pkg_id and manifest.get("id") != pkg_id:
continue
elif not pkg_id and manifest['name'] != name:
continue
# strict version or VCS HASH
if requirements and requirements == manifest['version']:
return manifest
try:
if requirements and not semantic_version.Spec(
requirements).match(
semantic_version.Version(
manifest['version'], partial=True)):
continue
elif not best or (semantic_version.Version(
manifest['version'], partial=True) >
semantic_version.Version(
best['version'], partial=True)):
best = manifest
except ValueError:
pass
return best
def get_package_dir(self, name, requirements=None, url=None):
manifest = self.get_package(name, requirements, url)
return manifest.get("__pkg_dir") if manifest else None
def find_pkg_root(self, src_dir):
if self.manifest_exists(src_dir):
return src_dir
for root, _, _ in os.walk(src_dir):
if self.manifest_exists(root): if self.manifest_exists(root):
return root return root
raise exception.MissingPackageManifest(", ".join(self.manifest_names))
raise exception.MissingPackageManifest(self.manifest_name)
def _install_from_piorepo(self, name, requirements): def _install_from_piorepo(self, name, requirements):
pkg_dir = None pkg_dir = None
@ -179,18 +339,25 @@ class PkgInstallerMixin(object):
util.get_systype()) util.get_systype())
return pkg_dir return pkg_dir
def _install_from_url(self, name, url, requirements=None, sha1=None): def _install_from_url(self,
name,
url,
requirements=None,
sha1=None,
track=False):
pkg_dir = None pkg_dir = None
tmp_dir = mkdtemp("-package", "installing-", self.package_dir) tmp_dir = mkdtemp("-package", "_tmp_installing-", self.package_dir)
src_manifest_dir = None
src_manifest = {"name": name, "url": url, "requirements": requirements}
try: try:
if url.startswith("file://"): if url.startswith("file://"):
url = url[7:] _url = url[7:]
if isfile(url): if isfile(_url):
self.unpack(url, tmp_dir) self.unpack(_url, tmp_dir)
else: else:
util.rmtree_(tmp_dir) util.rmtree_(tmp_dir)
shutil.copytree(url, tmp_dir) shutil.copytree(_url, tmp_dir)
elif url.startswith(("http://", "https://")): elif url.startswith(("http://", "https://")):
dlpath = self.download(url, tmp_dir, sha1) dlpath = self.download(url, tmp_dir, sha1)
assert isfile(dlpath) assert isfile(dlpath)
@ -199,71 +366,112 @@ class PkgInstallerMixin(object):
else: else:
vcs = VCSClientFactory.newClient(tmp_dir, url) vcs = VCSClientFactory.newClient(tmp_dir, url)
assert vcs.export() assert vcs.export()
with open(join(vcs.storage_dir, self.VCS_MANIFEST_NAME), src_manifest_dir = vcs.storage_dir
"w") as fp: src_manifest['version'] = vcs.get_current_revision()
json.dump({
"name": name, pkg_dir = self.find_pkg_root(tmp_dir)
"version": vcs.get_current_revision(),
"url": url, # write source data to a special manifest
"requirements": requirements if track:
}, fp) if not src_manifest_dir:
src_manifest_dir = join(pkg_dir, ".pio")
self._update_src_manifest(src_manifest, src_manifest_dir)
pkg_dir = self.check_pkg_structure(tmp_dir)
pkg_dir = self._install_from_tmp_dir(pkg_dir, requirements) pkg_dir = self._install_from_tmp_dir(pkg_dir, requirements)
finally: finally:
if isdir(tmp_dir): if isdir(tmp_dir):
util.rmtree_(tmp_dir) util.rmtree_(tmp_dir)
return pkg_dir return pkg_dir
def _install_from_tmp_dir(self, tmp_dir, requirements=None): def _update_src_manifest(self, data, src_dir):
tmpmanifest = self.load_manifest(tmp_dir) if not isdir(src_dir):
assert set(["name", "version"]) <= set(tmpmanifest.keys()) os.makedirs(src_dir)
name = tmpmanifest['name'] src_manifest_path = join(src_dir, self.SRC_MANIFEST_NAME)
pkg_dir = join(self.package_dir, name) _data = {}
if "id" in tmpmanifest: if isfile(src_manifest_path):
name += "_ID%d" % tmpmanifest['id'] _data = util.load_json(src_manifest_path)
pkg_dir = join(self.package_dir, name) _data.update(data)
with open(src_manifest_path, "w") as fp:
json.dump(_data, fp)
def _install_from_tmp_dir( # pylint: disable=too-many-branches
self, tmp_dir, requirements=None):
tmp_manifest = self.load_manifest(tmp_dir)
assert set(["name", "version"]) <= set(tmp_manifest.keys())
pkg_dirname = self.get_install_dirname(tmp_manifest)
pkg_dir = join(self.package_dir, pkg_dirname)
cur_manifest = self.load_manifest(pkg_dir)
tmp_semver = None
cur_semver = None
try:
tmp_semver = semantic_version.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:
mismatch_error = ( mismatch_error = (
"Package version %s doesn't satisfy requirements %s" % ( "Package version %s doesn't satisfy requirements %s" % (
tmpmanifest['version'], requirements)) tmp_manifest['version'], requirements))
try: try:
reqspec = semantic_version.Spec(requirements) assert tmp_semver and tmp_semver in semantic_version.Spec(
tmpmanver = semantic_version.Version( requirements), mismatch_error
tmpmanifest['version'], partial=True) except (AssertionError, ValueError):
assert tmpmanver in reqspec, mismatch_error assert tmp_manifest['version'] == requirements, mismatch_error
if self.manifest_exists(pkg_dir): # check if package already exists
curmanifest = self.load_manifest(pkg_dir) if cur_manifest:
curmanver = semantic_version.Version( # 0-overwrite, 1-rename, 2-fix to a version
curmanifest['version'], partial=True) action = 0
# if current package version < new package, backup it if "__src_url" in cur_manifest:
if tmpmanver > curmanver: if cur_manifest['__src_url'] != tmp_manifest.get("__src_url"):
os.rename(pkg_dir, action = 1
join(self.package_dir, "%s@%s" % elif "__src_url" in tmp_manifest:
(name, curmanifest['version']))) action = 2
elif tmpmanver < curmanver: else:
pkg_dir = join(self.package_dir, "%s@%s" % if tmp_semver and (not cur_semver or tmp_semver > cur_semver):
(name, tmpmanifest['version'])) action = 1
except ValueError: elif tmp_semver and cur_semver and tmp_semver != cur_semver:
assert tmpmanifest['version'] == requirements, mismatch_error action = 2
# rename
if action == 1:
target_dirname = "%s@%s" % (pkg_dirname,
cur_manifest['version'])
if "__src_url" in cur_manifest:
target_dirname = "%s@src-%s" % (
pkg_dirname,
hashlib.md5(cur_manifest['__src_url']).hexdigest())
os.rename(pkg_dir, join(self.package_dir, target_dirname))
# fix to a version
elif action == 2:
target_dirname = "%s@%s" % (pkg_dirname,
tmp_manifest['version'])
if "__src_url" in tmp_manifest:
target_dirname = "%s@src-%s" % (
pkg_dirname,
hashlib.md5(tmp_manifest['__src_url']).hexdigest())
pkg_dir = join(self.package_dir, target_dirname)
# remove previous/not-satisfied package # remove previous/not-satisfied package
if isdir(pkg_dir): if isdir(pkg_dir):
util.rmtree_(pkg_dir) util.rmtree_(pkg_dir)
os.rename(tmp_dir, pkg_dir) os.rename(tmp_dir, pkg_dir)
assert isdir(pkg_dir) assert isdir(pkg_dir)
self.cache_reset()
return pkg_dir return pkg_dir
class BasePkgManager(PkgRepoMixin, PkgInstallerMixin): class BasePkgManager(PkgRepoMixin, PkgInstallerMixin):
_INSTALLED_CACHE = {} # Handle circle dependencies
INSTALL_HISTORY = None
FILE_CACHE_VALID = "1m" # 1 month
FILE_CACHE_MAX_SIZE = 1024 * 1024
def __init__(self, package_dir, repositories=None): def __init__(self, package_dir, repositories=None):
self.repositories = repositories self.repositories = repositories
@ -273,57 +481,27 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin):
assert isdir(self.package_dir) assert isdir(self.package_dir)
@property @property
def manifest_name(self): def manifest_names(self):
raise NotImplementedError() raise NotImplementedError()
def download(self, url, dest_dir, sha1=None):
cache_key_fname = app.ContentCache.key_from_args(url, "fname")
cache_key_data = app.ContentCache.key_from_args(url, "data")
if self.FILE_CACHE_VALID:
with app.ContentCache() as cc:
fname = cc.get(cache_key_fname)
cache_path = cc.get_cache_path(cache_key_data)
if fname and isfile(cache_path):
dst_path = join(dest_dir, fname)
shutil.copy(cache_path, dst_path)
return dst_path
fd = FileDownloader(url, dest_dir)
fd.start()
if sha1:
fd.verify(sha1)
dst_path = fd.get_filepath()
if not self.FILE_CACHE_VALID or getsize(
dst_path) > BasePkgManager.FILE_CACHE_MAX_SIZE:
return dst_path
with app.ContentCache() as cc:
cc.set(cache_key_fname, basename(dst_path), self.FILE_CACHE_VALID)
cc.set(cache_key_data, "DUMMY", self.FILE_CACHE_VALID)
shutil.copy(dst_path, cc.get_cache_path(cache_key_data))
return dst_path
@staticmethod
def unpack(source_path, dest_dir):
fu = FileUnpacker(source_path, dest_dir)
return fu.start()
def reset_cache(self):
if self.package_dir in BasePkgManager._INSTALLED_CACHE:
del BasePkgManager._INSTALLED_CACHE[self.package_dir]
def print_message(self, message, nl=True): def print_message(self, message, nl=True):
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_name( # pylint: disable=too-many-branches def parse_pkg_input( # pylint: disable=too-many-branches
text, requirements=None): text, requirements=None):
text = str(text) text = str(text)
url_marker = "://" # git@github.com:user/package.git
if not any([ url_marker = text[:4]
requirements, "@" not in text, text.startswith("git@"), if url_marker not in ("git@", "git+") or ":" not in text:
url_marker in text url_marker = "://"
]):
req_conditions = [
not requirements, "@" in text,
(url_marker != "git@" and "://git@" not in text) or
text.count("@") > 1
]
if all(req_conditions):
text, requirements = text.rsplit("@", 1) text, requirements = text.rsplit("@", 1)
if text.isdigit(): if text.isdigit():
text = "id=" + text text = "id=" + text
@ -339,22 +517,18 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin):
url.startswith("http") and url.startswith("http") and
(url.split("#", 1)[0] if "#" in url else url).endswith(".git") (url.split("#", 1)[0] if "#" in url else url).endswith(".git")
] ]
if any(git_conditions): if any(git_conditions):
url = "git+" + url url = "git+" + url
# Handle Developer Mbed URL # Handle Developer Mbed URL
# (https://developer.mbed.org/users/user/code/package/) # (https://developer.mbed.org/users/user/code/package/)
elif url.startswith("https://developer.mbed.org"): if url.startswith("https://developer.mbed.org"):
url = "hg+" + url url = "hg+" + url
# git@github.com:user/package.git
if url.startswith("git@"):
url_marker = "git@"
if any([s in url for s in ("\\", "/")]) and url_marker not in url: if any([s in url for s in ("\\", "/")]) and url_marker not in url:
if isfile(url) or isdir(url): if isfile(url) or isdir(url):
url = "file://" + url url = "file://" + url
elif url.count("/") == 1 and not url.startswith("git@"): elif url.count("/") == 1 and "git" not in url_marker:
url = "git+https://github.com/" + url url = "git+https://github.com/" + url
# determine name # determine name
@ -364,91 +538,73 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin):
_url = _url[:-1] _url = _url[:-1]
name = basename(_url) name = basename(_url)
if "." in name and not name.startswith("."): if "." in name and not name.startswith("."):
name = name.split(".", 1)[0] name = name.rsplit(".", 1)[0]
if url_marker not in url: if url_marker not in url:
url = None url = None
return (name or text, requirements, url) return (name or text, requirements, url)
def get_installed(self): def outdated(self, pkg_dir, requirements=None):
if self.package_dir in BasePkgManager._INSTALLED_CACHE: """
return BasePkgManager._INSTALLED_CACHE[self.package_dir] Has 3 different results:
items = [] `None` - unknown package, VCS is fixed to commit
for p in sorted(os.listdir(self.package_dir)): `False` - package is up-to-date
pkg_dir = join(self.package_dir, p) `String` - a found latest version
if not isdir(pkg_dir): """
continue assert isdir(pkg_dir)
manifest = self.load_manifest(pkg_dir) latest = None
if not manifest: manifest = self.load_manifest(pkg_dir)
continue # skip a fixed package to a specific version
assert set(["name", "version"]) <= set(manifest.keys()) if "@" in pkg_dir and "__src_url" not in manifest:
items.append(manifest) return None
BasePkgManager._INSTALLED_CACHE[self.package_dir] = items
return items
def get_package(self, name, requirements=None, url=None): if "__src_url" in manifest:
pkg_id = int(name[3:]) if name.startswith("id=") else 0
best = None
reqspec = None
if requirements:
try: try:
reqspec = semantic_version.Spec(requirements) vcs = VCSClientFactory.newClient(
except ValueError: pkg_dir, manifest['__src_url'], silent=True)
pass except (AttributeError, exception.PlatformioException):
return None
for manifest in self.get_installed(): if not vcs.can_be_updated:
if pkg_id and manifest.get("id") != pkg_id: return None
continue latest = vcs.get_latest_revision()
elif not pkg_id and manifest['name'] != name: else:
continue try:
elif not reqspec and requirements: latest = self.get_latest_repo_version(
if requirements == manifest['version']: "id=%d" % manifest['id']
best = manifest if "id" in manifest else manifest['name'],
break requirements,
continue silent=True)
try: except (exception.PlatformioException, ValueError):
if reqspec and not reqspec.match(
semantic_version.Version(
manifest['version'], partial=True)):
continue
elif not best or (semantic_version.Version(
manifest['version'], partial=True) >
semantic_version.Version(
best['version'], partial=True)):
best = manifest
except ValueError:
pass
if best:
# check that URL is the same in installed package (VCS)
if url and best.get("url") != url:
return None return None
return best
return None
def get_package_dir(self, name, requirements=None, url=None): if not latest:
package = self.get_package(name, requirements, url) return None
return package.get("__pkg_dir") if package else None
def is_outdated(self, name, requirements=None): up_to_date = False
package_dir = self.get_package_dir(name, requirements) try:
if not package_dir: assert "__src_url" not in manifest
click.secho( up_to_date = (semantic_version.Version.coerce(manifest['version'])
"%s @ %s is not installed" % (name, requirements or "*"), >= semantic_version.Version.coerce(latest))
fg="yellow") except (AssertionError, ValueError):
return up_to_date = latest == manifest['version']
if self.get_vcs_manifest_path(package_dir):
return False return False if up_to_date else latest
manifest = self.load_manifest(package_dir)
return manifest['version'] != self.get_latest_repo_version(
name, requirements)
def install(self, def install(self,
name, name,
requirements=None, requirements=None,
silent=False, silent=False,
trigger_event=True, trigger_event=True):
interactive=False): # pylint: disable=unused-argument
name, requirements, url = self.parse_pkg_name(name, requirements) # avoid circle dependencies
if not self.INSTALL_HISTORY:
self.INSTALL_HISTORY = []
history_key = "%s-%s" % (name, requirements) if requirements else name
if history_key in self.INSTALL_HISTORY:
return
self.INSTALL_HISTORY.append(history_key)
name, requirements, url = self.parse_pkg_input(name, requirements)
package_dir = self.get_package_dir(name, requirements, url) package_dir = self.get_package_dir(name, requirements, url)
if not package_dir or not silent: if not package_dir or not silent:
@ -465,15 +621,16 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin):
return package_dir return package_dir
if url: if url:
pkg_dir = self._install_from_url(name, url, requirements) pkg_dir = self._install_from_url(
name, url, requirements, track=True)
else: else:
pkg_dir = self._install_from_piorepo(name, requirements) pkg_dir = self._install_from_piorepo(name, requirements)
if not pkg_dir or not self.manifest_exists(pkg_dir): if not pkg_dir or not self.manifest_exists(pkg_dir):
raise exception.PackageInstallError(name, requirements or "*", raise exception.PackageInstallError(name, requirements or "*",
util.get_systype()) util.get_systype())
self.reset_cache()
manifest = self.load_manifest(pkg_dir) manifest = self.load_manifest(pkg_dir)
assert manifest
if trigger_event: if trigger_event:
telemetry.on_event( telemetry.on_event(
@ -481,37 +638,48 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin):
action="Install", action="Install",
label=manifest['name']) label=manifest['name'])
click.secho( if not silent:
"{name} @ {version} has been successfully installed!".format( click.secho(
**manifest), "{name} @ {version} has been successfully installed!".format(
fg="green") **manifest),
fg="green")
return pkg_dir return pkg_dir
def uninstall(self, name, requirements=None, trigger_event=True): def uninstall(self, package, requirements=None, trigger_event=True):
name, requirements, url = self.parse_pkg_name(name, requirements) if isdir(package):
package_dir = self.get_package_dir(name, requirements, url) pkg_dir = package
if not package_dir: else:
click.secho( name, requirements, url = self.parse_pkg_input(package,
"%s @ %s is not installed" % (name, requirements or "*"), requirements)
fg="yellow") pkg_dir = self.get_package_dir(name, requirements, url)
return
manifest = self.load_manifest(package_dir) if not pkg_dir:
raise exception.UnknownPackage("%s @ %s" %
(package, requirements or "*"))
manifest = self.load_manifest(pkg_dir)
click.echo( click.echo(
"Uninstalling %s @ %s: \t" % (click.style( "Uninstalling %s @ %s: \t" % (click.style(
manifest['name'], fg="cyan"), manifest['version']), manifest['name'], fg="cyan"), manifest['version']),
nl=False) nl=False)
if isdir(package_dir): if islink(pkg_dir):
if islink(package_dir): os.unlink(pkg_dir)
os.unlink(package_dir) else:
else: util.rmtree_(pkg_dir)
util.rmtree_(package_dir) self.cache_reset()
# unfix package with the same name
pkg_dir = self.get_package_dir(manifest['name'])
if pkg_dir and "@" in pkg_dir:
os.rename(
pkg_dir,
join(self.package_dir, self.get_install_dirname(manifest)))
self.cache_reset()
click.echo("[%s]" % click.style("OK", fg="green")) click.echo("[%s]" % click.style("OK", fg="green"))
self.reset_cache()
if trigger_event: if trigger_event:
telemetry.on_event( telemetry.on_event(
category=self.__class__.__name__, category=self.__class__.__name__,
@ -521,77 +689,49 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin):
def update( # pylint: disable=too-many-return-statements def update( # pylint: disable=too-many-return-statements
self, self,
name, package,
requirements=None, requirements=None,
only_check=False): only_check=False):
name, requirements, url = self.parse_pkg_name(name, requirements) if isdir(package):
package_dir = self.get_package_dir(name, None, url) pkg_dir = package
if not package_dir: else:
click.secho( pkg_dir = self.get_package_dir(*self.parse_pkg_input(package))
"%s @ %s is not installed" % (name, requirements or "*"),
fg="yellow") if not pkg_dir:
raise exception.UnknownPackage("%s @ %s" %
(package, requirements or "*"))
manifest = self.load_manifest(pkg_dir)
name = manifest['name']
click.echo(
"{} {:<40} @ {:<15}".format(
"Checking" if only_check else "Updating",
click.style(manifest['name'], fg="cyan"), manifest['version']),
nl=False)
if not util.internet_on():
click.echo("[%s]" % (click.style("Off-line", fg="yellow")))
return return
is_vcs_pkg = False latest = self.outdated(pkg_dir, requirements)
if self.get_vcs_manifest_path(package_dir): if latest:
is_vcs_pkg = True click.echo("[%s]" % (click.style(latest, fg="red")))
manifest_path = self.get_vcs_manifest_path(package_dir) elif latest is False:
click.echo("[%s]" % (click.style("Up-to-date", fg="green")))
else: else:
manifest_path = self.get_manifest_path(package_dir) click.echo("[%s]" % (click.style("Skip", fg="yellow")))
manifest = self.load_manifest(manifest_path) if only_check or not latest:
click.echo( return
"%s %s @ %s: \t" % ("Checking"
if only_check else "Updating", click.style( if "__src_url" in manifest:
manifest['name'], fg="cyan"), vcs = VCSClientFactory.newClient(pkg_dir, manifest['__src_url'])
manifest['version']),
nl=False)
if is_vcs_pkg:
if only_check:
click.echo("[%s]" % (click.style("Skip", fg="yellow")))
return
click.echo("[%s]" % (click.style("VCS", fg="yellow")))
vcs = VCSClientFactory.newClient(package_dir, manifest['url'])
if not vcs.can_be_updated:
click.secho(
"Skip update because repository is fixed "
"to %s revision" % manifest['version'],
fg="yellow")
return
assert vcs.update() assert vcs.update()
with open(manifest_path, "w") as fp: self._update_src_manifest(
manifest['version'] = vcs.get_current_revision() dict(version=vcs.get_current_revision()), vcs.storage_dir)
json.dump(manifest, fp)
else: else:
latest_version = None self.uninstall(pkg_dir, trigger_event=False)
try: self.install(name, latest, trigger_event=False)
latest_version = self.get_latest_repo_version(name,
requirements)
except exception.PlatformioException:
pass
if not latest_version:
click.echo("[%s]" % (click.style(
"Off-line" if not util.internet_on() else "Unknown",
fg="yellow")))
return
up_to_date = False
try:
up_to_date = (
semantic_version.Version.coerce(manifest['version']) >=
semantic_version.Version.coerce(latest_version))
except ValueError:
up_to_date = latest_version == manifest['version']
if up_to_date:
click.echo("[%s]" % (click.style("Up-to-date", fg="green")))
return
click.echo("[%s]" % (click.style("Out-of-date", fg="red")))
if only_check:
return
self.uninstall(name, manifest['version'], trigger_event=False)
self.install(name, latest_version, trigger_event=False)
telemetry.on_event( telemetry.on_event(
category=self.__class__.__name__, category=self.__class__.__name__,
@ -605,5 +745,5 @@ class PackageManager(BasePkgManager):
FILE_CACHE_VALID = None # disable package caching FILE_CACHE_VALID = None # disable package caching
@property @property
def manifest_name(self): def manifest_names(self):
return "package.json" return ["package.json"]

View File

@ -20,8 +20,10 @@ from multiprocessing import cpu_count
from os.path import basename, dirname, isdir, isfile, join from os.path import basename, dirname, isdir, isfile, join
import click import click
import semantic_version
from platformio import app, exception, util from platformio import __version__, app, exception, util
from platformio.managers.core import get_core_package_dir
from platformio.managers.package import BasePkgManager, PackageManager from platformio.managers.package import BasePkgManager, PackageManager
@ -41,8 +43,17 @@ class PlatformManager(BasePkgManager):
repositories) repositories)
@property @property
def manifest_name(self): def manifest_names(self):
return "platform.json" return ["platform.json"]
def get_manifest_path(self, pkg_dir):
if not isdir(pkg_dir):
return None
for name in self.manifest_names:
manifest_path = join(pkg_dir, name)
if isfile(manifest_path):
return manifest_path
return None
def install(self, def install(self,
name, name,
@ -50,50 +61,80 @@ class PlatformManager(BasePkgManager):
with_packages=None, with_packages=None,
without_packages=None, without_packages=None,
skip_default_package=False, skip_default_package=False,
trigger_event=True,
silent=False,
**_): # pylint: disable=too-many-arguments **_): # pylint: disable=too-many-arguments
platform_dir = BasePkgManager.install(self, name, requirements) platform_dir = BasePkgManager.install(
p = PlatformFactory.newPlatform(self.get_manifest_path(platform_dir)) self, name, requirements, silent=silent)
p.install_packages(with_packages, without_packages, p = PlatformFactory.newPlatform(platform_dir)
skip_default_package)
# @Hook: when 'update' operation (trigger_event is False),
# don't cleanup packages or install them
if not trigger_event:
return True
p.install_packages(
with_packages,
without_packages,
skip_default_package,
silent=silent)
self.cleanup_packages(p.packages.keys()) self.cleanup_packages(p.packages.keys())
return True return True
def uninstall(self, name, requirements=None, trigger_event=True): def uninstall(self, package, requirements=None, trigger_event=True):
name, requirements, _ = self.parse_pkg_name(name, requirements) if isdir(package):
p = PlatformFactory.newPlatform(name, requirements) pkg_dir = package
BasePkgManager.uninstall(self, name, requirements) else:
# trigger event is disabled when upgrading operation name, requirements, url = self.parse_pkg_input(package,
# don't cleanup packages, "install" will do that requirements)
if trigger_event: pkg_dir = self.get_package_dir(name, requirements, url)
self.cleanup_packages(p.packages.keys())
p = PlatformFactory.newPlatform(pkg_dir)
BasePkgManager.uninstall(self, pkg_dir, requirements)
# @Hook: when 'update' operation (trigger_event is False),
# don't cleanup packages or install them
if not trigger_event:
return True
self.cleanup_packages(p.packages.keys())
return True return True
def update( # pylint: disable=arguments-differ def update( # pylint: disable=arguments-differ
self, self,
name, package,
requirements=None, requirements=None,
only_packages=False, only_check=False,
only_check=False): only_packages=False):
name, requirements, _ = self.parse_pkg_name(name, requirements) if isdir(package):
pkg_dir = package
else:
name, requirements, url = self.parse_pkg_input(package,
requirements)
pkg_dir = self.get_package_dir(name, requirements, url)
p = PlatformFactory.newPlatform(pkg_dir)
pkgs_before = pkgs_after = p.get_installed_packages().keys()
if not only_packages: if not only_packages:
BasePkgManager.update(self, name, requirements, only_check) BasePkgManager.update(self, pkg_dir, requirements, only_check)
p = PlatformFactory.newPlatform(name, requirements) p = PlatformFactory.newPlatform(pkg_dir)
pkgs_after = 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 pkgs_missed:
p.install_packages(
with_packages=pkgs_missed, skip_default_package=True)
return True return True
def is_outdated(self, name, requirements=None):
if BasePkgManager.is_outdated(self, name, requirements):
return True
p = PlatformFactory.newPlatform(name, requirements)
return p.are_outdated_packages()
def cleanup_packages(self, names): def cleanup_packages(self, names):
self.reset_cache() self.cache_reset()
deppkgs = {} deppkgs = {}
for manifest in PlatformManager().get_installed(): for manifest in PlatformManager().get_installed():
p = PlatformFactory.newPlatform(manifest['name'], p = PlatformFactory.newPlatform(manifest['__pkg_dir'])
manifest['version'])
for pkgname, pkgmanifest in p.get_installed_packages().items(): for pkgname, pkgmanifest in p.get_installed_packages().items():
if pkgname not in deppkgs: if pkgname not in deppkgs:
deppkgs[pkgname] = set() deppkgs[pkgname] = set()
@ -105,32 +146,34 @@ class PlatformManager(BasePkgManager):
continue continue
if (manifest['name'] not in deppkgs or if (manifest['name'] not in deppkgs or
manifest['version'] not in deppkgs[manifest['name']]): manifest['version'] not in deppkgs[manifest['name']]):
pm.uninstall( pm.uninstall(manifest['__pkg_dir'], trigger_event=False)
manifest['name'], manifest['version'], trigger_event=False)
self.reset_cache() self.cache_reset()
return True return True
def get_installed_boards(self): def get_installed_boards(self):
boards = [] boards = []
for manifest in self.get_installed(): for manifest in self.get_installed():
p = PlatformFactory.newPlatform( p = PlatformFactory.newPlatform(manifest['__pkg_dir'])
self.get_manifest_path(manifest['__pkg_dir']))
for config in p.get_boards().values(): for config in p.get_boards().values():
boards.append(config.get_brief_data()) board = config.get_brief_data()
if board not in boards:
boards.append(board)
return boards return boards
@staticmethod @staticmethod
@util.memoized @util.memoized
def get_registered_boards(): def get_registered_boards():
return util.get_api_result("/boards", cache_valid="365d") return util.get_api_result("/boards", cache_valid="30d")
def board_config(self, id_): def board_config(self, id_, platform=None):
for manifest in self.get_installed_boards(): for manifest in self.get_installed_boards():
if manifest['id'] == id_: if manifest['id'] == id_ and (not platform or
manifest['platform'] == platform):
return manifest return manifest
for manifest in self.get_registered_boards(): for manifest in self.get_registered_boards():
if manifest['id'] == id_: if manifest['id'] == id_ and (not platform or
manifest['platform'] == platform):
return manifest return manifest
raise exception.UnknownBoard(id_) raise exception.UnknownBoard(id_)
@ -155,7 +198,10 @@ class PlatformFactory(object):
@classmethod @classmethod
def newPlatform(cls, name, requirements=None): def newPlatform(cls, name, requirements=None):
platform_dir = None platform_dir = None
if name.endswith("platform.json") and isfile(name): if isdir(name):
platform_dir = name
name = PlatformManager().load_manifest(platform_dir)['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:
@ -189,8 +235,8 @@ class PlatformPackagesMixin(object):
without_packages=None, without_packages=None,
skip_default_package=False, skip_default_package=False,
silent=False): silent=False):
with_packages = set(self.pkg_types_to_names(with_packages or [])) with_packages = set(self.find_pkg_names(with_packages or []))
without_packages = set(self.pkg_types_to_names(without_packages or [])) without_packages = set(self.find_pkg_names(without_packages or []))
upkgs = with_packages | without_packages upkgs = with_packages | without_packages
ppkgs = set(self.packages.keys()) ppkgs = set(self.packages.keys())
@ -198,44 +244,86 @@ class PlatformPackagesMixin(object):
raise exception.UnknownPackage(", ".join(upkgs - ppkgs)) raise exception.UnknownPackage(", ".join(upkgs - ppkgs))
for name, opts in self.packages.items(): for name, opts in self.packages.items():
version = opts.get("version", "")
if name in without_packages: if name in without_packages:
continue continue
elif (name in with_packages or elif (name in with_packages or
not (skip_default_package or opts.get("optional", False))): not (skip_default_package or opts.get("optional", False))):
if any([s in opts.get("version", "") for s in ("\\", "/")]): if self.is_valid_requirements(version):
self.pm.install( self.pm.install(name, version, silent=silent)
"%s=%s" % (name, opts['version']), silent=silent)
else: else:
self.pm.install(name, opts.get("version"), silent=silent) requirements = None
if "@" in version:
version, requirements = version.rsplit("@", 1)
self.pm.install(
"%s=%s" % (name, version), requirements, silent=silent)
return True return True
def get_installed_packages(self): def find_pkg_names(self, items):
items = {} result = []
for name, opts in self.packages.items(): for item in items:
package = self.pm.get_package(name, opts['version']) candidate = item
if package:
items[name] = package # lookup by package types
return items for _name, _opts in self.packages.items():
if _opts.get("type") == item:
candidate = _name
if (self.frameworks and item.startswith("framework-") and
item[10:] in self.frameworks):
candidate = self.frameworks[item[10:]]['package']
result.append(candidate)
return result
def update_packages(self, only_check=False): def update_packages(self, only_check=False):
for name in self.get_installed_packages(): for name, manifest in self.get_installed_packages().items():
self.pm.update(name, self.packages[name]['version'], only_check) version = self.packages[name].get("version", "")
if "@" in version:
_, version = version.rsplit("@", 1)
self.pm.update(manifest['__pkg_dir'], version, only_check)
def get_installed_packages(self):
items = {}
for name in self.packages:
pkg_dir = self.get_package_dir(name)
if pkg_dir:
items[name] = self.pm.load_manifest(pkg_dir)
return items
def are_outdated_packages(self): def are_outdated_packages(self):
for name, opts in self.get_installed_packages().items(): for name, manifest in self.get_installed_packages().items():
if (opts['version'] != self.pm.get_latest_repo_version( version = self.packages[name].get("version", "")
name, self.packages[name].get("version"))): if "@" in version:
_, version = version.rsplit("@", 1)
if self.pm.outdated(manifest['__pkg_dir'], version):
return True return True
return False return False
def get_package_dir(self, name): def get_package_dir(self, name):
return self.pm.get_package_dir(name, version = self.packages[name].get("version", "")
self.packages[name].get("version")) if self.is_valid_requirements(version):
return self.pm.get_package_dir(name, version)
else:
return self.pm.get_package_dir(*self._parse_pkg_input(name,
version))
def get_package_version(self, name): def get_package_version(self, name):
package = self.pm.get_package(name, self.packages[name].get("version")) pkg_dir = self.get_package_dir(name)
return package['version'] if package else None if not pkg_dir:
return None
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):
@ -270,7 +358,7 @@ class PlatformRunMixin(object):
def _run_scons(self, variables, targets): def _run_scons(self, variables, targets):
cmd = [ cmd = [
util.get_pythonexe_path(), util.get_pythonexe_path(),
join(self.get_package_dir("tool-scons"), "script", "scons"), "-Q", join(get_core_package_dir("tool-scons"), "script", "scons"), "-Q",
"-j %d" % self.get_job_nums(), "--warn=no-no-parallel-support", "-j %d" % self.get_job_nums(), "--warn=no-no-parallel-support",
"-f", join(util.get_source_dir(), "builder", "main.py") "-f", join(util.get_source_dir(), "builder", "main.py")
] ]
@ -316,8 +404,10 @@ class PlatformRunMixin(object):
return 1 return 1
class PlatformBase(PlatformPackagesMixin, PlatformRunMixin): class PlatformBase( # pylint: disable=too-many-public-methods
PlatformPackagesMixin, PlatformRunMixin):
PIO_VERSION = semantic_version.Version(util.pepver_to_semver(__version__))
_BOARDS_CACHE = {} _BOARDS_CACHE = {}
def __init__(self, manifest_path): def __init__(self, manifest_path):
@ -332,6 +422,12 @@ class PlatformBase(PlatformPackagesMixin, PlatformRunMixin):
self.silent = False self.silent = False
self.verbose = False self.verbose = False
if self.engines and "platformio" in self.engines:
if self.PIO_VERSION not in semantic_version.Spec(
self.engines['platformio']):
raise exception.IncompatiblePlatform(self.name,
str(self.PIO_VERSION))
@property @property
def name(self): def name(self):
return self._manifest['name'] return self._manifest['name']
@ -356,6 +452,10 @@ class PlatformBase(PlatformPackagesMixin, PlatformRunMixin):
def vendor_url(self): def vendor_url(self):
return self._manifest.get("url") return self._manifest.get("url")
@property
def repository_url(self):
return self._manifest.get("repository", {}).get("url")
@property @property
def license(self): def license(self):
return self._manifest.get("license") return self._manifest.get("license")
@ -364,6 +464,10 @@ class PlatformBase(PlatformPackagesMixin, PlatformRunMixin):
def frameworks(self): def frameworks(self):
return self._manifest.get("frameworks") return self._manifest.get("frameworks")
@property
def engines(self):
return self._manifest.get("engines")
@property @property
def manifest(self): def manifest(self):
return self._manifest return self._manifest
@ -435,20 +539,6 @@ class PlatformBase(PlatformPackagesMixin, PlatformRunMixin):
def get_package_type(self, name): def get_package_type(self, name):
return self.packages[name].get("type") return self.packages[name].get("type")
def pkg_types_to_names(self, types):
names = []
for type_ in types:
name = type_
# lookup by package types
for _name, _opts in self.packages.items():
if _opts.get("type") == type_:
name = None
names.append(_name)
# if type is the right name
if name:
names.append(name)
return names
def configure_default_packages(self, variables, targets): def configure_default_packages(self, variables, targets):
# enable used frameworks # enable used frameworks
frameworks = variables.get("pioframework", []) frameworks = variables.get("pioframework", [])
@ -460,8 +550,9 @@ class PlatformBase(PlatformPackagesMixin, PlatformRunMixin):
framework = framework.lower().strip() framework = framework.lower().strip()
if not framework or framework not in self.frameworks: if not framework or framework not in self.frameworks:
continue continue
_pkg_name = self.frameworks[framework]['package'] _pkg_name = self.frameworks[framework].get("package")
self.packages[_pkg_name]['optional'] = False if _pkg_name:
self.packages[_pkg_name]['optional'] = False
# enable upload tools for upload targets # enable upload tools for upload targets
if any(["upload" in t for t in targets] + ["program" in targets]): if any(["upload" in t for t in targets] + ["program" in targets]):
@ -472,16 +563,29 @@ class PlatformBase(PlatformPackagesMixin, PlatformRunMixin):
# skip all packages, allow only upload tools # skip all packages, allow only upload tools
self.packages[_name]['optional'] = True self.packages[_name]['optional'] = True
if "__test" in targets and "tool-unity" not in self.packages: def get_lib_storages(self):
self.packages['tool-unity'] = { storages = []
"version": "~1.20302.1", for opts in (self.frameworks or {}).values():
"optional": False if "package" not in opts:
} continue
if "tool-scons" not in self.packages: pkg_dir = self.get_package_dir(opts['package'])
self.packages['tool-scons'] = { if not pkg_dir or not isdir(join(pkg_dir, "libraries")):
"version": "~3.20501.2", continue
"optional": False libs_dir = join(pkg_dir, "libraries")
} storages.append({"name": opts['package'], "path": libs_dir})
libcores_dir = join(libs_dir, "__cores__")
if not isdir(libcores_dir):
continue
for item in os.listdir(libcores_dir):
libcore_dir = join(libcores_dir, item)
if not isdir(libcore_dir):
continue
storages.append({
"name": "%s-core-%s" % (opts['package'], item),
"path": libcore_dir
})
return storages
class PlatformBoardConfig(object): class PlatformBoardConfig(object):

View File

@ -121,8 +121,8 @@ class MeasurementProtocol(TelemetryBase):
"settings", "account"): "settings", "account"):
cmd_path = args[:2] cmd_path = args[:2]
if args[0] == "lib" and len(args) > 1: if args[0] == "lib" and len(args) > 1:
lib_subcmds = ("install", "list", "register", "search", "show", lib_subcmds = ("builtin", "install", "list", "register", "search",
"uninstall", "update") "show", "stats", "uninstall", "update")
sub_cmd = _first_arg_from_list(args[1:], lib_subcmds) sub_cmd = _first_arg_from_list(args[1:], lib_subcmds)
if sub_cmd: if sub_cmd:
cmd_path.append(sub_cmd) cmd_path.append(sub_cmd)
@ -227,8 +227,13 @@ class MPDataPusher(object):
timeout=1) timeout=1)
r.raise_for_status() r.raise_for_status()
return True return True
except requests.exceptions.HTTPError as e:
# skip Bad Request
if 400 >= e.response.status_code < 500:
return True
except: # pylint: disable=W0702 except: # pylint: disable=W0702
self._http_offline = True pass
self._http_offline = True
return False return False
@ -304,7 +309,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() if is_crash else e) mp['exd'] = ("%s: %s" % (type(e).__name__, format_exc()
if is_crash else e))[:2048]
mp['exf'] = 1 if is_crash else 0 mp['exf'] = 1 if is_crash else 0
mp.send("exception") mp.send("exception")

View File

@ -141,7 +141,12 @@ class memoized(object):
def __get__(self, obj, objtype): def __get__(self, obj, objtype):
'''Support instance methods.''' '''Support instance methods.'''
return functools.partial(self.__call__, obj) fn = functools.partial(self.__call__, obj)
fn.reset = self._reset
return fn
def _reset(self):
self.cache = {}
def singleton(cls): def singleton(cls):
@ -157,8 +162,12 @@ def singleton(cls):
def load_json(file_path): def load_json(file_path):
with open(file_path, "r") as f: try:
return json.load(f) with open(file_path, "r") as f:
return json.load(f)
except ValueError:
raise exception.PlatformioException("Could not load broken JSON: %s" %
file_path)
def get_systype(): def get_systype():
@ -458,11 +467,12 @@ def _get_api_result(
auth=auth, auth=auth,
verify=disable_ssl_check) verify=disable_ssl_check)
else: else:
r = _api_request_session().get(url, r = _api_request_session().get(
params=params, url,
headers=headers, params=params,
auth=auth, headers=headers,
verify=disable_ssl_check) auth=auth,
verify=disable_ssl_check)
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:
@ -558,7 +568,7 @@ def where_is_program(program, envpath=None):
def pepver_to_semver(pepver): def pepver_to_semver(pepver):
return re.sub(r"(\.\d+)\.?(dev|a|b|rc|post)", r"\1-\2", pepver, 1) return re.sub(r"(\.\d+)\.?(dev|a|b|rc|post)", r"\1-\2.", pepver, 1)
def rmtree_(path): def rmtree_(path):
@ -568,3 +578,28 @@ def rmtree_(path):
os.remove(name) os.remove(name)
return rmtree(path, onerror=_onerror) return rmtree(path, onerror=_onerror)
#
# Glob.Escape from Python 3.4
# https://github.com/python/cpython/blob/master/Lib/glob.py#L161
#
try:
from glob import escape as glob_escape # pylint: disable=unused-import
except ImportError:
magic_check = re.compile('([*?[])')
magic_check_bytes = re.compile(b'([*?[])')
def glob_escape(pathname):
"""Escape all special characters.
"""
# Escaping is done by wrapping any of "*?[" between square brackets.
# Metacharacters do not work in the drive part and shouldn't be
# escaped.
drive, pathname = os.path.splitdrive(pathname)
if isinstance(pathname, bytes):
pathname = magic_check_bytes.sub(br'[\1]', pathname)
else:
pathname = magic_check.sub(r'[\1]', pathname)
return drive + pathname

View File

@ -25,21 +25,22 @@ from platformio.exception import PlatformioException
class VCSClientFactory(object): class VCSClientFactory(object):
@staticmethod @staticmethod
def newClient(src_dir, remote_url): def newClient(src_dir, remote_url, silent=False):
result = urlparse(remote_url) result = urlparse(remote_url)
type_ = result.scheme type_ = result.scheme
tag = None
if not type_ and remote_url.startswith("git@"): if not type_ and remote_url.startswith("git@"):
type_ = "git" type_ = "git"
elif "+" in result.scheme: elif "+" in result.scheme:
type_, _ = result.scheme.split("+", 1) type_, _ = result.scheme.split("+", 1)
remote_url = remote_url[len(type_) + 1:] remote_url = remote_url[len(type_) + 1:]
if result.fragment: if "#" in remote_url:
remote_url = remote_url.rsplit("#", 1)[0] remote_url, tag = remote_url.rsplit("#", 1)
if not type_: if not type_:
raise PlatformioException("VCS: Unknown repository type %s" % raise PlatformioException("VCS: Unknown repository type %s" %
remote_url) remote_url)
obj = getattr(modules[__name__], "%sClient" % type_.title())( obj = getattr(modules[__name__], "%sClient" % type_.title())(
src_dir, remote_url, result.fragment) src_dir, remote_url, tag, silent)
assert isinstance(obj, VCSClientBase) assert isinstance(obj, VCSClientBase)
return obj return obj
@ -48,17 +49,21 @@ class VCSClientBase(object):
command = None command = None
def __init__(self, src_dir, remote_url=None, tag=None): def __init__(self, src_dir, remote_url=None, tag=None, silent=False):
self.src_dir = src_dir self.src_dir = src_dir
self.remote_url = remote_url self.remote_url = remote_url
self.tag = tag self.tag = tag
self.silent = silent
self.check_client() self.check_client()
def check_client(self): def check_client(self):
try: try:
assert self.command assert self.command
assert self.run_cmd(["--version"]) if self.silent:
except (AssertionError, OSError): self.get_cmd_output(["--version"])
else:
assert self.run_cmd(["--version"])
except (AssertionError, OSError, PlatformioException):
raise PlatformioException( raise PlatformioException(
"VCS: `%s` client is not installed in your system" % "VCS: `%s` client is not installed in your system" %
self.command) self.command)
@ -81,6 +86,9 @@ class VCSClientBase(object):
def get_current_revision(self): def get_current_revision(self):
raise NotImplementedError raise NotImplementedError
def get_latest_revision(self):
return None if self.can_be_updated else self.get_current_revision()
def run_cmd(self, args, **kwargs): def run_cmd(self, args, **kwargs):
args = [self.command] + args args = [self.command] + args
if "cwd" not in kwargs: if "cwd" not in kwargs:
@ -108,6 +116,16 @@ class GitClient(VCSClientBase):
output = output.replace("*", "") # fix active branch output = output.replace("*", "") # fix active branch
return [b.strip() for b in output.split("\n")] return [b.strip() for b in output.split("\n")]
def get_current_branch(self):
output = self.get_cmd_output(["branch"])
for line in output.split("\n"):
line = line.strip()
if line.startswith("*"):
branch = line[1:].strip()
if branch != "(no branch)":
return branch
return None
def get_tags(self): def get_tags(self):
output = self.get_cmd_output(["tag", "-l"]) output = self.get_cmd_output(["tag", "-l"])
return [t.strip() for t in output.split("\n")] return [t.strip() for t in output.split("\n")]
@ -140,6 +158,19 @@ class GitClient(VCSClientBase):
def get_current_revision(self): def get_current_revision(self):
return self.get_cmd_output(["rev-parse", "--short", "HEAD"]) return self.get_cmd_output(["rev-parse", "--short", "HEAD"])
def get_latest_revision(self):
if not self.can_be_updated:
return self.get_current_revision()
branch = self.get_current_branch()
if not branch:
return self.get_current_revision()
result = self.get_cmd_output(["ls-remote"])
for line in result.split("\n"):
ref_pos = line.strip().find("refs/heads/" + branch)
if ref_pos > 0:
return line[:ref_pos].strip()[:7]
return None
class HgClient(VCSClientBase): class HgClient(VCSClientBase):
@ -159,6 +190,11 @@ class HgClient(VCSClientBase):
def get_current_revision(self): def get_current_revision(self):
return self.get_cmd_output(["identify", "--id"]) return self.get_cmd_output(["identify", "--id"])
def get_latest_revision(self):
if not self.can_be_updated:
return self.get_latest_revision()
return self.get_cmd_output(["identify", "--id", self.remote_url])
class SvnClient(VCSClientBase): class SvnClient(VCSClientBase):
@ -177,9 +213,8 @@ class SvnClient(VCSClientBase):
return self.run_cmd(args) return self.run_cmd(args)
def get_current_revision(self): def get_current_revision(self):
output = self.get_cmd_output([ output = self.get_cmd_output(
"info", "--non-interactive", "--trust-server-cert", "-r", "HEAD" ["info", "--non-interactive", "--trust-server-cert", "-r", "HEAD"])
])
for line in output.split("\n"): for line in output.split("\n"):
line = line.strip() line = line.strip()
if line.startswith("Revision:"): if line.startswith("Revision:"):

View File

@ -93,16 +93,15 @@ Packages
:header-rows: 1 :header-rows: 1
* - Name * - Name
- Contents""") - Description""")
for name in sorted(packagenames): for name in sorted(packagenames):
assert name in API_PACKAGES, name assert name in API_PACKAGES, name
contitems = [
"`{name} <{url}>`_".format(**item) for item in API_PACKAGES[name]
]
lines.append(""" lines.append("""
* - ``{name}`` * - `{name} <{url}>`__
- {contents}""".format( - {description}""".format(
name=name, contents=", ".join(contitems))) name=name,
url=API_PACKAGES[name]['url'],
description=API_PACKAGES[name]['description']))
if is_embedded: if is_embedded:
lines.append(""" lines.append("""
@ -172,8 +171,8 @@ For more detailed information please visit `vendor site <%s>`_.""" %
# #
# Packages # Packages
# #
_packages_content = generate_packages(name, p.packages.keys(), _packages_content = generate_packages(name,
p.is_embedded()) p.packages.keys(), p.is_embedded())
if _packages_content: if _packages_content:
lines.append(_packages_content) lines.append(_packages_content)
@ -288,10 +287,11 @@ Platforms
continue continue
_found_platform = True _found_platform = True
p = PlatformFactory.newPlatform(manifest['name']) p = PlatformFactory.newPlatform(manifest['name'])
lines.append(""" lines.append(
"""
* - :ref:`platform_{type_}` * - :ref:`platform_{type_}`
- {description}""".format( - {description}"""
type_=manifest['name'], description=p.description)) .format(type_=manifest['name'], description=p.description))
if not _found_platform: if not _found_platform:
del lines[-1] del lines[-1]
@ -347,19 +347,21 @@ Packages
:header-rows: 1 :header-rows: 1
* - Name * - Name
- Contents""") - Description""")
for name, items in sorted(API_PACKAGES.iteritems()): for name, items in sorted(API_PACKAGES.iteritems()):
contitems = ["`{name} <{url}>`_".format(**item) for item in items]
lines.append(""" lines.append("""
* - ``{name}`` * - `{name} <{url}>`__
- {contents}""".format( - {description}""".format(
name=name, contents=", ".join(contitems))) name=name,
url=API_PACKAGES[name]['url'],
description=API_PACKAGES[name]['description']))
with open( with open(
join(util.get_source_dir(), "..", "docs", "platforms", join(util.get_source_dir(), "..", "docs", "platforms",
"creating_platform.rst"), "r+") as fp: "creating_platform.rst"), "r+") as fp:
content = fp.read() content = fp.read()
fp.seek(0, 0) fp.seek(0)
fp.truncate()
fp.write(content[:content.index(".. _platform_creating_packages:")] + fp.write(content[:content.index(".. _platform_creating_packages:")] +
"\n".join(lines) + "\n\n" + content[content.index( "\n".join(lines) + "\n\n" + content[content.index(
".. _platform_creating_manifest_file:"):]) ".. _platform_creating_manifest_file:"):])

View File

@ -114,11 +114,7 @@ def install_platformio():
r = None r = None
cmd = ["-m", "pip.__main__" if sys.version_info < (2, 7, 0) else "pip"] cmd = ["-m", "pip.__main__" if sys.version_info < (2, 7, 0) else "pip"]
try: try:
# r = exec_python_cmd(cmd + ["install", "-U", "platformio"]) r = exec_python_cmd(cmd + ["install", "-U", "platformio"])
r = exec_python_cmd(cmd + [
"install", "-U",
"https://github.com/platformio/platformio-core/archive/develop.zip"
])
assert r['returncode'] == 0 assert r['returncode'] == 0
except AssertionError: except AssertionError:
r = exec_python_cmd(cmd + ["--no-cache-dir", "install", "-U", r = exec_python_cmd(cmd + ["--no-cache-dir", "install", "-U",

View File

@ -18,13 +18,14 @@ from platformio import (__author__, __description__, __email__, __license__,
__title__, __url__, __version__) __title__, __url__, __version__)
install_requires = [ install_requires = [
"arrow<1",
"bottle<0.13", "bottle<0.13",
"click>=5,<6", "click>=5,<6",
"lockfile>=0.9.1,<0.13",
"requests>=2.4.0,<3",
"semantic_version>=2.5.0",
"colorama", "colorama",
"pyserial>=3,<4" "lockfile>=0.9.1,<0.13",
"pyserial>=3,<4",
"requests>=2.4.0,<3",
"semantic_version>=2.5.0"
] ]
setup( setup(
@ -69,6 +70,6 @@ setup(
"iot", "ide", "build", "compile", "library manager", "iot", "ide", "build", "compile", "library manager",
"embedded", "ci", "continuous integration", "arduino", "mbed", "embedded", "ci", "continuous integration", "arduino", "mbed",
"esp8266", "framework", "ide", "ide integration", "library.json", "esp8266", "framework", "ide", "ide integration", "library.json",
"make", "cmake", "makefile", "mk", "pic32", "fpga" "make", "cmake", "makefile", "mk", "pic32", "fpga", "artik"
] ]
) )

View File

@ -16,7 +16,7 @@ import json
import re import re
from os.path import basename from os.path import basename
from platformio import util from platformio import exception, util
from platformio.commands.init import cli as cmd_init from platformio.commands.init import cli as cmd_init
from platformio.commands.lib import cli as cmd_lib from platformio.commands.lib import cli as cmd_lib
@ -37,15 +37,35 @@ def test_search(clirunner, validate_cliresult):
def test_global_install_registry(clirunner, validate_cliresult, def test_global_install_registry(clirunner, validate_cliresult,
isolated_pio_home): isolated_pio_home):
result = clirunner.invoke(cmd_lib, [ result = clirunner.invoke(cmd_lib, [
"-g", "install", "58", "OneWire", "-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.6" "ArduinoJson@5.6.7", "ArduinoJson@~5.7.0", "1089@fee16e880b"
]) ])
validate_cliresult(result) validate_cliresult(result)
# check lib with duplicate URL
result = clirunner.invoke(cmd_lib, [
"-g", "install",
"http://dl.platformio.org/libraries/archives/3/5174.tar.gz"
])
validate_cliresult(result)
assert "is already installed" in result.output
# check lib with duplicate ID
result = clirunner.invoke(cmd_lib, ["-g", "install", "305"])
validate_cliresult(result)
assert "is already installed" in result.output
# install unknown library
result = clirunner.invoke(cmd_lib, ["-g", "install", "Unknown"])
assert result.exit_code != 0
assert isinstance(result.exception, exception.LibNotFound)
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 = [
"DHT22_ID58", "ArduinoJson_ID64", "ArduinoJson_ID64@5.6.7", "ArduinoJson_ID64", "ArduinoJson_ID64@5.6.7", "DallasTemperature_ID54",
"OneWire_ID1", "ESPAsyncTCP_ID305" "DHT22_ID58", "ESPAsyncTCP_ID305", "NeoPixelBus_ID547", "OneWire_ID1",
"IRremoteESP8266_ID1089"
] ]
assert set(items1) == set(items2) assert set(items1) == set(items2)
@ -55,11 +75,29 @@ def test_global_install_archive(clirunner, validate_cliresult,
result = clirunner.invoke(cmd_lib, [ result = clirunner.invoke(cmd_lib, [
"-g", "install", "https://github.com/adafruit/Adafruit-ST7735-Library/" "-g", "install", "https://github.com/adafruit/Adafruit-ST7735-Library/"
"archive/master.zip", "archive/master.zip",
"http://www.airspayce.com/mikem/arduino/RadioHead/RadioHead-1.62.zip",
"https://github.com/bblanchon/ArduinoJson/archive/v5.8.2.zip",
"https://github.com/bblanchon/ArduinoJson/archive/v5.8.2.zip@5.8.2"
])
validate_cliresult(result)
# incorrect requirements
result = clirunner.invoke(cmd_lib, [
"-g", "install",
"https://github.com/bblanchon/ArduinoJson/archive/v5.8.2.zip@1.2.3"
])
assert result.exit_code != 0
# check lib with duplicate URL
result = clirunner.invoke(cmd_lib, [
"-g", "install",
"http://www.airspayce.com/mikem/arduino/RadioHead/RadioHead-1.62.zip" "http://www.airspayce.com/mikem/arduino/RadioHead/RadioHead-1.62.zip"
]) ])
validate_cliresult(result) validate_cliresult(result)
assert "is already installed" in result.output
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 = ["Adafruit ST7735 Library", "RadioHead"] items2 = ["Adafruit ST7735 Library", "RadioHead-1.62"]
assert set(items1) >= set(items2) assert set(items1) >= set(items2)
@ -71,14 +109,20 @@ def test_global_install_repository(clirunner, validate_cliresult,
"-g", "-g",
"install", "install",
"https://github.com/gioblu/PJON.git#3.0", "https://github.com/gioblu/PJON.git#3.0",
"https://github.com/gioblu/PJON.git#6.2",
"https://github.com/bblanchon/ArduinoJson.git",
"https://gitlab.com/ivankravets/rs485-nodeproto.git", "https://gitlab.com/ivankravets/rs485-nodeproto.git",
# "https://developer.mbed.org/users/simon/code/TextLCD/", # "https://developer.mbed.org/users/simon/code/TextLCD/",
"knolleary/pubsubclient" "knolleary/pubsubclient"
]) ])
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 = ["PJON", "ESPAsyncTCP", "PubSubClient"] items2 = [
assert set(items2) & set(items1) "PJON", "PJON@src-79de467ebe19de18287becff0a1fb42d",
"ArduinoJson@src-69ebddd821f771debe7ee734d3c7fa81", "rs485-nodeproto",
"PubSubClient"
]
assert set(items1) >= set(items2)
def test_global_lib_list(clirunner, validate_cliresult, isolated_pio_home): def test_global_lib_list(clirunner, validate_cliresult, isolated_pio_home):
@ -89,81 +133,119 @@ 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 ("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" "RadioHead-1.62", "DallasTemperature", "NeoPixelBus", "IRremoteESP8266"
] ]
assert set(items1) == set(items2) assert set(items1) == set(items2)
def test_global_lib_show(clirunner, validate_cliresult, isolated_pio_home): def test_global_lib_update_check(clirunner, validate_cliresult,
result = clirunner.invoke(cmd_lib, ["-g", "show", "64@5.6.7"]) isolated_pio_home):
result = clirunner.invoke(
cmd_lib, ["-g", "update", "--only-check", "--json-output"])
validate_cliresult(result) validate_cliresult(result)
assert all([ output = json.loads(result.output)
s in result.output for s in ("Json", "arduino", "atmelavr", "5.6.7") assert set(["ArduinoJson", "IRremoteESP8266", "NeoPixelBus"]) == set(
]) [l['name'] for l in output])
result = clirunner.invoke(cmd_lib, ["-g", "show", "ArduinoJson@>5.6.7"])
validate_cliresult(result)
assert all(
[s in result.output for s in ("ArduinoJson", "arduino", "atmelavr")])
assert "5.6.7" not in result.output
result = clirunner.invoke(cmd_lib, ["-g", "show", "1"])
validate_cliresult(result)
assert "OneWire" in result.output
def test_global_lib_update(clirunner, validate_cliresult, isolated_pio_home): def test_global_lib_update(clirunner, validate_cliresult, isolated_pio_home):
# update library using package directory
result = clirunner.invoke(
cmd_lib,
["-g", "update", "NeoPixelBus", "--only-check", "--json-output"])
validate_cliresult(result)
oudated = json.loads(result.output)
assert len(oudated) == 1
assert "__pkg_dir" in oudated[0]
result = clirunner.invoke(cmd_lib,
["-g", "update", oudated[0]['__pkg_dir']])
validate_cliresult(result)
assert "Uninstalling NeoPixelBus @ 2.2.4" in result.output
# update rest libraries
result = clirunner.invoke(cmd_lib, ["-g", "update"]) result = clirunner.invoke(cmd_lib, ["-g", "update"])
validate_cliresult(result) validate_cliresult(result)
assert all([s in result.output for s in ("[Up-to-date]", "[VCS]")]) validate_cliresult(result)
assert result.output.count("[Skip]") == 5
assert result.output.count("[Up-to-date]") == 9
assert "Uninstalling ArduinoJson @ 5.7.3" in result.output
assert "Uninstalling IRremoteESP8266 @ fee16e880b" in result.output
# update unknown library
result = clirunner.invoke(cmd_lib, ["-g", "update", "Unknown"])
assert result.exit_code != 0
assert isinstance(result.exception, exception.UnknownPackage)
def test_global_lib_uninstall(clirunner, validate_cliresult, def test_global_lib_uninstall(clirunner, validate_cliresult,
isolated_pio_home): isolated_pio_home):
# uninstall using package directory
result = clirunner.invoke(cmd_lib, ["-g", "list", "--json-output"])
validate_cliresult(result)
items = json.loads(result.output)
result = clirunner.invoke(cmd_lib,
["-g", "uninstall", items[0]['__pkg_dir']])
validate_cliresult(result)
assert "Uninstalling Adafruit ST7735 Library" in result.output
# uninstall the rest libraries
result = clirunner.invoke(cmd_lib, [ result = clirunner.invoke(cmd_lib, [
"-g", "uninstall", "1", "ArduinoJson@!=5.6.7", "TextLCD", "-g", "uninstall", "1", "ArduinoJson@!=5.6.7",
"Adafruit ST7735 Library" "https://github.com/bblanchon/ArduinoJson.git", "IRremoteESP8266@>=0.2"
]) ])
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 = [
"DHT22_ID58", "ArduinoJson_ID64@5.6.7", "ESPAsyncTCP_ID305", "ArduinoJson", "ArduinoJson_ID64@5.6.7", "DallasTemperature_ID54",
"pubsubclient", "PJON", "rs485-nodeproto", "RadioHead_ID124" "DHT22_ID58", "ESPAsyncTCP_ID305", "NeoPixelBus_ID547", "PJON",
"PJON@src-79de467ebe19de18287becff0a1fb42d", "PubSubClient",
"RadioHead-1.62", "rs485-nodeproto"
] ]
assert set(items1) == set(items2) assert set(items1) == set(items2)
# uninstall unknown library
result = clirunner.invoke(cmd_lib, ["-g", "uninstall", "Unknown"])
assert result.exit_code != 0
assert isinstance(result.exception, exception.UnknownPackage)
def test_project_lib_complex(clirunner, validate_cliresult, tmpdir):
with tmpdir.as_cwd():
# init
result = clirunner.invoke(cmd_init)
validate_cliresult(result)
# isntall def test_lib_show(clirunner, validate_cliresult, isolated_pio_home):
result = clirunner.invoke(cmd_lib, ["install", "54", "ArduinoJson"]) result = clirunner.invoke(cmd_lib, ["show", "64"])
validate_cliresult(result) validate_cliresult(result)
items1 = [ assert all(
d.basename [s in result.output for s in ("ArduinoJson", "Arduino", "Atmel AVR")])
for d in tmpdir.join(basename(util.get_projectlibdeps_dir())) result = clirunner.invoke(cmd_lib, ["show", "OneWire"])
.listdir() validate_cliresult(result)
] assert "OneWire" in result.output
items2 = ["DallasTemperature_ID54", "OneWire_ID1", "ArduinoJson_ID64"]
assert set(items1) == set(items2)
# list
result = clirunner.invoke(cmd_lib, ["list", "--json-output"])
validate_cliresult(result)
items1 = [i['name'] for i in json.loads(result.output)]
items2 = ["DallasTemperature", "OneWire", "ArduinoJson"]
assert set(items1) == set(items2)
# update def test_lib_builtin(clirunner, validate_cliresult, isolated_pio_home):
result = clirunner.invoke(cmd_lib, ["update"]) result = clirunner.invoke(cmd_lib, ["builtin"])
validate_cliresult(result) validate_cliresult(result)
assert "[Up-to-date]" in result.output result = clirunner.invoke(cmd_lib, ["builtin", "--json-output"])
validate_cliresult(result)
def test_lib_stats(clirunner, validate_cliresult, isolated_pio_home):
result = clirunner.invoke(cmd_lib, ["stats"])
validate_cliresult(result)
assert all([
s in result.output
for s in ("UPDATED", "ago", "http://platformio.org/lib/show")
])
result = clirunner.invoke(cmd_lib, ["stats", "--json-output"])
validate_cliresult(result)
assert set([
"dlweek", "added", "updated", "topkeywords", "dlmonth", "dlday",
"lastkeywords"
]) == set(json.loads(result.output).keys())

View File

@ -13,30 +13,12 @@
# limitations under the License. # limitations under the License.
import json import json
import os
from os.path import join
from platformio import exception, util from platformio import exception
from platformio.commands import platform as cli_platform from platformio.commands import platform as cli_platform
def test_list_json_output(clirunner, validate_cliresult): def test_search_json_output(clirunner, validate_cliresult, isolated_pio_home):
result = clirunner.invoke(cli_platform.platform_list, ["--json-output"])
validate_cliresult(result)
list_result = json.loads(result.output)
assert isinstance(list_result, list)
assert len(list_result)
platforms = [item['name'] for item in list_result]
assert "titiva" in platforms
def test_list_raw_output(clirunner, validate_cliresult):
result = clirunner.invoke(cli_platform.platform_list)
validate_cliresult(result)
assert "teensy" in result.output
def test_search_json_output(clirunner, validate_cliresult):
result = clirunner.invoke(cli_platform.platform_search, result = clirunner.invoke(cli_platform.platform_search,
["arduino", "--json-output"]) ["arduino", "--json-output"])
validate_cliresult(result) validate_cliresult(result)
@ -47,73 +29,86 @@ def test_search_json_output(clirunner, validate_cliresult):
assert "atmelsam" in platforms assert "atmelsam" in platforms
def test_search_raw_output(clirunner, validate_cliresult): def test_search_raw_output(clirunner, validate_cliresult, isolated_pio_home):
result = clirunner.invoke(cli_platform.platform_search, ["arduino"]) result = clirunner.invoke(cli_platform.platform_search, ["arduino"])
validate_cliresult(result) validate_cliresult(result)
assert "teensy" in result.output assert "teensy" in result.output
def test_install_uknown_from_registry(clirunner, validate_cliresult): def test_install_unknown_version(clirunner, validate_cliresult,
result = clirunner.invoke(cli_platform.platform_install, isolated_pio_home):
["uknown-platform"])
assert result.exit_code == -1
assert isinstance(result.exception, exception.UnknownPackage)
def test_install_uknown_version(clirunner, validate_cliresult):
result = clirunner.invoke(cli_platform.platform_install, result = clirunner.invoke(cli_platform.platform_install,
["atmelavr@99.99.99"]) ["atmelavr@99.99.99"])
assert result.exit_code == -1 assert result.exit_code == -1
assert isinstance(result.exception, exception.UndefinedPackageVersion) assert isinstance(result.exception, exception.UndefinedPackageVersion)
def test_complex(clirunner, validate_cliresult): def test_install_unknown_from_registry(clirunner, validate_cliresult,
with clirunner.isolated_filesystem(): isolated_pio_home):
os.environ["PLATFORMIO_HOME_DIR"] = os.getcwd() result = clirunner.invoke(cli_platform.platform_install,
try: ["unknown-platform"])
result = clirunner.invoke( assert result.exit_code == -1
cli_platform.platform_install, assert isinstance(result.exception, exception.UnknownPackage)
["teensy", "--with-package", "framework-arduinoteensy"])
validate_cliresult(result)
assert all([
s in result.output
for s in ("teensy", "Downloading", "Unpacking")
])
# show platform information
result = clirunner.invoke(cli_platform.platform_show, ["teensy"])
validate_cliresult(result)
assert "teensy" in result.output
# list platforms def test_install_known_version(clirunner, validate_cliresult,
result = clirunner.invoke(cli_platform.platform_list, isolated_pio_home):
["--json-output"]) result = clirunner.invoke(cli_platform.platform_install, [
validate_cliresult(result) "atmelavr@1.1.0", "--skip-default-package", "--with-package",
list_result = json.loads(result.output) "tool-avrdude"
assert isinstance(list_result, list) ])
assert len(list_result) == 1 validate_cliresult(result)
assert list_result[0]["name"] == "teensy" assert "atmelavr @ 1.1.0" in result.output
assert list_result[0]["packages"] == ["framework-arduinoteensy"] assert "Installing tool-avrdude @" in result.output
assert len(isolated_pio_home.join("packages").listdir()) == 1
# try to install again
result = clirunner.invoke(cli_platform.platform_install,
["teensy"])
validate_cliresult(result)
assert "is already installed" in result.output
# try to update def test_install_from_vcs(clirunner, validate_cliresult, isolated_pio_home):
for _ in range(2): result = clirunner.invoke(cli_platform.platform_install, [
result = clirunner.invoke(cli_platform.platform_update) "https://github.com/platformio/"
validate_cliresult(result) "platform-espressif8266.git#feature/stage", "--skip-default-package"
assert "teensy" in result.output ])
assert "Up-to-date" in result.output validate_cliresult(result)
assert "Out-of-date" not in result.output assert "espressif8266_stage" in result.output
# try to uninstall
result = clirunner.invoke(cli_platform.platform_uninstall, def test_list_json_output(clirunner, validate_cliresult, isolated_pio_home):
["teensy"]) result = clirunner.invoke(cli_platform.platform_list, ["--json-output"])
validate_cliresult(result) validate_cliresult(result)
for folder in ("platforms", "packages"): list_result = json.loads(result.output)
assert len(os.listdir(join(util.get_home_dir(), folder))) == 0 assert isinstance(list_result, list)
finally: assert len(list_result)
del os.environ["PLATFORMIO_HOME_DIR"] platforms = [item['name'] for item in list_result]
assert set(["atmelavr", "espressif8266_stage"]) == set(platforms)
def test_list_raw_output(clirunner, validate_cliresult, isolated_pio_home):
result = clirunner.invoke(cli_platform.platform_list)
validate_cliresult(result)
assert all(
[s in result.output for s in ("atmelavr", "espressif8266_stage")])
def test_update_check(clirunner, validate_cliresult, isolated_pio_home):
result = clirunner.invoke(cli_platform.platform_update,
["--only-check", "--json-output"])
validate_cliresult(result)
output = json.loads(result.output)
assert len(output) == 1
assert output[0]['name'] == "atmelavr"
assert len(isolated_pio_home.join("packages").listdir()) == 1
def test_update_raw(clirunner, validate_cliresult, isolated_pio_home):
result = clirunner.invoke(cli_platform.platform_update)
validate_cliresult(result)
assert "Uninstalling atmelavr @ 1.1.0:" in result.output
assert "PlatformManager: Installing atmelavr @" in result.output
assert len(isolated_pio_home.join("packages").listdir()) == 1
def test_uninstall(clirunner, validate_cliresult, isolated_pio_home):
result = clirunner.invoke(cli_platform.platform_uninstall,
["atmelavr", "espressif8266_stage"])
validate_cliresult(result)
assert len(isolated_pio_home.join("platforms").listdir()) == 0

View File

@ -26,12 +26,15 @@ Foo foo(&fooCallback);
// //
template<class T> T Add(T n1, T n2) {
return n1 + n2;
}
void setup() { void setup() {
struct Item item1; struct Item item1;
myFunction(&item1); myFunction(&item1);
} }
void loop() { void loop() {
} }
@ -40,7 +43,7 @@ void myFunction(struct Item *item) {
} }
#warning "Line number is 43" #warning "Line number is 46"
void fooCallback(){ void fooCallback(){

View File

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

View File

@ -13,6 +13,7 @@
# limitations under the License. # limitations under the License.
import json import json
import re
from time import time from time import time
from platformio import app, maintenance from platformio import app, maintenance
@ -135,7 +136,8 @@ def test_check_and_update_libraries(clirunner, validate_cliresult,
assert ("There are the new updates for libraries (ArduinoJson)" in assert ("There are the new updates for libraries (ArduinoJson)" in
result.output) result.output)
assert "Please wait while updating libraries" in result.output assert "Please wait while updating libraries" in result.output
assert "[Out-of-date]" in result.output assert re.search(r"Updating ArduinoJson\s+@ 5.6.7\s+\[[\d\.]+\]",
result.output)
# check updated version # check updated version
result = clirunner.invoke(cli_pio, ["lib", "-g", "list", "--json-output"]) result = clirunner.invoke(cli_pio, ["lib", "-g", "list", "--json-output"])
@ -154,7 +156,7 @@ def test_check_platform_updates(clirunner, validate_cliresult,
manifest['version'] = "0.0.0" manifest['version'] = "0.0.0"
manifest_path.write(json.dumps(manifest)) manifest_path.write(json.dumps(manifest))
# reset cached manifests # reset cached manifests
PlatformManager().reset_cache() PlatformManager().cache_reset()
# reset check time # reset check time
interval = int(app.get_setting("check_platforms_interval")) * 3600 * 24 interval = int(app.get_setting("check_platforms_interval")) * 3600 * 24
@ -188,7 +190,8 @@ def test_check_and_update_platforms(clirunner, validate_cliresult,
validate_cliresult(result) validate_cliresult(result)
assert "There are the new updates for platforms (native)" in result.output assert "There are the new updates for platforms (native)" in result.output
assert "Please wait while updating platforms" in result.output assert "Please wait while updating platforms" in result.output
assert "[Out-of-date]" in result.output assert re.search(r"Updating native\s+@ 0.0.0\s+\[[\d\.]+\]",
result.output)
# check updated version # check updated version
result = clirunner.invoke(cli_pio, ["platform", "list", "--json-output"]) result = clirunner.invoke(cli_pio, ["platform", "list", "--json-output"])

View File

@ -12,70 +12,118 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import json
from os.path import join
from platformio import util from platformio import util
from platformio.managers.package import BasePkgManager from platformio.managers.package import PackageManager
def test_pkg_name_parser(): def test_pkg_input_parser():
items = [ items = [
["PkgName", ("PkgName", None, None)], ["PkgName", ("PkgName", None, None)],
[("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())
], [ ],
[
"LocalName=" + util.get_home_dir(), "LocalName=" + util.get_home_dir(),
("LocalName", None, "file://" + util.get_home_dir()) ("LocalName", None, "file://" + util.get_home_dir())
], [ ],
[
"LocalName=%s@>2.3.0" % util.get_home_dir(),
("LocalName", ">2.3.0", "file://" + util.get_home_dir())
],
[
"https://github.com/user/package.git", "https://github.com/user/package.git",
("package", None, "git+https://github.com/user/package.git") ("package", None, "git+https://github.com/user/package.git")
], [ ],
"https://gitlab.com/user/package.git", [
("package", None, "git+https://gitlab.com/user/package.git") "MyPackage=https://gitlab.com/user/package.git",
], [ ("MyPackage", None, "git+https://gitlab.com/user/package.git")
],
[
"MyPackage=https://gitlab.com/user/package.git@3.2.1,!=2",
("MyPackage", "3.2.1,!=2",
"git+https://gitlab.com/user/package.git")
],
[
"https://somedomain.com/path/LibraryName-1.2.3.zip",
("LibraryName-1.2.3", None,
"https://somedomain.com/path/LibraryName-1.2.3.zip")
],
[
"https://github.com/user/package/archive/branch.zip", "https://github.com/user/package/archive/branch.zip",
("branch", None, ("branch", None,
"https://github.com/user/package/archive/branch.zip") "https://github.com/user/package/archive/branch.zip")
], [ ],
[
"https://github.com/user/package/archive/branch.zip@~1.2.3",
("branch", "~1.2.3",
"https://github.com/user/package/archive/branch.zip")
],
[
"https://github.com/user/package/archive/branch.tar.gz", "https://github.com/user/package/archive/branch.tar.gz",
("branch", None, ("branch.tar", None,
"https://github.com/user/package/archive/branch.tar.gz") "https://github.com/user/package/archive/branch.tar.gz")
], [ ],
[
"https://github.com/user/package/archive/branch.tar.gz@!=5",
("branch.tar", "!=5",
"https://github.com/user/package/archive/branch.tar.gz")
],
[
"https://developer.mbed.org/users/user/code/package/", "https://developer.mbed.org/users/user/code/package/",
("package", None, ("package", None,
"hg+https://developer.mbed.org/users/user/code/package/") "hg+https://developer.mbed.org/users/user/code/package/")
], [ ],
[
"https://github.com/user/package#v1.2.3", "https://github.com/user/package#v1.2.3",
("package", None, "git+https://github.com/user/package#v1.2.3") ("package", None, "git+https://github.com/user/package#v1.2.3")
], [ ],
[
"https://github.com/user/package.git#branch", "https://github.com/user/package.git#branch",
("package", None, "git+https://github.com/user/package.git#branch") ("package", None, "git+https://github.com/user/package.git#branch")
], [ ],
[
"PkgName=https://github.com/user/package.git#a13d344fg56", "PkgName=https://github.com/user/package.git#a13d344fg56",
("PkgName", None, ("PkgName", None,
"git+https://github.com/user/package.git#a13d344fg56") "git+https://github.com/user/package.git#a13d344fg56")
], [ ],
[
"user/package",
("package", None, "git+https://github.com/user/package")
],
[
"PkgName=user/package", "PkgName=user/package",
("PkgName", None, "git+https://github.com/user/package") ("PkgName", None, "git+https://github.com/user/package")
], [ ],
[
"PkgName=user/package#master", "PkgName=user/package#master",
("PkgName", None, "git+https://github.com/user/package#master") ("PkgName", None, "git+https://github.com/user/package#master")
], [ ],
[
"git+https://github.com/user/package", "git+https://github.com/user/package",
("package", None, "git+https://github.com/user/package") ("package", None, "git+https://github.com/user/package")
], [ ],
[
"hg+https://example.com/user/package", "hg+https://example.com/user/package",
("package", None, "hg+https://example.com/user/package") ("package", None, "hg+https://example.com/user/package")
], [ ],
[
"git@github.com:user/package.git", "git@github.com:user/package.git",
("package", None, "git@github.com:user/package.git") ("package", None, "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@github.com:user/package.git#v1.2.0")
], [ ],
[
"git+ssh://git@gitlab.private-server.com/user/package#1.2.0", "git+ssh://git@gitlab.private-server.com/user/package#1.2.0",
("package", None, ("package", None,
"git+ssh://git@gitlab.private-server.com/user/package#1.2.0") "git+ssh://git@gitlab.private-server.com/user/package#1.2.0")
@ -83,6 +131,72 @@ def test_pkg_name_parser():
] ]
for params, result in items: for params, result in items:
if isinstance(params, tuple): if isinstance(params, tuple):
assert BasePkgManager.parse_pkg_name(*params) == result assert PackageManager.parse_pkg_input(*params) == result
else: else:
assert BasePkgManager.parse_pkg_name(params) == result assert PackageManager.parse_pkg_input(params) == result
def test_install_packages(isolated_pio_home, tmpdir):
packages = [
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.1.0"),
dict(id=1, name="name_1", version="1.2.0"),
dict(id=1, name="name_1", version="1.0.0"),
dict(name="name_2", version="1.0.0"),
dict(name="name_2", version="2.0.0",
__src_url="git+https://github.com"),
dict(name="name_2", version="3.0.0",
__src_url="git+https://github2.com"),
dict(name="name_2", version="4.0.0",
__src_url="git+https://github2.com")
]
pm = PackageManager(join(util.get_home_dir(), "packages"))
for package in packages:
tmp_dir = tmpdir.mkdir("tmp-package")
tmp_dir.join("package.json").write(json.dumps(package))
pm._install_from_url(package['name'], "file://%s" % str(tmp_dir))
tmp_dir.remove(rec=1)
assert len(pm.get_installed()) == len(packages) - 1
pkg_dirnames = [
'name_1_ID1', 'name_1_ID1@1.0.0', 'name_1_ID1@1.2.0',
'name_1_ID1@2.0.0', 'name_1_ID1@shasum', 'name_2',
'name_2@src-177cbce1f0705580d17790fda1cc2ef5',
'name_2@src-f863b537ab00f4c7b5011fc44b120e1f'
]
assert set([p.basename for p in isolated_pio_home.join(
"packages").listdir()]) == set(pkg_dirnames)
def test_get_package(isolated_pio_home):
tests = [
[("unknown", ), None],
[("1", ), None],
[("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", "^1"), dict(id=1, name="name_1", version="1.2.0")],
[("id=1", "^1"), dict(id=1, name="name_1", version="1.2.0")],
[("name_1", "<2"), dict(id=1, name="name_1", version="1.2.0")],
[("name_1", ">2"), None],
[("name_1", "2-0-0"), dict(id=1, name="name_1", version="2.1.0")],
[("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")],
[("url_has_higher_priority", None, "git+https://github.com"),
dict(name="name_2", version="2.0.0",
__src_url="git+https://github.com")],
[("name_2", None, "git+https://github.com"),
dict(name="name_2", version="2.0.0",
__src_url="git+https://github.com")],
]
pm = PackageManager(join(util.get_home_dir(), "packages"))
for test in tests:
manifest = pm.get_package(*test[0])
if test[1] is None:
assert manifest is None, test
continue
for key, value in test[1].items():
assert manifest[key] == value, test

View File

@ -16,19 +16,6 @@ import pytest
import requests import requests
def pytest_generate_tests(metafunc):
if "package_data" not in metafunc.fixturenames:
return
pkgs_manifest = requests.get(
"https://dl.bintray.com/platformio/dl-packages/manifest.json").json()
assert isinstance(pkgs_manifest, dict)
packages = []
for _, variants in pkgs_manifest.iteritems():
for item in variants:
packages.append(item)
metafunc.parametrize("package_data", packages)
def validate_response(req): def validate_response(req):
assert req.status_code == 200 assert req.status_code == 200
assert int(req.headers['Content-Length']) > 0 assert int(req.headers['Content-Length']) > 0
@ -36,13 +23,22 @@ def validate_response(req):
"application/octet-stream") "application/octet-stream")
def test_package(package_data): def test_packages():
assert package_data['url'].endswith(".tar.gz") pkgs_manifest = requests.get(
"https://dl.bintray.com/platformio/dl-packages/manifest.json").json()
assert isinstance(pkgs_manifest, dict)
items = []
for _, variants in pkgs_manifest.iteritems():
for item in variants:
items.append(item)
r = requests.head(package_data['url'], allow_redirects=True) for item in items:
validate_response(r) assert item['url'].endswith(".tar.gz"), item
if "X-Checksum-Sha1" not in r.headers: r = requests.head(item['url'], allow_redirects=True)
return pytest.skip("X-Checksum-Sha1 is not provided") validate_response(r)
assert package_data['sha1'] == r.headers.get("X-Checksum-Sha1") if "X-Checksum-Sha1" not in r.headers:
return pytest.skip("X-Checksum-Sha1 is not provided")
assert item['sha1'] == r.headers.get("X-Checksum-Sha1"), item

View File

@ -24,6 +24,7 @@ deps =
yapf yapf
pylint pylint
pytest pytest
show
commands = python --version commands = python --version
[testenv:docs] [testenv:docs]
@ -61,6 +62,13 @@ commands =
{envpython} --version {envpython} --version
py.test -v --basetemp="{envtmpdir}" tests py.test -v --basetemp="{envtmpdir}" tests
[testenv:skipexamples]
basepython = python2.7
deps =
pytest
commands =
py.test -v --basetemp="{envtmpdir}" tests --ignore tests/test_examples.py
[testenv:coverage] [testenv:coverage]
basepython = python2.7 basepython = python2.7
passenv = * passenv = *