From 3cccc69aac056c2a6d6e8d3c5ae46737cfc4d67a Mon Sep 17 00:00:00 2001 From: Paul Sokolovsky Date: Sun, 8 Feb 2015 09:15:07 +0200 Subject: [PATCH] upip: Initial steps towards self-hosted package manager. Already capable of installing packages from command line, resolving dependencies recursively. Downloading is handled via wget due to SSL, so currently this is not self-hosted. --- upip/upip.py | 158 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 upip/upip.py diff --git a/upip/upip.py b/upip/upip.py new file mode 100644 index 00000000..e4f69d47 --- /dev/null +++ b/upip/upip.py @@ -0,0 +1,158 @@ +import sys +import os +import os.path +import errno +import gzip +try: + import utarfile as tarfile +except ImportError: + import tarfile +try: + import ujson as json +except ImportError: + import json + + +DEFAULT_MICROPYPATH = "~/.micropython/lib:/usr/lib/micropython" + +def save_file(fname, subf): + outf = open(fname, "wb") + while True: + buf = subf.read(1024) + if not buf: + break + outf.write(buf) + outf.close() + +def install_tar(f, prefix): + meta = {} + for info in f: + #print(info) + fname = info.name + try: + fname = fname[fname.index("/") + 1:] + except ValueError: + fname = "" + + save = True + for p in ("setup.", "PKG-INFO"): + #print(fname, p) + if fname.startswith(p) or ".egg-info" in fname: + if fname.endswith("/requires.txt"): + meta["deps"] = f.extractfile(info).read() + save = False + print("Skipping", fname) + break + + if save: + outfname = prefix + fname + if info.type == tarfile.DIRTYPE: + try: + os.makedirs(outfname) + print("Created " + outfname) + except OSError as e: + if e.args[0] != errno.EEXIST: + raise + else: + print("Extracting " + outfname) + subf = f.extractfile(info) + save_file(outfname, subf) + return meta + +def expandhome(s): + h = os.getenv("HOME") + s = s.replace("~/", h + "/") + return s + +def download(url, local_name): + os.system("wget -q %s -O %s" % (url, local_name)) + +def get_pkg_metadata(name): + download("https://pypi.python.org/pypi/%s/json" % name, "pkg.json") + with open("pkg.json") as f: + s = f.read() + return json.loads(s) + + +def fatal(msg): + print(msg) + sys.exit(1) + +def gzdecompress(package_fname): + f = open(package_fname, "rb") + zipdata = f.read() + data = gzip.decompress(zipdata) + return data + +def gzdecompress_(package_fname): + os.system("gzip -d -c %s > ungz" % package_fname) + with open("ungz", "rb") as f: + return f.read() + +def install_pkg(pkg_spec, install_path): + data = get_pkg_metadata(pkg_spec) + + latest_ver = data["info"]["version"] + print("Installing %s %s" % (pkg_spec, latest_ver)) + packages = data["releases"][latest_ver] + assert len(packages) == 1 + package_url = packages[0]["url"] + package_fname = os.path.basename(package_url) + print(package_url) + download(package_url, package_fname) + + data = gzdecompress(package_fname) + + f = open("pkg.tar", "wb") + f.write(data) + f.close() + + f = tarfile.TarFile("pkg.tar") + return install_tar(f, install_path) + +def main(): + install_path = None + + if sys.argv[1] != "install": + fatal("Only 'install' command supported") + + i = 2 + while sys.argv[i][0] == "-": + opt = sys.argv[i][1] + i += 1 + if opt == "p": + install_path = sys.argv[i] + i += 1 + else: + fatal("Unknown/unsupported option: " + opt) + + if install_path is None: + install_path = DEFAULT_MICROPYPATH + + install_path = install_path.split(":", 1)[0] + + install_path = expandhome(install_path) + + if install_path[-1] != "/": + install_path += "/" + + print("Installing to: " + install_path) + + to_install = sys.argv[i:] + # sets would be perfect here, but don't depend on them + installed = [] + while to_install: + print("Queue:", to_install) + pkg_spec = to_install.pop(0) + if pkg_spec in installed: + continue + meta = install_pkg(pkg_spec, install_path) + installed.append(pkg_spec) + print(meta) + deps = meta.get("deps", "").rstrip() + if deps: + deps = deps.decode("utf-8").split("\n") + to_install.extend(deps) + + +main()