blob: df2478cb1461324dfb2315db1bc8d616b037c399 [file] [log] [blame]
#
# Copyright (C) 2021 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Package to manage Rust source files when building a toolchain distributable.
"""
from pathlib import Path
import shutil
import subprocess
import sys
from typing import Any
from paths import OUT_PATH_PATCHS_LOG, PATCH_PATH, OUT_PATH_PATCHED_FILES
from utils import prepare_command
def ensure_unique_patch_numbers(patch_list: list[Path]) -> None:
patches_seen = set()
for filepath in patch_list:
patch_num = filepath.name.split("-", 3)[1]
if patch_num in patches_seen:
raise RuntimeError(f"Duplicate patch number {patch_num} for {filepath.name}")
patches_seen.add(patch_num)
def apply_patches(code_dir: Path, patch_list: list[Path], patch_abort: bool = False) -> None:
count_padding = len(str(len(patch_list)))
# We will overwrite the log file if it already existed.
with OUT_PATH_PATCHS_LOG.open("w") as f:
for idx, filepath in enumerate(patch_list):
print(f"\33[2K\rApplying patch ({(idx + 1):>{count_padding}}/{len(patch_list)}): {filepath.name}", end="")
command_list: list[str] = prepare_command([PATCH_PATH, "-p1", "-l", "-i", filepath])
# We collect the stdout and stderr output and then print it to the
# log so that when an error is encountered we can display the
# relevent message and don't have to refer the user to the log.
# The log is intended as a resource for investigating patch
# drift.
result = subprocess.run(command_list, cwd=code_dir, capture_output=True)
if result.stdout or result.stderr:
f.write(f"Patch {filepath.name}:\n")
if result.stdout:
f.write(result.stdout.decode("UTF-8") + "\n")
if result.stderr:
f.write(result.stderr.decode("UTF-8") + "\n")
if result.returncode != 0 and patch_abort:
print(f"\nBuild failed when applying patch {filepath}")
print("If developing locally, try the --no-patch-abort flag")
if result.stdout:
print("\nOutput (stdout):")
print(result.stdout.decode("UTF-8"))
if result.stderr:
print("\nOutput (stderr):")
print(result.stderr.decode("UTF-8"))
print("Failed")
f.truncate()
sys.exit(result.returncode)
# Remove any possible leftovers from the previous log
f.truncate()
# If all patches applied cleanly we need to advance to the next line in the
# terminal
print()
def setup_files(input_dir: Path, output_dir: Path, patches_dir: Path, patch_abort: bool = False, repatch: bool = False) -> None:
"""Copy source and apply patches in a performant and fault-tolerant manner.
This function creates a copy of the source directory and
applies the patches contained in the patch directory.
"""
patch_list = sorted(patches_dir.glob("**/rustc-*"), key=lambda p: p.name)
ensure_unique_patch_numbers(patch_list)
if not output_dir.parent.exists():
output_dir.parent.mkdir(parents=True)
if repatch and OUT_PATH_PATCHED_FILES.exists():
with OUT_PATH_PATCHED_FILES.open("r") as previously_patched_files:
for line in previously_patched_files.readlines():
file = line.strip()
print(f"\33[2K\rRestoring {file}", end="")
if (input_dir / file).exists():
shutil.copy2(input_dir / file, output_dir / file)
else:
(output_dir / file).unlink(missing_ok=True)
print()
else:
print("Creating copy of Rust source")
if output_dir.exists():
shutil.rmtree(output_dir)
shutil.copytree(input_dir, output_dir, ignore=shutil.ignore_patterns(".git"))
(output_dir / "Android.mk").unlink()
with OUT_PATH_PATCHED_FILES.open("w") as patched_files:
for filepath in patch_list:
with filepath.open("r") as patchfile:
for line in patchfile:
if line.startswith('+++ b/'):
file = line.removeprefix('+++ b/').split()[0].strip()
patched_files.write(f"{file}\n")
apply_patches(output_dir, patch_list, patch_abort=patch_abort)