Allow to install development platform from local directory // Issue #479

This commit is contained in:
Ivan Kravets
2016-05-26 22:33:17 +03:00
parent d68eb28629
commit 9a1f1ddb8b
5 changed files with 101 additions and 32 deletions

View File

@ -21,14 +21,17 @@ Usage
.. code-block:: bash .. code-block:: bash
# install platform by name
platformio platform install [OPTIONS] [PLATFORMS] platformio platform install [OPTIONS] [PLATFORMS]
# install platform from local directory
platformio platform install [OPTIONS] [file:///local/path/to/platform/dir]
Description Description
----------- -----------
Install pre-built development :ref:`platforms` with related Install development :ref:`platforms` and dependent packages.
packages.
There are several predefined aliases for packages, such as: There are several predefined aliases for packages, such as:

View File

@ -53,7 +53,7 @@ class FileDownloader(object):
return self._destination return self._destination
def get_lmtime(self): def get_lmtime(self):
return self._request.headers['last-modified'] return self._request.headers.get("last-modified")
def get_size(self): def get_size(self):
if "content-length" not in self._request.headers: if "content-length" not in self._request.headers:
@ -77,7 +77,8 @@ class FileDownloader(object):
f.close() f.close()
self._request.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): def verify(self, sha1=None):
_dlsize = getsize(self._destination) _dlsize = getsize(self._destination)

View File

@ -70,6 +70,11 @@ class UnknownPackage(PlatformioException):
MESSAGE = "Detected unknown package '{0}'" MESSAGE = "Detected unknown package '{0}'"
class InvalidLocalPackage(PlatformioException):
MESSAGE = "Invalid local package '{0}'. Can not find manifest '{1}'"
class UndefinedPackageVersion(PlatformioException): class UndefinedPackageVersion(PlatformioException):
MESSAGE = "Can not find package '{0}' with version requirements '{1}'"\ MESSAGE = "Can not find package '{0}' with version requirements '{1}'"\

View File

@ -14,7 +14,7 @@
import os import os
from os.path import dirname, isdir, isfile, join from os.path import dirname, isdir, isfile, join
from shutil import rmtree from shutil import copyfile, copytree, rmtree
import click import click
import requests import requests
@ -41,6 +41,10 @@ class PackageManager(object):
def reset_cache(): def reset_cache():
PackageManager._INSTALLED_CACHE = {} PackageManager._INSTALLED_CACHE = {}
@staticmethod
def get_manifest_name():
return "package.json"
@staticmethod @staticmethod
def download(url, dest_dir, sha1=None): def download(url, dest_dir, sha1=None):
fd = FileDownloader(url, dest_dir) fd = FileDownloader(url, dest_dir)
@ -50,13 +54,56 @@ class PackageManager(object):
return fd.get_filepath() return fd.get_filepath()
@staticmethod @staticmethod
def unpack(pkgpath, dest_dir): def unpack(source_path, dest_dir):
fu = FileUnpacker(pkgpath, dest_dir) fu = FileUnpacker(source_path, dest_dir)
return fu.start() return fu.start()
@staticmethod def check_structure(self, pkg_dir):
def get_manifest_name(): if isfile(join(pkg_dir, self.get_manifest_name())):
return "package.json" 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 @staticmethod
def max_satisfying_version(versions, requirements=None): def max_satisfying_version(versions, requirements=None):
@ -114,7 +161,8 @@ class PackageManager(object):
def install(self, name, requirements, silent=False, trigger_event=True): def install(self, name, requirements, silent=False, trigger_event=True):
installed = self.is_installed(name, requirements) installed = self.is_installed(name, requirements)
if not installed or not silent: 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"), click.style(name, fg="cyan"),
requirements if requirements else "latest")) requirements if requirements else "latest"))
if installed: if installed:
@ -122,18 +170,24 @@ class PackageManager(object):
click.secho("Already installed", fg="yellow") click.secho("Already installed", fg="yellow")
return 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( raise exception.PackageInstallError(
name, requirements, util.get_systype()) name, requirements or "latest", util.get_systype())
self.reset_cache() self.reset_cache()
if trigger_event: if trigger_event:
telemetry.on_event( telemetry.on_event(
category="PackageManager", action="Install", label=name) category="PackageManager", action="Install", label=name)
return manifest_path
def _install_from_piorepo(self, name, requirements): def _install_from_piorepo(self, name, requirements):
pkg_dir = None pkg_dir = None
success = False
pkgdata = None pkgdata = None
versions = None versions = None
for versions in PackageRepoIterator(name, self.repositories): for versions in PackageRepoIterator(name, self.repositories):
@ -142,26 +196,17 @@ class PackageManager(object):
if not pkgdata: if not pkgdata:
continue continue
pkg_dir = join(self.package_dir, name) pkg_dir = self.make_pkg_dir(name, pkgdata['version'])
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)
try: try:
dlpath = self.download( dlpath = self.download(
pkgdata['url'], pkg_dir, pkgdata.get("sha1")) pkgdata['url'], pkg_dir, pkgdata.get("sha1"))
assert isfile(dlpath) assert isfile(dlpath)
self.unpack(dlpath, pkg_dir) self.unpack(dlpath, pkg_dir)
success = True self.check_structure(pkg_dir)
break break
except Exception as e: # pylint: disable=broad-except except Exception as e: # pylint: disable=broad-except
click.secho("Warning! Package Mirror: %s" % e, fg="yellow") 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: finally:
if dlpath and isfile(dlpath): if dlpath and isfile(dlpath):
os.remove(dlpath) os.remove(dlpath)
@ -170,12 +215,26 @@ class PackageManager(object):
raise exception.UnknownPackage(name) raise exception.UnknownPackage(name)
elif not pkgdata: elif not pkgdata:
raise exception.UndefinedPackageVersion( 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): 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"), click.style(name, fg="cyan"),
requirements if requirements else "latest"), nl=False) requirements if requirements else "latest"), nl=False)
found = False found = False
@ -201,7 +260,8 @@ class PackageManager(object):
category="PackageManager", action="Uninstall", label=name) category="PackageManager", action="Uninstall", label=name)
def update(self, name, requirements=None, keep_versions=None): 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"), click.style(name, fg="yellow"),
requirements if requirements else "latest")) requirements if requirements else "latest"))

View File

@ -56,9 +56,9 @@ class PlatformManager(PackageManager):
def install(self, # pylint: disable=too-many-arguments,arguments-differ def install(self, # pylint: disable=too-many-arguments,arguments-differ
name, requirements=None, with_packages=None, name, requirements=None, with_packages=None,
without_packages=None, skip_default_packages=False): without_packages=None, skip_default_packages=False):
PackageManager.install(self, name, requirements) manifest_path = PackageManager.install(self, name, requirements)
return PlatformFactory.newPlatform( return PlatformFactory.newPlatform(
name, requirements).install_packages( manifest_path, requirements).install_packages(
with_packages, without_packages, skip_default_packages) with_packages, without_packages, skip_default_packages)
def uninstall(self, # pylint: disable=arguments-differ def uninstall(self, # pylint: disable=arguments-differ