diff --git a/HISTORY.rst b/HISTORY.rst index 4bcb68f6..ffa4b489 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -4,6 +4,30 @@ Release Notes PlatformIO 3.0 -------------- +3.5.4 (2018-07-03) +~~~~~~~~~~~~~~~~~~ + +* Improved removing of default build flags using `build_unflags `__ option + (`issue #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 `_) +* Handle "architectures" data from "library.properties" manifest in + `lib_compat_mode = strict `__ +* Added workaround for Python SemVer package's `issue #61 `_ with caret range and pre-releases +* Replaced conflicted "env" pattern by "sysenv" for `"platformio.ini" Dynamic Variables" `__ + (`issue #1705 `_) +* Removed "date&time" when processing project with `platformio run `__ command + (`issue #1343 `_) +* Fixed issue with invalid LD script if path contains space +* Fixed preprocessor for Arduino sketch when function returns certain type + (`issue #1683 `_) +* Fixed issue when `platformio lib uninstall `__ + removes initial source code + (`issue #1023 `_) + 3.5.3 (2018-06-01) ~~~~~~~~~~~~~~~~~~ @@ -19,7 +43,7 @@ PlatformIO 3.0 (`issue #1612 `_) * Configure a custom path to SVD file using `debug_svd_path `__ option -* Custom project `description `_ +* Custom project `description `_ which will be used by `PlatformIO Home `_ * Updated Unity tool to 2.4.3 * Improved support for Black Magic Probe in "uploader" mode @@ -45,9 +69,9 @@ PlatformIO 3.0 - Multiple themes (Dark & Light) - Ability to specify a name for new project -* Control `PIO Unified Debugger `__ +* Control `PIO Unified Debugger `__ and its firmware loading mode using - `debug_load_mode `__ option + `debug_load_mode `__ option * Added aliases (off, light, strict) for `LDF Compatibility Mode `__ * Search for a library using PIO Library Registry ID ``id:X`` (e.g. ``pio lib search id:13``) diff --git a/docs b/docs index 3ad76be8..0b8ac5fb 160000 --- a/docs +++ b/docs @@ -1 +1 @@ -Subproject commit 3ad76be8f73ab1b3766bafa7ffca4284051aca4c +Subproject commit 0b8ac5fbf71416aaa9420f0cf10027efb0d3fe9e diff --git a/platformio/__init__.py b/platformio/__init__.py index 6de67c99..7daf5a85 100644 --- a/platformio/__init__.py +++ b/platformio/__init__.py @@ -14,7 +14,7 @@ import sys -VERSION = (3, 5, 3) +VERSION = (3, 5, 4) __version__ = ".".join([str(s) for s in VERSION]) __title__ = "platformio" diff --git a/platformio/builder/tools/piolib.py b/platformio/builder/tools/piolib.py index 9048ea1c..787b3bdf 100644 --- a/platformio/builder/tools/piolib.py +++ b/platformio/builder/tools/piolib.py @@ -279,9 +279,8 @@ class LibBuilderBase(object): if (key in item and not util.items_in_list(self.env[env_key], item[key])): if self.verbose: - sys.stderr.write( - "Skip %s incompatible dependency %s\n" % (key[:-1], - item)) + sys.stderr.write("Skip %s incompatible dependency %s\n" + % (key[:-1], item)) skip = True if skip: continue @@ -394,9 +393,9 @@ class LibBuilderBase(object): if self != lb: if _already_depends(lb): if self.verbose: - sys.stderr.write("Warning! Circular dependencies detected " - "between `%s` and `%s`\n" % (self.path, - lb.path)) + sys.stderr.write( + "Warning! Circular dependencies detected " + "between `%s` and `%s`\n" % (self.path, lb.path)) self._circular_deps.append(lb) elif lb not in self._depbuilders: self._depbuilders.append(lb) @@ -502,6 +501,28 @@ class ArduinoLibBuilder(LibBuilderBase): def is_frameworks_compatible(self, frameworks): 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): @@ -803,7 +824,7 @@ def GetLibBuilders(env): # pylint: disable=too-many-branches return items -def BuildProjectLibraries(env): +def ConfigureProjectLibBuilder(env): def correct_found_libs(lib_builders): # build full dependency graph @@ -822,7 +843,7 @@ def BuildProjectLibraries(env): title = "<%s>" % lb.name vcs_info = lb.vcs_info if lb.version: - title += " v%s" % lb.version + title += " %s" % lb.version if vcs_info and vcs_info.get("version"): title += " #%s" % vcs_info.get("version") sys.stdout.write("%s|-- %s" % (margin, title)) @@ -837,7 +858,6 @@ def BuildProjectLibraries(env): print_deps_tree(lb, level + 1) project = ProjectAsLibBuilder(env, "$PROJECT_DIR") - project.env = env ldf_mode = LibBuilderBase.lib_ldf_mode.fget(project) print "Library Dependency Finder -> http://bit.ly/configure-pio-ldf" @@ -859,7 +879,7 @@ def BuildProjectLibraries(env): else: print "No dependencies" - return project.build() + return project def exists(_): @@ -868,5 +888,5 @@ def exists(_): def generate(env): env.AddMethod(GetLibBuilders) - env.AddMethod(BuildProjectLibraries) + env.AddMethod(ConfigureProjectLibBuilder) return env diff --git a/platformio/builder/tools/piomisc.py b/platformio/builder/tools/piomisc.py index b01ea1a2..c15812c3 100644 --- a/platformio/builder/tools/piomisc.py +++ b/platformio/builder/tools/piomisc.py @@ -30,9 +30,10 @@ from platformio.managers.core import get_core_package_dir 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_,\.\*\&\[\]\s\d]*\) # arguments )\s*\{ # must end with { @@ -89,8 +90,8 @@ class InoToCPPConverter(object): self.env.Execute( self.env.VerboseAction( '$CXX -o "{0}" -x c++ -fpreprocessed -dD -E "{1}"'.format( - out_file, - tmp_path), "Converting " + basename(out_file[:-4]))) + out_file, tmp_path), + "Converting " + basename(out_file[:-4]))) atexit.register(_delete_file, tmp_path) return isfile(out_file) @@ -163,18 +164,17 @@ class InoToCPPConverter(object): prototype_names = set([m.group(3).strip() for m in prototypes]) split_pos = prototypes[0].start() - match_ptrs = re.search(self.PROTOPTRS_TPLRE % - ("|".join(prototype_names)), - contents[:split_pos], re.M) + match_ptrs = re.search( + self.PROTOPTRS_TPLRE % ("|".join(prototype_names)), + contents[:split_pos], re.M) if match_ptrs: split_pos = contents.rfind("\n", 0, match_ptrs.start()) + 1 result = [] result.append(contents[:split_pos].strip()) result.append("%s;" % ";\n".join([m.group(1) for m in prototypes])) - result.append('#line %d "%s"' % - (self._get_total_lines(contents[:split_pos]), - self._main_ino.replace("\\", "/"))) + result.append('#line %d "%s"' % (self._get_total_lines( + contents[:split_pos]), self._main_ino.replace("\\", "/"))) result.append(contents[split_pos:].strip()) return "\n".join(result) @@ -231,14 +231,25 @@ def GetActualLDScript(env): return None script = None + script_in_next = False for f in env.get("LINKFLAGS", []): - if f.startswith("-Wl,-T"): - script = env.subst(f[6:].replace('"', "").strip()) - if isfile(script): - return script - path = _lookup_in_ldpath(script) - if path: - return path + raw_script = None + if f == "-T": + script_in_next = True + continue + elif script_in_next: + script_in_next = False + 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: sys.stderr.write( @@ -295,9 +306,6 @@ def ProcessTest(env): src_filter.append("+<%s%s>" % (env['PIOTEST'], sep)) env.Replace(PIOTEST_SRC_FILTER=src_filter) - return env.CollectBuildFiles("$BUILDTEST_DIR", "$PROJECTTEST_DIR", - "$PIOTEST_SRC_FILTER") - def GetExtraScripts(env, scope): items = [] diff --git a/platformio/builder/tools/pioupload.py b/platformio/builder/tools/pioupload.py index 1ba523e4..7c6c2df0 100644 --- a/platformio/builder/tools/pioupload.py +++ b/platformio/builder/tools/pioupload.py @@ -209,9 +209,9 @@ def CheckUploadSize(_, target, source, env): # pylint: disable=W0613,W0621 used_size = int(values[0]) + int(values[1]) if used_size > max_size: - sys.stderr.write("Error: The program size (%d bytes) is greater " - "than maximum allowed (%s bytes)\n" % (used_size, - max_size)) + sys.stderr.write( + "Error: The program size (%d bytes) is greater " + "than maximum allowed (%s bytes)\n" % (used_size, max_size)) env.Exit(1) diff --git a/platformio/builder/tools/platformio.py b/platformio/builder/tools/platformio.py index 9c6f6d0d..1c72bf3d 100644 --- a/platformio/builder/tools/platformio.py +++ b/platformio/builder/tools/platformio.py @@ -22,7 +22,7 @@ from os.path import basename, dirname, isdir, join, realpath from SCons import Builder, Util from SCons.Script import (COMMAND_LINE_TARGETS, AlwaysBuild, - DefaultEnvironment, SConscript) + DefaultEnvironment, Export, SConscript) from platformio.util import glob_escape, pioversion_to_intstr @@ -41,6 +41,43 @@ def scons_patched_match_splitext(path, suffixes=None): 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 _append_pio_macros(): @@ -62,6 +99,7 @@ def BuildProgram(env): # process extra flags from board if "BOARD" in env and "build.extra_flags" in env.BoardConfig(): env.ProcessFlags(env.BoardConfig().get("build.extra_flags")) + # apply user flags env.ProcessFlags(env.get("BUILD_FLAGS")) @@ -74,37 +112,19 @@ def BuildProgram(env): # remove specified flags env.ProcessUnFlags(env.get("BUILD_UNFLAGS")) - # build dependent libs; place them before built-in libs - env.Prepend(LIBS=env.BuildProjectLibraries()) + # build project with dependencies + _build_project_deps(env) - # append specified LD_SCRIPT - if ("LDSCRIPT_PATH" in env + # append into the beginning a main LD script + if (env.get("LDSCRIPT_PATH") 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 if env.get("LIBS") and env.GetCompilerType() == "gcc": env.Prepend(_LIBFLAGS="-Wl,--start-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( 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): if not flags: 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 current in env.get(key, []): conditions = [ @@ -275,7 +306,7 @@ def BuildFrameworks(env, frameworks): env.ConvertInoToCpp() if f in board_frameworks: - SConscript(env.GetFrameworkScript(f)) + SConscript(env.GetFrameworkScript(f), exports="env") else: sys.stderr.write( "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): + nodes = env.CollectBuildFiles(variant_dir, src_dir, src_filter) DefaultEnvironment().Append( - PIOBUILDFILES=env.CollectBuildFiles(variant_dir, src_dir, src_filter)) + PIOBUILDFILES=[env.Object(node) for node in nodes]) def exists(_): diff --git a/platformio/commands/lib.py b/platformio/commands/lib.py index 81400684..826cd344 100644 --- a/platformio/commands/lib.py +++ b/platformio/commands/lib.py @@ -439,8 +439,8 @@ def lib_stats(json_output): printitem_tpl.format( name=click.style(name, fg="cyan"), url=click.style( - "https://platformio.org/lib/search?query=" + - quote("keyword:%s" % name), + "https://platformio.org/lib/search?query=" + quote( + "keyword:%s" % name), fg="blue"))) for key in ("updated", "added"): diff --git a/platformio/commands/platform.py b/platformio/commands/platform.py index 8ec1c259..394803a1 100644 --- a/platformio/commands/platform.py +++ b/platformio/commands/platform.py @@ -273,8 +273,8 @@ def platform_show(platform, json_output): # pylint: disable=too-many-branches 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)")) + 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: diff --git a/platformio/commands/run.py b/platformio/commands/run.py index f2d4636c..c61dbc06 100644 --- a/platformio/commands/run.py +++ b/platformio/commands/run.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from datetime import datetime from hashlib import sha1 from os import getcwd, makedirs, walk 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)))) if not self.silent: - click.echo("[%s] Processing %s (%s)" % - (datetime.now().strftime("%c"), - click.style(self.name, fg="cyan", bold=True), - "; ".join(env_dump))) + click.echo("Processing %s (%s)" % (click.style( + self.name, fg="cyan", bold=True), "; ".join(env_dump))) click.secho("-" * terminal_width, bold=True) self.options = self._validate_options(self.options) @@ -296,10 +293,10 @@ class EnvironmentProcessor(object): if d.strip() ], self.verbose) if "lib_deps" in self.options: - _autoinstall_libdeps(self.cmd_ctx, - util.parse_conf_multi_values( - self.options['lib_deps']), - self.verbose) + _autoinstall_libdeps( + self.cmd_ctx, + util.parse_conf_multi_values(self.options['lib_deps']), + self.verbose) try: p = PlatformFactory.newPlatform(self.options['platform']) @@ -385,10 +382,10 @@ def print_summary(results, start_time): err=status is False) print_header( - "[%s] Took %.2f seconds" % - ((click.style("SUCCESS", fg="green", bold=True) - if successed else click.style("ERROR", fg="red", bold=True)), - time() - start_time), + "[%s] Took %.2f seconds" % ( + (click.style("SUCCESS", fg="green", bold=True) + if successed else click.style("ERROR", fg="red", bold=True)), + time() - start_time), is_error=not successed) diff --git a/platformio/commands/settings.py b/platformio/commands/settings.py index f163ff6d..a29d3997 100644 --- a/platformio/commands/settings.py +++ b/platformio/commands/settings.py @@ -32,8 +32,8 @@ def settings_get(name): click.echo( list_tpl.format( name=click.style("Name", fg="cyan"), - value=(click.style("Value", fg="green") + - click.style(" [Default]", fg="yellow")), + value=(click.style("Value", fg="green") + click.style( + " [Default]", fg="yellow")), description="Description")) click.echo("-" * terminal_width) diff --git a/platformio/downloader.py b/platformio/downloader.py index 34f597b2..62c19385 100644 --- a/platformio/downloader.py +++ b/platformio/downloader.py @@ -43,9 +43,8 @@ class FileDownloader(object): disposition = self._request.headers.get("content-disposition") if disposition and "filename=" in disposition: - self._fname = disposition[ - disposition.index("filename=") + 9:].replace('"', "").replace( - "'", "") + self._fname = disposition[disposition.index("filename=") + + 9:].replace('"', "").replace("'", "") self._fname = self._fname.encode("utf8") else: self._fname = [p for p in url.split("/") if p][-1] diff --git a/platformio/exception.py b/platformio/exception.py index 8b3df0f2..01c1be7f 100644 --- a/platformio/exception.py +++ b/platformio/exception.py @@ -192,6 +192,11 @@ class InvalidLibConfURL(PlatformioException): MESSAGE = "Invalid library config URL '{0}'" +class InvalidProjectConf(PlatformioException): + + MESSAGE = "Invalid `platformio.ini`, project configuration file: '{0}'" + + class BuildScriptNotFound(PlatformioException): MESSAGE = "Invalid path '{0}' to build script" @@ -248,7 +253,7 @@ class CygwinEnvDetected(PlatformioException): class DebugSupportError(PlatformioException): 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 >") diff --git a/platformio/ide/tpls/vscode/.vscode/c_cpp_properties.json.tpl b/platformio/ide/tpls/vscode/.vscode/c_cpp_properties.json.tpl index d481de89..b98a0a3f 100644 --- a/platformio/ide/tpls/vscode/.vscode/c_cpp_properties.json.tpl +++ b/platformio/ide/tpls/vscode/.vscode/c_cpp_properties.json.tpl @@ -18,6 +18,7 @@ "name": "Win32", % elif systype == "darwin": "name": "Mac", + "macFrameworkPath": [], % else: "name": "Linux", % end diff --git a/platformio/maintenance.py b/platformio/maintenance.py index 35f0e173..7b1a5f58 100644 --- a/platformio/maintenance.py +++ b/platformio/maintenance.py @@ -208,8 +208,8 @@ def after_upgrade(ctx): # PlatformIO banner click.echo("*" * terminal_width) - click.echo("If you like %s, please:" % - (click.style("PlatformIO", fg="cyan"))) + click.echo( + "If you like %s, please:" % (click.style("PlatformIO", fg="cyan"))) click.echo("- %s us on Twitter to stay up-to-date " "on the latest project news > %s" % (click.style("follow", fg="cyan"), @@ -224,9 +224,9 @@ def after_upgrade(ctx): (click.style("try", fg="cyan"), click.style("https://platformio.org/platformio-ide", fg="cyan"))) if not util.is_ci(): - click.echo("- %s us with PlatformIO Plus > %s" % - (click.style("support", fg="cyan"), - click.style("https://pioplus.com", fg="cyan"))) + click.echo("- %s us with PlatformIO Plus > %s" % (click.style( + "support", fg="cyan"), click.style( + "https://pioplus.com", fg="cyan"))) click.echo("*" * terminal_width) click.echo("") @@ -296,8 +296,8 @@ def check_internal_updates(ctx, what): if manifest['name'] in outdated_items: continue conds = [ - pm.outdated(manifest['__pkg_dir']), - what == "platforms" and PlatformFactory.newPlatform( + pm.outdated(manifest['__pkg_dir']), what == "platforms" + and PlatformFactory.newPlatform( manifest['__pkg_dir']).are_outdated_packages() ] if any(conds): @@ -318,8 +318,8 @@ def check_internal_updates(ctx, what): if not app.get_setting("auto_update_" + what): click.secho("Please update them via ", fg="yellow", nl=False) click.secho( - "`platformio %s update`" % - ("lib --global" if what == "libraries" else "platform"), + "`platformio %s update`" % ("lib --global" if what == "libraries" + else "platform"), fg="cyan", nl=False) click.secho(" command.\n", fg="yellow") diff --git a/platformio/managers/core.py b/platformio/managers/core.py index 3aa37fe7..9bead905 100644 --- a/platformio/managers/core.py +++ b/platformio/managers/core.py @@ -107,6 +107,7 @@ def pioplus_call(args, **kwargs): pythonexe_path = util.get_pythonexe_path() os.environ['PYTHONEXEPATH'] = pythonexe_path os.environ['PYTHONPYSITEDIR'] = get_core_package_dir("contrib-pysite") + os.environ['PIOCOREPYSITEDIR'] = dirname(util.get_source_dir() or "") os.environ['PATH'] = (os.pathsep).join( [dirname(pythonexe_path), os.environ['PATH']]) util.copy_pythonpath_to_osenv() diff --git a/platformio/managers/lib.py b/platformio/managers/lib.py index 5613b668..ba81870a 100644 --- a/platformio/managers/lib.py +++ b/platformio/managers/lib.py @@ -208,9 +208,9 @@ class LibraryManager(BasePkgManager): cache_valid="30d") assert dl_data - return self._install_from_url(name, dl_data['url'].replace( - "http://", "https://") if app.get_setting("enable_ssl") else - dl_data['url'], requirements) + return self._install_from_url( + name, dl_data['url'].replace("http://", "https://") + if app.get_setting("enable_ssl") else dl_data['url'], requirements) def search_lib_id( # pylint: disable=too-many-branches self, @@ -237,9 +237,9 @@ class LibraryManager(BasePkgManager): if not isinstance(values, list): values = [v.strip() for v in values.split(",") if v] for value in values: - query.append('%s:"%s"' % (key[:-1] - if key.endswith("s") else key, - value)) + query.append( + '%s:"%s"' % (key[:-1] + if key.endswith("s") else key, value)) lib_info = None result = util.get_api_result( @@ -290,9 +290,9 @@ class LibraryManager(BasePkgManager): def _get_lib_id_from_installed(self, filters): if filters['name'].startswith("id="): return int(filters['name'][3:]) - package_dir = self.get_package_dir(filters['name'], - filters.get("requirements", - filters.get("version"))) + package_dir = self.get_package_dir( + filters['name'], filters.get("requirements", + filters.get("version"))) if not package_dir: return None manifest = self.load_manifest(package_dir) diff --git a/platformio/managers/package.py b/platformio/managers/package.py index 3996bc06..3e5bd4a0 100644 --- a/platformio/managers/package.py +++ b/platformio/managers/package.py @@ -18,7 +18,7 @@ import json import os import re 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 import click @@ -90,7 +90,8 @@ class PkgRepoMixin(object): reqspec = None if requirements: try: - reqspec = semantic_version.Spec(requirements) + reqspec = self.parse_semver_spec( + requirements, raise_exception=True) except ValueError: pass @@ -98,8 +99,8 @@ class PkgRepoMixin(object): if not self.is_system_compatible(v.get("system")): continue if "platformio" in v.get("engines", {}): - if PkgRepoMixin.PIO_VERSION not in semantic_version.Spec( - v['engines']['platformio']): + if PkgRepoMixin.PIO_VERSION not in self.parse_semver_spec( + v['engines']['platformio'], raise_exception=True): continue specver = semantic_version.Version(v['version']) if reqspec and specver not in reqspec: @@ -224,7 +225,20 @@ class PkgInstallerMixin(object): @staticmethod def parse_semver_spec(value, raise_exception=False): 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: if raise_exception: raise e @@ -367,6 +381,12 @@ class PkgInstallerMixin(object): return manifest.get("__pkg_dir") if manifest and isdir( 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): if self.manifest_exists(src_dir): return src_dir @@ -474,8 +494,8 @@ class PkgInstallerMixin(object): "Package version %s doesn't satisfy requirements %s" % (tmp_manifest['version'], requirements)) try: - assert tmp_semver and tmp_semver in semantic_version.Spec( - requirements), mismatch_error + assert tmp_semver and tmp_semver in self.parse_semver_spec( + requirements, raise_exception=True), mismatch_error except (AssertionError, ValueError): assert tmp_manifest['version'] == requirements, mismatch_error @@ -500,8 +520,8 @@ class PkgInstallerMixin(object): cur_manifest['version']) if "__src_url" in cur_manifest: target_dirname = "%s@src-%s" % ( - pkg_dirname, - hashlib.md5(cur_manifest['__src_url']).hexdigest()) + pkg_dirname, hashlib.md5( + cur_manifest['__src_url']).hexdigest()) shutil.move(pkg_dir, join(self.package_dir, target_dirname)) # fix to a version elif action == 2: @@ -509,8 +529,8 @@ class PkgInstallerMixin(object): tmp_manifest['version']) if "__src_url" in tmp_manifest: target_dirname = "%s@src-%s" % ( - pkg_dirname, - hashlib.md5(tmp_manifest['__src_url']).hexdigest()) + pkg_dirname, hashlib.md5( + tmp_manifest['__src_url']).hexdigest()) pkg_dir = join(self.package_dir, target_dirname) # remove previous/not-satisfied package @@ -715,20 +735,20 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin): return pkg_dir 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 else: name, requirements, url = self.parse_pkg_uri(package, requirements) pkg_dir = self.get_package_dir(name, requirements, url) if not pkg_dir: - raise exception.UnknownPackage("%s @ %s" % (package, - requirements or "*")) + raise exception.UnknownPackage( + "%s @ %s" % (package, requirements or "*")) manifest = self.load_manifest(pkg_dir) click.echo( - "Uninstalling %s @ %s: \t" % - (click.style(manifest['name'], fg="cyan"), manifest['version']), + "Uninstalling %s @ %s: \t" % (click.style( + manifest['name'], fg="cyan"), manifest['version']), nl=False) if islink(pkg_dir): @@ -740,9 +760,9 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin): # unfix package with the same name pkg_dir = self.get_package_dir(manifest['name']) if pkg_dir and "@" in pkg_dir: - shutil.move(pkg_dir, - join(self.package_dir, - self.get_install_dirname(manifest))) + shutil.move( + pkg_dir, + join(self.package_dir, self.get_install_dirname(manifest))) self.cache_reset() click.echo("[%s]" % click.style("OK", fg="green")) @@ -755,14 +775,14 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin): return True 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 else: pkg_dir = self.get_package_dir(*self.parse_pkg_uri(package)) if not pkg_dir: - raise exception.UnknownPackage("%s @ %s" % (package, - requirements or "*")) + raise exception.UnknownPackage( + "%s @ %s" % (package, requirements or "*")) manifest = self.load_manifest(pkg_dir) name = manifest['name'] diff --git a/platformio/managers/platform.py b/platformio/managers/platform.py index 03010fdc..5a947c4c 100644 --- a/platformio/managers/platform.py +++ b/platformio/managers/platform.py @@ -39,9 +39,9 @@ class PlatformManager(BasePkgManager): "{0}://dl.platformio.org/platforms/manifest.json".format( "https" if app.get_setting("enable_ssl") else "http") ] - BasePkgManager.__init__(self, package_dir - or join(util.get_home_dir(), "platforms"), - repositories) + BasePkgManager.__init__( + self, package_dir or join(util.get_home_dir(), "platforms"), + repositories) @property def manifest_names(self): @@ -331,8 +331,8 @@ class PlatformPackagesMixin(object): def get_package_dir(self, name): version = self.packages[name].get("version", "") if ":" in version: - return self.pm.get_package_dir(*self.pm.parse_pkg_uri( - "%s=%s" % (name, version))) + return self.pm.get_package_dir( + *self.pm.parse_pkg_uri("%s=%s" % (name, version))) return self.pm.get_package_dir(name, version) def get_package_version(self, name): diff --git a/platformio/util.py b/platformio/util.py index e37660e2..eab6512f 100644 --- a/platformio/util.py +++ b/platformio/util.py @@ -36,30 +36,40 @@ from platformio import __apiurl__, __version__, exception # pylint: disable=wrong-import-order, too-many-ancestors try: - from configparser import ConfigParser + import configparser as ConfigParser except ImportError: - from ConfigParser import ConfigParser + import ConfigParser as ConfigParser -class ProjectConfig(ConfigParser): +class ProjectConfig(ConfigParser.ConfigParser): VARTPL_RE = re.compile(r"\$\{([^\.\}]+)\.([^\}]+)\}") def items(self, section, **_): # pylint: disable=arguments-differ items = [] - for option in ConfigParser.options(self, section): + for option in ConfigParser.ConfigParser.options(self, section): items.append((option, self.get(section, option))) return items 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: return value return self.VARTPL_RE.sub(self._re_sub_handler, value) def _re_sub_handler(self, match): 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 self.get(section, option) @@ -331,7 +341,10 @@ def load_project_config(path=None): raise exception.NotPlatformIOProject( dirname(path) if path.endswith("platformio.ini") else path) cp = ProjectConfig() - cp.read(path) + try: + cp.read(path) + except ConfigParser.Error as e: + raise exception.InvalidProjectConf(str(e)) return cp diff --git a/tests/test_builder.py b/tests/test_builder.py index cafd1cee..4048c065 100644 --- a/tests/test_builder.py +++ b/tests/test_builder.py @@ -29,8 +29,9 @@ build_flags = %s """ % " ".join([f[0] for f in build_flags])) tmpdir.join("extra.py").write(""" -Import("env") -env.Append(CPPDEFINES="POST_SCRIPT_MACRO") +Import("projenv") + +projenv.Append(CPPDEFINES="POST_SCRIPT_MACRO") """) tmpdir.mkdir("src").join("main.cpp").write("""