diff options
Diffstat (limited to 'testing/mozharness/scripts/openh264_build.py')
-rwxr-xr-x | testing/mozharness/scripts/openh264_build.py | 470 |
1 files changed, 470 insertions, 0 deletions
diff --git a/testing/mozharness/scripts/openh264_build.py b/testing/mozharness/scripts/openh264_build.py new file mode 100755 index 0000000000..9905ca7b31 --- /dev/null +++ b/testing/mozharness/scripts/openh264_build.py @@ -0,0 +1,470 @@ +#!/usr/bin/env python +# ***** BEGIN LICENSE BLOCK ***** +# 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/. +# ***** END LICENSE BLOCK ***** +import glob +import os +import re +import subprocess +import sys + +# load modules from parent dir +sys.path.insert(1, os.path.dirname(sys.path[0])) + +# import the guts +import mozharness +from mozharness.base.log import DEBUG, ERROR, FATAL +from mozharness.base.transfer import TransferMixin +from mozharness.base.vcs.vcsbase import VCSScript +from mozharness.mozilla.tooltool import TooltoolMixin + +external_tools_path = os.path.join( + os.path.abspath(os.path.dirname(os.path.dirname(mozharness.__file__))), + "external_tools", +) + + +class OpenH264Build(TransferMixin, VCSScript, TooltoolMixin): + all_actions = [ + "clobber", + "get-tooltool", + "checkout-sources", + "build", + "test", + "package", + "dump-symbols", + ] + + default_actions = [ + "get-tooltool", + "checkout-sources", + "build", + "package", + "dump-symbols", + ] + + config_options = [ + [ + ["--repo"], + { + "dest": "repo", + "help": "OpenH264 repository to use", + "default": "https://github.com/dminor/openh264.git", + }, + ], + [ + ["--rev"], + {"dest": "revision", "help": "revision to checkout", "default": "master"}, + ], + [ + ["--debug"], + { + "dest": "debug_build", + "action": "store_true", + "help": "Do a debug build", + }, + ], + [ + ["--arch"], + { + "dest": "arch", + "help": "Arch type to use (x64, x86, arm, or aarch64)", + }, + ], + [ + ["--os"], + { + "dest": "operating_system", + "help": "Specify the operating system to build for", + }, + ], + [ + ["--branch"], + { + "dest": "branch", + "help": "dummy option", + }, + ], + [ + ["--build-pool"], + { + "dest": "build_pool", + "help": "dummy option", + }, + ], + ] + + def __init__( + self, + require_config_file=False, + config={}, + all_actions=all_actions, + default_actions=default_actions, + ): + + # Default configuration + default_config = { + "debug_build": False, + "upload_ssh_key": "~/.ssh/ffxbld_rsa", + "upload_ssh_user": "ffxbld", + "upload_ssh_host": "upload.ffxbld.productdelivery.prod.mozaws.net", + "upload_path_base": "/tmp/openh264", + } + default_config.update(config) + + VCSScript.__init__( + self, + config_options=self.config_options, + require_config_file=require_config_file, + config=default_config, + all_actions=all_actions, + default_actions=default_actions, + ) + + def query_abs_dirs(self): + if self.abs_dirs: + return self.abs_dirs + dirs = super(OpenH264Build, self).query_abs_dirs() + dirs["abs_upload_dir"] = os.path.join(dirs["abs_work_dir"], "upload") + self.abs_dirs = dirs + return self.abs_dirs + + def get_tooltool(self): + c = self.config + if not c.get("tooltool_manifest_file"): + self.info("Skipping tooltool fetching since no tooltool manifest") + return + dirs = self.query_abs_dirs() + self.mkdir_p(dirs["abs_work_dir"]) + manifest = os.path.join( + dirs["abs_src_dir"], + "testing", + "mozharness", + "configs", + "openh264", + "tooltool-manifests", + c["tooltool_manifest_file"], + ) + self.info("Getting tooltool files from manifest (%s)" % manifest) + try: + self.tooltool_fetch( + manifest=manifest, + output_dir=os.path.join(dirs["abs_work_dir"]), + cache=c.get("tooltool_cache"), + ) + except KeyError: + self.error("missing a required key.") + + def query_package_name(self): + if self.config["arch"] in ("x64", "aarch64"): + bits = "64" + else: + bits = "32" + version = self.config["revision"] + + if sys.platform in ("linux2", "linux"): + if self.config.get("operating_system") == "android": + return "openh264-android-{arch}-{version}.zip".format( + version=version, arch=self.config["arch"] + ) + elif self.config.get("operating_system") == "darwin": + suffix = "" + if self.config["arch"] != "x64": + suffix = "-" + self.config["arch"] + return "openh264-macosx{bits}{suffix}-{version}.zip".format( + version=version, bits=bits, suffix=suffix + ) + else: + return "openh264-linux{bits}-{version}.zip".format( + version=version, bits=bits + ) + elif sys.platform == "win32": + if self.config["arch"] == "aarch64": + return "openh264-win64-aarch64-{version}.zip".format(version=version) + else: + return "openh264-win{bits}-{version}.zip".format( + version=version, bits=bits + ) + self.fatal("can't determine platform") + + def query_make_params(self): + retval = [] + if self.config["debug_build"]: + retval.append("BUILDTYPE=Debug") + + if self.config["arch"] in ("x64", "aarch64"): + retval.append("ENABLE64BIT=Yes") + else: + retval.append("ENABLE64BIT=No") + + if self.config["arch"] == "x86": + retval.append("ARCH=x86") + elif self.config["arch"] == "x64": + retval.append("ARCH=x86_64") + elif self.config["arch"] == "aarch64": + retval.append("ARCH=arm64") + else: + self.fatal("Unknown arch: {}".format(self.config["arch"])) + + if "operating_system" in self.config: + retval.append("OS=%s" % self.config["operating_system"]) + if self.config["operating_system"] == "android": + retval.append("TARGET=invalid") + retval.append("NDKLEVEL=%s" % self.config["min_sdk"]) + retval.append("NDKROOT=%s/android-ndk" % os.environ["MOZ_FETCHES_DIR"]) + retval.append("NDK_TOOLCHAIN_VERSION=clang") + if self.config["operating_system"] == "darwin": + retval.append("OS=darwin") + + if self._is_windows(): + retval.append("OS=msvc") + retval.append("CC=clang-cl") + retval.append("CXX=clang-cl") + if self.config["arch"] == "aarch64": + retval.append("CXX_LINK_O=-nologo --target=aarch64-windows-msvc -Fe$@") + else: + retval.append("CC=clang") + retval.append("CXX=clang++") + + return retval + + def query_upload_ssh_key(self): + return self.config["upload_ssh_key"] + + def query_upload_ssh_host(self): + return self.config["upload_ssh_host"] + + def query_upload_ssh_user(self): + return self.config["upload_ssh_user"] + + def query_upload_ssh_path(self): + return "%s/%s" % (self.config["upload_path_base"], self.config["revision"]) + + def run_make(self, target, capture_output=False): + make = ( + f"{os.environ['MOZ_FETCHES_DIR']}/mozmake/mozmake" + if sys.platform == "win32" + else "make" + ) + cmd = [make, target] + self.query_make_params() + dirs = self.query_abs_dirs() + repo_dir = os.path.join(dirs["abs_work_dir"], "openh264") + env = None + if self.config.get("partial_env"): + env = self.query_env(self.config["partial_env"]) + kwargs = dict(cwd=repo_dir, env=env) + if capture_output: + return self.get_output_from_command(cmd, **kwargs) + else: + return self.run_command(cmd, **kwargs) + + def _git_checkout(self, repo, repo_dir, rev): + try: + subprocess.run(["git", "clone", "-q", "--no-checkout", repo, repo_dir]) + subprocess.run(["git", "checkout", "-q", "-f", f"{rev}^0"], cwd=repo_dir) + except Exception: + self.rmtree(repo_dir) + raise + return True + + def checkout_sources(self): + repo = self.config["repo"] + rev = self.config["revision"] + + dirs = self.query_abs_dirs() + repo_dir = os.path.join(dirs["abs_work_dir"], "openh264") + + if self._is_windows(): + # We don't have git on our windows builders, so download a zip + # package instead. + path = repo.replace(".git", "/archive/") + rev + ".zip" + self.download_file(path) + self.unzip(rev + ".zip", dirs["abs_work_dir"]) + self.move( + os.path.join(dirs["abs_work_dir"], "openh264-" + rev), + os.path.join(dirs["abs_work_dir"], "openh264"), + ) + + # Retrieve in-tree version of gmp-api + self.copytree( + os.path.join(dirs["abs_src_dir"], "dom", "media", "gmp", "gmp-api"), + os.path.join(repo_dir, "gmp-api"), + ) + + # We need gas-preprocessor.pl for arm64 builds + if self.config["arch"] == "aarch64": + openh264_dir = os.path.join(dirs["abs_work_dir"], "openh264") + self.download_file( + ( + "https://raw.githubusercontent.com/libav/" + "gas-preprocessor/c2bc63c96678d9739509e58" + "7aa30c94bdc0e636d/gas-preprocessor.pl" + ), + parent_dir=openh264_dir, + ) + self.chmod(os.path.join(openh264_dir, "gas-preprocessor.pl"), 744) + + # gas-preprocessor.pl expects cpp to exist + # os.symlink is not available on Windows until we switch to + # Python 3. + os.system( + "ln -s %s %s" + % ( + os.path.join( + os.environ["MOZ_FETCHES_DIR"], "clang", "bin", "clang.exe" + ), + os.path.join(openh264_dir, "cpp"), + ) + ) + return 0 + + self.retry( + self._git_checkout, + error_level=FATAL, + error_message="Automation Error: couldn't clone repo", + args=(repo, repo_dir, rev), + ) + + # Checkout gmp-api + # TODO: Nothing here updates it yet, or enforces versions! + if not os.path.exists(os.path.join(repo_dir, "gmp-api")): + retval = self.run_make("gmp-bootstrap") + if retval != 0: + self.fatal("couldn't bootstrap gmp") + else: + self.info("skipping gmp bootstrap - we have it locally") + + # Checkout gtest + # TODO: Requires svn! + if not os.path.exists(os.path.join(repo_dir, "gtest")): + retval = self.run_make("gtest-bootstrap") + if retval != 0: + self.fatal("couldn't bootstrap gtest") + else: + self.info("skipping gtest bootstrap - we have it locally") + + return retval + + def build(self): + retval = self.run_make("plugin") + if retval != 0: + self.fatal("couldn't build plugin") + + def package(self): + dirs = self.query_abs_dirs() + srcdir = os.path.join(dirs["abs_work_dir"], "openh264") + package_name = self.query_package_name() + package_file = os.path.join(dirs["abs_work_dir"], package_name) + if os.path.exists(package_file): + os.unlink(package_file) + to_package = [] + for f in glob.glob(os.path.join(srcdir, "*gmpopenh264*")): + if not re.search( + "(?:lib)?gmpopenh264(?!\.\d)\.(?:dylib|so|dll|info)(?!\.\d)", f + ): + # Don't package unnecessary zip bloat + # Blocks things like libgmpopenh264.2.dylib and libgmpopenh264.so.1 + self.log("Skipping packaging of {package}".format(package=f)) + continue + to_package.append(os.path.basename(f)) + self.log("Packaging files %s" % to_package) + cmd = ["zip", package_file] + to_package + retval = self.run_command(cmd, cwd=srcdir) + if retval != 0: + self.fatal("couldn't make package") + self.copy_to_upload_dir( + package_file, dest=os.path.join(srcdir, "artifacts", package_name) + ) + + # Taskcluster expects this path to exist, but we don't use it + # because our builds are private. + path = os.path.join( + self.query_abs_dirs()["abs_work_dir"], "..", "public", "build" + ) + self.mkdir_p(path) + + def dump_symbols(self): + dirs = self.query_abs_dirs() + c = self.config + srcdir = os.path.join(dirs["abs_work_dir"], "openh264") + package_name = self.run_make("echo-plugin-name", capture_output=True) + if not package_name: + self.fatal("failure running make") + zip_package_name = self.query_package_name() + if not zip_package_name[-4:] == ".zip": + self.fatal("Unexpected zip_package_name") + symbol_package_name = "{base}.symbols.zip".format(base=zip_package_name[:-4]) + symbol_zip_path = os.path.join(srcdir, "artifacts", symbol_package_name) + repo_dir = os.path.join(dirs["abs_work_dir"], "openh264") + env = None + if self.config.get("partial_env"): + env = self.query_env(self.config["partial_env"]) + kwargs = dict(cwd=repo_dir, env=env) + dump_syms = os.path.join(dirs["abs_work_dir"], c["dump_syms_binary"]) + self.chmod(dump_syms, 0o755) + python = self.query_exe("python3") + cmd = [ + python, + os.path.join(external_tools_path, "packagesymbols.py"), + "--symbol-zip", + symbol_zip_path, + dump_syms, + os.path.join(srcdir, package_name), + ] + self.run_command(cmd, **kwargs) + + def test(self): + retval = self.run_make("test") + if retval != 0: + self.fatal("test failures") + + def copy_to_upload_dir( + self, + target, + dest=None, + log_level=DEBUG, + error_level=ERROR, + compress=False, + upload_dir=None, + ): + """Copy target file to upload_dir/dest. + + Potentially update a manifest in the future if we go that route. + + Currently only copies a single file; would be nice to allow for + recursive copying; that would probably done by creating a helper + _copy_file_to_upload_dir(). + """ + dest_filename_given = dest is not None + if upload_dir is None: + upload_dir = self.query_abs_dirs()["abs_upload_dir"] + if dest is None: + dest = os.path.basename(target) + if dest.endswith("/"): + dest_file = os.path.basename(target) + dest_dir = os.path.join(upload_dir, dest) + dest_filename_given = False + else: + dest_file = os.path.basename(dest) + dest_dir = os.path.join(upload_dir, os.path.dirname(dest)) + if compress and not dest_filename_given: + dest_file += ".gz" + dest = os.path.join(dest_dir, dest_file) + if not os.path.exists(target): + self.log("%s doesn't exist!" % target, level=error_level) + return None + self.mkdir_p(dest_dir) + self.copyfile(target, dest, log_level=log_level, compress=compress) + if os.path.exists(dest): + return dest + else: + self.log("%s doesn't exist after copy!" % dest, level=error_level) + return None + + +# main {{{1 +if __name__ == "__main__": + myScript = OpenH264Build() + myScript.run_and_exit() |