Skip to content

Commit cc0dbdd

Browse files
committed
Add Android to add_to_pydotorg.py
1 parent fa37a7f commit cc0dbdd

File tree

3 files changed

+121
-61
lines changed

3 files changed

+121
-61
lines changed

add_to_pydotorg.py

Lines changed: 92 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,10 @@
44
To use (RELEASE is something like 3.3.5rc2):
55
66
* Copy this script to dl-files (it needs access to all the release files).
7-
You could also download all files, then you need to adapt the "ftp_root"
8-
string below.
7+
You could also download all files, then you need to use the "--ftp-root"
8+
argument.
99
10-
* Make sure all download files are in place in the correct /srv/www.python.org
11-
subdirectory.
10+
* Make sure all download files are in place in the correct FTP subdirectory.
1211
1312
* Create a new Release object via the Django admin (adding via API is
1413
currently broken), the name MUST be "Python RELEASE".
@@ -23,6 +22,7 @@
2322
Georg Brandl, March 2014.
2423
"""
2524

25+
import argparse
2626
import hashlib
2727
import json
2828
import os
@@ -70,8 +70,6 @@ def run_cmd(
7070
)
7171
sys.exit()
7272

73-
base_url = "https://www.python.org/api/v1/"
74-
ftp_root = "/srv/www.python.org/ftp/python/"
7573
download_root = "https://www.python.org/ftp/python/"
7674

7775
tag_cre = re.compile(r"(\d+)(?:\.(\d+)(?:\.(\d+))?)?(?:([ab]|rc)(\d+))?$")
@@ -98,44 +96,47 @@ def get_file_descriptions(
9896
) -> list[tuple[re.Pattern[str], tuple[str, int, bool, str]]]:
9997
v = minor_version_tuple(release)
10098
rx = re.compile
101-
# value is (file "name", OS id, download button, file "description").
102-
# OS=0 means no ReleaseFile object. Only one matching *file* (not regex)
99+
# value is (file "name", OS slug, download button, file "description").
100+
# OS=None means no ReleaseFile object. Only one matching *file* (not regex)
103101
# per OS can have download=True.
104102
return [
105-
(rx(r"\.tgz$"), ("Gzipped source tarball", 3, False, "")),
106-
(rx(r"\.tar\.xz$"), ("XZ compressed source tarball", 3, True, "")),
103+
(rx(r"\.tgz$"), ("Gzipped source tarball", "source", False, "")),
104+
(rx(r"\.tar\.xz$"), ("XZ compressed source tarball", "source", True, "")),
107105
(
108106
rx(r"windows-.+\.json"),
109107
(
110108
"Windows release manifest",
111-
1,
109+
"windows",
112110
False,
113111
f"Install with 'py install {v[0]}.{v[1]}'",
114112
),
115113
),
116114
(
117115
rx(r"-embed-amd64\.zip$"),
118-
("Windows embeddable package (64-bit)", 1, False, ""),
116+
("Windows embeddable package (64-bit)", "windows", False, ""),
119117
),
120118
(
121119
rx(r"-embed-arm64\.zip$"),
122-
("Windows embeddable package (ARM64)", 1, False, ""),
120+
("Windows embeddable package (ARM64)", "windows", False, ""),
121+
),
122+
(
123+
rx(r"-arm64\.exe$"),
124+
("Windows installer (ARM64)", "windows", False, "Experimental"),
123125
),
124-
(rx(r"-arm64\.exe$"), ("Windows installer (ARM64)", 1, False, "Experimental")),
125126
(
126127
rx(r"-amd64\.exe$"),
127-
("Windows installer (64-bit)", 1, v >= (3, 9), "Recommended"),
128+
("Windows installer (64-bit)", "windows", v >= (3, 9), "Recommended"),
128129
),
129130
(
130131
rx(r"-embed-win32\.zip$"),
131-
("Windows embeddable package (32-bit)", 1, False, ""),
132+
("Windows embeddable package (32-bit)", "windows", False, ""),
132133
),
133-
(rx(r"\.exe$"), ("Windows installer (32-bit)", 1, v < (3, 9), "")),
134+
(rx(r"\.exe$"), ("Windows installer (32-bit)", "windows", v < (3, 9), "")),
134135
(
135136
rx(r"-macosx10\.5(_rev\d)?\.(dm|pk)g$"),
136137
(
137138
"macOS 32-bit i386/PPC installer",
138-
2,
139+
"macos",
139140
False,
140141
"for Mac OS X 10.5 and later",
141142
),
@@ -144,7 +145,7 @@ def get_file_descriptions(
144145
rx(r"-macosx10\.6(_rev\d)?\.(dm|pk)g$"),
145146
(
146147
"macOS 64-bit/32-bit Intel installer",
147-
2,
148+
"macos",
148149
False,
149150
"for Mac OS X 10.6 and later",
150151
),
@@ -153,7 +154,7 @@ def get_file_descriptions(
153154
rx(r"-macos(x)?10\.9\.(dm|pk)g$"),
154155
(
155156
"macOS 64-bit Intel-only installer",
156-
2,
157+
"macos",
157158
False,
158159
"for macOS 10.9 and later, deprecated",
159160
),
@@ -162,11 +163,19 @@ def get_file_descriptions(
162163
rx(r"-macos(x)?1[1-9](\.[0-9]*)?\.pkg$"),
163164
(
164165
"macOS 64-bit universal2 installer",
165-
2,
166+
"macos",
166167
True,
167168
f"for macOS {'10.13' if v >= (3, 12, 6) else '10.9'} and later",
168169
),
169170
),
171+
(
172+
rx(r"aarch64-linux-android.tar.gz$"),
173+
("Android embeddable package (aarch64)", "android", False, ""),
174+
),
175+
(
176+
rx(r"x86_64-linux-android.tar.gz$"),
177+
("Android embeddable package (x86_64)", "android", False, ""),
178+
),
170179
]
171180

172181

@@ -182,14 +191,14 @@ def sigfile_for(release: str, rfile: str) -> str:
182191
return download_root + f"{release}/{rfile}.asc"
183192

184193

185-
def md5sum_for(release: str, rfile: str) -> str:
194+
def md5sum_for(filename: str) -> str:
186195
return hashlib.md5(
187-
open(ftp_root + base_version(release) + "/" + rfile, "rb").read()
196+
open(filename, "rb").read(),
188197
).hexdigest()
189198

190199

191-
def filesize_for(release: str, rfile: str) -> int:
192-
return path.getsize(ftp_root + base_version(release) + "/" + rfile)
200+
def filesize_for(filename: str) -> int:
201+
return path.getsize(filename)
193202

194203

195204
def make_slug(text: str) -> str:
@@ -215,6 +224,7 @@ def minor_version_tuple(release: str) -> tuple[int, int]:
215224

216225

217226
def build_file_dict(
227+
ftp_root: str,
218228
release: str,
219229
rfile: str,
220230
rel_pk: int,
@@ -224,6 +234,7 @@ def build_file_dict(
224234
add_desc: str,
225235
) -> dict[str, Any]:
226236
"""Return a dictionary with all needed fields for a ReleaseFile object."""
237+
filename = path.join(ftp_root, base_version(release), rfile)
227238
d = {
228239
"name": file_desc,
229240
"slug": slug_for(release) + "-" + make_slug(file_desc)[:40],
@@ -232,36 +243,38 @@ def build_file_dict(
232243
"description": add_desc,
233244
"is_source": os_pk == 3,
234245
"url": download_root + f"{base_version(release)}/{rfile}",
235-
"md5_sum": md5sum_for(release, rfile),
236-
"filesize": filesize_for(release, rfile),
246+
"md5_sum": md5sum_for(filename),
247+
"filesize": filesize_for(filename),
237248
"download_button": add_download,
238249
}
239250
# Upload GPG signature
240-
if os.path.exists(ftp_root + f"{base_version(release)}/{rfile}.asc"):
251+
if os.path.exists(filename + ".asc"):
241252
d["gpg_signature_file"] = sigfile_for(base_version(release), rfile)
242253
# Upload Sigstore signature
243-
if os.path.exists(ftp_root + f"{base_version(release)}/{rfile}.sig"):
254+
if os.path.exists(filename + ".sig"):
244255
d["sigstore_signature_file"] = (
245256
download_root + f"{base_version(release)}/{rfile}.sig"
246257
)
247258
# Upload Sigstore certificate
248-
if os.path.exists(ftp_root + f"{base_version(release)}/{rfile}.crt"):
259+
if os.path.exists(filename + ".crt"):
249260
d["sigstore_cert_file"] = download_root + f"{base_version(release)}/{rfile}.crt"
250261
# Upload Sigstore bundle
251-
if os.path.exists(ftp_root + f"{base_version(release)}/{rfile}.sigstore"):
262+
if os.path.exists(filename + ".sigstore"):
252263
d["sigstore_bundle_file"] = (
253264
download_root + f"{base_version(release)}/{rfile}.sigstore"
254265
)
255266
# Upload SPDX SBOM file
256-
if os.path.exists(ftp_root + f"{base_version(release)}/{rfile}.spdx.json"):
267+
if os.path.exists(filename + ".spdx.json"):
257268
d["sbom_spdx2_file"] = (
258269
download_root + f"{base_version(release)}/{rfile}.spdx.json"
259270
)
260271

261272
return d
262273

263274

264-
def list_files(release: str) -> Generator[tuple[str, str, int, bool, str], None, None]:
275+
def list_files(
276+
ftp_root: str, release: str
277+
) -> Generator[tuple[str, str, int, bool, str], None, None]:
265278
"""List all of the release's download files."""
266279
reldir = base_version(release)
267280
for rfile in os.listdir(path.join(ftp_root, reldir)):
@@ -283,15 +296,14 @@ def list_files(release: str) -> Generator[tuple[str, str, int, bool, str], None,
283296

284297
for rx, info in get_file_descriptions(release):
285298
if rx.search(rfile):
286-
file_desc, os_pk, add_download, add_desc = info
287-
yield rfile, file_desc, os_pk, add_download, add_desc
299+
yield (rfile, *info)
288300
break
289301
else:
290302
print(f" File {reldir}/{rfile} not recognized")
291303
continue
292304

293305

294-
def query_object(objtype: str, **params: Any) -> int:
306+
def query_object(base_url: str, objtype: str, **params: Any) -> int:
295307
"""Find an API object by query parameters."""
296308
uri = base_url + f"downloads/{objtype}/"
297309
uri += "?" + "&".join(f"{k}={v}" for k, v in params.items())
@@ -302,7 +314,7 @@ def query_object(objtype: str, **params: Any) -> int:
302314
return int(obj["resource_uri"].strip("/").split("/")[-1])
303315

304316

305-
def post_object(objtype: str, datadict: dict[str, Any]) -> int:
317+
def post_object(base_url: str, objtype: str, datadict: dict[str, Any]) -> int:
306318
"""Create a new API object."""
307319
resp = requests.post(
308320
base_url + "downloads/" + objtype + "/",
@@ -324,11 +336,11 @@ def post_object(objtype: str, datadict: dict[str, Any]) -> int:
324336

325337

326338
def sign_release_files_with_sigstore(
327-
release: str, release_files: list[tuple[str, str, int, bool, str]]
339+
ftp_root: str, release: str, release_files: list[tuple[str, str, int, bool, str]]
328340
) -> None:
329341
filenames = [
330342
ftp_root + f"{base_version(release)}/{rfile}"
331-
for rfile, file_desc, os_pk, add_download, add_desc in release_files
343+
for rfile, *_ in release_files
332344
]
333345

334346
def has_sigstore_signature(filename: str) -> bool:
@@ -445,34 +457,65 @@ def has_sigstore_signature(filename: str) -> bool:
445457
)
446458

447459

460+
def parse_args() -> argparse.Namespace:
461+
def ensure_trailing_slash(s: str):
462+
if not s.endswith("/"):
463+
s += "/"
464+
return s
465+
466+
parser = argparse.ArgumentParser()
467+
parser.add_argument(
468+
"--base-url",
469+
metavar="URL",
470+
type=ensure_trailing_slash,
471+
default="https://www.python.org/api/v1/",
472+
help="API URL; defaults to %(default)s",
473+
)
474+
parser.add_argument(
475+
"--ftp-root",
476+
metavar="DIR",
477+
type=ensure_trailing_slash,
478+
default="/srv/www.python.org/ftp/python",
479+
help="FTP root; defaults to %(default)s",
480+
)
481+
parser.add_argument(
482+
"release",
483+
help="Python version number, e.g. 3.3.5rc2",
484+
)
485+
return parser.parse_args()
486+
487+
448488
def main() -> None:
449-
rel = sys.argv[1]
489+
args = parse_args()
490+
rel = args.release
450491
print("Querying python.org for release", rel)
451-
rel_pk = query_object("release", name="Python+" + rel)
492+
rel_pk = query_object(args.base_url, "release", name="Python+" + rel)
452493
print("Found Release object: id =", rel_pk)
453-
release_files = list(list_files(rel))
454-
sign_release_files_with_sigstore(rel, release_files)
494+
495+
release_files = list(list_files(args.ftp_root, rel))
496+
sign_release_files_with_sigstore(args.ftp_root, rel, release_files)
455497
n = 0
456498
file_dicts = {}
457-
for rfile, file_desc, os_pk, add_download, add_desc in release_files:
499+
for rfile, file_desc, os_slug, add_download, add_desc in release_files:
500+
if not os_slug:
501+
continue
502+
os_pk = query_object(args.base_url, "os", slug=os_slug)
458503
file_dict = build_file_dict(
459-
rel, rfile, rel_pk, file_desc, os_pk, add_download, add_desc
504+
args.ftp_root, rel, rfile, rel_pk, file_desc, os_pk, add_download, add_desc
460505
)
461506
key = file_dict["slug"]
462-
if not os_pk:
463-
continue
464507
print("Creating ReleaseFile object for", rfile, key)
465508
if key in file_dicts:
466509
raise RuntimeError(f"duplicate slug generated: {key}")
467510
file_dicts[key] = file_dict
468511
print("Deleting previous release files")
469512
resp = requests.delete(
470-
base_url + f"downloads/release_file/?release={rel_pk}", headers=headers
513+
args.base_url + f"downloads/release_file/?release={rel_pk}", headers=headers
471514
)
472515
if resp.status_code != 204:
473516
raise RuntimeError(f"deleting previous releases failed: {resp.status_code}")
474517
for file_dict in file_dicts.values():
475-
file_pk = post_object("release_file", file_dict)
518+
file_pk = post_object(args.base_url, "release_file", file_dict)
476519
if file_pk >= 0:
477520
print("Created as id =", file_pk)
478521
n += 1

tests/fake-ftp-files.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,7 @@ python-3.14.0b2.exe.spdx.json
521521
python-3.14.0b2t-amd64.zip
522522
python-3.14.0b2t-arm64.zip
523523
python-3.14.0b2t-win32.zip
524+
python-3.14.0b3-aarch64-linux-android.tar.gz
524525
python-3.14.0b3-amd64.exe
525526
python-3.14.0b3-amd64.exe.crt
526527
python-3.14.0b3-amd64.exe.sig
@@ -559,6 +560,7 @@ python-3.14.0b3-test-amd64.zip
559560
python-3.14.0b3-test-arm64.zip
560561
python-3.14.0b3-test-win32.zip
561562
python-3.14.0b3-win32.zip
563+
python-3.14.0b3-x86_64-linux-android.tar.gz
562564
python-3.14.0b3.exe
563565
python-3.14.0b3.exe.crt
564566
python-3.14.0b3.exe.sig

0 commit comments

Comments
 (0)