diff --git a/docs/projectconf.rst b/docs/projectconf.rst index 3ddf6ea5..13c1b3ca 100644 --- a/docs/projectconf.rst +++ b/docs/projectconf.rst @@ -196,6 +196,31 @@ General options :ref:`platforms` name. +PlatformIO allows to use specific platform versions using +`Semantic Versioning `_ (X.Y.Z=MAJOR.MINOR.PATCH). +Version specifications can take any of the following forms: + +* ``0.1.2``: an exact version number. Use only this exact version +* ``^0.1.2``: any compatible version (exact version for ``0.x.x`` versions +* ``~0.1.2``: any version with the same major and minor versions, and an + equal or greater patch version +* ``>0.1.2``: any version greater than ``0.1.2``. ``>=``, ``<``, and ``<=`` + are also possible +* ``>0.1.0,!=0.2.0,<0.3.0``: any version greater than ``0.1.0``, not equal to + ``0.2.0`` and less than ``0.3.0`` + +Examples: + +.. code-block:: ini + + [env:the_latest_version] + platform = atmelavr + + [env:specific_major_version] + platform = atmelavr@^0.1.2 + + [env:specific_major_and_minor_version] + platform = atmelavr@~0.1.2 .. _projectconf_env_framework: diff --git a/docs/userguide/platforms/cmd_install.rst b/docs/userguide/platforms/cmd_install.rst index 86b678af..64298e01 100644 --- a/docs/userguide/platforms/cmd_install.rst +++ b/docs/userguide/platforms/cmd_install.rst @@ -22,22 +22,90 @@ Usage .. code-block:: bash # install platform by name - platformio platform install [OPTIONS] [PLATFORMS] + platformio platform install [OPTIONS] PLATFORM - # install platform from local directory - platformio platform install [OPTIONS] [file:///local/path/to/platform/dir] + # install specific platform version using Semantic Versioning + platformio platform install [OPTIONS] PLATFORM@X.Y.Z + + # install platform using URL + platformio platform install [OPTIONS] URL Description ----------- -Install development :ref:`platforms` and dependent packages. +Install :ref:`platforms` and dependent packages. There are several predefined aliases for packages, such as: +* ``framework`` * ``toolchain`` * ``uploader`` +Local +~~~~~ + +PlatformIO supports installing development platform from local directory. Here +is supported form: + +* file:///local/path/to/the/platform/dir + +VCS +~~~ + +PlatformIO supports installing from Git, Mercurial and Subversion, and detects +the type of VCS using url prefixes: "git+", "hg+", or "svn+". + +PlatformIO requires a working VCS command on your path: git, hg or svn. + +Git +^^^ + +The supported schemes are: ``git``, ``git+https`` and ``git+ssh``. Here are +the supported forms: + +* https://github.com/platformio/platform-NAME.git +* git+git://git.server.org/my-platform +* git+https://git.server.org/my-platform +* git+ssh://git.server.org/my-platform + +Passing branch names, a commit hash or a tag name is possible like so: + +* https://github.com/platformio/platform-name.git#master +* git+git://git.server.org/my-platform#master +* git+https://git.server.org/my-platform#v1.0 +* git+ssh://git.server.org/my-platform#7846d8ad52f983f2f2887bdc0f073fe9755a806d + +Mercurial +^^^^^^^^^ + +The supported schemes are: ``hg+http``, ``hg+https`` and ``hg+ssh``. Here are +the supported forms: + +* hg+hg://hg.server.org/my-platform +* hg+https://hg.server.org/my-platform +* hg+ssh://hg.server.org/my-platform + +Passing branch names, a commit hash or a tag name is possible like so: + +* hg+hg://hg.server.org/my-platform#master +* hg+https://hg.server.org/my-platform#v1.0 +* hg+ssh://hg.server.org/my-platform#4cfe2fa00668 + +Subversion +^^^^^^^^^^ + +The supported schemes are: ``svn``, ``svn+svn``, ``svn+http``, ``svn+https`` +and ``svn+ssh``. Here are the supported forms: + +* svn+svn://svn.server.org/my-platform +* svn+https://svn.server.org/my-platform +* svn+ssh://svn.server.org/my-platform + +You can also give specific revisions to an SVN URL, like so: + +* svn+svn://svn.server.org/my-platform#13 + Options ------- @@ -62,30 +130,68 @@ Skip default packages Examples -------- -1. Install :ref:`platform_timsp430` with default packages +1. Install :ref:`platform_atmelavr` with default packages .. code-block:: bash - $ platformio platform install timsp430 - Installing toolchain-timsp430 package: + $ platformio platform install atmelavr + Installing platform atmelavr @ latest: + Downloading... + Unpacking [####################################] 100% + Installing package tool-scons @ >=2.3.0,<2.6.0: Downloading [####################################] 100% Unpacking [####################################] 100% - Installing tool-mspdebug package: + Installing package toolchain-atmelavr @ ~1.40801.0: Downloading [####################################] 100% Unpacking [####################################] 100% - Installing framework-energiamsp430 package: - Downloading [####################################] 100% - Unpacking [####################################] 100% - The platform 'timsp430' 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_timsp430` with ``uploader`` utility only and skip +2. Install :ref:`platform_atmelavr` with ``uploader`` utility only and skip default packages .. code-block:: bash - $ platformio platform install timsp430 --skip-default-package --with-package=uploader - Installing tool-mspdebug package: + $ platformio platform install atmelavr --skip-default-package --with-package=uploader + Installing platform atmelavr @ latest: Downloading [####################################] 100% Unpacking [####################################] 100% - The platform 'timsp430' has been successfully installed! + Installing package tool-micronucleus @ ~1.200.0: + Downloading [####################################] 100% + Unpacking [####################################] 100% + Installing package tool-avrdude @ >=1.60001.0,<1.60101.0: + Downloading [####################################] 100% + Unpacking [####################################] 100% + The platform 'atmelavr' has been successfully installed! + The rest of packages will be installed automatically depending on your build environment. + +3. Install the latest development :ref:`platform_atmelavr` from Git repository + +.. code-block:: bash + + $ platformio platform install https://github.com/platformio/platform-atmelavr.git + Installing platform https://github.com/platformio/platform-atmelavr.git @ latest: + 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. + 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. + Checking connectivity... done. + Submodule path 'examples/arduino-external-libs/lib/OneWire': checked out '57c18c6de80c13429275f70875c7c341f1719201' + Installing package tool-scons @ >=2.3.0,<2.6.0: + Downloading [####################################] 100% + Unpacking [####################################] 100% + Installing package toolchain-atmelavr @ ~1.40801.0: + Downloading [####################################] 100% + Unpacking [####################################] 100% + 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 67cc273a..f1375d5e 100644 --- a/docs/userguide/platforms/cmd_uninstall.rst +++ b/docs/userguide/platforms/cmd_uninstall.rst @@ -23,6 +23,9 @@ Usage platformio platform uninstall PLATFORM + # uninstall specific platform version using Semantic Versioning + platformio platform uninstall PLATFORM@X.Y.Z + Description ----------- @@ -35,8 +38,8 @@ Examples .. code-block:: bash - $ platformio platform uninstall timsp430 - Uninstalling toolchain-timsp430 package: [OK] - Uninstalling tool-mspdebug package: [OK] - Uninstalling framework-energiamsp430 package: [OK] - The platform 'timsp430' has been successfully uninstalled! + $ platformio platform uninstall atmelavr + Uninstalling platform atmelavr @ latest: [OK] + Uninstalling package tool-scons @ 2.5.0: [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 8e4fec64..918aca8a 100644 --- a/docs/userguide/platforms/cmd_update.rst +++ b/docs/userguide/platforms/cmd_update.rst @@ -46,75 +46,53 @@ Examples .. code-block:: bash $ platformio platform update - - Platform atmelavr + Platform atmelavr @ 0.0.0 -------- - Updating toolchain-atmelavr package: - Versions: Current=1, Latest=1 [Up-to-date] - Updating tool-avrdude package: - Versions: Current=2, Latest=2 [Up-to-date] - Updating framework-arduinoavr package: - Versions: Current=12, Latest=12 [Up-to-date] - Updating tool-micronucleus package: - Versions: Current=1, Latest=1 [Up-to-date] + 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] - Platform atmelsam + Platform atmelsam @ 0.0.0 -------- - Updating framework-arduinosam package: - Versions: Current=3, Latest=3 [Up-to-date] - Updating ldscripts package: - Versions: Current=1, Latest=1 [Up-to-date] - Updating toolchain-gccarmnoneeabi package: - Versions: Current=1, Latest=1 [Up-to-date] - Updating tool-bossac package: - Versions: Current=1, Latest=1 [Up-to-date] + 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] - Platform stm32 + Platform espressif @ 0.0.0 -------- - Updating toolchain-gccarmnoneeabi package: - Versions: Current=1, Latest=1 [Up-to-date] - Updating tool-stlink package: - Versions: Current=1, Latest=1 [Up-to-date] - Updating framework-spl package: - Versions: Current=1, Latest=1 [Up-to-date] - Updating framework-cmsis package: - Versions: Current=2, Latest=2 [Up-to-date] - Updating framework-opencm3 package: - Versions: Current=1, Latest=1 [Up-to-date] - Updating ldscripts package: - Versions: Current=1, Latest=1 [Up-to-date] + 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] - Platform teensy - -------- - Updating toolchain-atmelavr package: - Versions: Current=1, Latest=1 [Up-to-date] - Updating ldscripts package: - Versions: Current=1, Latest=1 [Up-to-date] - Updating framework-arduinoteensy package: - Versions: Current=1, Latest=1 [Up-to-date] - Updating toolchain-gccarmnoneeabi package: - Versions: Current=1, Latest=1 [Up-to-date] - Updating tool-teensy package: - Versions: Current=1, Latest=1 [Up-to-date] - - Platform timsp430 - -------- - Updating toolchain-timsp430 package: - Versions: Current=1, Latest=1 [Up-to-date] - Updating tool-mspdebug package: - Versions: Current=1, Latest=1 [Up-to-date] - Updating framework-energiamsp430 package: - Versions: Current=2, Latest=2 [Up-to-date] - - Platform titiva - -------- - Updating ldscripts package: - Versions: Current=1, Latest=1 [Up-to-date] - Updating toolchain-gccarmnoneeabi package: - Versions: Current=1, Latest=1 [Up-to-date] - Updating tool-lm4flash package: - Versions: Current=1, Latest=1 [Up-to-date] - Updating framework-opencm3 package: - Versions: Current=1, Latest=1 [Up-to-date] - Updating framework-energiativa package: - Versions: Current=4, Latest=4 [Up-to-date] + ... diff --git a/platformio/commands/run.py b/platformio/commands/run.py index 8b80d26d..76f375a5 100644 --- a/platformio/commands/run.py +++ b/platformio/commands/run.py @@ -205,7 +205,8 @@ class EnvironmentProcessor(object): try: p = PlatformFactory.newPlatform(platform, version) except exception.UnknownPlatform: - self.cmd_ctx.invoke(cmd_platform_install, platforms=[platform]) + self.cmd_ctx.invoke( + cmd_platform_install, platforms=[self.options['platform']]) p = PlatformFactory.newPlatform(platform, version) return p.run(build_vars, build_targets, self.verbose_level) diff --git a/platformio/exception.py b/platformio/exception.py index a375b587..3fe366f2 100644 --- a/platformio/exception.py +++ b/platformio/exception.py @@ -70,11 +70,6 @@ class UnknownPackage(PlatformioException): MESSAGE = "Detected unknown package '{0}'" -class InvalidLocalPackage(PlatformioException): - - MESSAGE = "Invalid local package '{0}'. Can not find manifest '{1}'" - - class UndefinedPackageVersion(PlatformioException): MESSAGE = "Can not find package '{0}' with version requirements '{1}'"\ diff --git a/platformio/managers/package.py b/platformio/managers/package.py index 8b37520b..5d0e0a63 100644 --- a/platformio/managers/package.py +++ b/platformio/managers/package.py @@ -15,6 +15,7 @@ import os from os.path import dirname, isdir, isfile, islink, join from shutil import copyfile, copytree, rmtree +from tempfile import mkdtemp import click import requests @@ -23,6 +24,7 @@ import semantic_version from platformio import exception, telemetry, util from platformio.downloader import FileDownloader from platformio.unpacker import FileUnpacker +from platformio.vcsclient import VCSClientFactory class PackageManager(object): @@ -87,32 +89,6 @@ class PackageManager(object): "Could not find '%s' manifest file in the package" % self.manifest_name) - def make_pkg_dir(self, name, version): - pkg_dir = join(self.package_dir, name) - if isfile(join(pkg_dir, self.manifest_name)): - manifest = util.load_json( - join(pkg_dir, self.manifest_name)) - - cmp_result = semantic_version.compare(version, manifest['version']) - if cmp_result == 1: - # if main package version < new package, backup it - print pkg_dir, join( - self.package_dir, "%s@%s" % (name, manifest['version'])) - 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, version)) - - # remove previous/not-satisfied package - if isdir(pkg_dir): - rmtree(pkg_dir) - os.makedirs(pkg_dir) - assert isdir(pkg_dir) - - self.reset_cache() - return pkg_dir - @staticmethod def max_satisfying_repo_version(versions, requirements=None): item = None @@ -196,12 +172,11 @@ class PackageManager(object): return self.max_satisfying_version( name, requirements).get("_manifest_path") - manifest_path = None - if name.startswith("file://"): - manifest_path = self._install_from_local_dir(name[7:]) + if "://" in name: + pkg_dir = self._install_from_url(name, requirements) else: - manifest_path = self._install_from_piorepo(name, requirements) - if not isfile(manifest_path): + pkg_dir = self._install_from_piorepo(name, requirements) + if not pkg_dir or not isfile(join(pkg_dir, self.manifest_name)): raise exception.PackageInstallError( name, requirements or "latest", util.get_systype()) @@ -210,32 +185,23 @@ class PackageManager(object): telemetry.on_event( category="PackageManager", action="Install", label=name) - return manifest_path + return join(pkg_dir, self.manifest_name) def _install_from_piorepo(self, name, requirements): pkg_dir = None pkgdata = None versions = None for versions in PackageRepoIterator(name, self.repositories): - dlpath = None pkgdata = self.max_satisfying_repo_version(versions, requirements) if not pkgdata: continue - - pkg_dir = self.make_pkg_dir(name, pkgdata['version']) try: - dlpath = self.download( - pkgdata['url'], pkg_dir, pkgdata.get("sha1")) - assert isfile(dlpath) - self.unpack(dlpath, pkg_dir) - self.check_structure(pkg_dir) + pkg_dir = self._install_from_url( + 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") - finally: - if dlpath and isfile(dlpath): - os.remove(dlpath) if versions is None: raise exception.UnknownPackage(name) @@ -246,21 +212,64 @@ class PackageManager(object): else: raise exception.UndefinedPackageVersion( name, requirements or "latest", util.get_systype()) + return pkg_dir - return join(pkg_dir, self.manifest_name) + def _install_from_url(self, url, requirements=None, sha1=None): + pkg_dir = None + tmp_dir = mkdtemp("-package", "installing-", self.package_dir) - def _install_from_local_dir(self, local_dir): - if not isfile(join(local_dir, self.manifest_name)): - raise exception.InvalidLocalPackage( - local_dir, self.manifest_name) + # Handle GitHub URL (https://github.com/user/repo.git) + if url.endswith(".git") and not url.startswith("git"): + url = "git+" + url - manifest = util.load_json(join(local_dir, self.manifest_name)) - assert set(["name", "version"]) <= set(manifest.keys()) - pkg_dir = self.make_pkg_dir(manifest['name'], manifest['version']) - rmtree(pkg_dir) - copytree(local_dir, pkg_dir, symlinks=True) + try: + if url.startswith("file://"): + rmtree(tmp_dir) + copytree(url[7:], 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: + repo = VCSClientFactory.newClient(url) + repo.export(tmp_dir) - return join(pkg_dir, self.manifest_name) + self.check_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 = util.load_json(join(tmp_dir, self.manifest_name)) + assert set(["name", "version"]) <= set(tmpmanifest.keys()) + name = tmpmanifest['name'] + + # package should satisfy requirements + if requirements: + assert semantic_version.match(requirements, tmpmanifest['version']) + + pkg_dir = join(self.package_dir, name) + if isfile(join(pkg_dir, self.manifest_name)): + manifest = util.load_json(join(pkg_dir, self.manifest_name)) + 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'])) + + # remove previous/not-satisfied package + if isdir(pkg_dir): + rmtree(pkg_dir) + os.rename(tmp_dir, pkg_dir) + assert isdir(pkg_dir) + return pkg_dir def uninstall(self, name, requirements=None, trigger_event=True): click.echo("Uninstalling %s %s @ %s: \t" % ( diff --git a/platformio/managers/platform.py b/platformio/managers/platform.py index 4697dc61..ed5140a8 100644 --- a/platformio/managers/platform.py +++ b/platformio/managers/platform.py @@ -44,25 +44,26 @@ class PlatformManager(PackageManager): name, requirements=None, with_packages=None, without_packages=None, skip_default_packages=False): manifest_path = PackageManager.install(self, name, requirements) - PlatformFactory.newPlatform( - manifest_path, requirements).install_packages( - with_packages, without_packages, skip_default_packages) - self.cleanup_packages() + p = PlatformFactory.newPlatform(manifest_path, requirements) + p.install_packages( + with_packages, without_packages, skip_default_packages) + self.cleanup_packages(p.packages.keys()) return True def uninstall(self, # pylint: disable=arguments-differ name, requirements=None): + p = PlatformFactory.newPlatform(name, requirements) PackageManager.uninstall(self, name, requirements) - self.cleanup_packages() + self.cleanup_packages(p.packages.keys()) return True def update(self, # pylint: disable=arguments-differ name, requirements=None, only_packages=False): if not only_packages: PackageManager.update(self, name) - PlatformFactory.newPlatform( - name, requirements).update_packages() - self.cleanup_packages() + p = PlatformFactory.newPlatform(name, requirements) + p.update_packages() + self.cleanup_packages(p.packages.keys()) return True def is_outdated(self, name, requirements=None): @@ -70,7 +71,7 @@ class PlatformManager(PackageManager): return (p.are_outdated_packages() or p.version != self.get_latest_repo_version(name, requirements)) - def cleanup_packages(self): + def cleanup_packages(self, names): self.reset_cache() deppkgs = {} for manifest in PlatformManager().get_installed(): @@ -83,9 +84,10 @@ class PlatformManager(PackageManager): pm = PackageManager() for manifest in pm.get_installed(): - if manifest['name'] not in deppkgs: + if manifest['name'] not in names: continue - if manifest['version'] not in deppkgs[manifest['name']]: + if (manifest['name'] not in deppkgs or + manifest['version'] not in deppkgs[manifest['name']]): pm.uninstall( manifest['name'], manifest['version'], trigger_event=False) diff --git a/platformio/vcsclient.py b/platformio/vcsclient.py new file mode 100644 index 00000000..612f287a --- /dev/null +++ b/platformio/vcsclient.py @@ -0,0 +1,97 @@ +# 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 platform import system +from subprocess import check_call +from sys import modules +from urlparse import urlsplit, urlunsplit + +from platformio.exception import PlatformioException + + +class VCSClientFactory(object): + + @staticmethod + def newClient(url): + scheme, netloc, path, query, fragment = urlsplit(url) + type_ = scheme + if "+" in type_: + type_, scheme = type_.split("+", 1) + url = urlunsplit((scheme, netloc, path, query, None)) + clsname = "%sClient" % type_.title() + obj = getattr(modules[__name__], clsname)(url, fragment) + assert isinstance(obj, VCSClientBase) + return obj + + +class VCSClientBase(object): + + command = None + + def __init__(self, url, branch=None): + self.url = url + self.branch = branch + self.check_client() + + def check_client(self): + try: + assert self.command + assert self.run_cmd(["--version"]) == 0 + except (AssertionError, OSError): + raise PlatformioException( + "VCS: `%s` client is not installed in your system" % + self.command) + return True + + def export(self, dst_dir): + raise NotImplementedError + + def run_cmd(self, args): + return check_call([self.command] + args, shell=system() == "Windows") + + +class GitClient(VCSClientBase): + + command = "git" + + def export(self, dst_dir): + args = ["clone", "--recursive", "--depth", "1"] + if self.branch: + args.extend(["--branch", self.branch]) + args.extend([self.url, dst_dir]) + self.run_cmd(args) + + +class HgClient(VCSClientBase): + + command = "hg" + + def export(self, dst_dir): + args = ["clone"] + if self.branch: + args.extend(["--updaterev", self.branch]) + args.extend([self.url, dst_dir]) + self.run_cmd(args) + + +class SvnClient(VCSClientBase): + + command = "svn" + + def export(self, dst_dir): + args = ["export", "--force"] + if self.branch: + args.extend(["--revision", self.branch]) + args.extend([self.url, dst_dir]) + self.run_cmd(args)