Source code

Revision control

Copy as Markdown

Other Tools

# 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 atexit
import datetime
import os
import subprocess
import sys
from fast_forward_libwebrtc import (
FastForwardError,
fast_forward_libwebrtc,
find_base_commit,
find_next_commit,
)
from filter_git_changes import filter_git_changes
from run_operations import (
ErrorHelp,
RepoType,
detect_repo_type,
get_last_line,
git_status,
run_git,
run_hg,
run_shell,
)
# This script drives the libwebrtc fast-forward process one upstream commit
# at a time. It is the Python port of loop-ff.sh and expects to be invoked
# with a fully-prepared environment (see loop-ff.sh, which sources
# use_config_env.sh before calling this script).
script_name = os.path.basename(__file__)
error_help = ErrorHelp()
error_help.set_prefix(
f"===loop-ff=== *** ERROR *** {script_name} did not complete successfully!"
)
# If DEBUG_LOOP_FF is set, echo commands run via run_command before executing.
DEBUG = os.environ.get("DEBUG_LOOP_FF", "") != ""
def early_exit_handler():
error_help.print_help()
def echo_log(msg):
print(f"===loop-ff=== {msg}")
def run_command(cmd, shell=False, extra_env=None, ignore_errors=False):
# Run a command, letting its stdout/stderr flow through to our own so that
# the loop-ff.sh wrapper captures everything in the log file.
display = cmd if isinstance(cmd, str) else " ".join(cmd)
if DEBUG:
print(f"+ {display}")
run_env = None
if extra_env is not None:
run_env = os.environ.copy()
run_env.update(extra_env)
run_kwargs = {"env": run_env}
if shell:
run_kwargs["shell"] = True
run_kwargs["executable"] = "/bin/bash"
# Flush first so our output stays ordered ahead of the child's output.
sys.stdout.flush()
res = subprocess.run(cmd, check=False, **run_kwargs)
if res.returncode != 0 and not ignore_errors:
caller_line = sys._getframe(1).f_lineno
echo_log(
f"Hit return code {res.returncode} at {script_name} line "
f"{caller_line}. Aborting."
)
sys.exit(res.returncode)
return res.returncode
def clean_exit(code=0):
# Unregister the error help handler so a normal/early exit doesn't
# falsely report as an error, then exit.
atexit.unregister(early_exit_handler)
sys.exit(code)
def count_moz_changed(is_git):
# Count the files changed in the newest local vendor commit, ignoring the
# README.mozilla bookkeeping files (and hg's '# files changed,' summary).
if is_git:
lines = run_git("git show --format= --name-status", ".")
else:
lines = run_hg("hg diff -c tip --stat")
lines = [line for line in lines if "files changed," not in line]
return len([line for line in lines if "README.mozilla" not in line])
def main():
script_dir = os.environ["SCRIPT_DIR"]
state_dir = os.environ["STATE_DIR"]
log_dir = os.environ["LOG_DIR"]
tmp_dir = os.environ["TMP_DIR"]
libwebrtc_src = os.environ.get("MOZ_LIBWEBRTC_SRC", "")
libwebrtc_branch = os.environ.get("MOZ_LIBWEBRTC_BRANCH", "")
target = os.environ.get("MOZ_TARGET_UPSTREAM_BRANCH_HEAD", "")
fastforward_bug = os.environ.get("MOZ_FASTFORWARD_BUG", "")
stop_after_commit = os.environ.get("MOZ_STOP_AFTER_COMMIT", "")
advance_one_commit = os.environ.get("MOZ_ADVANCE_ONE_COMMIT", "")
if libwebrtc_src == "":
print("MOZ_LIBWEBRTC_SRC is not defined, see README.md")
sys.exit(0)
if not os.path.isdir(libwebrtc_src):
print(f"Path {libwebrtc_src} is not found, see README.md")
sys.exit(0)
if libwebrtc_branch == "":
print("MOZ_LIBWEBRTC_BRANCH is not defined, see README.md")
sys.exit(0)
if stop_after_commit == "":
stdout_lines = run_git(
f"git show {target} --format=%h --name-only", libwebrtc_src
)
stop_after_commit = stdout_lines[0]
print(f"No MOZ_STOP_AFTER_COMMIT variable defined - stopping at {target}")
repo_type = detect_repo_type()
is_git = repo_type == RepoType.GIT
print(f"repo type: {'git' if is_git else 'hg'}")
# Print an error message (and any current error help) if a command
# fails and causes the script to exit.
atexit.register(early_exit_handler)
# make sure third_party/libwebrtc/README.mozilla.last-vendor is the committed
# version so we properly determine the base and next-base commits in the loop
# below
if is_git:
run_git("git restore third_party/libwebrtc/README.mozilla.last-vendor", ".")
else:
run_hg("hg revert -C third_party/libwebrtc/README.mozilla.last-vendor")
# check for a resume situation from fast_forward_libwebrtc.py
resume_file = os.path.join(state_dir, "fast_forward.resume")
resume = ""
if os.path.exists(resume_file):
resume = get_last_line(resume_file)
# check for the situation where we've encountered an error when running
# detect_upstream_revert.sh and should skip running it a second time.
skip_revert_file = os.path.join(state_dir, "loop.skip-revert-detect")
skip_next_revert_chk = ""
if os.path.exists(skip_revert_file):
skip_next_revert_chk = get_last_line(skip_revert_file)
print(f"SKIP_NEXT_REVERT_CHK: '{skip_next_revert_chk}'")
error_help.set_help(
f"""
It appears that verification of initial vendoring from our local copy
of the moz-libwebrtc git repo containing our patch-stack has failed.
- If you have never previously run the fast-forward (loop-ff.sh) script,
you may need to prepare the github repository by running prep_repo.sh.
- If you have previously run loop-ff.sh successfully, there may be a new
change to third_party/libwebrtc that should be extracted from mercurial
and added to the patch stack in github. It may be as easy as running:
./mach python {script_dir}/extract-for-git.py tip::tip
mv mailbox.patch {libwebrtc_src}
(cd {libwebrtc_src} && \\
git am mailbox.patch)
To verify vendoring, run:
bash {script_dir}/verify_vendoring.sh
When verify_vendoring.sh is successful, please run the following command
in bash:
(source {script_dir}/use_config_env.sh ; \\
./mach python {script_dir}/save_patch_stack.py \\
--repo-path {libwebrtc_src} \\
--target-branch-head {target} )
You may resume running this script with the following command:
bash {script_dir}/loop-ff.sh
"""
)
# if we're not in the resume situation from fast_forward_libwebrtc.py
if resume == "":
# start off by verifying the vendoring process to make sure no changes
# have been added to elm to fix bugs.
echo_log("Verifying vendoring...")
# The script outputs its own error message when verifying fails, so
# capture that output of verify_vendoring.sh quietly.
with open(os.path.join(log_dir, "log-verify.txt"), "w") as logf:
res = subprocess.run(
["bash", f"{script_dir}/verify_vendoring.sh"],
stdout=logf,
stderr=subprocess.STDOUT,
text=True,
check=False,
)
if res.returncode != 0:
sys.exit(res.returncode)
echo_log("Done verifying vendoring.")
error_help.set_help(None)
while True:
try:
base = find_base_commit(libwebrtc_src, target)
next_base = find_next_commit(libwebrtc_src, target)
except FastForwardError as e:
error_help.set_help(str(e))
sys.exit(1)
if base == next_base:
echo_log(f"Processing complete, already at upstream {base}")
clean_exit(0)
echo_log("===================")
commits_remaining = len(
run_git(f"git log --oneline {base}..{target}", libwebrtc_src)
)
echo_log(f"Commits remaining: {commits_remaining}")
print(
f"Before revert detection, SKIP_NEXT_REVERT_CHK: '{skip_next_revert_chk}'"
)
print(f"Before revert detection, RESUME: '{resume}'")
if is_git:
cleanup_cmds = (
"git restore --staged third_party/libwebrtc && "
"git restore third_party/libwebrtc && "
"git clean -f third_party/libwebrtc"
)
else:
cleanup_cmds = (
"hg revert third_party/libwebrtc && hg purge third_party/libwebrtc"
)
error_help.set_help(
f"""Some portion of the detection and/or fixing of upstream revert commits
has failed. This is usually a result of too many changes in the same file
between the original commit and the upstream revert commit. There are two
common ways forward:
1: Fix the state of the libwebrtc repo so that it matches the expected
patch stack. For more information what to expect for the patch stack,
The libwebrtc repo is here: {libwebrtc_src}
2: Run the following commands to revert the state of the libwebrtc repo
and temporarily disable the upstream revert commit processing:
{cleanup_cmds} ; \\
(source dom/media/webrtc/third_party_build/use_config_env.sh ; \\
rm $STATE_DIR/*.resume ; \\
./mach python $SCRIPT_DIR/restore_patch_stack.py \\
--repo-path $MOZ_LIBWEBRTC_SRC && \\
MOZ_ADVANCE_ONE_COMMIT=1 SKIP_NEXT_REVERT_CHK=1 bash $SCRIPT_DIR/loop-ff.sh \\
)
When fixed, please resume this script with the following command:
bash {script_dir}/loop-ff.sh
"""
)
if skip_next_revert_chk == "" and resume == "":
echo_log("Check for upcoming revert commit")
with open(skip_revert_file, "w") as ofile:
ofile.write("true\n")
run_command(
["bash", f"{script_dir}/detect_upstream_revert.sh"],
extra_env={"AUTO_FIX_REVERT_AS_NOOP": "1"},
)
with open(skip_revert_file, "w") as ofile:
ofile.write("\n")
error_help.set_help(None)
noop_commit_path = os.path.join(state_dir, f"{next_base}.no-op-cherry-pick-msg")
echo_log(f"Looking for {noop_commit_path}")
handle_noop_commit = os.path.exists(noop_commit_path)
if handle_noop_commit:
echo_log("Detected special commit msg, setting HANDLE_NOOP_COMMIT=1")
echo_log(f"Moving from moz-libwebrtc commit {base} to {next_base}")
fast_forward_libwebrtc(
"third_party/libwebrtc",
state_dir,
log_dir,
tmp_dir,
script_dir,
libwebrtc_src,
libwebrtc_branch,
int(fastforward_bug),
target,
)
moz_changed = count_moz_changed(is_git)
git_changed = len(filter_git_changes(libwebrtc_src, next_base, None))
echo_log(
f"Verify number of files changed MOZ({moz_changed}) GIT({git_changed})"
)
if handle_noop_commit:
echo_log("NO-OP commit detected, we expect file changed counts to differ")
elif moz_changed != git_changed:
echo_log(
f"MOZ_CHANGED {moz_changed} should equal GIT_CHANGED {git_changed}"
)
print(
f"""
The number of files changed in the upstream commit ({git_changed}) does
not match the number of files changed in the local Mozilla repo
commit ({moz_changed}). This may indicate a mismatch between the vendoring
script and this script, or it could be a true error in the import
processing. Once the issue has been resolved, the following steps
remain for this commit:
# save the patch-stack
./mach python {script_dir}/save_patch_stack.py \\
--skip-startup-sanity \\
--repo-path {libwebrtc_src} \\
--branch {libwebrtc_branch} \\
--patch-path "third_party/libwebrtc/moz-patch-stack" \\
--state-path {state_dir} \\
--target-branch-head {target}
# generate moz.build files (may not be necessary)
./mach python build/gn_processor.py \\
{script_dir}/gn-configs/webrtc.json
# commit the updated moz.build files with the appropriate commit msg
bash {script_dir}/commit-build-file-changes.sh
# do a (hopefully) quick test build
./mach build && ./mach build recurse_gtest && echo "Successful build"
After a successful build, you may resume this script:
bash {script_dir}/loop-ff.sh
"""
)
clean_exit(1)
# save the current patch stack in case we need to reconstitute it later
echo_log("Save patch-stack")
run_command([
"./mach",
"python",
f"{script_dir}/save_patch_stack.py",
"--skip-startup-sanity",
"--repo-path",
libwebrtc_src,
"--branch",
libwebrtc_branch,
"--patch-path",
"third_party/libwebrtc/moz-patch-stack",
"--state-path",
state_dir,
"--target-branch-head",
target,
])
modified_build_related_file_cnt = len(
run_shell(f"bash {script_dir}/get_build_file_changes.sh")
)
error_help.set_help(
f"""
Generating build files has failed. This likely means changes to one or more
BUILD.gn files are required. Commit those changes following the instructions
Then complete these steps:
# generate moz.build files (may not be necessary)
./mach python build/gn_processor.py \\
{script_dir}/gn-configs/webrtc.json
# commit the updated moz.build files with the appropriate commit msg
bash {script_dir}/commit-build-file-changes.sh
# do a (hopefully) quick test build
./mach build && ./mach build recurse_gtest && echo "Successful build"
After a successful build, you may resume this script:
bash {script_dir}/loop-ff.sh
"""
)
echo_log(
f"Modified .gn, **/BUILD.gn, or **/*.gni files: "
f"{modified_build_related_file_cnt}"
)
moz_build_change_cnt = 0
if modified_build_related_file_cnt != 0:
echo_log("Regenerate build files")
run_command([
"./mach",
"python",
"build/gn_processor.py",
f"{script_dir}/gn-configs/webrtc.json",
])
if is_git:
moz_build_change_cnt = len(
git_status(".", "third_party/libwebrtc/**moz.build")
)
else:
moz_build_change_cnt = len(
run_hg(
"hg status third_party/libwebrtc "
"--include third_party/libwebrtc/**moz.build"
)
)
if moz_build_change_cnt != 0:
echo_log("Detected modified moz.build files, committing")
run_command(["bash", f"{script_dir}/commit-build-file-changes.sh"])
error_help.set_help(None)
error_help.set_help(
f"""
The test build has failed. Most likely this is due to an upstream api
change that must be reflected in Mozilla code outside of the
third_party/libwebrtc directory. After fixing the build, you may resume
running this script with the following command:
./mach build && ./mach build recurse_gtest && \\
bash {script_dir}/loop-ff.sh
"""
)
echo_log("Test build - ./mach build")
run_command(["./mach", "build"])
echo_log("Test build - ./mach build recurse_gtest")
run_command(["./mach", "build", "recurse_gtest"])
error_help.set_help(None)
# If we've committed moz.build changes, spin up try builds.
if moz_build_change_cnt != 0:
try_fuzzy_query_string = "^build-"
push_to_vcs = "" if is_git else "--push-to-vcs"
current_time = datetime.datetime.now().ctime()
echo_log("Detected modified moz.build files, starting try builds with")
echo_log(f"'{try_fuzzy_query_string}' at {current_time}")
echo_log(
"This try push is started to help earlier detection of build issues"
)
echo_log("across different platforms supported by Mozilla.")
echo_log(
"Note - this step can take a long time (occasionally in the 10min range)"
)
echo_log(" with little or no feedback.")
# Show the time used for this command, and don't let it fail if the
# command times out so the script continues running. This command
# can take quite long, occasionally 10min.
run_command(
f"time ./mach try fuzzy {push_to_vcs} --full -q {try_fuzzy_query_string}",
shell=True,
ignore_errors=True,
)
if stop_after_commit != "":
if next_base == stop_after_commit:
break
if advance_one_commit != "":
echo_log("Done advancing one commit.")
clean_exit(0)
# successfully completed one iteration through the loop, so we can reset
# resume
resume = ""
echo_log(f"Completed fast-forward to {stop_after_commit}")
atexit.unregister(early_exit_handler)
if __name__ == "__main__":
main()