forked from mirrors/gecko-dev
Ideally, we wouldn't rely on tooltool, but until we can have private fetches that can actually be used forever without having to be retriggered for CoT or other reasons, tooltool is the best we have. Differential Revision: https://phabricator.services.mozilla.com/D197305
105 lines
3.7 KiB
Python
105 lines
3.7 KiB
Python
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
import hashlib
|
|
import os
|
|
import shutil
|
|
import stat
|
|
import sys
|
|
import tempfile
|
|
from io import BytesIO
|
|
from urllib.request import urlopen
|
|
|
|
from mozpack.macpkg import Pbzx, uncpio, unxar
|
|
|
|
|
|
def unpack_sdk(url, sha512, extract_prefix, out_dir="."):
|
|
if "MOZ_AUTOMATION" in os.environ:
|
|
url = f"http://taskcluster/tooltool.mozilla-releng.net/sha512/{sha512}"
|
|
with tempfile.TemporaryFile() as pkg:
|
|
hash = hashlib.sha512()
|
|
for attempt in range(3):
|
|
if attempt != 0:
|
|
print(f"Failed to download from {url}. Retrying", file=sys.stderr)
|
|
|
|
with urlopen(url) as fh:
|
|
# Equivalent to shutil.copyfileobj, but computes sha512 at the same time.
|
|
while True:
|
|
buf = fh.read(1024 * 1024)
|
|
if not buf:
|
|
break
|
|
hash.update(buf)
|
|
pkg.write(buf)
|
|
digest = hash.hexdigest()
|
|
if digest == sha512:
|
|
break
|
|
else:
|
|
raise Exception(f"(actual) {digest} != (expected) {sha512}")
|
|
|
|
pkg.seek(0, os.SEEK_SET)
|
|
|
|
for name, content in unxar(pkg):
|
|
if name in ("Payload", "Content"):
|
|
extract_payload(content, extract_prefix, out_dir)
|
|
|
|
|
|
def extract_payload(fileobj, extract_prefix, out_dir="."):
|
|
hardlinks = {}
|
|
for path, st, content in uncpio(Pbzx(fileobj)):
|
|
if not path:
|
|
continue
|
|
path = path.decode()
|
|
matches = path.startswith(extract_prefix)
|
|
if matches:
|
|
path = os.path.join(out_dir, path[len(extract_prefix) :].lstrip("/"))
|
|
|
|
# When there are hardlinks, normally a cpio stream is supposed to
|
|
# contain the data for all of them, but, even with compression, that
|
|
# can be a waste of space, so in some cpio streams (*cough* *cough*,
|
|
# Apple's, e.g. in Xcode), the files after the first one have dummy
|
|
# data.
|
|
# As we may be filtering the first file out (if it doesn't match
|
|
# extract_prefix), we need to keep its data around (we're not going
|
|
# to be able to rewind).
|
|
if stat.S_ISREG(st.mode) and st.nlink > 1:
|
|
key = (st.dev, st.ino)
|
|
hardlink = hardlinks.get(key)
|
|
if hardlink:
|
|
hardlink[0] -= 1
|
|
if hardlink[0] == 0:
|
|
del hardlinks[key]
|
|
content = hardlink[1]
|
|
if isinstance(content, BytesIO):
|
|
content.seek(0)
|
|
if matches:
|
|
hardlink[1] = path
|
|
elif matches:
|
|
hardlink = hardlinks[key] = [st.nlink - 1, path]
|
|
else:
|
|
hardlink = hardlinks[key] = [st.nlink - 1, BytesIO(content.read())]
|
|
content = hardlink[1]
|
|
|
|
if not matches:
|
|
continue
|
|
if stat.S_ISDIR(st.mode):
|
|
os.makedirs(path, exist_ok=True)
|
|
else:
|
|
parent = os.path.dirname(path)
|
|
if parent:
|
|
os.makedirs(parent, exist_ok=True)
|
|
|
|
if stat.S_ISLNK(st.mode):
|
|
os.symlink(content.read(), path)
|
|
elif stat.S_ISREG(st.mode):
|
|
if isinstance(content, str):
|
|
os.link(content, path)
|
|
else:
|
|
with open(path, "wb") as out:
|
|
shutil.copyfileobj(content, out)
|
|
else:
|
|
raise Exception(f"File mode {st.mode:o} is not supported")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unpack_sdk(*sys.argv[1:])
|