Skip to content

Commit 28ddf95

Browse files
BryceBeaglestlehmann
authored andcommitted
Script to build upstream
1 parent e6c8458 commit 28ddf95

File tree

1 file changed

+118
-0
lines changed

1 file changed

+118
-0
lines changed

build_upstream.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import argparse
2+
import io
3+
import re
4+
import tarfile
5+
from pathlib import Path
6+
import typing
7+
8+
import docker
9+
from docker import DockerClient
10+
from docker.models.images import Image
11+
from docker.utils.json_stream import json_stream
12+
13+
DEFAULT_DOCKERFILE = Path("Dockerfile")
14+
DEFAULT_OUTPUT_DIR = Path("PyQt5-stubs")
15+
16+
17+
def parse_args() -> argparse.Namespace:
18+
parser = argparse.ArgumentParser(description="Build PyQt stubs in Docker")
19+
20+
# noinspection PyTypeChecker
21+
parser.add_argument('-d', '--dockerfile', type=Path,
22+
default=DEFAULT_DOCKERFILE,
23+
help="Dockerfile to build")
24+
25+
# noinspection PyTypeChecker
26+
parser.add_argument('-o', '--output-dir', type=Path,
27+
default=DEFAULT_OUTPUT_DIR,
28+
help="Directory to find package(s) to be built. "
29+
"Defaults to ./pkg")
30+
31+
return parser.parse_args()
32+
33+
34+
def main():
35+
args = parse_args()
36+
37+
docker_client = docker.from_env()
38+
39+
image_id = build_image(docker_client, args.dockerfile)
40+
41+
extract_output(docker_client, image_id, args.output_dir)
42+
43+
44+
def build_image(docker_client: DockerClient, dockerfile: Path) -> str:
45+
image_name = "pyqt5-stubs"
46+
47+
# Using low-level API so that we can log as it occurs instead of only
48+
# after build has finished/failed
49+
resp = docker_client.api.build(
50+
path=str(dockerfile.parent),
51+
rm=True,
52+
tag=image_name)
53+
54+
image_id: str = typing.cast(str, None)
55+
for chunk in json_stream(resp):
56+
if 'error' in chunk:
57+
message = f"Error while building Dockerfile for " \
58+
f"{image_name}:\n{chunk['error']}"
59+
print(message)
60+
raise DockerBuildError(message)
61+
62+
elif 'stream' in chunk:
63+
print(chunk['stream'].rstrip('\n'))
64+
# Taken from the high level API implementation of build
65+
match = re.search(r'(^Successfully built |sha256:)([0-9a-f]+)$',
66+
chunk['stream'])
67+
if match:
68+
image_id = match.group(2)
69+
70+
if not image_id:
71+
message = f"Unknown Error while building Dockerfile for " \
72+
f"{image_name}. Build did not return an image ID"
73+
raise DockerBuildError(message)
74+
75+
return image_id
76+
77+
78+
def extract_output(docker_client: DockerClient, image_id: str,
79+
output_dir: Path) -> None:
80+
image = docker_client.images.get(image_id)
81+
container = docker_client.containers.create(image)
82+
83+
# Get archive tar bytes from the container as a sequence of bytes
84+
package_tar_byte_gen: typing.Generator[bytes, None, None]
85+
package_tar_byte_gen, _ = container.get_archive("/output/",
86+
chunk_size=None)
87+
88+
# Concat all the chunks together
89+
package_tar_bytes: bytes
90+
package_tar_bytes = b"".join(package_tar_byte_gen)
91+
92+
# Create a tarfile from the tar bytes
93+
tar_file_object = io.BytesIO(package_tar_bytes)
94+
package_tar = tarfile.open(fileobj=tar_file_object)
95+
96+
# Extract the files from the tarfile to the disk
97+
for tar_deb_info in package_tar.getmembers():
98+
# Ignore directories
99+
if not tar_deb_info.isfile():
100+
continue
101+
102+
# Directory that will contain the output files
103+
output_dir.mkdir(parents=True, exist_ok=True)
104+
105+
# Filename (without outer directory)
106+
tar_deb_info.name = Path(tar_deb_info.name).name
107+
108+
# Extract
109+
package_tar.extract(tar_deb_info, output_dir)
110+
111+
112+
class DockerBuildError(RuntimeError):
113+
def __init__(self, message):
114+
self.message = message
115+
116+
117+
if __name__ == '__main__':
118+
main()

0 commit comments

Comments
 (0)