Merge branch 'hotfix/v3.5.4'

This commit is contained in:
Ivan Kravets
2018-07-03 15:11:32 +03:00
21 changed files with 264 additions and 143 deletions

View File

@ -4,6 +4,30 @@ Release Notes
PlatformIO 3.0 PlatformIO 3.0
-------------- --------------
3.5.4 (2018-07-03)
~~~~~~~~~~~~~~~~~~
* Improved removing of default build flags using `build_unflags <http://docs.platformio.org/page/projectconf/section_env_build.html#build-unflags>`__ option
(`issue #1712 <https://github.com/platformio/platformio-core/issues/1712>`_)
* Export ``LIBS``, ``LIBPATH``, and ``LINKFLAGS`` data from project dependent
libraries to the global build environment
* Don't export ``CPPPATH`` data of project dependent libraries to framework's
build environment
(`issue #1665 <https://github.com/platformio/platformio-core/issues/1665>`_)
* Handle "architectures" data from "library.properties" manifest in
`lib_compat_mode = strict <http://docs.platformio.org/en/page/librarymanager/ldf.html#compatibility-mode>`__
* Added workaround for Python SemVer package's `issue #61 <https://github.com/rbarrois/python-semanticversion/issues/61>`_ with caret range and pre-releases
* Replaced conflicted "env" pattern by "sysenv" for `"platformio.ini" Dynamic Variables" <http://docs.platformio.org/page/projectconf/dynamic_variables.html>`__
(`issue #1705 <https://github.com/platformio/platformio-core/issues/1705>`_)
* Removed "date&time" when processing project with `platformio run <http://docs.platformio.org/page/userguide/cmd_run.html>`__ command
(`issue #1343 <https://github.com/platformio/platformio-core/issues/1343>`_)
* Fixed issue with invalid LD script if path contains space
* Fixed preprocessor for Arduino sketch when function returns certain type
(`issue #1683 <https://github.com/platformio/platformio-core/issues/1683>`_)
* Fixed issue when `platformio lib uninstall <http://docs.platformio.org/page/userguide/lib/cmd_uninstall.html>`__
removes initial source code
(`issue #1023 <https://github.com/platformio/platformio-core/issues/1023>`_)
3.5.3 (2018-06-01) 3.5.3 (2018-06-01)
~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~
@ -19,7 +43,7 @@ PlatformIO 3.0
(`issue #1612 <https://github.com/platformio/platformio-core/issues/1612>`_) (`issue #1612 <https://github.com/platformio/platformio-core/issues/1612>`_)
* Configure a custom path to SVD file using `debug_svd_path <http://docs.platformio.org/page/projectconf/section_env_debug.html#debug-svd-path>`__ * Configure a custom path to SVD file using `debug_svd_path <http://docs.platformio.org/page/projectconf/section_env_debug.html#debug-svd-path>`__
option option
* Custom project `description <http://docs.platformio.org/en/latest/projectconf/section_platformio.html#description>`_ * Custom project `description <http://docs.platformio.org/en/page/projectconf/section_platformio.html#description>`_
which will be used by `PlatformIO Home <http://docs.platformio.org/page/home/index.html>`_ which will be used by `PlatformIO Home <http://docs.platformio.org/page/home/index.html>`_
* Updated Unity tool to 2.4.3 * Updated Unity tool to 2.4.3
* Improved support for Black Magic Probe in "uploader" mode * Improved support for Black Magic Probe in "uploader" mode
@ -45,9 +69,9 @@ PlatformIO 3.0
- Multiple themes (Dark & Light) - Multiple themes (Dark & Light)
- Ability to specify a name for new project - Ability to specify a name for new project
* Control `PIO Unified Debugger <http://docs.platformio.org/en/latest/plus/debugging.html>`__ * Control `PIO Unified Debugger <http://docs.platformio.org/en/page/plus/debugging.html>`__
and its firmware loading mode using and its firmware loading mode using
`debug_load_mode <http://docs.platformio.org/en/latest/projectconf/section_env_debug.html#debug-load-mode>`__ option `debug_load_mode <http://docs.platformio.org/en/page/projectconf/section_env_debug.html#debug-load-mode>`__ option
* Added aliases (off, light, strict) for * Added aliases (off, light, strict) for
`LDF Compatibility Mode <http://docs.platformio.org/page/librarymanager/ldf.html>`__ `LDF Compatibility Mode <http://docs.platformio.org/page/librarymanager/ldf.html>`__
* Search for a library using PIO Library Registry ID ``id:X`` (e.g. ``pio lib search id:13``) * Search for a library using PIO Library Registry ID ``id:X`` (e.g. ``pio lib search id:13``)

2
docs

Submodule docs updated: 3ad76be8f7...0b8ac5fbf7

View File

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

View File

