Skip to content

Commit 3408b69

Browse files
committed
Add plex build script
1 parent eae6065 commit 3408b69

File tree

2 files changed

+328
-0
lines changed

2 files changed

+328
-0
lines changed

plex/Artifactory.spec.in

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{{
2+
"files": [
3+
{{
4+
"pattern": "{build_root}/build/{package_name}",
5+
"target": "qt-builds/{qt_version}",
6+
"props": "plex.v1.os={os_name};plex.v1.qt_version={qt_version};plex.v1.qt_fork_sha={git_sha};plex.v1.build_type={build_type};build.name=Plex Qt Fork;build.number={full_version}"
7+
}}
8+
]
9+
}}

plex/build.py

+319
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
1+
#! /usr/bin/env python3
2+
3+
import platform
4+
import os
5+
import re
6+
import subprocess as sp
7+
import shutil
8+
9+
from pathlib import Path
10+
from io import StringIO
11+
from contextlib import contextmanager
12+
13+
14+
SCRIPT_PATH = Path(__file__).parent.resolve()
15+
BUILD_ROOT = SCRIPT_PATH.parent.resolve()
16+
17+
18+
class Build:
19+
skip_modules = [
20+
"qt3d",
21+
"qtdoc",
22+
"qtmultimedia",
23+
"qtsensors",
24+
"qtserialport",
25+
"qtactiveqt",
26+
"qtcharts",
27+
"qtdatavis3d",
28+
"qtgraphicaleffects",
29+
"qtpurchasing",
30+
"qtwebview",
31+
"qtscript",
32+
"qtlocation",
33+
"qtscxml",
34+
"qtspeech",
35+
"qtlottie",
36+
"qtwebglplugin",
37+
]
38+
39+
common_flags = [
40+
"-opensource",
41+
"-confirm-license",
42+
"-webengine-proprietary-codecs",
43+
"-nomake tests",
44+
"-nomake examples",
45+
"-no-gif",
46+
"-qt-libpng",
47+
"-qt-libjpeg",
48+
"-qt-pcre",
49+
"-no-cups",
50+
"-no-dbus",
51+
"-pch",
52+
"-no-qml-debug",
53+
"-no-openssl",
54+
"-c++std c++14",
55+
]
56+
57+
prefix = "qt-install"
58+
59+
def __init__(self, profile: str):
60+
self.profile = profile
61+
self.common_flags.append(f"-prefix {str(self.build_root / self.prefix)}")
62+
63+
def run(self, is_dry=False):
64+
flags = self.compute_flags()
65+
env = self.compute_env()
66+
if self.is_windows:
67+
return self.run_windows(is_dry, flags, env)
68+
elif self.is_macos:
69+
return self.run_macos(is_dry, flags, env)
70+
71+
def run_windows(self, is_dry, flags, env):
72+
self._download_jom()
73+
# to make the path as short as possible we create a junction here.
74+
rootdrive = os.path.splitdrive(os.getenv("JENKINS_WORKSPACE_ROOT", "c:\\"))[0]
75+
root_junction = Path(f"{rootdrive}\\j")
76+
root_junction.mkdir(exist_ok=True)
77+
junctiondir = root_junction / os.getenv("PLEX_BUILD_HASH", "xx")
78+
if junctiondir.exists():
79+
# junctions? more like junk-tions
80+
sp.run(["fsutil", "reparsepoint", "delete", str(junctiondir)], shell=True)
81+
try:
82+
shutil.rmtree(junctiondir)
83+
except FileNotFoundError:
84+
pass
85+
mklink = sp.run(["mklink", "/J", str(junctiondir), str(self.build_root)], shell=True)
86+
87+
paths = [self.python_path, *self._sanitize_path()]
88+
for extra_path in ("gnuwin/bin", "qtbase/bin", str(self.script_path)):
89+
paths.append(str(self.build_root / extra_path))
90+
env["PATH"] = os.pathsep.join(paths)
91+
env["PYTHONHOME"] = self.python_path
92+
93+
with StringIO() as script:
94+
for key, value in env.items():
95+
print(f"set {key}={value}", file=script)
96+
print(file=script)
97+
98+
vs_dir = self._get_vs_dir()
99+
print(f"python -V", file=script)
100+
print(f'call "{vs_dir}\\VC\\Auxiliary\\Build\\vcvarsall.bat" amd64', file=script)
101+
print(f"call {str(self.build_root / 'configure.bat')} {' '.join(flags)}", file=script)
102+
print(f"jom /j{self.jobs}", file=script)
103+
print(f"if %ERRORLEVEL% GEQ 1 EXIT /B %ERRORLEVEL%", file=script)
104+
print(f"jom install", file=script)
105+
print(f"if %ERRORLEVEL% GEQ 1 EXIT /B %ERRORLEVEL%", file=script)
106+
print(script.getvalue())
107+
build_script = Path("plex_build.cmd")
108+
with build_script.open("w") as fp:
109+
fp.write(script.getvalue())
110+
if not is_dry:
111+
sp.run(["cmd", "/C", str(build_script.resolve())]).check_returncode()
112+
else:
113+
print(script.getvalue())
114+
115+
def run_macos(self, is_dry, flags, env):
116+
with StringIO() as script:
117+
print("#! /bin/bash", file=script)
118+
print("set +x", file=script)
119+
print("set +v", file=script)
120+
print("set +e", file=script)
121+
print(file=script)
122+
for key, value in env.items():
123+
print(f"export {key}='{value}'", file=script)
124+
print(file=script)
125+
print(f"python -V", file=script)
126+
print(f"{self.build_root}/configure {' '.join(flags)}", file=script)
127+
print(f"make -j {self.jobs}", file=script)
128+
print(f"make install", file=script)
129+
build_script = Path("plex_build.sh")
130+
with build_script.open("w") as fp:
131+
fp.write(script.getvalue())
132+
import stat
133+
st = os.stat(build_script).st_mode
134+
os.chmod(build_script, st | stat.S_IEXEC)
135+
print(script.getvalue())
136+
if not is_dry:
137+
sp.run(["bash", "-c", str(build_script.resolve())]).check_returncode()
138+
139+
def package(self):
140+
with chdir(self.build_root / self.prefix):
141+
print(f"Creating {self.package_name}")
142+
cmake = sp.run(["cmake", "-E", "tar", "cJf",
143+
f"../{self.package_name}",
144+
"--format=gnutar", "."])
145+
cmake.check_returncode()
146+
147+
def compute_flags(self) -> str:
148+
flags = self.common_flags
149+
flags += [f"-skip {mod}" for mod in self.skip_modules]
150+
151+
if self.is_macos:
152+
flags += [
153+
"-securetransport",
154+
"-opengl desktop",
155+
"-sdk macosx10.15",
156+
"-device-option QMAKE_APPLE_DEVICE_ARCHS=x86_64",
157+
"-xplatform macx-clang",
158+
"-reduce-exports",
159+
]
160+
if self.is_debug:
161+
flags += ["-debug-and-release"]
162+
elif self.is_windows:
163+
flags += [ "-schannel", "-opengl dynamic" ]
164+
if self.is_debug:
165+
flags += ["-debug"]
166+
167+
if not self.is_debug:
168+
flags += ["-release", "-ltcg", "-optimize-size"]
169+
else:
170+
flags += ["-separate-debug-info"]
171+
172+
return flags
173+
174+
def compute_env(self):
175+
jobs = self.jobs
176+
environment = {
177+
"CFLAGS": "", "CXXFLAGS": "", "LDFLAGS": "", "CL": "",
178+
"NINJAFLAGS": f"-j{jobs} -v",
179+
"PATH": f"{self.python_path}{os.pathsep}{os.environ['PATH']}"
180+
}
181+
return environment
182+
183+
def write_spec(self):
184+
template = open(self.script_path / "Artifactory.spec.in").read()
185+
os_name = {
186+
"Darwin": "Macos",
187+
"Windows": "Windows",
188+
"Linux": "Linux"
189+
}[platform.system()]
190+
subst = {
191+
"build_root": str(self.build_root).replace("\\", "/"),
192+
"package_name": self.package_name,
193+
"qt_version": self.qt_version,
194+
"git_sha": self.git_sha,
195+
"full_version": self.full_version,
196+
"build_type": self.build_type,
197+
"os_name": os_name,
198+
}
199+
with open("Artifactory.spec", "w") as spec:
200+
spec.write(template.format(**subst))
201+
202+
@property
203+
def is_macos(self):
204+
return platform.system() == "Darwin"
205+
206+
@property
207+
def is_windows(self):
208+
return platform.system() == "Windows"
209+
210+
@property
211+
def is_debug(self):
212+
return self.profile.endswith("-debug")
213+
214+
@property
215+
def build_type(self):
216+
return "debug" if self.is_debug else "release"
217+
218+
@property
219+
def build_root(self):
220+
return BUILD_ROOT
221+
222+
@property
223+
def python_path(self):
224+
if self.is_windows:
225+
return "C:\\Python27"
226+
elif self.is_macos:
227+
return "/usr/bin"
228+
229+
@property
230+
def script_path(self):
231+
return SCRIPT_PATH
232+
233+
@property
234+
def jobs(self):
235+
return os.getenv("PLEX_JOBS", "6")
236+
237+
@property
238+
def qt_version(self):
239+
with open(self.build_root / "qtbase/.qmake.conf") as qconfig:
240+
for line in qconfig:
241+
match = re.search(r'MODULE_VERSION = (.+)', line)
242+
if match:
243+
return match.group(1)
244+
raise RuntimeError("Could not read MODULE_VERSION from qtbase/.qmake.conf")
245+
246+
@property
247+
def full_version(self):
248+
return f"{self.qt_version}-{self.git_sha[:8]}"
249+
250+
@property
251+
def git_sha(self):
252+
if "GIT_COMMIT" in os.environ:
253+
sha = os.environ["GIT_COMMIT"]
254+
else:
255+
git = sp.run(["git", "rev-parse", "HEAD"], stdout=sp.PIPE)
256+
git.check_returncode()
257+
sha = git.stdout.decode().strip()
258+
return sha
259+
260+
@property
261+
def package_name(self):
262+
os_name = platform.system().lower()
263+
return f"qt-{self.full_version}-{os_name}-x86_64-{self.build_type}.tar.xz"
264+
265+
def _download_jom(self):
266+
# we have curl on the build nodes, but not requests (it's also quicker)
267+
sp.run(["curl", "-L", "-o", "jom.zip",
268+
"https://download.qt.io/official_releases/jom/jom.zip"])
269+
from zipfile import ZipFile
270+
with ZipFile("jom.zip", "r") as zfp:
271+
zfp.extractall()
272+
os.remove("jom.zip")
273+
274+
def _download_vswhere(self):
275+
sp.run(["curl", "-L", "-o", "vswhere.exe",
276+
"https://github.com/microsoft/vswhere/releases/download/2.8.4/vswhere.exe"])
277+
278+
def _get_vs_dir(self):
279+
import json
280+
self._download_vswhere()
281+
vswhere = sp.run([str(Path.cwd() / "vswhere.exe"), "-format", "json",
282+
"-version", "15.0"], stdout=sp.PIPE)
283+
vswhere.check_returncode()
284+
vs_data = json.loads(vswhere.stdout.decode())
285+
return vs_data[0]["installationPath"]
286+
287+
def _sanitize_path(self):
288+
paths = os.environ["PATH"].split(os.pathsep)
289+
result = []
290+
for path in paths:
291+
if "python" in path.lower() or "pyenv" in path.lower():
292+
continue
293+
result.append(path)
294+
return result
295+
296+
297+
@contextmanager
298+
def chdir(dirname):
299+
try:
300+
cwd = os.getcwd()
301+
os.chdir(dirname)
302+
yield
303+
finally:
304+
os.chdir(cwd)
305+
306+
307+
if __name__ == "__main__":
308+
from argparse import ArgumentParser
309+
parser = ArgumentParser()
310+
parser.add_argument("--dry-run", action="store_true", help="Only display steps")
311+
parser.add_argument("--make-package", action="store_true", help="Create tarball")
312+
parser.add_argument("profile")
313+
args = parser.parse_args()
314+
build = Build(args.profile)
315+
build.run(args.dry_run)
316+
if args.make_package:
317+
build.package()
318+
build.write_spec()
319+

0 commit comments

Comments
 (0)