diff --git a/docs/userguide/platforms/cmd_install.rst b/docs/userguide/platforms/cmd_install.rst index c43c3123..3ada1992 100644 --- a/docs/userguide/platforms/cmd_install.rst +++ b/docs/userguide/platforms/cmd_install.rst @@ -22,7 +22,7 @@ Usage .. code-block:: bash # install platform by name - platformio platform install [OPTIONS] PLATFORM + platformio platform install [OPTIONS] [PLATFORM...] # install specific platform version using Semantic Versioning platformio platform install [OPTIONS] PLATFORM@X.Y.Z @@ -45,10 +45,16 @@ There are several predefined aliases for packages, such as: Local ~~~~~ -PlatformIO supports installing development platform from local directory. Here -is supported form: +PlatformIO supports installing development platform from local directory or +archive. Need to use ``file://`` prefix before local path. Also, platform +directory or archive should contain ``platform.json`` manifest. * ``file:///local/path/to/the/platform/dir`` +* ``file:///local/path/to/the/platform.zip`` +* ``file:///local/path/to/the/platform.tar.gz`` + +Remote +~~~~~~ VCS ~~~ @@ -135,34 +141,38 @@ Examples .. code-block:: bash $ platformio platform install atmelavr - Installing platform atmelavr @ latest: + PlatformManager: Installing atmelavr Downloading... Unpacking [####################################] 100% - Installing package tool-scons @ >=2.3.0,<2.6.0: + atmelavr @ 0.0.0 has been successfully installed! + PackageManager: Installing tool-scons @ >=2.3.0,<2.6.0 Downloading [####################################] 100% Unpacking [####################################] 100% - Installing package toolchain-atmelavr @ ~1.40801.0: + tool-scons @ 2.4.1 has been successfully installed! + PackageManager: Installing toolchain-atmelavr @ ~1.40801.0 Downloading [####################################] 100% Unpacking [####################################] 100% + toolchain-atmelavr @ 1.40801.0 has been successfully installed! The platform 'atmelavr' has been successfully installed! The rest of packages will be installed automatically depending on your build environment. - 2. Install :ref:`platform_atmelavr` with ``uploader`` utility only and skip default packages .. code-block:: bash - $ platformio platform install atmelavr --skip-default-package --with-package=uploader - Installing platform atmelavr @ latest: + PlatformManager: Installing atmelavr Downloading [####################################] 100% Unpacking [####################################] 100% - Installing package tool-micronucleus @ ~1.200.0: + atmelavr @ 0.0.0 has been successfully installed! + PackageManager: Installing tool-micronucleus @ ~1.200.0 Downloading [####################################] 100% Unpacking [####################################] 100% - Installing package tool-avrdude @ >=1.60001.0,<1.60101.0: + tool-micronucleus @ 1.200.0 has been successfully installed! + PackageManager: Installing tool-avrdude @ ~1.60001.0 Downloading [####################################] 100% Unpacking [####################################] 100% + tool-avrdude @ 1.60001.1 has been successfully installed! The platform 'atmelavr' has been successfully installed! The rest of packages will be installed automatically depending on your build environment. @@ -171,27 +181,31 @@ Examples .. code-block:: bash $ platformio platform install https://github.com/platformio/platform-atmelavr.git - Installing platform https://github.com/platformio/platform-atmelavr.git @ latest: + + PlatformManager: Installing platform-atmelavr git version 2.7.4 (Apple Git-66) - Cloning into '/Users/ikravets/.platformio/platforms/installing-XMIsAE-package'... - remote: Counting objects: 172, done. - remote: Compressing objects: 100% (51/51), done. - remote: Total 172 (delta 109), reused 168 (delta 109), pack-reused 0 - Receiving objects: 100% (172/172), 38.18 KiB | 0 bytes/s, done. - Resolving deltas: 100% (109/109), done. + Cloning into '/Volumes/MEDIA/tmp/pio3_test_projects/arduino-digihead-master/home_dir/platforms/installing-U3ucN0-package'... + remote: Counting objects: 176, done. + remote: Compressing objects: 100% (55/55), done. + remote: Total 176 (delta 114), reused 164 (delta 109), pack-reused 0 + Receiving objects: 100% (176/176), 38.86 KiB | 0 bytes/s, done. + Resolving deltas: 100% (114/114), done. Checking connectivity... done. Submodule 'examples/arduino-external-libs/lib/OneWire' (https://github.com/PaulStoffregen/OneWire.git) registered for path 'examples/arduino-external-libs/lib/OneWire' Cloning into 'examples/arduino-external-libs/lib/OneWire'... - remote: Counting objects: 87, done. - remote: Total 87 (delta 0), reused 0 (delta 0), pack-reused 87 - Unpacking objects: 100% (87/87), done. + remote: Counting objects: 91, done. + remote: Total 91 (delta 0), reused 0 (delta 0), pack-reused 91 + Unpacking objects: 100% (91/91), done. Checking connectivity... done. Submodule path 'examples/arduino-external-libs/lib/OneWire': checked out '57c18c6de80c13429275f70875c7c341f1719201' - Installing package tool-scons @ >=2.3.0,<2.6.0: + atmelavr @ 0.0.0 has been successfully installed! + PackageManager: Installing tool-scons @ >=2.3.0,<2.6.0 Downloading [####################################] 100% Unpacking [####################################] 100% - Installing package toolchain-atmelavr @ ~1.40801.0: + tool-scons @ 2.4.1 has been successfully installed! + PackageManager: Installing toolchain-atmelavr @ ~1.40801.0 Downloading [####################################] 100% Unpacking [####################################] 100% + toolchain-atmelavr @ 1.40801.0 has been successfully installed! The platform 'https://github.com/platformio/platform-atmelavr.git' has been successfully installed! The rest of packages will be installed automatically depending on your build environment. diff --git a/docs/userguide/platforms/cmd_uninstall.rst b/docs/userguide/platforms/cmd_uninstall.rst index f1375d5e..3292f147 100644 --- a/docs/userguide/platforms/cmd_uninstall.rst +++ b/docs/userguide/platforms/cmd_uninstall.rst @@ -21,7 +21,7 @@ Usage .. code-block:: bash - platformio platform uninstall PLATFORM + platformio platform uninstall [PLATFORM...] # uninstall specific platform version using Semantic Versioning platformio platform uninstall PLATFORM@X.Y.Z @@ -39,7 +39,7 @@ Examples .. code-block:: bash $ platformio platform uninstall atmelavr - Uninstalling platform atmelavr @ latest: [OK] - Uninstalling package tool-scons @ 2.5.0: [OK] + Uninstalling platform atmelavr @ 0.0.0: [OK] + Uninstalling package tool-scons @ 2.4.1: [OK] Uninstalling package toolchain-atmelavr @ 1.40801.0: [OK] The platform 'atmelavr' has been successfully uninstalled! diff --git a/docs/userguide/platforms/cmd_update.rst b/docs/userguide/platforms/cmd_update.rst index 918aca8a..c48f802b 100644 --- a/docs/userguide/platforms/cmd_update.rst +++ b/docs/userguide/platforms/cmd_update.rst @@ -21,7 +21,10 @@ Usage .. code-block:: bash - platformio platform update + platformio platform update [OPTIONS] [PLATFORM...] + + # update specific platform version using Semantic Versioning + platformio platform update PLATFORM@X.Y.Z Description @@ -35,64 +38,48 @@ Options .. program:: platformio platform update .. option:: - --only-packages + -p, --only-packages Update only platform related packages. Do not update development platform build scripts, board configs and etc. +.. option:: + -c, --only-check + +Do not update, only check for new version + Examples -------- .. code-block:: bash $ platformio platform update - Platform atmelavr @ 0.0.0 + Platform atmelavr -------- - Updating platform atmelavr @ latest: - Versions: Current=0.0.0, Latest=0.0.0 [Up-to-date] - Updating package framework-arduinoavr @ ~1.10608.0: - Versions: Current=1.10608.0, Latest=1.10608.0 [Up-to-date] - Updating package toolchain-atmelavr @ ~1.40801.0: - Versions: Current=1.40801.0, Latest=1.40801.0 [Up-to-date] - Updating package framework-simba @ ~1.500.0: - Versions: Current=1.500.0, Latest=1.500.0 [Up-to-date] - Updating package tool-scons @ >=2.3.0,<2.6.0: - Versions: Current=2.5.0, Latest=2.5.0 [Up-to-date] + Updating atmelavr @ 0.0.0: [Up-to-date] + Updating framework-arduinoavr @ 1.10608.1: [Up-to-date] + Updating tool-avrdude @ 1.60001.1: [Up-to-date] + Updating toolchain-atmelavr @ 1.40801.0: [Up-to-date] + Updating tool-scons @ 2.4.1: [Up-to-date] - Platform atmelsam @ 0.0.0 + Platform espressif -------- - Updating platform atmelsam @ latest: - Versions: Current=0.0.0, Latest=0.0.0 [Up-to-date] - Updating package toolchain-gccarmnoneeabi @ >=1.40803.0,<1.40805.0: - Versions: Current=1.40804.0, Latest=1.40804.0 [Up-to-date] - Updating package framework-arduinosam @ ~1.10607.0: - Versions: Current=1.10607.0, Latest=1.10607.0 [Up-to-date] - Updating package framework-simba @ ~1.500.0: - Versions: Current=1.500.0, Latest=1.500.0 [Up-to-date] - Updating package framework-mbed @ ~1.117.0: - Versions: Current=1.117.0, Latest=1.117.0 [Up-to-date] - Updating package tool-scons @ >=2.3.0,<2.6.0: - Versions: Current=2.5.0, Latest=2.5.0 [Up-to-date] - Updating package tool-bossac @ ~1.10500.0: - Versions: Current=1.10500.0, Latest=1.10500.0 [Up-to-date] + Updating espressif @ 0.0.0: [Up-to-date] + Updating tool-scons @ 2.4.1: [Up-to-date] + Updating toolchain-xtensa @ 1.40802.0: [Up-to-date] + Updating tool-esptool @ 1.409.0: [Up-to-date] + Updating tool-mkspiffs @ 1.102.0: [Up-to-date] + Updating framework-arduinoespressif @ 1.20300.0: [Up-to-date] + Updating sdk-esp8266 @ 1.10502.0: [Up-to-date] - Platform espressif @ 0.0.0 + Platform teensy -------- - Updating platform espressif @ latest: - Versions: Current=0.0.0, Latest=0.0.0 [Up-to-date] - Updating package tool-scons @ >=2.3.0,<2.6.0: - Versions: Current=2.5.0, Latest=2.5.0 [Up-to-date] - Updating package toolchain-xtensa @ ~1.40802.0: - Versions: Current=1.40802.0, Latest=1.40802.0 [Up-to-date] - Updating package framework-simba @ ~1.500.0: - Versions: Current=1.500.0, Latest=1.500.0 [Up-to-date] - Updating package tool-esptool @ ~1.408.0: - Versions: Current=1.408.0, Latest=1.408.0 [Up-to-date] - Updating package tool-mkspiffs @ ~1.102.0: - Versions: Current=1.102.0, Latest=1.102.0 [Up-to-date] - Updating package framework-arduinoespressif @ ~1.20200.0: - Versions: Current=1.20200.0, Latest=1.20200.0 [Up-to-date] - Updating package sdk-esp8266 @ ~1.10502.0: - Versions: Current=1.10502.0, Latest=1.10502.0 [Up-to-date] + Updating teensy @ 0.0.0: [Up-to-date] + Updating framework-arduinoteensy @ 1.128.0: [Up-to-date] + Updating tool-teensy @ 1.1.0: [Up-to-date] + Updating framework-mbed @ 1.121.0: [Up-to-date] + Updating tool-scons @ 2.4.1: [Up-to-date] + Updating toolchain-atmelavr @ 1.40801.0: [Up-to-date] + Updating toolchain-gccarmnoneeabi @ 1.40804.0: [Up-to-date] ... diff --git a/platformio/commands/platform.py b/platformio/commands/platform.py index eb81ddac..9a6d7704 100644 --- a/platformio/commands/platform.py +++ b/platformio/commands/platform.py @@ -13,7 +13,6 @@ # limitations under the License. import json -from os.path import basename import click @@ -29,13 +28,14 @@ def cli(): def _print_platforms(platforms): for platform in platforms: click.echo("{name} ~ {title}".format( - name=click.style(platform['name'], fg="cyan"), + name=click.style( + platform['name'], fg="cyan"), title=platform['title'])) click.echo("=" * (3 + len(platform['name'] + platform['title']))) click.echo(platform['description']) click.echo() - click.echo("Home: %s" % - "http://platformio.org/platforms/" + platform['name']) + click.echo("Home: %s" % "http://platformio.org/platforms/" + platform[ + 'name']) if platform['packages']: click.echo("Packages: %s" % ", ".join(platform['packages'])) if "version" in platform: @@ -71,22 +71,19 @@ def platform_search(query, json_output): @cli.command("install", short_help="Install new platforms") -@click.argument("platforms", nargs=-1, required=True) +@click.argument("platforms", nargs=-1, required=True, metavar="[PLATFORM...]") @click.option("--with-package", multiple=True) @click.option("--without-package", multiple=True) @click.option("--skip-default-package", is_flag=True) def platform_install(platforms, with_package, without_package, skip_default_package): + pm = PlatformManager() for platform in platforms: - _platform = platform - _version = None - if any([s in platform for s in ("\\", "/")]): - _platform = basename(platform) - _version = platform - elif "@" in platform: - _platform, _version = platform.rsplit("@", 1) - if PlatformManager().install(_platform, _version, with_package, - without_package, skip_default_package): + if pm.install( + name=platform, + with_packages=with_package, + without_packages=without_package, + skip_default_package=skip_default_package): click.secho( "The platform '%s' has been successfully installed!\n" "The rest of packages will be installed automatically " @@ -94,12 +91,49 @@ def platform_install(platforms, with_package, without_package, fg="green") +@cli.command("uninstall", short_help="Uninstall platforms") +@click.argument("platforms", nargs=-1, required=True, metavar="[PLATFORM...]") +def platform_uninstall(platforms): + pm = PlatformManager() + for platform in platforms: + if pm.uninstall(platform): + click.secho( + "The platform '%s' has been successfully " + "uninstalled!" % platform, + fg="green") + + +@cli.command("update", short_help="Update installed Platforms") +@click.argument("platforms", nargs=-1, required=False, metavar="[PLATFORM...]") +@click.option( + "-p", + "--only-packages", + is_flag=True, + help="Update only platform packages") +@click.option( + "-c", + "--only-check", + is_flag=True, + help="Do not update, only check for 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 platforms") @click.option("--json-output", is_flag=True) def platform_list(json_output): platforms = [] - for manifest in PlatformManager().get_installed(): - p = PlatformFactory.newPlatform(manifest['_manifest_path']) + pm = PlatformManager() + for manifest in pm.get_installed(): + p = PlatformFactory.newPlatform( + pm.get_manifest_path(manifest['__pkg_dir'])) platforms.append({ "name": p.name, "title": p.title, @@ -129,7 +163,8 @@ def platform_show(ctx, platform): raise exception.PlatformNotInstalledYet(platform) click.echo("{name} ~ {title}".format( - name=click.style(p.name, fg="cyan"), title=p.title)) + name=click.style( + p.name, fg="cyan"), title=p.title)) click.echo("=" * (3 + len(p.name + p.title))) click.echo(p.description) click.echo() @@ -152,35 +187,9 @@ def platform_show(ctx, platform): 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)")) + 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"): click.echo("%s: %s" % (key.title(), value)) - - -@cli.command("uninstall", short_help="Uninstall platforms") -@click.argument("platforms", nargs=-1, required=True) -def platform_uninstall(platforms): - for platform in platforms: - _platform = platform - _version = None - if "@" in platform: - _platform, _version = platform.rsplit("@", 1) - if PlatformManager().uninstall(_platform, _version): - click.secho("The platform '%s' has been successfully " - "uninstalled!" % platform, fg="green") - - -@cli.command("update", short_help="Update installed Platforms") -@click.option("--only-packages", is_flag=True) -def platform_update(only_packages): - pm = PlatformManager() - for manifest in pm.get_installed(): - click.echo("Platform %s @ %s" % ( - click.style(manifest['name'], fg="cyan"), manifest['version'])) - click.echo("--------") - pm.update(manifest['name'], manifest['version'], - only_packages=only_packages) - click.echo() diff --git a/platformio/commands/run.py b/platformio/commands/run.py index f29e053e..53fc96de 100644 --- a/platformio/commands/run.py +++ b/platformio/commands/run.py @@ -190,17 +190,13 @@ class EnvironmentProcessor(object): if "lib_install" in self.options: _autoinstall_libs(self.cmd_ctx, self.options['lib_install']) - platform = self.options['platform'] - version = None - if "@" in platform: - platform, version = platform.rsplit("@", 1) try: - p = PlatformFactory.newPlatform(platform, version) + p = PlatformFactory.newPlatform(self.options['platform']) except exception.UnknownPlatform: self.cmd_ctx.invoke( cmd_platform_install, platforms=[self.options['platform']]) - p = PlatformFactory.newPlatform(platform, version) + p = PlatformFactory.newPlatform(self.options['platform']) return p.run(build_vars, build_targets, self.verbose) diff --git a/platformio/commands/test.py b/platformio/commands/test.py index 55ea49df..31811afd 100644 --- a/platformio/commands/test.py +++ b/platformio/commands/test.py @@ -174,11 +174,7 @@ class TestProcessor(object): if self.options.get("upload_port", envdata.get("upload_port")): return self.options.get("upload_port", envdata.get("upload_port")) - platform = envdata['platform'] - version = None - if "@" in platform: - platform, version = platform.rsplit("@", 1) - p = PlatformFactory.newPlatform(platform, version) + p = PlatformFactory.newPlatform(envdata['platform']) bconfig = p.board_config(envdata['board']) port = None diff --git a/platformio/exception.py b/platformio/exception.py index 33a49e77..197ec0f9 100644 --- a/platformio/exception.py +++ b/platformio/exception.py @@ -72,24 +72,14 @@ class UnknownPackage(PlatformioException): class UndefinedPackageVersion(PlatformioException): - MESSAGE = "Can not find package '{0}' with version requirements '{1}'"\ - " for your system '{2}'" - - -class UndefinedPlatformVersion(PlatformioException): - - MESSAGE = "Can not find platform '{0}' with version requirements '{1}'" + MESSAGE = "Could not find a version that satisfies the requirement '{0}'"\ + " for your system '{1}'" class PackageInstallError(PlatformioException): - MESSAGE = "Can not install package '{0}' with version requirements '{1}' "\ - "for your system '{2}'" - - -class NonSystemPackage(PlatformioException): - - MESSAGE = "The package '{0}' is not available for your system '{1}'" + MESSAGE = "Can not install '{0}' with version requirements '{1}' "\ + "for your system '{2}'" class FDUnrecognizedStatusCode(PlatformioException): diff --git a/platformio/maintenance.py b/platformio/maintenance.py index 5bc31f2c..ccea3cd1 100644 --- a/platformio/maintenance.py +++ b/platformio/maintenance.py @@ -130,7 +130,7 @@ def after_upgrade(ctx): # patch development platforms pm = PlatformManager() for manifest in pm.get_installed(): - pm.update(manifest['name'], "~" + manifest['version']) + pm.update(manifest['name'], "^" + manifest['version']) click.secho("PlatformIO has been successfully upgraded to %s!\n" % __version__, fg="green") @@ -225,9 +225,9 @@ def check_internal_updates(ctx, what): if what == "platforms": pm = PlatformManager() for manifest in pm.get_installed(): - if pm.is_outdated(manifest['name'], manifest['version']): - outdated_items.append( - "%s@%s" % (manifest['name'], manifest['version'])) + if manifest['name'] not in outdated_items and \ + pm.is_outdated(manifest['name']): + outdated_items.append(manifest['name']) elif what == "libraries": lm = LibraryManager() outdated_items = lm.get_outdated() diff --git a/platformio/managers/package.py b/platformio/managers/package.py index 352c0d00..45e3e174 100644 --- a/platformio/managers/package.py +++ b/platformio/managers/package.py @@ -14,7 +14,7 @@ import json import os -from os.path import dirname, isdir, isfile, islink, join +from os.path import basename, dirname, isdir, isfile, islink, join from shutil import copyfile, copytree, rmtree from tempfile import mkdtemp @@ -75,18 +75,21 @@ class PkgRepoMixin(object): def max_satisfying_repo_version(versions, requirements=None): item = None systype = util.get_systype() - if requirements is not None: - requirements = str(requirements) + reqspec = None + if requirements: + try: + reqspec = semantic_version.Spec(requirements) + except ValueError: + pass + for v in versions: - if isinstance(v['version'], int): + if ("system" in v and v['system'] not in ("all", "*") and + systype not in v['system']): continue - if v['system'] not in ("all", "*") and systype not in v['system']: + specver = semantic_version.Version(v['version']) + if reqspec and specver not in reqspec: continue - if requirements and not semantic_version.match( - requirements, v['version']): - continue - if item is None or semantic_version.compare( - v['version'], item['version']) == 1: + if not item or semantic_version.Version(item['version']) < specver: item = v return item @@ -96,8 +99,8 @@ class PkgRepoMixin(object): pkgdata = self.max_satisfying_repo_version(versions, requirements) if not pkgdata: continue - if (not version or semantic_version.compare( - pkgdata['version'], version) == 1): + if not version or semantic_version.compare(pkgdata['version'], + version) == 1: version = pkgdata['version'] return version @@ -126,77 +129,11 @@ class PkgInstallerMixin(object): manifest_path = self.get_manifest_path(pkg_dir) if manifest_path: manifest = util.load_json(manifest_path) - manifest['_manifest_path'] = manifest_path manifest['__pkg_dir'] = pkg_dir return manifest return None - def _install_from_piorepo(self, name, requirements): - pkg_dir = None - pkgdata = None - versions = None - for versions in PackageRepoIterator(name, self.repositories): - pkgdata = self.max_satisfying_repo_version(versions, requirements) - if not pkgdata: - continue - try: - pkg_dir = self._install_from_url( - name, pkgdata['url'], requirements, pkgdata.get("sha1")) - break - except Exception as e: # pylint: disable=broad-except - click.secho("Warning! Package Mirror: %s" % e, fg="yellow") - click.secho("Looking for another mirror...", fg="yellow") - - if versions is None: - raise exception.UnknownPackage(name) - elif not pkgdata: - if "platform" in self.manifest_name: - raise exception.UndefinedPlatformVersion( - name, requirements or "latest") - else: - raise exception.UndefinedPackageVersion( - name, requirements or "latest", util.get_systype()) - return pkg_dir - - def _install_from_url(self, name, url, requirements=None, sha1=None): - pkg_dir = None - tmp_dir = mkdtemp("-package", "installing-", self.package_dir) - - # Handle GitHub URL (https://github.com/user/repo.git) - if url.endswith(".git") and not url.startswith("git"): - url = "git+" + url - - try: - if url.startswith("file://"): - url = url[7:] - if isfile(url): - self.unpack(url, tmp_dir) - else: - rmtree(tmp_dir) - copytree(url, tmp_dir) - elif url.startswith(("http://", "https://", "ftp://")): - dlpath = self.download(url, tmp_dir, sha1) - assert isfile(dlpath) - self.unpack(dlpath, tmp_dir) - os.remove(dlpath) - else: - vcs = VCSClientFactory.newClient(tmp_dir, url) - assert vcs.export() - with open(join(vcs.storage_dir, - self.VCS_MANIFEST_NAME), "w") as fp: - json.dump({ - "name": name, - "version": "0.0.0+rev%s" % vcs.get_latest_revision(), - "url": url}, fp) - - self._check_pkg_structure(tmp_dir) - pkg_dir = self._install_from_tmp_dir(tmp_dir, requirements) - finally: - if isdir(tmp_dir): - rmtree(tmp_dir) - return pkg_dir - - def _check_pkg_structure(self, pkg_dir): + def check_pkg_structure(self, pkg_dir): if self.manifest_exists(pkg_dir): return True @@ -225,28 +162,98 @@ class PkgInstallerMixin(object): "Could not find '%s' manifest file in the package" % self.manifest_name) + def _install_from_piorepo(self, name, requirements): + pkg_dir = None + pkgdata = None + versions = None + for versions in PackageRepoIterator(name, self.repositories): + pkgdata = self.max_satisfying_repo_version(versions, requirements) + if not pkgdata: + continue + try: + pkg_dir = self._install_from_url( + name, pkgdata['url'], requirements, pkgdata.get("sha1")) + break + except Exception as e: # pylint: disable=broad-except + click.secho("Warning! Package Mirror: %s" % e, fg="yellow") + click.secho("Looking for the another mirror...", fg="yellow") + + if versions is None: + raise exception.UnknownPackage(name) + elif not pkgdata: + raise exception.UndefinedPackageVersion(requirements or "latest", + util.get_systype()) + return pkg_dir + + def _install_from_url(self, name, url, requirements=None, sha1=None): + pkg_dir = None + tmp_dir = mkdtemp("-package", "installing-", self.package_dir) + + try: + if url.startswith("file://"): + url = url[7:] + if isfile(url): + self.unpack(url, tmp_dir) + else: + rmtree(tmp_dir) + copytree(url, tmp_dir) + elif url.startswith(("http://", "https://")): + dlpath = self.download(url, tmp_dir, sha1) + assert isfile(dlpath) + self.unpack(dlpath, tmp_dir) + os.remove(dlpath) + else: + vcs = VCSClientFactory.newClient(tmp_dir, url) + assert vcs.export() + with open(join(vcs.storage_dir, self.VCS_MANIFEST_NAME), + "w") as fp: + json.dump({ + "name": name, + "version": vcs.get_current_revision(), + "url": url, + "requirements": requirements + }, fp) + + self.check_pkg_structure(tmp_dir) + pkg_dir = self._install_from_tmp_dir(tmp_dir, requirements) + finally: + if isdir(tmp_dir): + rmtree(tmp_dir) + return pkg_dir + def _install_from_tmp_dir(self, tmp_dir, requirements=None): tmpmanifest = self.load_manifest(tmp_dir) assert set(["name", "version"]) <= set(tmpmanifest.keys()) name = tmpmanifest['name'] pkg_dir = join(self.package_dir, name) + if "id" in tmpmanifest: + name += "_ID%d" % tmpmanifest['id'] + pkg_dir = join(self.package_dir, name) # package should satisfy requirements if requirements: - assert semantic_version.match( - requirements, tmpmanifest['version']) + mismatch_error = ( + "Package version %s doesn't satisfy requirements %s" % ( + tmpmanifest['version'], requirements)) + try: + reqspec = semantic_version.Spec(requirements) + tmpmanver = semantic_version.Version( + tmpmanifest['version'], partial=True) + assert tmpmanver in reqspec, mismatch_error - if self.manifest_exists(pkg_dir): - manifest = self.load_manifest(pkg_dir) - cmp_result = semantic_version.compare( - tmpmanifest['version'], manifest['version']) - if cmp_result == 1: - # if main package version < new package, backup it - os.rename(pkg_dir, join( - self.package_dir, "%s@%s" % (name, manifest['version']))) - elif cmp_result == -1: - pkg_dir = join( - self.package_dir, "%s@%s" % (name, tmpmanifest['version'])) + if self.manifest_exists(pkg_dir): + curmanifest = self.load_manifest(pkg_dir) + curmanver = semantic_version.Version( + curmanifest['version'], partial=True) + # if current package version < new package, backup it + if tmpmanver > curmanver: + os.rename(pkg_dir, join(self.package_dir, "%s@%s" % ( + name, curmanifest['version']))) + elif tmpmanver < curmanver: + pkg_dir = join(self.package_dir, "%s@%s" % + (name, tmpmanifest['version'])) + except ValueError: + assert tmpmanifest['version'] == requirements, mismatch_error # remove previous/not-satisfied package if isdir(pkg_dir): @@ -291,6 +298,50 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin): def print_message(self, message, nl=True): click.echo("%s: %s" % (self.__class__.__name__, message), nl=nl) + @staticmethod + def parse_pkg_name( # pylint: disable=too-many-branches + text, requirements=None): + text = str(text) + if not requirements and "@" in text and not text.startswith("git@"): + text, requirements = text.rsplit("@", 1) + if text.isdigit(): + text = "id=" + text + + url_marker = "://" + name, url = (None, text) + if "=" in text and not text.startswith("id="): + name, url = text.split("=", 1) + + # Handle GitHub URL (https://github.com/user/package.git) + if url.startswith("https://github.com/") and \ + not url.endswith((".zip", ".tar.gz")): + url = "git+" + url + # Handle Developer Mbed URL + # (https://developer.mbed.org/users/user/code/package/) + if url.startswith("https://developer.mbed.org"): + url = "hg+" + url + + # 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 isfile(url) or isdir(url): + url = "file://" + url + elif url.count("/") == 1 and not url.startswith("git@"): + url = "git+https://github.com/" + url + if url_marker in url and not name: + _url = url.split("#", 1)[0] if "#" in url else url + if _url.endswith(("\\", "/")): + _url = _url[:-1] + name = basename(_url) + if "." in name and not name.startswith("."): + name = name.split(".", 1)[0] + + if url_marker not in url: + url = None + return (name or text, requirements, url) + def get_installed(self): if self.package_dir in BasePkgManager._INSTALLED_CACHE: return BasePkgManager._INSTALLED_CACHE[self.package_dir] @@ -304,26 +355,8 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin): BasePkgManager._INSTALLED_CACHE[self.package_dir] = items return items - def is_installed(self, name, requirements=None): - installed = self.get_installed() - reqspec = None - if requirements: - try: - reqspec = semantic_version.Spec(requirements) - except ValueError: - pass - - if not reqspec: - return any([p['name'] == name for p in installed]) - - for p in installed: - if p['name'] != name: - continue - elif reqspec.match(semantic_version.Version(p['version'])): - return True - return None - - def max_installed_version(self, name, requirements=None): + def get_installed_dir(self, name, requirements=None, url=None): + pkg_id = int(name[3:]) if name.startswith("id=") else 0 best = None reqspec = None if requirements: @@ -333,123 +366,152 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin): pass for manifest in self.get_installed(): - if manifest['name'] != name: + if pkg_id and manifest.get("id") != pkg_id: continue - elif reqspec and not reqspec.match( - semantic_version.Version(manifest['version'])): + elif not pkg_id and manifest['name'] != name: continue - elif (not best or semantic_version.compare( - manifest['version'], best['version']) == 1): - best = manifest - + elif not reqspec and requirements: + if requirements == manifest['version']: + best = manifest + break + continue + try: + 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 best.get("__pkg_dir") return None - def install(self, name, requirements, silent=False, trigger_event=True): - installed = self.is_installed(name, requirements) - if not installed or not silent: - self.print_message("Installing %s @ %s:" % ( - click.style(name, fg="cyan"), - requirements if requirements else "latest")) - if installed: - if not silent: - click.secho("Already installed", fg="yellow") - return self.max_installed_version( - name, requirements) + def install(self, name, requirements=None, quiet=False, + trigger_event=True): + name, requirements, url = self.parse_pkg_name(name, requirements) + installed_dir = self.get_installed_dir(name, requirements, url) - if (requirements and any([s in requirements for s in ("\\", "/")]) and - "://" not in requirements and ( - isfile(requirements) or isdir(requirements))): - requirements = "file://" + requirements + if not installed_dir or not quiet: + msg = "Installing " + click.style(name, fg="cyan") + if requirements: + msg += " @ " + requirements + self.print_message(msg) + if installed_dir: + if not quiet: + click.secho( + "{name} @ {version} is already installed".format( + **self.load_manifest(installed_dir)), + fg="yellow") + return installed_dir - if requirements and "://" in requirements: - pkg_dir = self._install_from_url(name, requirements) + if url: + pkg_dir = self._install_from_url(name, url, requirements) else: pkg_dir = self._install_from_piorepo(name, requirements) if not pkg_dir or not self.manifest_exists(pkg_dir): - raise exception.PackageInstallError( - name, requirements or "latest", util.get_systype()) + raise exception.PackageInstallError(name, requirements or "*", + util.get_systype()) self.reset_cache() + manifest = self.load_manifest(pkg_dir) + if trigger_event: telemetry.on_event( category=self.__class__.__name__, - action="Install", label=name) + action="Install", + label=manifest['name']) + + click.secho( + "{name} @ {version} has been successfully installed!".format( + **manifest), + fg="green") return pkg_dir def uninstall(self, name, requirements=None, trigger_event=True): - self.print_message("Uninstalling %s @ %s: \t" % ( - click.style(name, fg="cyan"), - requirements if requirements else "latest"), nl=False) - found = False - for manifest in self.get_installed(): - if manifest['name'] != name: - continue - if (requirements and not semantic_version.match( - requirements, manifest['version'])): - continue - found = True - if isdir(manifest['__pkg_dir']): - if islink(manifest['__pkg_dir']): - os.unlink(manifest['__pkg_dir']) - else: - rmtree(manifest['__pkg_dir']) + name, requirements, url = self.parse_pkg_name(name, requirements) + installed_dir = self.get_installed_dir(name, requirements, url) + if not installed_dir: + click.secho( + "%s @ %s is not installed" % (name, requirements or "*"), + fg="yellow") + return - if not found: - click.secho("Not installed", fg="yellow") - return False - else: - click.echo("[%s]" % click.style("OK", fg="green")) + manifest = self.load_manifest(installed_dir) + click.echo( + "Uninstalling %s @ %s: \t" % (click.style( + manifest['name'], fg="cyan"), manifest['version']), + nl=False) + + if isdir(installed_dir): + if islink(installed_dir): + os.unlink(installed_dir) + else: + rmtree(installed_dir) + + click.echo("[%s]" % click.style("OK", fg="green")) self.reset_cache() if trigger_event: telemetry.on_event( category=self.__class__.__name__, - action="Uninstall", label=name) + action="Uninstall", + label=manifest['name']) + return True - def update(self, name, requirements=None): - self.print_message("Updating %s @ %s:" % ( - click.style(name, fg="yellow"), - requirements if requirements else "latest")) - - latest_version = self.get_latest_repo_version(name, requirements) - if latest_version is None: + def update(self, name, requirements=None, only_check=False): + name, requirements, url = self.parse_pkg_name(name, requirements) + installed_dir = self.get_installed_dir(name, requirements, url) + if not installed_dir: click.secho( - "Ignored! '%s' is not listed in registry" % name, + "%s @ %s is not installed" % (name, requirements or "*"), fg="yellow") return - current = None - for manifest in self.get_installed(): - if manifest['name'] != name: - continue - if (requirements and not semantic_version.match( - requirements, manifest['version'])): - continue - if (not current or semantic_version.compare( - manifest['version'], current['version']) == 1): - current = manifest - - if current is None: - return - - current_version = current['version'] - click.echo("Versions: Current=%s, Latest=%s \t " % - (current_version, latest_version), nl=False) - - if current_version == latest_version: - click.echo("[%s]" % (click.style("Up-to-date", fg="green"))) - return True + manifest = self.load_manifest(installed_dir) + click.echo( + "Updating %s @ %s: \t" % (click.style( + manifest['name'], fg="cyan"), manifest['version']), + nl=False) + manifest_path = self.get_manifest_path(installed_dir) + if manifest_path.endswith(self.VCS_MANIFEST_NAME): + if only_check: + click.echo("[%s]" % (click.style("Skip", fg="yellow"))) + return + click.echo("[%s]" % (click.style("Checking", fg="yellow"))) + vcs = VCSClientFactory.newClient(installed_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() + with open(manifest_path, "w") as fp: + manifest['version'] = vcs.get_current_revision() + json.dump(manifest, fp) else: + latest_version = self.get_latest_repo_version(name, requirements) + if manifest['version'] == latest_version: + click.echo("[%s]" % (click.style("Up-to-date", fg="green"))) + return click.echo("[%s]" % (click.style("Out-of-date", fg="red"))) - - self.install(name, latest_version, trigger_event=False) + if only_check: + return + self.install(name, latest_version, trigger_event=False) telemetry.on_event( category=self.__class__.__name__, - action="Update", label=name) + action="Update", + label=manifest['name']) return True diff --git a/platformio/managers/platform.py b/platformio/managers/platform.py index db7ea51a..2a485f07 100644 --- a/platformio/managers/platform.py +++ b/platformio/managers/platform.py @@ -26,17 +26,17 @@ import semantic_version from platformio import app, exception, util from platformio.managers.package import BasePkgManager, PackageManager -PLATFORMS_DIR = join(util.get_home_dir(), "platforms") -PACKAGES_DIR = join(util.get_home_dir(), "packages") - class PlatformManager(BasePkgManager): def __init__(self, package_dir=None, repositories=None): if not repositories: - repositories = ["http://dl.platformio.org/platforms/manifest.json"] - BasePkgManager.__init__( - self, package_dir or PLATFORMS_DIR, repositories) + repositories = [ + "https://dl.platformio.org/platforms/manifest.json" + ] + BasePkgManager.__init__(self, package_dir or + join(util.get_home_dir(), "platforms"), + repositories) @property def manifest_name(self): @@ -44,27 +44,29 @@ class PlatformManager(BasePkgManager): def install(self, # pylint: disable=too-many-arguments,arguments-differ name, requirements=None, with_packages=None, - without_packages=None, skip_default_packages=False): + without_packages=None, skip_default_package=False): platform_dir = BasePkgManager.install(self, name, requirements) p = PlatformFactory.newPlatform(self.get_manifest_path(platform_dir)) - p.install_packages( - with_packages, without_packages, skip_default_packages) + p.install_packages(with_packages, without_packages, + skip_default_package) self.cleanup_packages(p.packages.keys()) return True - def uninstall(self, # pylint: disable=arguments-differ - name, requirements=None): + def uninstall( # pylint: disable=arguments-differ + self, name, requirements=None): + name, requirements, _ = self.parse_pkg_name(name, requirements) p = PlatformFactory.newPlatform(name, requirements) BasePkgManager.uninstall(self, name, requirements) self.cleanup_packages(p.packages.keys()) return True def update(self, # pylint: disable=arguments-differ - name, requirements=None, only_packages=False): + name, requirements=None, only_packages=False, only_check=False): + name, requirements, _ = self.parse_pkg_name(name, requirements) if not only_packages: - BasePkgManager.update(self, name, requirements) + BasePkgManager.update(self, name, requirements, only_check) p = PlatformFactory.newPlatform(name, requirements) - p.update_packages() + p.update_packages(only_check) self.cleanup_packages(p.packages.keys()) return True @@ -77,14 +79,14 @@ class PlatformManager(BasePkgManager): self.reset_cache() deppkgs = {} for manifest in PlatformManager().get_installed(): - p = PlatformFactory.newPlatform( - manifest['name'], manifest['version']) + p = PlatformFactory.newPlatform(manifest['name'], + manifest['version']) for pkgname, pkgmanifest in p.get_installed_packages().items(): if pkgname not in deppkgs: deppkgs[pkgname] = set() deppkgs[pkgname].add(pkgmanifest['version']) - pm = PackageManager(PACKAGES_DIR) + pm = PackageManager(join(util.get_home_dir(), "packages")) for manifest in pm.get_installed(): if manifest['name'] not in names: continue @@ -126,35 +128,36 @@ class PlatformFactory(object): def load_module(name, path): module = None try: - module = load_source( - "platformio.managers.platform.%s" % name, path) + module = load_source("platformio.managers.platform.%s" % name, + path) except ImportError: raise exception.UnknownPlatform(name) return module @classmethod def newPlatform(cls, name, requirements=None): + if not requirements and "@" in name: + name, requirements = name.rsplit("@", 1) platform_dir = None if name.endswith("platform.json") and isfile(name): platform_dir = dirname(name) name = util.load_json(name)['name'] else: - platform_dir = PlatformManager().max_installed_version( - name, requirements) + platform_dir = PlatformManager().get_installed_dir(name, + requirements) if not platform_dir: - raise exception.UnknownPlatform( - name if not requirements else "%s@%s" % (name, requirements)) + raise exception.UnknownPlatform(name if not requirements else + "%s@%s" % (name, requirements)) platform_cls = None if isfile(join(platform_dir, "platform.py")): platform_cls = getattr( cls.load_module(name, join(platform_dir, "platform.py")), - cls.get_clsname(name) - ) + cls.get_clsname(name)) else: platform_cls = type( - str(cls.get_clsname(name)), (PlatformBase,), {}) + str(cls.get_clsname(name)), (PlatformBase, ), {}) _instance = platform_cls(join(platform_dir, "platform.json")) assert isinstance(_instance, PlatformBase) @@ -187,12 +190,13 @@ class PlatformPackagesMixin(object): items[name] = manifest return items - def install_packages(self, with_packages=None, without_packages=None, - skip_default_packages=False, silent=False): - with_packages = set( - self.pkg_types_to_names(with_packages or [])) - without_packages = set( - self.pkg_types_to_names(without_packages or [])) + def install_packages(self, + with_packages=None, + without_packages=None, + skip_default_package=False, + quiet=False): + with_packages = set(self.pkg_types_to_names(with_packages or [])) + without_packages = set(self.pkg_types_to_names(without_packages or [])) upkgs = with_packages | without_packages ppkgs = set(self.packages.keys()) @@ -203,14 +207,18 @@ class PlatformPackagesMixin(object): if name in without_packages: continue elif (name in with_packages or - not (skip_default_packages or opts.get("optional", False))): - self.pm.install(name, opts.get("version"), silent=silent) + not (skip_default_package or opts.get("optional", False))): + if any([s in opts.get("version", "") for s in ("\\", "/")]): + self.pm.install( + "%s=%s" % (name, opts['version']), quiet=quiet) + else: + self.pm.install(name, opts.get("version"), quiet=quiet) return True - def update_packages(self): + def update_packages(self, only_check=False): for name in self.get_installed_packages(): - self.pm.update(name, self.packages[name]['version']) + self.pm.update(name, self.packages[name]['version'], only_check) def are_outdated_packages(self): for name, opts in self.get_installed_packages().items(): @@ -229,7 +237,7 @@ class PlatformRunMixin(object): assert isinstance(targets, list) self.configure_default_packages(variables, targets) - self.install_packages(silent=True) + self.install_packages(quiet=True) self._verbose = verbose or app.get_setting("force_verbose") @@ -261,10 +269,8 @@ class PlatformRunMixin(object): cmd = [ os.path.normpath(sys.executable), - join(self.get_package_dir("tool-scons"), "script", "scons"), - "-Q", - "-j %d" % self.get_job_nums(), - "--warn=no-no-parallel-support", + join(self.get_package_dir("tool-scons"), "script", "scons"), "-Q", + "-j %d" % self.get_job_nums(), "--warn=no-no-parallel-support", "-f", join(util.get_source_dir(), "builder", "main.py") ] if not self._verbose and "-c" not in targets: @@ -278,8 +284,7 @@ class PlatformRunMixin(object): result = util.exec_command( cmd, stdout=util.AsyncPipe(self.on_run_out), - stderr=util.AsyncPipe(self.on_run_err) - ) + stderr=util.AsyncPipe(self.on_run_err)) return result def on_run_out(self, line): @@ -315,7 +320,8 @@ class PlatformBase(PlatformPackagesMixin, PlatformRunMixin): self._manifest = util.load_json(manifest_path) self.pm = PackageManager( - PACKAGES_DIR, self._manifest.get("packageRepositories")) + join(util.get_home_dir(), "packages"), + self._manifest.get("packageRepositories")) self._verbose = False diff --git a/platformio/vcsclient.py b/platformio/vcsclient.py index f915e0e3..fffd6792 100644 --- a/platformio/vcsclient.py +++ b/platformio/vcsclient.py @@ -12,12 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -from os import listdir -from os.path import isdir, join -from platform import system +import re +from os.path import join from subprocess import check_call from sys import modules -from urlparse import urlsplit, urlunsplit +from urlparse import urlparse from platformio import util from platformio.exception import PlatformioException @@ -26,28 +25,16 @@ from platformio.exception import PlatformioException class VCSClientFactory(object): @staticmethod - def newClient(src_dir, remote_url=None, branch=None): - clsnametpl = "%sClient" - vcscls = None - type_ = None - if remote_url: - scheme, netloc, path, query, branch = urlsplit(remote_url) - type_ = scheme - if "+" in type_: - type_, scheme = type_.split("+", 1) - remote_url = urlunsplit((scheme, netloc, path, query, None)) - vcscls = getattr(modules[__name__], clsnametpl % type_.title()) - elif isdir(src_dir): - for item in listdir(src_dir): - if not isdir(join(src_dir, item)) or not item.startswith("."): - continue - try: - vcscls = getattr( - modules[__name__], clsnametpl % item[1:].title()) - except AttributeError: - pass - assert vcscls - obj = vcscls(src_dir, remote_url, branch) + def newClient(src_dir, remote_url): + result = urlparse(remote_url) + type_ = result.scheme + if "+" in result.scheme: + type_, _ = result.scheme.split("+", 1) + remote_url = remote_url[len(type_) + 1:] + if result.fragment: + remote_url = remote_url.rsplit("#", 1)[0] + obj = getattr(modules[__name__], "%sClient" % type_.title())( + src_dir, remote_url, result.fragment) assert isinstance(obj, VCSClientBase) return obj @@ -79,19 +66,29 @@ class VCSClientBase(object): def export(self): raise NotImplementedError - def get_latest_revision(self): + def update(self): + raise NotImplementedError + + @property + def can_be_updated(self): + return not self.branch + + def get_current_revision(self): raise NotImplementedError def run_cmd(self, args, **kwargs): args = [self.command] + args - kwargs['shell'] = system() == "Windows" + if "cwd" not in kwargs: + kwargs['cwd'] = self.src_dir return check_call(args, **kwargs) == 0 def get_cmd_output(self, args, **kwargs): args = [self.command] + args + if "cwd" not in kwargs: + kwargs['cwd'] = self.src_dir result = util.exec_command(args, **kwargs) if result['returncode'] == 0: - return result['out'] + return result['out'].strip() raise PlatformioException( "VCS: Could not receive an output from `%s` command (%s)" % ( args, result)) @@ -101,16 +98,42 @@ class GitClient(VCSClientBase): command = "git" + def get_branches(self): + output = self.get_cmd_output(["branch"]) + output = output.replace("*", "") # fix active branch + return [b.strip() for b in output.split("\n")] + + def get_tags(self): + output = self.get_cmd_output(["tag", "-l"]) + return [t.strip() for t in output.split("\n")] + + @staticmethod + def is_commit_id(text): + return text and re.match(r"[0-9a-f]{7,}$", text) is not None + + @property + def can_be_updated(self): + return not self.branch or not self.is_commit_id(self.branch) + def export(self): - args = ["clone", "--recursive", "--depth", "1"] - if self.branch: - args.extend(["--branch", self.branch]) - args.extend([self.remote_url, self.src_dir]) + is_commit = self.is_commit_id(self.branch) + args = ["clone", "--recursive"] + if not self.branch or not is_commit: + args += ["--depth", "1"] + if self.branch: + args += ["--branch", self.branch] + args += [self.remote_url, self.src_dir] + assert self.run_cmd(args) + if is_commit: + return self.run_cmd(["reset", "--hard", self.branch]) + return True + + def update(self): + args = ["pull"] return self.run_cmd(args) - def get_latest_revision(self): - return self.get_cmd_output(["rev-parse", "--short", "HEAD"], - cwd=self.src_dir).strip() + def get_current_revision(self): + return self.get_cmd_output(["rev-parse", "--short", "HEAD"]) class HgClient(VCSClientBase): @@ -124,9 +147,12 @@ class HgClient(VCSClientBase): args.extend([self.remote_url, self.src_dir]) return self.run_cmd(args) - def get_latest_revision(self): - return self.get_cmd_output(["identify", "--id"], - cwd=self.src_dir).strip() + def update(self): + args = ["pull", "--update"] + return self.run_cmd(args) + + def get_current_revision(self): + return self.get_cmd_output(["identify", "--id"]) class SvnClient(VCSClientBase): @@ -134,12 +160,22 @@ class SvnClient(VCSClientBase): command = "svn" def export(self): - args = ["export", "--force"] + args = ["checkout"] if self.branch: args.extend(["--revision", self.branch]) args.extend([self.remote_url, self.src_dir]) return self.run_cmd(args) - def get_latest_revision(self): - return self.get_cmd_output(["info", "-r", "HEAD"], - cwd=self.src_dir).strip() + def update(self): + + args = ["update"] + return self.run_cmd(args) + + def get_current_revision(self): + output = self.get_cmd_output(["info", "--non-interactive", + "--trust-server-cert", "-r", "HEAD"]) + for line in output.split("\n"): + line = line.strip() + if line.startswith("Revision:"): + return line.split(":", 1)[1].strip() + raise PlatformioException("Could not detect current SVN revision") diff --git a/tests/commands/test_platform.py b/tests/commands/test_platform.py index db4d735e..bd96230e 100644 --- a/tests/commands/test_platform.py +++ b/tests/commands/test_platform.py @@ -13,15 +13,15 @@ # limitations under the License. import json +import os +from os.path import join -from platformio.commands.platform import \ - platform_list as cmd_platform_list -from platformio.commands.platform import \ - platform_search as cmd_platform_search +from platformio.commands import platform as cli_platform +from platformio import exception, util def test_list_json_output(clirunner, validate_cliresult): - result = clirunner.invoke(cmd_platform_list, ["--json-output"]) + result = clirunner.invoke(cli_platform.platform_list, ["--json-output"]) validate_cliresult(result) list_result = json.loads(result.output) assert isinstance(list_result, list) @@ -31,13 +31,13 @@ def test_list_json_output(clirunner, validate_cliresult): def test_list_raw_output(clirunner, validate_cliresult): - result = clirunner.invoke(cmd_platform_list) + 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(cmd_platform_search, + result = clirunner.invoke(cli_platform.platform_search, ["arduino", "--json-output"]) validate_cliresult(result) search_result = json.loads(result.output) @@ -48,6 +48,79 @@ def test_search_json_output(clirunner, validate_cliresult): def test_search_raw_output(clirunner, validate_cliresult): - result = clirunner.invoke(cmd_platform_search, ["arduino"]) + result = clirunner.invoke(cli_platform.platform_search, ["arduino"]) validate_cliresult(result) assert "teensy" in result.output + + +def test_install_uknown_from_registry(clirunner, validate_cliresult): + result = clirunner.invoke(cli_platform.platform_install, + ["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, + ["atmelavr@99.99.99"]) + assert result.exit_code == -1 + assert isinstance(result.exception, exception.UndefinedPackageVersion) + + +def test_complex(clirunner, validate_cliresult): + items = [ + "teensy", + "https://github.com/platformio/platform-teensy/archive/develop.zip", + "https://github.com/platformio/platform-teensy.git", + "platformio/platform-teensy", + ] + for item in items: + with clirunner.isolated_filesystem(): + os.environ["PLATFORMIO_HOME_DIR"] = os.getcwd() + try: + result = clirunner.invoke(cli_platform.platform_install, + [item]) + validate_cliresult(result) + assert all([ + s in result.output + for s in ("teensy", "Downloading", "Unpacking", + "tool-scons") + ]) + + # show platform information + result = clirunner.invoke(cli_platform.platform_show, + ["teensy"]) + validate_cliresult(result) + assert "teensy" in result.output + + # list platforms + 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) == 1 + assert list_result[0]["name"] == "teensy" + assert list_result[0]["packages"] == ["tool-scons"] + + # 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 + result = clirunner.invoke(cli_platform.platform_update) + validate_cliresult(result) + assert "teensy" in result.output + assert "Up-to-date" in result.output + + # try to uninstall + result = clirunner.invoke(cli_platform.platform_uninstall, + ["teensy"]) + validate_cliresult(result) + for folder in ("platforms", "packages"): + assert len(os.listdir(join(util.get_home_dir(), + folder))) == 0 + finally: + del os.environ["PLATFORMIO_HOME_DIR"] diff --git a/tests/test_managers.py b/tests/test_managers.py new file mode 100644 index 00000000..abed10a1 --- /dev/null +++ b/tests/test_managers.py @@ -0,0 +1,66 @@ +# Copyright 2014-present Ivan Kravets +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from platformio import util +from platformio.managers.package import BasePkgManager + + +def test_pkg_name_parser(): + items = [ + ["PkgName", ("PkgName", None, 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", "1.2.5"), ("PkgName@1.2.3", "1.2.5", None)], + ["id:13", ("id:13", None, None)], + ["id:13@~1.2.3", ("id:13", "~1.2.3", None)], + [util.get_home_dir(), + (".platformio", None, "file://" + util.get_home_dir())], + ["LocalName=" + util.get_home_dir(), + ("LocalName", None, "file://" + util.get_home_dir())], + ["https://github.com/user/package.git", + ("package", None, "git+https://github.com/user/package.git")], + ["https://github.com/user/package/archive/branch.zip", + ("branch", None, + "https://github.com/user/package/archive/branch.zip")], + ["https://github.com/user/package/archive/branch.tar.gz", + ("branch", None, + "https://github.com/user/package/archive/branch.tar.gz")], + ["https://developer.mbed.org/users/user/code/package/", + ("package", None, + "hg+https://developer.mbed.org/users/user/code/package/")], + ["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", + ("package", None, "git+https://github.com/user/package.git#branch")], + ["PkgName=https://github.com/user/package.git#a13d344fg56", + ("PkgName", None, + "git+https://github.com/user/package.git#a13d344fg56")], + ["PkgName=user/package", + ("PkgName", None, "git+https://github.com/user/package")], + ["PkgName=user/package#master", + ("PkgName", None, "git+https://github.com/user/package#master")], + ["git+https://github.com/user/package", + ("package", None, "git+https://github.com/user/package")], + ["hg+https://example.com/user/package", + ("package", None, "hg+https://example.com/user/package")], + ["git@github.com:user/package.git", + ("package", None, "git@github.com:user/package.git")], + ["git@github.com:user/package.git#v1.2.0", + ("package", None, "git@github.com:user/package.git#v1.2.0")] + ] + for params, result in items: + if isinstance(params, tuple): + assert BasePkgManager.parse_pkg_name(*params) == result + else: + assert BasePkgManager.parse_pkg_name(params) == result