@ -279,9 +279,8 @@ class LibBuilderBase(object):
if (key in item and if (key in item and
not util.items_in_list(self.env[env_key], item[key])): not util.items_in_list(self.env[env_key], item[key])):
if self.verbose: if self.verbose:
sys.stderr.write( sys.stderr.write("Skip %s incompatible dependency %s\n"
"Skip %s incompatible dependency %s\n" % (key[:-1], % (key[:-1], item))
item))
skip = True skip = True
if skip: if skip:
continue continue
@ -394,9 +393,9 @@ class LibBuilderBase(object):
if self != lb: if self != lb:
if _already_depends(lb): if _already_depends(lb):
if self.verbose: if self.verbose:
sys.stderr.write("Warning! Circular dependencies detected " sys.stderr.write(
"between `%s` and `%s`\n" % (self.path, "Warning! Circular dependencies detected "
lb.path)) "between `%s` and `%s`\n" % (self.path, lb.path))
self._circular_deps.append(lb) self._circular_deps.append(lb)
elif lb not in self._depbuilders: elif lb not in self._depbuilders:
self._depbuilders.append(lb) self._depbuilders.append(lb)
@ -502,6 +501,28 @@ class ArduinoLibBuilder(LibBuilderBase):
def is_frameworks_compatible(self, frameworks): def is_frameworks_compatible(self, frameworks):
return util.items_in_list(frameworks, ["arduino", "energia"]) return util.items_in_list(frameworks, ["arduino", "energia"])
def is_platforms_compatible(self, platforms):
platforms_map = {
"avr": "atmelavr",
"sam": "atmelsam",
"samd": "atmelsam",
"esp8266": "espressif8266",
"esp32": "espressif32",
"arc32": "intel_arc32",
"stm32": "ststm32"
}
items = []
for arch in self._manifest.get("architectures", "").split(","):
arch = arch.strip()
if arch == "*":
items = "*"
break
if arch in platforms_map:
items.append(platforms_map[arch])
if not items:
return LibBuilderBase.is_platforms_compatible(self, platforms)
return util.items_in_list(platforms, items)
class MbedLibBuilder(LibBuilderBase): class MbedLibBuilder(LibBuilderBase):
@ -803,7 +824,7 @@ def GetLibBuilders(env): # pylint: disable=too-many-branches
return items return items
def BuildProjectLibraries(env): def ConfigureProjectLibBuilder(env):
def correct_found_libs(lib_builders): def correct_found_libs(lib_builders):
# build full dependency graph # build full dependency graph
@ -822,7 +843,7 @@ def BuildProjectLibraries(env):
title = "<%s>" % lb.name title = "<%s>" % lb.name
vcs_info = lb.vcs_info vcs_info = lb.vcs_info
if lb.version: if lb.version:
title += " v%s" % lb.version title += " %s" % lb.version
if vcs_info and vcs_info.get("version"): if vcs_info and vcs_info.get("version"):
title += " #%s" % vcs_info.get("version") title += " #%s" % vcs_info.get("version")
sys.stdout.write("%s|-- %s" % (margin, title)) sys.stdout.write("%s|-- %s" % (margin, title))
@ -837,7 +858,6 @@ def BuildProjectLibraries(env):
print_deps_tree(lb, level + 1) print_deps_tree(lb, level + 1)
project = ProjectAsLibBuilder(env, "$PROJECT_DIR") project = ProjectAsLibBuilder(env, "$PROJECT_DIR")
project.env = env
ldf_mode = LibBuilderBase.lib_ldf_mode.fget(project) ldf_mode = LibBuilderBase.lib_ldf_mode.fget(project)
print "Library Dependency Finder -> http://bit.ly/configure-pio-ldf" print "Library Dependency Finder -> http://bit.ly/configure-pio-ldf"
@ -859,7 +879,7 @@ def BuildProjectLibraries(env):
else: else:
print "No dependencies" print "No dependencies"
return project.build() return project
def exists(_): def exists(_):
@ -868,5 +888,5 @@ def exists(_):
def generate(env): def generate(env):
env.AddMethod(GetLibBuilders) env.AddMethod(GetLibBuilders)
env.AddMethod(BuildProjectLibraries) env.AddMethod(ConfigureProjectLibBuilder)
return env return env

View File

