#!/usr/bin/env 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 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
)
elif self.config[
"arch"] ==
"aarch64":
return "openh264-linux64-aarch64-{version}.zip".format(version=version)
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(
r
"(?: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()