diff --git a/docs/userguide/platforms/cmd_install.rst b/docs/userguide/platforms/cmd_install.rst index b3753537..86b678af 100644 --- a/docs/userguide/platforms/cmd_install.rst +++ b/docs/userguide/platforms/cmd_install.rst @@ -21,14 +21,17 @@ Usage .. code-block:: bash + # install platform by name platformio platform install [OPTIONS] [PLATFORMS] + # install platform from local directory + platformio platform install [OPTIONS] [file:///local/path/to/platform/dir] + Description ----------- -Install pre-built development :ref:`platforms` with related -packages. +Install development :ref:`platforms` and dependent packages. There are several predefined aliases for packages, such as: diff --git a/platformio/downloader.py b/platformio/downloader.py index bbb90e07..891e7bf5 100644 --- a/platformio/downloader.py +++ b/platformio/downloader.py @@ -53,7 +53,7 @@ class FileDownloader(object): return self._destination def get_lmtime(self): - return self._request.headers['last-modified'] + return self._request.headers.get("last-modified") def get_size(self): if "content-length" not in self._request.headers: @@ -77,7 +77,8 @@ class FileDownloader(object): f.close() self._request.close() - self._preserve_filemtime(self.get_lmtime()) + if self.get_lmtime(): + self._preserve_filemtime(self.get_lmtime()) def verify(self, sha1=None): _dlsize = getsize(self._destination) diff --git a/platformio/exception.py b/platformio/exception.py index ce3d7cc6..39630594 100644 --- a/platformio/exception.py +++ b/platformio/exception.py @@ -70,6 +70,11 @@ 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 7654109f..c1e94f1c 100644 --- a/platformio/managers/package.py +++ b/platformio/managers/package.py @@ -14,7 +14,7 @@ import os from os.path import dirname, isdir, isfile, join -from shutil import rmtree +from shutil import copyfile, copytree, rmtree import click import requests @@ -41,6 +41,10 @@ class PackageManager(object): def reset_cache(): PackageManager._INSTALLED_CACHE = {} + @staticmethod + def get_manifest_name(): + return "package.json" + @staticmethod def download(url, dest_dir, sha1=None): fd = FileDownloader(url, dest_dir) @@ -50,13 +54,56 @@ class PackageManager(object): return fd.get_filepath() @staticmethod - def unpack(pkgpath, dest_dir): - fu = FileUnpacker(pkgpath, dest_dir) + def unpack(source_path, dest_dir): + fu = FileUnpacker(source_path, dest_dir) return fu.start() - @staticmethod - def get_manifest_name(): - return "package.json" + def check_structure(self, pkg_dir): + if isfile(join(pkg_dir, self.get_manifest_name())): + return True + + for root, _, files in os.walk(pkg_dir): + if self.get_manifest_name() not in files: + continue + # copy contents to the root of package directory + for item in os.listdir(root): + item_path = join(root, item) + if isfile(item_path): + copyfile(item_path, join(pkg_dir, item)) + elif isdir(item_path): + copytree(item_path, join(pkg_dir, item), symlinks=True) + # remove not used contents + while True: + rmtree(root) + root = dirname(root) + if root == pkg_dir: + break + break + + if isfile(join(pkg_dir, self.get_manifest_name())): + return True + + raise exception.PlatformioException( + "Could not find '%s' manifest file in the package" % + self.get_manifest_name()) + + def make_pkg_dir(self, name, version): + pkg_dir = join(self.package_dir, name) + if isfile(join(pkg_dir, self.get_manifest_name())): + _manifest = util.load_json( + join(pkg_dir, self.get_manifest_name())) + if (_manifest['name'] == name and + _manifest['version'] != version): + 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) + + return pkg_dir @staticmethod def max_satisfying_version(versions, requirements=None): @@ -114,7 +161,8 @@ class PackageManager(object): def install(self, name, requirements, silent=False, trigger_event=True): installed = self.is_installed(name, requirements) if not installed or not silent: - click.echo("Installing package %s @ %s:" % ( + click.echo("Installing %s %s @ %s:" % ( + self.get_manifest_name().split(".")[0], click.style(name, fg="cyan"), requirements if requirements else "latest")) if installed: @@ -122,18 +170,24 @@ class PackageManager(object): click.secho("Already installed", fg="yellow") return - if not self._install_from_piorepo(name, requirements): + manifest_path = None + if name.startswith("file://"): + manifest_path = self._install_from_local_dir(name[7:]) + else: + manifest_path = self._install_from_piorepo(name, requirements) + if not isfile(manifest_path): raise exception.PackageInstallError( - name, requirements, util.get_systype()) + name, requirements or "latest", util.get_systype()) self.reset_cache() if trigger_event: telemetry.on_event( category="PackageManager", action="Install", label=name) + return manifest_path + def _install_from_piorepo(self, name, requirements): pkg_dir = None - success = False pkgdata = None versions = None for versions in PackageRepoIterator(name, self.repositories): @@ -142,26 +196,17 @@ class PackageManager(object): if not pkgdata: continue - pkg_dir = join(self.package_dir, name) - if isfile(join(pkg_dir, self.get_manifest_name())): - pkg_dir = join( - self.package_dir, "%s@%s" % (name, pkgdata['version'])) - - # remove previous/not-satisfied package - if isdir(pkg_dir): - rmtree(pkg_dir) - os.makedirs(pkg_dir) - + 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) - success = True + self.check_structure(pkg_dir) break except Exception as e: # pylint: disable=broad-except click.secho("Warning! Package Mirror: %s" % e, fg="yellow") - click.secho("Looking for other Package Mirror...", fg="yellow") + click.secho("Looking for another mirror...", fg="yellow") finally: if dlpath and isfile(dlpath): os.remove(dlpath) @@ -170,12 +215,26 @@ class PackageManager(object): raise exception.UnknownPackage(name) elif not pkgdata: raise exception.UndefinedPackageVersion( - name, requirements, util.get_systype()) + name, requirements or "latest", util.get_systype()) - return success + return join(pkg_dir, self.get_manifest_name()) + + def _install_from_local_dir(self, local_dir): + if not isfile(join(local_dir, self.get_manifest_name())): + raise exception.InvalidLocalPackage( + local_dir, self.get_manifest_name()) + + manifest = util.load_json(join(local_dir, self.get_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) + + return join(pkg_dir, self.get_manifest_name()) def uninstall(self, name, requirements=None, trigger_event=True): - click.echo("Uninstalling package %s @ %s: \t" % ( + click.echo("Uninstalling %s %s @ %s: \t" % ( + self.get_manifest_name().split(".")[0], click.style(name, fg="cyan"), requirements if requirements else "latest"), nl=False) found = False @@ -201,7 +260,8 @@ class PackageManager(object): category="PackageManager", action="Uninstall", label=name) def update(self, name, requirements=None, keep_versions=None): - click.echo("Updating package %s @ %s:" % ( + click.echo("Updating %s %s @ %s:" % ( + self.get_manifest_name().split(".")[0], click.style(name, fg="yellow"), requirements if requirements else "latest")) diff --git a/platformio/managers/platform.py b/platformio/managers/platform.py index 2526ad29..f903b6bb 100644 --- a/platformio/managers/platform.py +++ b/platformio/managers/platform.py @@ -56,9 +56,9 @@ class PlatformManager(PackageManager): def install(self, # pylint: disable=too-many-arguments,arguments-differ name, requirements=None, with_packages=None, without_packages=None, skip_default_packages=False): - PackageManager.install(self, name, requirements) + manifest_path = PackageManager.install(self, name, requirements) return PlatformFactory.newPlatform( - name, requirements).install_packages( + manifest_path, requirements).install_packages( with_packages, without_packages, skip_default_packages) def uninstall(self, # pylint: disable=arguments-differ