@ -30,9 +30,10 @@ from platformio.managers.core import get_core_package_dir
class InoToCPPConverter(object): class InoToCPPConverter(object):
PROTOTYPE_RE = re.compile(r"""^( PROTOTYPE_RE = re.compile(
r"""^(
(?:template\<.*\>\s*)? # template (?: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
)\s*\{ # must end with { )\s*\{ # must end with {
@ -89,8 +90,8 @@ class InoToCPPConverter(object):
self.env.Execute( self.env.Execute(
self.env.VerboseAction( self.env.VerboseAction(
'$CXX -o "{0}" -x c++ -fpreprocessed -dD -E "{1}"'.format( '$CXX -o "{0}" -x c++ -fpreprocessed -dD -E "{1}"'.format(
out_file, out_file, tmp_path),
tmp_path), "Converting " + basename(out_file[:-4]))) "Converting " + basename(out_file[:-4])))
atexit.register(_delete_file, tmp_path) atexit.register(_delete_file, tmp_path)
return isfile(out_file) return isfile(out_file)
@ -163,18 +164,17 @@ class InoToCPPConverter(object):
prototype_names = set([m.group(3).strip() for m in prototypes]) prototype_names = set([m.group(3).strip() for m in prototypes])
split_pos = prototypes[0].start() split_pos = prototypes[0].start()
match_ptrs = re.search(self.PROTOPTRS_TPLRE % match_ptrs = re.search(
("|".join(prototype_names)), self.PROTOPTRS_TPLRE % ("|".join(prototype_names)),
contents[:split_pos], re.M) contents[:split_pos], re.M)
if match_ptrs: if match_ptrs:
split_pos = contents.rfind("\n", 0, match_ptrs.start()) + 1 split_pos = contents.rfind("\n", 0, match_ptrs.start()) + 1
result = [] result = []
result.append(contents[:split_pos].strip()) result.append(contents[:split_pos].strip())
result.append("%s;" % ";\n".join([m.group(1) for m in prototypes])) result.append("%s;" % ";\n".join([m.group(1) for m in prototypes]))
result.append('#line %d "%s"' % result.append('#line %d "%s"' % (self._get_total_lines(
(self._get_total_lines(contents[:split_pos]), contents[:split_pos]), self._main_ino.replace("\\", "/")))
self._main_ino.replace("\\", "/")))
result.append(contents[split_pos:].strip()) result.append(contents[split_pos:].strip())
return "\n".join(result) return "\n".join(result)
@ -231,14 +231,25 @@ def GetActualLDScript(env):
return None return None
script = None script = None
script_in_next = False
for f in env.get("LINKFLAGS", []): for f in env.get("LINKFLAGS", []):
if f.startswith("-Wl,-T"): raw_script = None
script = env.subst(f[6:].replace('"', "").strip()) if f == "-T":
if isfile(script): script_in_next = True
return script continue
path = _lookup_in_ldpath(script) elif script_in_next:
if path: script_in_next = False
return path raw_script = f
elif f.startswith("-Wl,-T"):
raw_script = f[6:]
else:
continue
script = env.subst(raw_script.replace('"', "").strip())
if isfile(script):
return script
path = _lookup_in_ldpath(script)
if path:
return path
if script: if script:
sys.stderr.write( sys.stderr.write(
@ -295,9 +306,6 @@ def ProcessTest(env):
src_filter.append("+<%s%s>" % (env['PIOTEST'], sep)) src_filter.append("+<%s%s>" % (env['PIOTEST'], sep))
env.Replace(PIOTEST_SRC_FILTER=src_filter) env.Replace(PIOTEST_SRC_FILTER=src_filter)
return env.CollectBuildFiles("$BUILDTEST_DIR", "$PROJECTTEST_DIR",
"$PIOTEST_SRC_FILTER")
def GetExtraScripts(env, scope): def GetExtraScripts(env, scope):
items = [] items = []

View File

@ -209,9 +209,9 @@ def CheckUploadSize(_, target, source, env): # pylint: disable=W0613,W0621
used_size = int(values[0]) + int(values[1]) used_size = int(values[0]) + int(values[1])
if used_size > max_size: if used_size > max_size:
sys.stderr.write("Error: The program size (%d bytes) is greater " sys.stderr.write(
"than maximum allowed (%s bytes)\n" % (used_size, "Error: The program size (%d bytes) is greater "
max_size)) "than maximum allowed (%s bytes)\n" % (used_size, max_size))
env.Exit(1) env.Exit(1)

View File

@ -22,7 +22,7 @@ from os.path import basename, dirname, isdir, join, realpath
from SCons import Builder, Util from SCons import Builder, Util
from SCons.Script import (COMMAND_LINE_TARGETS, AlwaysBuild, from SCons.Script import (COMMAND_LINE_TARGETS, AlwaysBuild,
DefaultEnvironment, SConscript) DefaultEnvironment, Export, SConscript)
from platformio.util import glob_escape, pioversion_to_intstr from platformio.util import glob_escape, pioversion_to_intstr
@ -41,6 +41,43 @@ def scons_patched_match_splitext(path, suffixes=None):
return tokens return tokens
def _build_project_deps(env):
project_lib_builder = env.ConfigureProjectLibBuilder()
# append project libs to the beginning of list
env.Prepend(LIBS=project_lib_builder.build())
# append extra linker related options from libs
env.AppendUnique(
**{
key: project_lib_builder.env.get(key)
for key in ("LIBS", "LIBPATH", "LINKFLAGS")
if project_lib_builder.env.get(key)
})
if "__test" in COMMAND_LINE_TARGETS:
env.ProcessTest()
projenv = env.Clone()
projenv.BuildSources("$BUILDTEST_DIR", "$PROJECTTEST_DIR",
"$PIOTEST_SRC_FILTER")
else:
projenv = env.Clone()
projenv.BuildSources("$BUILDSRC_DIR", "$PROJECTSRC_DIR",
env.get("SRC_FILTER"))
# CPPPATH from dependencies
projenv.PrependUnique(CPPPATH=project_lib_builder.env.get("CPPPATH"))
# extra build flags from `platformio.ini`
projenv.ProcessFlags(env.get("SRC_BUILD_FLAGS"))
if not env.get("PIOBUILDFILES") and not COMMAND_LINE_TARGETS:
sys.stderr.write(
"Error: Nothing to build. Please put your source code files "
"to '%s' folder\n" % env.subst("$PROJECTSRC_DIR"))
env.Exit(1)
Export("projenv")
def BuildProgram(env): def BuildProgram(env):
def _append_pio_macros(): def _append_pio_macros():
@ -62,6 +99,7 @@ def BuildProgram(env):
# process extra flags from board # process extra flags from board
if "BOARD" in env and "build.extra_flags" in env.BoardConfig(): if "BOARD" in env and "build.extra_flags" in env.BoardConfig():
env.ProcessFlags(env.BoardConfig().get("build.extra_flags")) env.ProcessFlags(env.BoardConfig().get("build.extra_flags"))
# apply user flags # apply user flags
env.ProcessFlags(env.get("BUILD_FLAGS")) env.ProcessFlags(env.get("BUILD_FLAGS"))
@ -74,37 +112,19 @@ def BuildProgram(env):
# remove specified flags # remove specified flags
env.ProcessUnFlags(env.get("BUILD_UNFLAGS")) env.ProcessUnFlags(env.get("BUILD_UNFLAGS"))
# build dependent libs; place them before built-in libs # build project with dependencies
env.Prepend(LIBS=env.BuildProjectLibraries()) _build_project_deps(env)
# append specified LD_SCRIPT # append into the beginning a main LD script
if ("LDSCRIPT_PATH" in env if (env.get("LDSCRIPT_PATH")
and not any("-Wl,-T" in f for f in env['LINKFLAGS'])): and not any("-Wl,-T" in f for f in env['LINKFLAGS'])):
env.Append(LINKFLAGS=['-Wl,-T"$LDSCRIPT_PATH"']) env.Prepend(LINKFLAGS=["-T", "$LDSCRIPT_PATH"])
# enable "cyclic reference" for linker # enable "cyclic reference" for linker
if env.get("LIBS") and env.GetCompilerType() == "gcc": if env.get("LIBS") and env.GetCompilerType() == "gcc":
env.Prepend(_LIBFLAGS="-Wl,--start-group ") env.Prepend(_LIBFLAGS="-Wl,--start-group ")
env.Append(_LIBFLAGS=" -Wl,--end-group") env.Append(_LIBFLAGS=" -Wl,--end-group")
# Handle SRC_BUILD_FLAGS
env.ProcessFlags(env.get("SRC_BUILD_FLAGS"))
if "__test" in COMMAND_LINE_TARGETS:
env.Append(PIOBUILDFILES=env.ProcessTest())
else:
env.Append(
PIOBUILDFILES=env.CollectBuildFiles(
"$BUILDSRC_DIR",
"$PROJECTSRC_DIR",
src_filter=env.get("SRC_FILTER")))
if not env['PIOBUILDFILES'] and not COMMAND_LINE_TARGETS:
sys.stderr.write(
"Error: Nothing to build. Please put your source code files "
"to '%s' folder\n" % env.subst("$PROJECTSRC_DIR"))
env.Exit(1)
program = env.Program( program = env.Program(
join("$BUILD_DIR", env.subst("$PROGNAME")), env['PIOBUILDFILES']) join("$BUILD_DIR", env.subst("$PROGNAME")), env['PIOBUILDFILES'])
@ -171,7 +191,18 @@ def ProcessFlags(env, flags): # pylint: disable=too-many-branches
def ProcessUnFlags(env, flags): def ProcessUnFlags(env, flags):
if not flags: if not flags:
return return
for key, unflags in env.ParseFlagsExtended(flags).items(): parsed = env.ParseFlagsExtended(flags)
# get all flags and copy them to each "*FLAGS" variable
all_flags = []
for key, unflags in parsed.items():
if key.endswith("FLAGS"):
all_flags.extend(unflags)
for key, unflags in parsed.items():
if key.endswith("FLAGS"):
parsed[key].extend(all_flags)
for key, unflags in parsed.items():
for unflag in unflags: for unflag in unflags:
for current in env.get(key, []): for current in env.get(key, []):
conditions = [ conditions = [
@ -275,7 +306,7 @@ def BuildFrameworks(env, frameworks):
env.ConvertInoToCpp() env.ConvertInoToCpp()
if f in board_frameworks: if f in board_frameworks:
SConscript(env.GetFrameworkScript(f)) SConscript(env.GetFrameworkScript(f), exports="env")
else: else:
sys.stderr.write( sys.stderr.write(
"Error: This board doesn't support %s framework!\n" % f) "Error: This board doesn't support %s framework!\n" % f)
@ -289,8 +320,9 @@ def BuildLibrary(env, variant_dir, src_dir, src_filter=None):
def BuildSources(env, variant_dir, src_dir, src_filter=None): def BuildSources(env, variant_dir, src_dir, src_filter=None):
nodes = env.CollectBuildFiles(variant_dir, src_dir, src_filter)
DefaultEnvironment().Append( DefaultEnvironment().Append(
PIOBUILDFILES=env.CollectBuildFiles(variant_dir, src_dir, src_filter)) PIOBUILDFILES=[env.Object(node) for node in nodes])
def exists(_): def exists(_):

View File

@ -439,8 +439,8 @@ def lib_stats(json_output):
printitem_tpl.format( printitem_tpl.format(
name=click.style(name, fg="cyan"), name=click.style(name, fg="cyan"),
url=click.style( url=click.style(
"https://platformio.org/lib/search?query=" + "https://platformio.org/lib/search?query=" + quote(
quote("keyword:%s" % name), "keyword:%s" % name),
fg="blue"))) fg="blue")))
for key in ("updated", "added"): for key in ("updated", "added"):

View File

@ -273,8 +273,8 @@ def platform_show(platform, json_output): # pylint: disable=too-many-branches
if item['type']: if item['type']:
click.echo("Type: %s" % item['type']) click.echo("Type: %s" % item['type'])
click.echo("Requirements: %s" % item['requirements']) click.echo("Requirements: %s" % item['requirements'])
click.echo("Installed: %s" % click.echo("Installed: %s" % ("Yes" if item.get("version") else
("Yes" if item.get("version") else "No (optional)")) "No (optional)"))
if "version" in item: if "version" in item:
click.echo("Version: %s" % item['version']) click.echo("Version: %s" % item['version'])
if "originalVersion" in item: if "originalVersion" in item:

View File

@ -12,7 +12,6 @@
# 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.
from datetime import datetime
from hashlib import sha1 from hashlib import sha1
from os import getcwd, makedirs, walk from os import getcwd, makedirs, walk
from os.path import getmtime, isdir, isfile, join from os.path import getmtime, isdir, isfile, join
@ -197,10 +196,8 @@ class EnvironmentProcessor(object):
"%s: %s" % (k, ", ".join(util.parse_conf_multi_values(v)))) "%s: %s" % (k, ", ".join(util.parse_conf_multi_values(v))))
if not self.silent: if not self.silent:
click.echo("[%s] Processing %s (%s)" % click.echo("Processing %s (%s)" % (click.style(
(datetime.now().strftime("%c"), self.name, fg="cyan", bold=True), "; ".join(env_dump)))
click.style(self.name, fg="cyan", bold=True),
"; ".join(env_dump)))
click.secho("-" * terminal_width, bold=True) click.secho("-" * terminal_width, bold=True)
self.options = self._validate_options(self.options) self.options = self._validate_options(self.options)
@ -296,10 +293,10 @@ class EnvironmentProcessor(object):
if d.strip() if d.strip()
], self.verbose) ], self.verbose)
if "lib_deps" in self.options: if "lib_deps" in self.options:
_autoinstall_libdeps(self.cmd_ctx, _autoinstall_libdeps(
util.parse_conf_multi_values( self.cmd_ctx,
self.options['lib_deps']), util.parse_conf_multi_values(self.options['lib_deps']),
self.verbose) self.verbose)
try: try:
p = PlatformFactory.newPlatform(self.options['platform']) p = PlatformFactory.newPlatform(self.options['platform'])
@ -385,10 +382,10 @@ def print_summary(results, start_time):
err=status is False) err=status is False)
print_header( print_header(
"[%s] Took %.2f seconds" % "[%s] Took %.2f seconds" % (
((click.style("SUCCESS", fg="green", bold=True) (click.style("SUCCESS", fg="green", bold=True)
if successed else click.style("ERROR", fg="red", bold=True)), if successed else click.style("ERROR", fg="red", bold=True)),
time() - start_time), time() - start_time),
is_error=not successed) is_error=not successed)

