-
Notifications
You must be signed in to change notification settings - Fork 8
/
sysroot.py
executable file
·279 lines (230 loc) · 8.56 KB
/
sysroot.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
#!/usr/bin/env python3
from urllib.request import urlopen, urlretrieve
from urllib.parse import urlparse
from gzip import GzipFile
from pathlib import Path
import subprocess
import tarfile
import shutil
import pickle
import os
# WARNING: this script does not verify integrity and signature of packages!
RASPBIAN_VERSION = "buster"
RASPBIAN_ARCHIVE = "http://archive.raspberrypi.org/debian"
RASPBIAN_MAIN = "http://raspbian.raspberrypi.org/raspbian"
UBUNTU_VERSION = "bionic"
UBUNTU_MAIN = "http://ports.ubuntu.com/ubuntu-ports"
UBUNTU_RPI = "http://ppa.launchpad.net/ubuntu-raspi2/ppa/ubuntu"
ALARM = "http://mirror.archlinuxarm.org"
ALARM_REPOS = ["alarm", "core", "extra", "community"]
ALPINE_VERSION = "3.12"
ALPINE = "http://dl-cdn.alpinelinux.org/alpine"
IGNORED_PACKAGES = [
"raspberrypi-bootloader", "libasan3", "libubsan0", "libgomp1", "libatomic1",
"sh", "filesystem", "tzdata", "iana-etc", "libncursesw.so", "libp11-kit.so",
"libsystemd.so", "libidn2.so", "libacl.so"
]
# db is dict from package name to two element list - url and list of dependencies
def deb_collect_packages(url, version, section, db):
with urlopen(f"{url}/dists/{version}/{section}/binary-armhf/Packages.gz") as req:
with GzipFile(fileobj=req) as gz:
for line in gz:
line = line.strip().decode("utf-8")
if ": " not in line:
continue
key, value = line.split(": ", 1)
if key == "Package":
pkg = [url, []]
db[value] = pkg
elif key == "Depends":
deps = value.split(", ")
pkg[1] += [dep.split(" (")[0].split(" | ")[0] for dep in deps]
elif key == "Filename":
pkg[0] += "/" + value
def alarm_collect_packages(arch, repo, db):
base = f"{ALARM}/{arch}/{repo}"
with urlopen(f"{base}/{repo}.db.tar.gz") as req:
with tarfile.open(fileobj=req, mode="r:gz") as tar:
name = None
url = None
depends = []
processed = 0
for info in tar:
if not info.isfile():
continue
p = Path(info.name)
if p.stem == "desc":
next_name = False
next_url = False
for line in tar.extractfile(info):
line = line.strip().decode("utf-8")
if next_url:
url = f"{base}/{line}"
next_url = False
elif next_name:
name = line
next_name = False
elif line == "%FILENAME%":
next_url = True
elif line == "%NAME%":
next_name = True
processed += 1
elif p.stem == "depends":
next_depends = False
for line in tar.extractfile(info):
line = line.strip().decode("utf-8")
if next_depends:
if line:
depends.append(line.split("<")[0].split(">")[0].split("=")[0])
else:
next_depends = False
break
elif line == "%DEPENDS%":
next_depends = True
processed += 1
if processed == 2:
processed = 0
db[name] = [url, depends]
name = None
url = None
depends = []
def alpine_collect_packages(version, repo, arch, db):
base = f"{ALPINE}/v{version}/{repo}/{arch}"
packages = {}
with urlopen(f"{base}/APKINDEX.tar.gz") as req:
with tarfile.open(fileobj=req, mode="r:gz") as tar:
for info in tar:
if info.name != "APKINDEX":
continue
name = None
version = None
depends = []
provides = []
for line in tar.extractfile(info):
line = line.strip().decode("utf-8")
if line.startswith("P:"):
name = line[2:]
elif line.startswith("V:"):
version = line[2:]
elif line.startswith("D:"):
depends = line[2:].split(" ")
elif line.startswith("p:"):
provides = line[2:].split(" ")
elif not line:
for p in provides:
p = p.split("<")[0].split(">")[0].split("=")[0]
packages[p] = [name, version, depends]
packages[name] = [name, version, depends]
name = None
version = None
depends = []
provides = []
for p,v in packages.items():
name, version, depends = v
url = f"{base}/{name}-{version}.apk"
deps = []
for d in depends:
d = d.split("<")[0].split(">")[0].split("=")[0]
if d in packages:
deps.append(packages[d][0])
db[name] = [url, deps]
def resolve(package, packages, db):
if package in packages:
return
packages.add(package)
for dep in db[package][1]:
if dep not in IGNORED_PACKAGES:
resolve(dep, packages, db)
def install(distro, version, target, sysroot, packages):
sysroot = Path(sysroot)
cache = sysroot / ".db.pickle"
if cache.is_file():
print("Loading package database...")
with open(cache, "rb") as f:
db = pickle.load(f)
if distro == "alarm":
print("WARNING: ArchLinux package database can be out-of-date pretty quickly, remove db if downloading fails")
else:
print("Downloading package database...")
db = {}
if distro == "raspbian":
if version is None:
version = RASPBIAN_VERSION
deb_collect_packages(RASPBIAN_ARCHIVE, version, "main", db)
deb_collect_packages(RASPBIAN_MAIN, version, "main", db)
elif distro == "ubuntu":
if version is None:
version = UBUNTU_VERSION
deb_collect_packages(UBUNTU_RPI, version, "main", db)
for section in ["main", "universe"]:
deb_collect_packages(UBUNTU_MAIN, version, section, db)
elif distro == "alarm":
if target is None:
raise Exception("ALARM target not specified (use --target argument)")
elif target.startswith("armv6"):
arch = "armv6h"
elif target.startswith("armv7"):
arch = "armv7h"
elif target.startswith("aarch64"):
arch = "aarch64"
else:
raise Exception(f"Unsupported ALARM target {target}")
for repo in ALARM_REPOS:
alarm_collect_packages(arch, repo, db)
elif distro == "alpine":
if version is None:
version = ALPINE_VERSION
if target is None:
raise Exception("ALPINE target not specified (use --target argument)")
elif target.startswith("aarch64"):
arch = "aarch64"
elif target.startswith("arm"):
arch = "armhf"
else:
raise Exception(f"Unsupported ALARM target {target}")
alpine_collect_packages(version, "main", arch, db)
with open(cache, "wb") as f:
pickle.dump(db, f, pickle.HIGHEST_PROTOCOL)
print("Resolving dependencies...")
process = set()
for pkg in packages:
resolve(pkg, process, db)
print("Downloading...")
for i, pkg in enumerate(process):
print(f"({i+1}/{len(process)}) {pkg}")
url = db[pkg][0]
name = sysroot / Path(urlparse(url).path).name
if not name.is_file():
urlretrieve(url, name)
if distro == "raspbian" or distro == "ubuntu":
subprocess.check_call(["dpkg-deb", "-x", name, sysroot])
elif distro == "alarm":
subprocess.check_call(["tar", "--force-local", "-C", sysroot, "-xJf", name], stderr=subprocess.DEVNULL)
elif distro == "alpine":
subprocess.check_call(["tar", "--force-local", "-C", sysroot, "-xzf", name], stderr=subprocess.DEVNULL)
# remove files that makes life difficult when cross-compiling for alpine
if distro == "alpine":
t = sysroot / "usr" / target
if t.is_dir():
shutil.rmtree(t)
print("Fixing symlinks...")
for p in sysroot.glob("**/*"):
if p.is_symlink():
link = os.readlink(p)
if Path(link).is_absolute():
full = sysroot / ("." + link)
fixed = os.path.relpath(full.resolve(), p.parent)
p.unlink()
p.symlink_to(fixed)
print("Done!")
if __name__ == "__main__":
from argparse import ArgumentParser
ap = ArgumentParser(description="Download and install Raspbian packages to sysroot")
ap.add_argument("--distro", required=True, choices=["raspbian", "ubuntu", "alarm", "alpine"], help="distribution to use")
ap.add_argument("--version", help=f"distribution version to use for raspbian/ubuntu/alpine (default: {RASPBIAN_VERSION}/{UBUNTU_VERSION}/{ALPINE_VERSION})")
ap.add_argument("--target", help="target to download for alarm or alpine (ex: armv6l-unknown-linux-gnueabihf)")
ap.add_argument("--sysroot", required=True, help="sysroot folder")
ap.add_argument("packages", nargs="+")
args = ap.parse_args()
os.makedirs(args.sysroot, exist_ok=True)
install(args.distro, args.version, args.target, args.sysroot, args.packages)