8000 micropython/upip: Add a new upip library. · micropython/micropython-lib@f4e1522 · GitHub
[go: up one dir, main page]

Skip to content

Commit f4e1522

Browse files
committed
micropython/upip: Add a new upip library.
This is a replacement for the previous `upip` tool for on-device installation of packages. This work was funded through GitHub Sponsors. Signed-off-by: Jim Mussared <jim.mussared@gmail.com>
1 parent 9db0ced commit f4e1522

File tree

2 files changed

+168
-0
lines changed

2 files changed

+168
-0
lines changed

micropython/upip/manifest.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
metadata(version="0.1.0")
2+
3+
require("urequests")
4+
5+
module("upip.py")

micropython/upip/upip.py

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
# Micropython package installer
2+
# MIT license; Copyright (c) 2022 Jim Mussared
3+
4+
import urequests as requests
5+
import sys
6+
7+
8+
_PACKAGE_INDEX = const("https://micropython.org/pi/v2")
9+
_CHUNK_SIZE = 128
10+
11+
12+
# This implements os.makedirs(os.dirname(path))
13+
def _ensure_path_exists(path):
14+
import os
15+
16+
split = path.split("/")
17+
18+
# Handle paths starting with "/".
19+
if not split[0]:
20+
split.pop(0)
21+
split[0] = "/" + split[0]
22+
23+
prefix = ""
24+
for i in range(len(split) - 1):
25+
prefix += split[i]
26+
try:
27+
os.stat(prefix)
28+
except:
29+
os.mkdir(prefix)
30+
prefix += "/"
31+
32+
33+
# Copy from src (stream) to dest (function-taking-bytes)
34+
def _chunk(src, dest):
35+
buf = memoryview(bytearray(_CHUNK_SIZE))
36+
while True:
37+
n = src.readinto(buf)
38+
if n == 0:
39+
break
40+
dest(buf if n == _CHUNK_SIZE else buf[:n])
41+
42+
43+
# Check if the specified path exists and matches the hash.
44+
def _check_exists(path, short_hash):
45+
import os
46+
47+
try:
48+
import binascii
49+
import hashlib
50+
51+
with open(path, "rb") as f:
52+
hs256 = hashlib.sha256()
53+
_chunk(f, hs256.update)
54+
existing_hash = str(binascii.hexlify(hs256.digest())[: len(short_hash)], "utf-8")
55+
return existing_hash == short_hash
E864 56+
except:
57+
return False
58+
59+
60+
def _rewrite_url(url):
61+
if url.startswith("github:"):
62+
url = url[7:].split("/")
63+
url = (
64+
"https://raw.githubusercontent.com/"
65+
+ url[0]
66+
+ "/"
67+
+ url[1]
68+
+ "/HEAD/"
69+
+ "/".join(url[2:])
70+
)
71+
return url
72+
73+
74+
def _download_file(url, dest):
75+
response = requests.get(url)
76+
try:
77+
if response.status_code != 200:
78+
print("Error", response.status_code, "requesting", url)
79+
return False
80+
81+
print("Copying:", dest)
82+
_ensure_path_exists(dest)
83+
with open(dest, "wb") as f:
84+
_chunk(response.raw, f.write)
85+
86+
return True
87+
finally:
88+
response.close()
89+
90+
91+
def _install_json(package_json_url, index, target):
92+
response = requests.get(_rewrite_url(package_json_url))
93+
try:
94+
F438 if response.status_code != 200:
95+
print("Package not found:", package_json_url)
96+
return False
97+
98+
package_json = response.json()
99+
finally:
100+
response.close()
101+
for target_path, short_hash in package_json.get("hashes", ()):
102+
fs_target_path = target + "/" + target_path
103+
if _check_exists(fs_target_path, short_hash):
104+
print("Exists:", fs_target_path)
105+
else:
106+
file_url = "{}/file/{}/{}".format(index, short_hash[:2], short_hash[2:])
107+
if not _download_file(_rewrite_url(file_url), fs_target_path):
108+
print("File not found: {} {}".format(target_path, short_hash))
109+
return False
110+
for target_path, url in package_json.get("urls", ()):
111+
fs_target_path = target + "/" + target_path
112+
if not _download_file(_rewrite_url(url), fs_target_path):
113+
print("File not found: {} {}".format(target_path, url))
114+
return False
115+
for dep, version in package_json.get("deps", ()):
116+
if not _install_package(dep, index, target, version):
117+
return False
118+
return True
119+
120+
121+
def _install_lib(package, index, target, version):
122+
print("Installing {} from {} to {}".format(package, index, target))
123+
124+
mpy_version = sys.implementation._mpy & 0xFF
125+
126+
package_json_url = "{}/package/{}/{}/{}.json".format(index, mpy_version, package, version)
127+
return _install_json(package_json_url, index, target)
128+
129+
130+
def _install_package(package, index, target, version):
131+
if (
132+
package.startswith("http://")
133+
or package.startswith("https://")
134+
or package.startswith("github:")
135+
):
136+
if package.endswith(".py") or package.endswith(".mpy"):
137+
print("Downloading {} to {}".format(package, target))
138+
return _download_file(_rewrite_url(package), target + "/" + package.rsplit("/")[-1])
139+
else:
140+
if not package.endswith(".json"):
141+
if not package.endswith("/"):
142+
package += "/"
143+
package += "package.json"
144+
print("Installing {} to {}".format(package, target))
145+
return _install_json(package, index, target)
146+
else:
147+
return _install_lib(package, index, target, version)
148+
149+
150+
def install(package, index=_PACKAGE_INDEX, target=None, version="latest"):
151+
if not target:
152+
for p in sys.path:
153+
if p == "lib" or p.endswith("/lib"):
154+
target = p
155+
break
156+
else:
157+
print("Unable to find lib dir in sys.path.")
158+
return
159+
160+
if _install_package(package, index.rstrip("/"), target, version):
161+
print("Done")
162+
else:
163+
print("Package may be partially installed.")

0 commit comments

Comments
 (0)
0