View File

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

View File

@ -43,9 +43,8 @@ class FileDownloader(object):
disposition = self._request.headers.get("content-disposition") disposition = self._request.headers.get("content-disposition")
if disposition and "filename=" in disposition: if disposition and "filename=" in disposition:
self._fname = disposition[ self._fname = disposition[disposition.index("filename=") +
disposition.index("filename=") + 9:].replace('"', "").replace( 9:].replace('"', "").replace("'", "")
"'", "")
self._fname = self._fname.encode("utf8") self._fname = self._fname.encode("utf8")
else: else:
self._fname = [p for p in url.split("/") if p][-1] self._fname = [p for p in url.split("/") if p][-1]

View File

@ -192,6 +192,11 @@ class InvalidLibConfURL(PlatformioException):
MESSAGE = "Invalid library config URL '{0}'" MESSAGE = "Invalid library config URL '{0}'"
class InvalidProjectConf(PlatformioException):
MESSAGE = "Invalid `platformio.ini`, project configuration file: '{0}'"
class BuildScriptNotFound(PlatformioException): class BuildScriptNotFound(PlatformioException):
MESSAGE = "Invalid path '{0}' to build script" MESSAGE = "Invalid path '{0}' to build script"
@ -248,7 +253,7 @@ class CygwinEnvDetected(PlatformioException):
class DebugSupportError(PlatformioException): class DebugSupportError(PlatformioException):
MESSAGE = ("Currently, PlatformIO does not support debugging for `{0}`.\n" MESSAGE = ("Currently, PlatformIO does not support debugging for `{0}`.\n"
"Please mail contact@pioplus.com or visit " "Please contact support@pioplus.com or visit "
"< http://docs.platformio.org/page/plus/debugging.html >") "< http://docs.platformio.org/page/plus/debugging.html >")

