def upip_import(mod, sub=None): try: mod_ = mod if sub: mod_ += "_" + sub return __import__("upip_" + mod_) except ImportError: m = __import__(mod) if sub: return getattr(m, sub) return m sys = upip_import("sys") import _os as os errno = upip_import("errno") gzip = upip_import("gzip") try: tarfile = upip_import("utarfile") except ImportError: tarfile = upip_import("tarfile") try: json = upip_import("ujson") except ImportError: json = upip_import("json") DEFAULT_MICROPYPATH = "~/.micropython/lib:/usr/lib/micropython" debug = False cleanup_files = [".pkg.tar"] class NotFoundError(Exception): pass def op_split(path): if path == "": return ("", "") r = path.rsplit("/", 1) if len(r) == 1: return ("", path) head = r[0] if not head: head = "/" return (head, r[1]) def op_basename(path): return op_split(path)[1] def _makedirs(name, mode=0o777): ret = False s = "" for c in name.rstrip("/").split("/"): s += c + "/" try: os.mkdir(s) ret = True except OSError as e: if e.args[0] != errno.EEXIST: raise ret = False return ret 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", "README"): #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 if debug: print("Skipping", fname) break if save: outfname = prefix + fname if info.type == tarfile.DIRTYPE: if _makedirs(outfname): print("Created " + outfname) else: if debug: 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 try: import ussl import usocket def download(url, local_name): proto, _, host, urlpath = url.split('/', 3) ai = usocket.getaddrinfo(host, 443) #print("Address infos:", ai) addr = ai[0][4] s = usocket.socket() #print("Connect address:", addr) s.connect(addr) if proto == "https:": s = ussl.wrap_socket(s) # MicroPython rawsocket module supports file interface directly s.write("GET /%s HTTP/1.0\r\nHost: %s\r\n\r\n" % (urlpath, host)) l = s.readline() protover, status, msg = l.split(None, 2) if status != b"200": raise OSError() while 1: l = s.readline() if not l: raise OSError() if l == b'\r\n': break with open(local_name, "wb") as f: while 1: l = s.read(1024) if not l: break f.write(l) except ImportError: def download(url, local_name): if debug: print("wget -q %s -O %s" % (url, local_name)) rc = os.system("wget -q %s -O %s" % (url, local_name)) if local_name not in cleanup_files: cleanup_files.append(local_name) if rc == 8 * 256: raise NotFoundError 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"] packages = data["releases"][latest_ver] assert len(packages) == 1 package_url = packages[0]["url"] print("Installing %s %s from %s" % (pkg_spec, latest_ver, package_url)) package_fname = op_basename(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 cleanup(): for fname in cleanup_files: try: os.unlink(fname) except OSError: print("Warning: Cannot delete " + fname) def help(): print("upip - Simple PyPI package manager for MicroPython") print("Usage: micropython -m upip install ... | -r ") print("""\ Note: only MicroPython packages (usually, micropython-*) are supported for installation, upip does not support arbitrary code in setup.py.""") sys.exit(1) def main(): global debug install_path = None if len(sys.argv) < 2 or sys.argv[1] == "-h" or sys.argv[1] == "--help": help() if sys.argv[1] != "install": fatal("Only 'install' command supported") to_install = [] i = 2 while i < len(sys.argv) and sys.argv[i][0] == "-": opt = sys.argv[i] i += 1 if opt == "-h" or opt == "--help": help() elif opt == "-p": install_path = sys.argv[i] i += 1 elif opt == "-r": list_file = sys.argv[i] i += 1 with open(list_file) as f: while True: l = f.readline() if not l: break to_install.append(l.rstrip()) elif opt == "--debug": debug = True else: fatal("Unknown/unsupported option: " + opt) if install_path is None: install_path = os.getenv("MICROPYPATH") or 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.extend(sys.argv[i:]) if not to_install: help() # sets would be perfect here, but don't depend on them installed = [] try: while to_install: if debug: 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) if debug: print(meta) deps = meta.get("deps", "").rstrip() if deps: deps = deps.decode("utf-8").split("\n") to_install.extend(deps) except NotFoundError: print("Error: cannot find '%s' package (or server error), packages may be partially installed" \ % pkg_spec, file=sys.stderr) if not debug: cleanup() main()