View File

@ -18,6 +18,7 @@
"name": "Win32", "name": "Win32",
% elif systype == "darwin": % elif systype == "darwin":
"name": "Mac", "name": "Mac",
"macFrameworkPath": [],
% else: % else:
"name": "Linux", "name": "Linux",
% end % end

View File

@ -208,8 +208,8 @@ def after_upgrade(ctx):
# PlatformIO banner # PlatformIO banner
click.echo("*" * terminal_width) click.echo("*" * terminal_width)
click.echo("If you like %s, please:" % click.echo(
(click.style("PlatformIO", fg="cyan"))) "If you like %s, please:" % (click.style("PlatformIO", fg="cyan")))
click.echo("- %s us on Twitter to stay up-to-date " click.echo("- %s us on Twitter to stay up-to-date "
"on the latest project news > %s" % "on the latest project news > %s" %
(click.style("follow", fg="cyan"), (click.style("follow", fg="cyan"),
@ -224,9 +224,9 @@ def after_upgrade(ctx):
(click.style("try", fg="cyan"), (click.style("try", fg="cyan"),
click.style("https://platformio.org/platformio-ide", fg="cyan"))) click.style("https://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.echo("- %s us with PlatformIO Plus > %s" % (click.style(
(click.style("support", fg="cyan"), "support", fg="cyan"), click.style(
click.style("https://pioplus.com", fg="cyan"))) "https://pioplus.com", fg="cyan")))
click.echo("*" * terminal_width) click.echo("*" * terminal_width)
click.echo("") click.echo("")
@ -296,8 +296,8 @@ def check_internal_updates(ctx, what):
if manifest['name'] in outdated_items: if manifest['name'] in outdated_items:
continue continue
conds = [ conds = [
pm.outdated(manifest['__pkg_dir']), pm.outdated(manifest['__pkg_dir']), what == "platforms"
what == "platforms" and PlatformFactory.newPlatform( and PlatformFactory.newPlatform(
manifest['__pkg_dir']).are_outdated_packages() manifest['__pkg_dir']).are_outdated_packages()
] ]
if any(conds): if any(conds):
@ -318,8 +318,8 @@ def check_internal_updates(ctx, what):
if not app.get_setting("auto_update_" + what): if not app.get_setting("auto_update_" + what):
click.secho("Please update them via ", fg="yellow", nl=False) click.secho("Please update them via ", fg="yellow", nl=False)
click.secho( click.secho(
"`platformio %s update`" % "`platformio %s update`" % ("lib --global" if what == "libraries"
("lib --global" if what == "libraries" else "platform"), else "platform"),
fg="cyan", fg="cyan",
nl=False) nl=False)
click.secho(" command.\n", fg="yellow") click.secho(" command.\n", fg="yellow")

View File

@ -107,6 +107,7 @@ def pioplus_call(args, **kwargs):
pythonexe_path = util.get_pythonexe_path() pythonexe_path = util.get_pythonexe_path()
os.environ['PYTHONEXEPATH'] = pythonexe_path os.environ['PYTHONEXEPATH'] = pythonexe_path
os.environ['PYTHONPYSITEDIR'] = get_core_package_dir("contrib-pysite") os.environ['PYTHONPYSITEDIR'] = get_core_package_dir("contrib-pysite")
os.environ['PIOCOREPYSITEDIR'] = dirname(util.get_source_dir() or "")
os.environ['PATH'] = (os.pathsep).join( os.environ['PATH'] = (os.pathsep).join(
[dirname(pythonexe_path), os.environ['PATH']]) [dirname(pythonexe_path), os.environ['PATH']])
util.copy_pythonpath_to_osenv() util.copy_pythonpath_to_osenv()

View File

@ -208,9 +208,9 @@ class LibraryManager(BasePkgManager):
cache_valid="30d") cache_valid="30d")
assert dl_data assert dl_data
return self._install_from_url(name, dl_data['url'].replace( return self._install_from_url(
"http://", "https://") if app.get_setting("enable_ssl") else name, dl_data['url'].replace("http://", "https://")
dl_data['url'], requirements) if app.get_setting("enable_ssl") else dl_data['url'], requirements)
def search_lib_id( # pylint: disable=too-many-branches def search_lib_id( # pylint: disable=too-many-branches
self, self,
@ -237,9 +237,9 @@ class LibraryManager(BasePkgManager):
if not isinstance(values, list): if not isinstance(values, list):
values = [v.strip() for v in values.split(",") if v] values = [v.strip() for v in values.split(",") if v]
for value in values: for value in values:
query.append('%s:"%s"' % (key[:-1] query.append(
if key.endswith("s") else key, '%s:"%s"' % (key[:-1]
value)) if key.endswith("s") else key, value))
lib_info = None lib_info = None
result = util.get_api_result( result = util.get_api_result(
@ -290,9 +290,9 @@ class LibraryManager(BasePkgManager):
def _get_lib_id_from_installed(self, filters): def _get_lib_id_from_installed(self, filters):
if filters['name'].startswith("id="): if filters['name'].startswith("id="):
return int(filters['name'][3:]) return int(filters['name'][3:])
package_dir = self.get_package_dir(filters['name'], package_dir = self.get_package_dir(
filters.get("requirements", filters['name'], filters.get("requirements",
filters.get("version"))) filters.get("version")))
if not package_dir: if not package_dir:
return None return None
manifest = self.load_manifest(package_dir) manifest = self.load_manifest(package_dir)

View File

@ -18,7 +18,7 @@ import json
import os import os
import re import re
import shutil import shutil
from os.path import basename, getsize, isdir, isfile, islink, join from os.path import abspath, basename, getsize, isdir, isfile, islink, join
from tempfile import mkdtemp from tempfile import mkdtemp
import click import click
@ -90,7 +90,8 @@ class PkgRepoMixin(object):
reqspec = None reqspec = None
if requirements: if requirements:
try: try:
reqspec = semantic_version.Spec(requirements) reqspec = self.parse_semver_spec(
requirements, raise_exception=True)
except ValueError: except ValueError:
pass pass
@ -98,8 +99,8 @@ class PkgRepoMixin(object):
if not self.is_system_compatible(v.get("system")): if not self.is_system_compatible(v.get("system")):
continue continue
if "platformio" in v.get("engines", {}): if "platformio" in v.get("engines", {}):
if PkgRepoMixin.PIO_VERSION not in semantic_version.Spec( if PkgRepoMixin.PIO_VERSION not in self.parse_semver_spec(
v['engines']['platformio']): v['engines']['platformio'], raise_exception=True):
continue continue
specver = semantic_version.Version(v['version']) specver = semantic_version.Version(v['version'])
if reqspec and specver not in reqspec: if reqspec and specver not in reqspec:
@ -224,7 +225,20 @@ class PkgInstallerMixin(object):
@staticmethod @staticmethod
def parse_semver_spec(value, raise_exception=False): def parse_semver_spec(value, raise_exception=False):
try: try:
return semantic_version.Spec(value) # Workaround for ^ issue and pre-releases
# https://github.com/rbarrois/python-semanticversion/issues/61
requirements = []
for item in str(value).split(","):
item = item.strip()
if not item:
continue
if item.startswith("^"):
major = semantic_version.Version.coerce(item[1:]).major
requirements.append(">=%s" % major)
requirements.append("<%s" % (int(major) + 1))
else:
requirements.append(item)
return semantic_version.Spec(*requirements)
except ValueError as e: except ValueError as e:
if raise_exception: if raise_exception:
raise e raise e
@ -367,6 +381,12 @@ class PkgInstallerMixin(object):
return manifest.get("__pkg_dir") if manifest and isdir( return manifest.get("__pkg_dir") if manifest and isdir(
manifest.get("__pkg_dir")) else None manifest.get("__pkg_dir")) else None
def get_package_by_dir(self, pkg_dir):
for manifest in self.get_installed():
if manifest['__pkg_dir'] == util.path_to_unicode(abspath(pkg_dir)):
return manifest
return None
def find_pkg_root(self, src_dir): def find_pkg_root(self, src_dir):
if self.manifest_exists(src_dir): if self.manifest_exists(src_dir):
return src_dir return src_dir
@ -474,8 +494,8 @@ class PkgInstallerMixin(object):
"Package version %s doesn't satisfy requirements %s" % "Package version %s doesn't satisfy requirements %s" %
(tmp_manifest['version'], requirements)) (tmp_manifest['version'], requirements))
try: try:
assert tmp_semver and tmp_semver in semantic_version.Spec( assert tmp_semver and tmp_semver in self.parse_semver_spec(
requirements), mismatch_error requirements, raise_exception=True), mismatch_error
except (AssertionError, ValueError): except (AssertionError, ValueError):
assert tmp_manifest['version'] == requirements, mismatch_error assert tmp_manifest['version'] == requirements, mismatch_error
@ -500,8 +520,8 @@ class PkgInstallerMixin(object):
cur_manifest['version']) cur_manifest['version'])
if "__src_url" in cur_manifest: if "__src_url" in cur_manifest:
target_dirname = "%s@src-%s" % ( target_dirname = "%s@src-%s" % (
pkg_dirname, pkg_dirname, hashlib.md5(
hashlib.md5(cur_manifest['__src_url']).hexdigest()) cur_manifest['__src_url']).hexdigest())
shutil.move(pkg_dir, join(self.package_dir, target_dirname)) shutil.move(pkg_dir, join(self.package_dir, target_dirname))
# fix to a version # fix to a version
elif action == 2: elif action == 2:
@ -509,8 +529,8 @@ class PkgInstallerMixin(object):
tmp_manifest['version']) tmp_manifest['version'])
if "__src_url" in tmp_manifest: if "__src_url" in tmp_manifest:
target_dirname = "%s@src-%s" % ( target_dirname = "%s@src-%s" % (
pkg_dirname, pkg_dirname, hashlib.md5(
hashlib.md5(tmp_manifest['__src_url']).hexdigest()) tmp_manifest['__src_url']).hexdigest())
pkg_dir = join(self.package_dir, target_dirname) pkg_dir = join(self.package_dir, target_dirname)
# remove previous/not-satisfied package # remove previous/not-satisfied package
@ -715,20 +735,20 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin):
return pkg_dir return pkg_dir
def uninstall(self, package, requirements=None, after_update=False): def uninstall(self, package, requirements=None, after_update=False):
if isdir(package): if isdir(package) and self.get_package_by_dir(package):
pkg_dir = package pkg_dir = package
else: else:
name, requirements, url = self.parse_pkg_uri(package, requirements) name, requirements, url = self.parse_pkg_uri(package, requirements)
pkg_dir = self.get_package_dir(name, requirements, url) pkg_dir = self.get_package_dir(name, requirements, url)
if not pkg_dir: if not pkg_dir:
raise exception.UnknownPackage("%s @ %s" % (package, raise exception.UnknownPackage(
requirements or "*")) "%s @ %s" % (package, requirements or "*"))
manifest = self.load_manifest(pkg_dir) manifest = self.load_manifest(pkg_dir)
click.echo( click.echo(
"Uninstalling %s @ %s: \t" % "Uninstalling %s @ %s: \t" % (click.style(
(click.style(manifest['name'], fg="cyan"), manifest['version']), manifest['name'], fg="cyan"), manifest['version']),
nl=False) nl=False)
if islink(pkg_dir): if islink(pkg_dir):
@ -740,9 +760,9 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin):
# unfix package with the same name # unfix package with the same name
pkg_dir = self.get_package_dir(manifest['name']) pkg_dir = self.get_package_dir(manifest['name'])
if pkg_dir and "@" in pkg_dir: if pkg_dir and "@" in pkg_dir:
shutil.move(pkg_dir, shutil.move(
join(self.package_dir, pkg_dir,
self.get_install_dirname(manifest))) join(self.package_dir, self.get_install_dirname(manifest)))
self.cache_reset() self.cache_reset()
click.echo("[%s]" % click.style("OK", fg="green")) click.echo("[%s]" % click.style("OK", fg="green"))
@ -755,14 +775,14 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin):
return True return True
def update(self, package, requirements=None, only_check=False): def update(self, package, requirements=None, only_check=False):
if isdir(package): if isdir(package) and self.get_package_by_dir(package):
pkg_dir = package pkg_dir = package
else: else:
pkg_dir = self.get_package_dir(*self.parse_pkg_uri(package)) pkg_dir = self.get_package_dir(*self.parse_pkg_uri(package))
if not pkg_dir: if not pkg_dir:
raise exception.UnknownPackage("%s @ %s" % (package, raise exception.UnknownPackage(
requirements or "*")) "%s @ %s" % (package, requirements or "*"))
manifest = self.load_manifest(pkg_dir) manifest = self.load_manifest(pkg_dir)
name = manifest['name'] name = manifest['name']

View File

@ -39,9 +39,9 @@ class PlatformManager(BasePkgManager):
"{0}://dl.platformio.org/platforms/manifest.json".format( "{0}://dl.platformio.org/platforms/manifest.json".format(
"https" if app.get_setting("enable_ssl") else "http") "https" if app.get_setting("enable_ssl") else "http")
] ]
BasePkgManager.__init__(self, package_dir BasePkgManager.__init__(
or join(util.get_home_dir(), "platforms"), self, package_dir or join(util.get_home_dir(), "platforms"),
repositories) repositories)
@property @property
def manifest_names(self): def manifest_names(self):
@ -331,8 +331,8 @@ class PlatformPackagesMixin(object):
def get_package_dir(self, name): def get_package_dir(self, name):
version = self.packages[name].get("version", "") version = self.packages[name].get("version", "")
if ":" in version: if ":" in version:
return self.pm.get_package_dir(*self.pm.parse_pkg_uri( return self.pm.get_package_dir(
"%s=%s" % (name, version))) *self.pm.parse_pkg_uri("%s=%s" % (name, version)))
return self.pm.get_package_dir(name, version) return self.pm.get_package_dir(name, version)
def get_package_version(self, name): def get_package_version(self, name):

View File

@ -36,30 +36,40 @@ from platformio import __apiurl__, __version__, exception
# pylint: disable=wrong-import-order, too-many-ancestors # pylint: disable=wrong-import-order, too-many-ancestors
try: try:
from configparser import ConfigParser import configparser as ConfigParser
except ImportError: except ImportError:
from ConfigParser import ConfigParser import ConfigParser as ConfigParser
class ProjectConfig(ConfigParser): class ProjectConfig(ConfigParser.ConfigParser):
VARTPL_RE = re.compile(r"\$\{([^\.\}]+)\.([^\}]+)\}") VARTPL_RE = re.compile(r"\$\{([^\.\}]+)\.([^\}]+)\}")
def items(self, section, **_): # pylint: disable=arguments-differ def items(self, section, **_): # pylint: disable=arguments-differ
items = [] items = []
for option in ConfigParser.options(self, section): for option in ConfigParser.ConfigParser.options(self, section):
items.append((option, self.get(section, option))) items.append((option, self.get(section, option)))
return items return items
def get(self, section, option, **kwargs): def get(self, section, option, **kwargs):
value = ConfigParser.get(self, section, option, **kwargs) try:
value = ConfigParser.ConfigParser.get(self, section, option,
**kwargs)
except ConfigParser.Error as e:
raise exception.InvalidProjectConf(str(e))
if "${" not in value or "}" not in value: if "${" not in value or "}" not in value:
return value return value
return self.VARTPL_RE.sub(self._re_sub_handler, value) return self.VARTPL_RE.sub(self._re_sub_handler, value)
def _re_sub_handler(self, match): def _re_sub_handler(self, match):
section, option = match.group(1), match.group(2) section, option = match.group(1), match.group(2)
if section == "env" and not self.has_section(section): if section in ("env", "sysenv") and not self.has_section(section):
if section == "env":
click.secho(
"Warning! Access to system environment variable via "
"`${{env.{0}}}` is deprecated. Please use "
"`${{sysenv.{0}}}` instead".format(option),
fg="yellow")
return os.getenv(option) return os.getenv(option)
return self.get(section, option) return self.get(section, option)
@ -331,7 +341,10 @@ def load_project_config(path=None):
raise exception.NotPlatformIOProject( raise exception.NotPlatformIOProject(
dirname(path) if path.endswith("platformio.ini") else path) dirname(path) if path.endswith("platformio.ini") else path)
cp = ProjectConfig() cp = ProjectConfig()
cp.read(path) try:
cp.read(path)
except ConfigParser.Error as e:
raise exception.InvalidProjectConf(str(e))
return cp return cp

View File

@ -29,8 +29,9 @@ build_flags = %s
""" % " ".join([f[0] for f in build_flags])) """ % " ".join([f[0] for f in build_flags]))
tmpdir.join("extra.py").write(""" tmpdir.join("extra.py").write("""
Import("env") Import("projenv")
env.Append(CPPDEFINES="POST_SCRIPT_MACRO")
projenv.Append(CPPDEFINES="POST_SCRIPT_MACRO")
""") """)
tmpdir.mkdir("src").join("main.cpp").write(""" tmpdir.mkdir("src").join("main.cpp").write("""