xref: /build/scripts/util/build_utils.py (revision 5f9996aa)
15f9996aaSopenharmony_ci#!/usr/bin/env python
25f9996aaSopenharmony_ci# -*- coding: utf-8 -*-
35f9996aaSopenharmony_ci# Copyright 2013 The Chromium Authors. All rights reserved.
45f9996aaSopenharmony_ci# Use of this source code is governed by a BSD-style license that can be
55f9996aaSopenharmony_ci# found in the LICENSE file.
65f9996aaSopenharmony_ci"""Contains common helpers for GN action()s."""
75f9996aaSopenharmony_ci
85f9996aaSopenharmony_ciimport collections
95f9996aaSopenharmony_ciimport contextlib
105f9996aaSopenharmony_cifrom distutils import extension
115f9996aaSopenharmony_ciimport filecmp
125f9996aaSopenharmony_ciimport fnmatch
135f9996aaSopenharmony_ciimport json
145f9996aaSopenharmony_ciimport os
155f9996aaSopenharmony_ciimport pipes
165f9996aaSopenharmony_ciimport re
175f9996aaSopenharmony_ciimport shutil
185f9996aaSopenharmony_ciimport stat
195f9996aaSopenharmony_ciimport subprocess
205f9996aaSopenharmony_ciimport sys
215f9996aaSopenharmony_ciimport tempfile
225f9996aaSopenharmony_ciimport zipfile
235f9996aaSopenharmony_ciimport optparse
245f9996aaSopenharmony_ci
255f9996aaSopenharmony_ci# Any new non-system import must be added to:
265f9996aaSopenharmony_ci
275f9996aaSopenharmony_cisys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir))
285f9996aaSopenharmony_ciimport gn_helpers
295f9996aaSopenharmony_ci
305f9996aaSopenharmony_ci# Some clients do not add //build/scripts/util to PYTHONPATH.
315f9996aaSopenharmony_cifrom . import md5_check  # pylint: disable=relative-import
325f9996aaSopenharmony_ci
335f9996aaSopenharmony_ci# Definition copied from pylib/constants/__init__.py to avoid adding
345f9996aaSopenharmony_ci# a dependency on pylib.
355f9996aaSopenharmony_ciDIR_SOURCE_ROOT = os.environ.get(
365f9996aaSopenharmony_ci    'CHECKOUT_SOURCE_ROOT',
375f9996aaSopenharmony_ci    os.path.abspath(
385f9996aaSopenharmony_ci        os.path.join(os.path.dirname(__file__), os.pardir, os.pardir,
395f9996aaSopenharmony_ci                     os.pardir, os.pardir)))
405f9996aaSopenharmony_ci
415f9996aaSopenharmony_ciHERMETIC_TIMESTAMP = (2001, 1, 1, 0, 0, 0)
425f9996aaSopenharmony_ci_HERMETIC_FILE_ATTR = (0o644 << 16)
435f9996aaSopenharmony_ci
445f9996aaSopenharmony_ci
455f9996aaSopenharmony_ci@contextlib.contextmanager
465f9996aaSopenharmony_cidef temp_dir():
475f9996aaSopenharmony_ci    dirname = tempfile.mkdtemp()
485f9996aaSopenharmony_ci    try:
495f9996aaSopenharmony_ci        yield dirname
505f9996aaSopenharmony_ci    finally:
515f9996aaSopenharmony_ci        shutil.rmtree(dirname)
525f9996aaSopenharmony_ci
535f9996aaSopenharmony_ci
545f9996aaSopenharmony_cidef make_directory(dir_path):
555f9996aaSopenharmony_ci    try:
565f9996aaSopenharmony_ci        os.makedirs(dir_path, exist_ok=True)
575f9996aaSopenharmony_ci    except OSError:
585f9996aaSopenharmony_ci        pass
595f9996aaSopenharmony_ci
605f9996aaSopenharmony_ci
615f9996aaSopenharmony_cidef delete_directory(dir_path):
625f9996aaSopenharmony_ci    if os.path.exists(dir_path):
635f9996aaSopenharmony_ci        shutil.rmtree(dir_path)
645f9996aaSopenharmony_ci
655f9996aaSopenharmony_ci
665f9996aaSopenharmony_cidef touch(path, fail_if_missing=False):
675f9996aaSopenharmony_ci    if fail_if_missing and not os.path.exists(path):
685f9996aaSopenharmony_ci        raise Exception(path + ' doesn\'t exist.')
695f9996aaSopenharmony_ci
705f9996aaSopenharmony_ci    make_directory(os.path.dirname(path))
715f9996aaSopenharmony_ci    with open(path, 'a'):
725f9996aaSopenharmony_ci        os.utime(path, None)
735f9996aaSopenharmony_ci
745f9996aaSopenharmony_ci
755f9996aaSopenharmony_cidef find_in_directory(directory, filename_filter):
765f9996aaSopenharmony_ci    files = []
775f9996aaSopenharmony_ci    for root, _dirnames, filenames in os.walk(directory):
785f9996aaSopenharmony_ci        matched_files = fnmatch.filter(filenames, filename_filter)
795f9996aaSopenharmony_ci        files.extend((os.path.join(root, f) for f in matched_files))
805f9996aaSopenharmony_ci    return files
815f9996aaSopenharmony_ci
825f9996aaSopenharmony_ci
835f9996aaSopenharmony_cidef read_build_vars(path):
845f9996aaSopenharmony_ci    """Parses a build_vars.txt into a dict."""
855f9996aaSopenharmony_ci    with open(path) as f:
865f9996aaSopenharmony_ci        return dict(l.rstrip().split('=', 1) for l in f)
875f9996aaSopenharmony_ci
885f9996aaSopenharmony_ci
895f9996aaSopenharmony_cidef parse_gn_list(gn_string):
905f9996aaSopenharmony_ci    """Converts a command-line parameter into a list.
915f9996aaSopenharmony_ci
925f9996aaSopenharmony_ci    If the input starts with a '[' it is assumed to be a GN-formatted list and
935f9996aaSopenharmony_ci    it will be parsed accordingly. When empty an empty list will be returned.
945f9996aaSopenharmony_ci    Otherwise, the parameter will be treated as a single raw string (not
955f9996aaSopenharmony_ci    GN-formatted in that it's not assumed to have literal quotes that must be
965f9996aaSopenharmony_ci    removed) and a list will be returned containing that string.
975f9996aaSopenharmony_ci
985f9996aaSopenharmony_ci    The common use for this behavior is in the ohos build where things can
995f9996aaSopenharmony_ci    take lists of @FileArg references that are expanded via expand_file_args.
1005f9996aaSopenharmony_ci    """
1015f9996aaSopenharmony_ci    if gn_string.startswith('['):
1025f9996aaSopenharmony_ci        parser = gn_helpers.GNValueParser(gn_string)
1035f9996aaSopenharmony_ci        return parser.parse_list()
1045f9996aaSopenharmony_ci    if len(gn_string):
1055f9996aaSopenharmony_ci        return [gn_string]
1065f9996aaSopenharmony_ci    return []
1075f9996aaSopenharmony_ci
1085f9996aaSopenharmony_ci
1095f9996aaSopenharmony_cidef parse_and_flatten_gn_lists(gn_lists):
1105f9996aaSopenharmony_ci    ret = []
1115f9996aaSopenharmony_ci    for arg in gn_lists:
1125f9996aaSopenharmony_ci        ret.extend(parse_gn_list(arg))
1135f9996aaSopenharmony_ci    return ret
1145f9996aaSopenharmony_ci
1155f9996aaSopenharmony_ci
1165f9996aaSopenharmony_cidef check_options(options, parser, required=None):
1175f9996aaSopenharmony_ci    if not required:
1185f9996aaSopenharmony_ci        return
1195f9996aaSopenharmony_ci    for option_name in required:
1205f9996aaSopenharmony_ci        if getattr(options, option_name) is None:
1215f9996aaSopenharmony_ci            parser.error('--%s is required' % option_name.replace('_', '-'))
1225f9996aaSopenharmony_ci
1235f9996aaSopenharmony_ci
1245f9996aaSopenharmony_cidef write_json(obj, path, only_if_changed=False):
1255f9996aaSopenharmony_ci    old_dump = None
1265f9996aaSopenharmony_ci    if os.path.exists(path):
1275f9996aaSopenharmony_ci        with open(path, 'r') as oldfile:
1285f9996aaSopenharmony_ci            old_dump = oldfile.read()
1295f9996aaSopenharmony_ci
1305f9996aaSopenharmony_ci    new_dump = json.dumps(obj,
1315f9996aaSopenharmony_ci                          sort_keys=True,
1325f9996aaSopenharmony_ci                          indent=2,
1335f9996aaSopenharmony_ci                          separators=(',', ': '))
1345f9996aaSopenharmony_ci
1355f9996aaSopenharmony_ci    if not only_if_changed or old_dump != new_dump:
1365f9996aaSopenharmony_ci        with open(path, 'w') as outfile:
1375f9996aaSopenharmony_ci            outfile.write(new_dump)
1385f9996aaSopenharmony_ci
1395f9996aaSopenharmony_ci
1405f9996aaSopenharmony_ci@contextlib.contextmanager
1415f9996aaSopenharmony_cidef atomic_output(path, only_if_changed=True):
1425f9996aaSopenharmony_ci    """Helper to prevent half-written outputs.
1435f9996aaSopenharmony_ci
1445f9996aaSopenharmony_ci    Args:
1455f9996aaSopenharmony_ci      path: Path to the final output file, which will be written atomically.
1465f9996aaSopenharmony_ci      only_if_changed: If True (the default), do not touch the filesystem
1475f9996aaSopenharmony_ci        if the content has not changed.
1485f9996aaSopenharmony_ci    Returns:
1495f9996aaSopenharmony_ci      A python context manager that yields a NamedTemporaryFile instance
1505f9996aaSopenharmony_ci      that must be used by clients to write the data to. On exit, the
1515f9996aaSopenharmony_ci      manager will try to replace the final output file with the
1525f9996aaSopenharmony_ci      temporary one if necessary. The temporary file is always destroyed
1535f9996aaSopenharmony_ci      on exit.
1545f9996aaSopenharmony_ci    Example:
1555f9996aaSopenharmony_ci      with build_utils.atomic_output(output_path) as tmp_file:
1565f9996aaSopenharmony_ci        subprocess.check_call(['prog', '--output', tmp_file.name])
1575f9996aaSopenharmony_ci    """
1585f9996aaSopenharmony_ci    # Create in same directory to ensure same filesystem when moving.
1595f9996aaSopenharmony_ci    with tempfile.NamedTemporaryFile(suffix=os.path.basename(path),
1605f9996aaSopenharmony_ci                                     dir=os.path.dirname(path),
1615f9996aaSopenharmony_ci                                     delete=False) as f:
1625f9996aaSopenharmony_ci        try:
1635f9996aaSopenharmony_ci            # Change tempfile permission to 664
1645f9996aaSopenharmony_ci            os.fchmod(f.fileno(), 0o664)
1655f9996aaSopenharmony_ci            yield f
1665f9996aaSopenharmony_ci
1675f9996aaSopenharmony_ci            # file should be closed before comparison/move.
1685f9996aaSopenharmony_ci            f.close()
1695f9996aaSopenharmony_ci            if not (only_if_changed and os.path.exists(path)
1705f9996aaSopenharmony_ci                    and filecmp.cmp(f.name, path)):
1715f9996aaSopenharmony_ci                shutil.move(f.name, path)
1725f9996aaSopenharmony_ci        finally:
1735f9996aaSopenharmony_ci            if os.path.exists(f.name):
1745f9996aaSopenharmony_ci                os.unlink(f.name)
1755f9996aaSopenharmony_ci
1765f9996aaSopenharmony_ci
1775f9996aaSopenharmony_ciclass CalledProcessError(Exception):
1785f9996aaSopenharmony_ci    """This exception is raised when the process run by check_output
1795f9996aaSopenharmony_ci    exits with a non-zero exit code.
1805f9996aaSopenharmony_ci    """
1815f9996aaSopenharmony_ci    def __init__(self, cwd, args, output):
1825f9996aaSopenharmony_ci        super(CalledProcessError, self).__init__()
1835f9996aaSopenharmony_ci        self.cwd = cwd
1845f9996aaSopenharmony_ci        self.args = args
1855f9996aaSopenharmony_ci        if isinstance(output, bytes):
1865f9996aaSopenharmony_ci            self.output = output.decode()
1875f9996aaSopenharmony_ci        else:
1885f9996aaSopenharmony_ci            self.output = output
1895f9996aaSopenharmony_ci
1905f9996aaSopenharmony_ci    def __str__(self):
1915f9996aaSopenharmony_ci        # A user should be able to simply copy and paste the command that failed
1925f9996aaSopenharmony_ci        # into their shell.
1935f9996aaSopenharmony_ci        copyable_command = '( cd {}; {} )'.format(
1945f9996aaSopenharmony_ci            os.path.abspath(self.cwd), ' '.join(map(pipes.quote, self.args)))
1955f9996aaSopenharmony_ci        return 'Command failed: {}\n{}'.format(copyable_command, self.output)
1965f9996aaSopenharmony_ci
1975f9996aaSopenharmony_ci
1985f9996aaSopenharmony_cidef filter_lines(output, filter_string):
1995f9996aaSopenharmony_ci    """Output filter from build_utils.check_output.
2005f9996aaSopenharmony_ci
2015f9996aaSopenharmony_ci    Args:
2025f9996aaSopenharmony_ci      output: Executable output as from build_utils.check_output.
2035f9996aaSopenharmony_ci      filter_string: An RE string that will filter (remove) matching
2045f9996aaSopenharmony_ci          lines from |output|.
2055f9996aaSopenharmony_ci
2065f9996aaSopenharmony_ci    Returns:
2075f9996aaSopenharmony_ci      The filtered output, as a single string.
2085f9996aaSopenharmony_ci    """
2095f9996aaSopenharmony_ci    re_filter = re.compile(filter_string)
2105f9996aaSopenharmony_ci    return '\n'.join(line for line in output.splitlines()
2115f9996aaSopenharmony_ci                     if not re_filter.search(line))
2125f9996aaSopenharmony_ci
2135f9996aaSopenharmony_ci
2145f9996aaSopenharmony_ci# This can be used in most cases like subprocess.check_output(). The output,
2155f9996aaSopenharmony_ci# particularly when the command fails, better highlights the command's failure.
2165f9996aaSopenharmony_ci# If the command fails, raises a build_utils.CalledProcessError.
2175f9996aaSopenharmony_cidef check_output(args,
2185f9996aaSopenharmony_ci                 cwd=None,
2195f9996aaSopenharmony_ci                 env=None,
2205f9996aaSopenharmony_ci                 print_stdout=False,
2215f9996aaSopenharmony_ci                 print_stderr=True,
2225f9996aaSopenharmony_ci                 stdout_filter=None,
2235f9996aaSopenharmony_ci                 stderr_filter=None,
2245f9996aaSopenharmony_ci                 fail_func=lambda returncode, stderr: returncode != 0):
2255f9996aaSopenharmony_ci    if not cwd:
2265f9996aaSopenharmony_ci        cwd = os.getcwd()
2275f9996aaSopenharmony_ci
2285f9996aaSopenharmony_ci    cache_exec = None
2295f9996aaSopenharmony_ci    if env and env.pop("useCompileCache", False):
2305f9996aaSopenharmony_ci        cache_exec = os.environ.get("COMPILE_CACHE_EXEC")
2315f9996aaSopenharmony_ci    if cache_exec:
2325f9996aaSopenharmony_ci        execute_args = [cache_exec, "--cwd", cwd]
2335f9996aaSopenharmony_ci        execute_args.extend(args)
2345f9996aaSopenharmony_ci        execute_args.extend(["--build-env"] + [f"{k}={v}" for k, v in env.items() if k != "addTestRunner"])
2355f9996aaSopenharmony_ci        if env.pop("addTestRunner", False):
2365f9996aaSopenharmony_ci            execute_args.append("--add-test-runner")
2375f9996aaSopenharmony_ci        child = subprocess.Popen(execute_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2385f9996aaSopenharmony_ci    else:
2395f9996aaSopenharmony_ci        child = subprocess.Popen(args,
2405f9996aaSopenharmony_ci                                stdout=subprocess.PIPE,
2415f9996aaSopenharmony_ci                                stderr=subprocess.PIPE,
2425f9996aaSopenharmony_ci                                cwd=cwd,
2435f9996aaSopenharmony_ci                                env=env)
2445f9996aaSopenharmony_ci    stdout, stderr = child.communicate()
2455f9996aaSopenharmony_ci
2465f9996aaSopenharmony_ci    if stdout_filter is not None:
2475f9996aaSopenharmony_ci        stdout = stdout_filter(stdout)
2485f9996aaSopenharmony_ci
2495f9996aaSopenharmony_ci    if stderr_filter is not None:
2505f9996aaSopenharmony_ci        stderr = stderr_filter(stderr)
2515f9996aaSopenharmony_ci    if isinstance(stdout, bytes):
2525f9996aaSopenharmony_ci        stdout = stdout.decode()
2535f9996aaSopenharmony_ci    if isinstance(stderr, bytes):
2545f9996aaSopenharmony_ci        stderr = stderr.decode()
2555f9996aaSopenharmony_ci
2565f9996aaSopenharmony_ci    if fail_func(child.returncode, stderr):
2575f9996aaSopenharmony_ci        raise CalledProcessError(cwd, args, stdout + stderr)
2585f9996aaSopenharmony_ci
2595f9996aaSopenharmony_ci    if print_stdout:
2605f9996aaSopenharmony_ci        if isinstance(stdout, bytes):
2615f9996aaSopenharmony_ci            stdout = stdout.decode()
2625f9996aaSopenharmony_ci        if stdout:
2635f9996aaSopenharmony_ci            sys.stdout.write(stdout)
2645f9996aaSopenharmony_ci    if print_stderr:
2655f9996aaSopenharmony_ci        if isinstance(stderr, bytes):
2665f9996aaSopenharmony_ci            stderr = stderr.decode()
2675f9996aaSopenharmony_ci        if stderr:
2685f9996aaSopenharmony_ci            sys.stderr.write(stderr)
2695f9996aaSopenharmony_ci    return stdout
2705f9996aaSopenharmony_ci
2715f9996aaSopenharmony_ci
2725f9996aaSopenharmony_cidef get_modified_time(path):
2735f9996aaSopenharmony_ci    # For a symlink, the modified time should be the greater of the link's
2745f9996aaSopenharmony_ci    # modified time and the modified time of the target.
2755f9996aaSopenharmony_ci    return max(os.lstat(path).st_mtime, os.stat(path).st_mtime)
2765f9996aaSopenharmony_ci
2775f9996aaSopenharmony_ci
2785f9996aaSopenharmony_cidef is_time_stale(output, inputs):
2795f9996aaSopenharmony_ci    if not os.path.exists(output):
2805f9996aaSopenharmony_ci        return True
2815f9996aaSopenharmony_ci
2825f9996aaSopenharmony_ci    output_time = get_modified_time(output)
2835f9996aaSopenharmony_ci    for i in inputs:
2845f9996aaSopenharmony_ci        if get_modified_time(i) > output_time:
2855f9996aaSopenharmony_ci            return True
2865f9996aaSopenharmony_ci    return False
2875f9996aaSopenharmony_ci
2885f9996aaSopenharmony_ci
2895f9996aaSopenharmony_cidef _check_zip_path(name):
2905f9996aaSopenharmony_ci    if os.path.normpath(name) != name:
2915f9996aaSopenharmony_ci        raise Exception('Non-canonical zip path: %s' % name)
2925f9996aaSopenharmony_ci    if os.path.isabs(name):
2935f9996aaSopenharmony_ci        raise Exception('Absolute zip path: %s' % name)
2945f9996aaSopenharmony_ci
2955f9996aaSopenharmony_ci
2965f9996aaSopenharmony_cidef _is_symlink(zip_file, name):
2975f9996aaSopenharmony_ci    zi = zip_file.getinfo(name)
2985f9996aaSopenharmony_ci
2995f9996aaSopenharmony_ci    # The two high-order bytes of ZipInfo.external_attr represent
3005f9996aaSopenharmony_ci    # UNIX permissions and file type bits.
3015f9996aaSopenharmony_ci    return stat.S_ISLNK(zi.external_attr >> 16)
3025f9996aaSopenharmony_ci
3035f9996aaSopenharmony_ci
3045f9996aaSopenharmony_cidef extract_all(zip_path,
3055f9996aaSopenharmony_ci                path=None,
3065f9996aaSopenharmony_ci                no_clobber=True,
3075f9996aaSopenharmony_ci                pattern=None,
3085f9996aaSopenharmony_ci                predicate=None):
3095f9996aaSopenharmony_ci    if path is None:
3105f9996aaSopenharmony_ci        path = os.getcwd()
3115f9996aaSopenharmony_ci    elif not os.path.exists(path):
3125f9996aaSopenharmony_ci        make_directory(path)
3135f9996aaSopenharmony_ci
3145f9996aaSopenharmony_ci    if not zipfile.is_zipfile(zip_path):
3155f9996aaSopenharmony_ci        raise Exception('Invalid zip file: %s' % zip_path)
3165f9996aaSopenharmony_ci
3175f9996aaSopenharmony_ci    extracted = []
3185f9996aaSopenharmony_ci    with zipfile.ZipFile(zip_path) as z:
3195f9996aaSopenharmony_ci        for name in z.namelist():
3205f9996aaSopenharmony_ci            if name.endswith('/'):
3215f9996aaSopenharmony_ci                make_directory(os.path.join(path, name))
3225f9996aaSopenharmony_ci                continue
3235f9996aaSopenharmony_ci            if pattern is not None:
3245f9996aaSopenharmony_ci                if not fnmatch.fnmatch(name, pattern):
3255f9996aaSopenharmony_ci                    continue
3265f9996aaSopenharmony_ci            if predicate and not predicate(name):
3275f9996aaSopenharmony_ci                continue
3285f9996aaSopenharmony_ci            _check_zip_path(name)
3295f9996aaSopenharmony_ci            if no_clobber:
3305f9996aaSopenharmony_ci                output_path = os.path.join(path, name)
3315f9996aaSopenharmony_ci                if os.path.exists(output_path):
3325f9996aaSopenharmony_ci                    raise Exception('Path already exists from zip: %s %s %s' %
3335f9996aaSopenharmony_ci                                    (zip_path, name, output_path))
3345f9996aaSopenharmony_ci            if _is_symlink(z, name):
3355f9996aaSopenharmony_ci                dest = os.path.join(path, name)
3365f9996aaSopenharmony_ci                make_directory(os.path.dirname(dest))
3375f9996aaSopenharmony_ci                os.symlink(z.read(name), dest)
3385f9996aaSopenharmony_ci                extracted.append(dest)
3395f9996aaSopenharmony_ci            else:
3405f9996aaSopenharmony_ci                z.extract(name, path)
3415f9996aaSopenharmony_ci                extracted.append(os.path.join(path, name))
3425f9996aaSopenharmony_ci
3435f9996aaSopenharmony_ci    return extracted
3445f9996aaSopenharmony_ci
3455f9996aaSopenharmony_ci
3465f9996aaSopenharmony_cidef add_to_zip_hermetic(zip_file,
3475f9996aaSopenharmony_ci                        zip_path,
3485f9996aaSopenharmony_ci                        src_path=None,
3495f9996aaSopenharmony_ci                        data=None,
3505f9996aaSopenharmony_ci                        compress=None,
3515f9996aaSopenharmony_ci                        compress_level=6):
3525f9996aaSopenharmony_ci    """Adds a file to the given ZipFile with a hard-coded modified time.
3535f9996aaSopenharmony_ci
3545f9996aaSopenharmony_ci    Args:
3555f9996aaSopenharmony_ci      zip_file: ZipFile instance to add the file to.
3565f9996aaSopenharmony_ci      zip_path: Destination path within the zip file.
3575f9996aaSopenharmony_ci      src_path: Path of the source file. Mutually exclusive with |data|.
3585f9996aaSopenharmony_ci      data: File data as a string.
3595f9996aaSopenharmony_ci      compress: Whether to enable compression. Default is taken from ZipFile
3605f9996aaSopenharmony_ci          constructor.
3615f9996aaSopenharmony_ci    """
3625f9996aaSopenharmony_ci    assert (src_path is None) != (data is None), (
3635f9996aaSopenharmony_ci        '|src_path| and |data| are mutually exclusive.')
3645f9996aaSopenharmony_ci    _check_zip_path(zip_path)
3655f9996aaSopenharmony_ci    zipinfo = zipfile.ZipInfo(filename=zip_path, date_time=HERMETIC_TIMESTAMP)
3665f9996aaSopenharmony_ci    zipinfo.external_attr = _HERMETIC_FILE_ATTR
3675f9996aaSopenharmony_ci
3685f9996aaSopenharmony_ci    if src_path and os.path.islink(src_path):
3695f9996aaSopenharmony_ci        zipinfo.filename = zip_path
3705f9996aaSopenharmony_ci        zipinfo.external_attr |= stat.S_IFLNK << 16  # mark as a symlink
3715f9996aaSopenharmony_ci        zip_file.writestr(zipinfo, os.readlink(src_path))
3725f9996aaSopenharmony_ci        return
3735f9996aaSopenharmony_ci
3745f9996aaSopenharmony_ci    # we want to use _HERMETIC_FILE_ATTR, so manually set
3755f9996aaSopenharmony_ci    # the few attr bits we care about.
3765f9996aaSopenharmony_ci    if src_path:
3775f9996aaSopenharmony_ci        st = os.stat(src_path)
3785f9996aaSopenharmony_ci        for mode in (stat.S_IXUSR, stat.S_IXGRP, stat.S_IXOTH):
3795f9996aaSopenharmony_ci            if st.st_mode & mode:
3805f9996aaSopenharmony_ci                zipinfo.external_attr |= mode << 16
3815f9996aaSopenharmony_ci
3825f9996aaSopenharmony_ci    if src_path:
3835f9996aaSopenharmony_ci        with open(src_path, 'rb') as f:
3845f9996aaSopenharmony_ci            data = f.read()
3855f9996aaSopenharmony_ci
3865f9996aaSopenharmony_ci    # zipfile will deflate even when it makes the file bigger. To avoid
3875f9996aaSopenharmony_ci    # growing files, disable compression at an arbitrary cut off point.
3885f9996aaSopenharmony_ci    if len(data) < 16:
3895f9996aaSopenharmony_ci        compress = False
3905f9996aaSopenharmony_ci
3915f9996aaSopenharmony_ci    # None converts to ZIP_STORED, when passed explicitly rather than the
3925f9996aaSopenharmony_ci    # default passed to the ZipFile constructor.
3935f9996aaSopenharmony_ci    compress_type = zip_file.compression
3945f9996aaSopenharmony_ci    if compress is not None:
3955f9996aaSopenharmony_ci        compress_type = zipfile.ZIP_DEFLATED if compress else zipfile.ZIP_STORED
3965f9996aaSopenharmony_ci    if os.getenv("ZIP_COMPRESS_LEVEL"):
3975f9996aaSopenharmony_ci        compress_level = int(os.getenv("ZIP_COMPRESS_LEVEL"))
3985f9996aaSopenharmony_ci    zip_file.writestr(zipinfo, data, compress_type, compress_level)
3995f9996aaSopenharmony_ci
4005f9996aaSopenharmony_ci
4015f9996aaSopenharmony_cidef do_zip(inputs,
4025f9996aaSopenharmony_ci           output,
4035f9996aaSopenharmony_ci           base_dir=None,
4045f9996aaSopenharmony_ci           compress_fn=None,
4055f9996aaSopenharmony_ci           zip_prefix_path=None):
4065f9996aaSopenharmony_ci    """Creates a zip file from a list of files.
4075f9996aaSopenharmony_ci
4085f9996aaSopenharmony_ci    Args:
4095f9996aaSopenharmony_ci      inputs: A list of paths to zip, or a list of (zip_path, fs_path) tuples.
4105f9996aaSopenharmony_ci      output: Destination .zip file.
4115f9996aaSopenharmony_ci      base_dir: Prefix to strip from inputs.
4125f9996aaSopenharmony_ci      compress_fn: Applied to each input to determine whether or not to compress.
4135f9996aaSopenharmony_ci          By default, items will be |zipfile.ZIP_STORED|.
4145f9996aaSopenharmony_ci      zip_prefix_path: Path prepended to file path in zip file.
4155f9996aaSopenharmony_ci    """
4165f9996aaSopenharmony_ci    input_tuples = []
4175f9996aaSopenharmony_ci    for tup in inputs:
4185f9996aaSopenharmony_ci        if isinstance(tup, str):
4195f9996aaSopenharmony_ci            tup = (os.path.relpath(tup, base_dir), tup)
4205f9996aaSopenharmony_ci        input_tuples.append(tup)
4215f9996aaSopenharmony_ci
4225f9996aaSopenharmony_ci    # Sort by zip path to ensure stable zip ordering.
4235f9996aaSopenharmony_ci    input_tuples.sort(key=lambda tup: tup[0])
4245f9996aaSopenharmony_ci    with zipfile.ZipFile(output, 'w') as outfile:
4255f9996aaSopenharmony_ci        for zip_path, fs_path in input_tuples:
4265f9996aaSopenharmony_ci            if zip_prefix_path:
4275f9996aaSopenharmony_ci                zip_path = os.path.join(zip_prefix_path, zip_path)
4285f9996aaSopenharmony_ci            compress = compress_fn(zip_path) if compress_fn else None
4295f9996aaSopenharmony_ci            add_to_zip_hermetic(outfile,
4305f9996aaSopenharmony_ci                                zip_path,
4315f9996aaSopenharmony_ci                                src_path=fs_path,
4325f9996aaSopenharmony_ci                                compress=compress)
4335f9996aaSopenharmony_ci
4345f9996aaSopenharmony_ci
4355f9996aaSopenharmony_cidef zip_dir(output, base_dir, compress_fn=None, zip_prefix_path=None):
4365f9996aaSopenharmony_ci    """Creates a zip file from a directory."""
4375f9996aaSopenharmony_ci    inputs = []
4385f9996aaSopenharmony_ci    for root, _, files in os.walk(base_dir):
4395f9996aaSopenharmony_ci        for f in files:
4405f9996aaSopenharmony_ci            inputs.append(os.path.join(root, f))
4415f9996aaSopenharmony_ci
4425f9996aaSopenharmony_ci    with atomic_output(output) as f:
4435f9996aaSopenharmony_ci        do_zip(inputs,
4445f9996aaSopenharmony_ci               f,
4455f9996aaSopenharmony_ci               base_dir,
4465f9996aaSopenharmony_ci               compress_fn=compress_fn,
4475f9996aaSopenharmony_ci               zip_prefix_path=zip_prefix_path)
4485f9996aaSopenharmony_ci
4495f9996aaSopenharmony_ci
4505f9996aaSopenharmony_cidef matches_glob(path, filters):
4515f9996aaSopenharmony_ci    """Returns whether the given path matches any of the given glob patterns."""
4525f9996aaSopenharmony_ci    return filters and any(fnmatch.fnmatch(path, f) for f in filters)
4535f9996aaSopenharmony_ci
4545f9996aaSopenharmony_ci
4555f9996aaSopenharmony_cidef _strip_dst_name(dst_name, options):
4565f9996aaSopenharmony_ci    # Strip specific directories and file if options is not None
4575f9996aaSopenharmony_ci    if options and options.stripFile:
4585f9996aaSopenharmony_ci        for f in options.stripFile:
4595f9996aaSopenharmony_ci            if fnmatch.fnmatch(dst_name, '*/' + f):
4605f9996aaSopenharmony_ci                return True
4615f9996aaSopenharmony_ci    if options and options.stripDir:
4625f9996aaSopenharmony_ci        for d in options.stripDir:
4635f9996aaSopenharmony_ci            if fnmatch.fnmatch(dst_name, d + '/*'):
4645f9996aaSopenharmony_ci                return True
4655f9996aaSopenharmony_ci    return False
4665f9996aaSopenharmony_ci
4675f9996aaSopenharmony_ci
4685f9996aaSopenharmony_cidef merge_zips(output, input_zips, path_transform=None, merge_args=None):
4695f9996aaSopenharmony_ci    """Combines all files from |input_zips| into |output|.
4705f9996aaSopenharmony_ci
4715f9996aaSopenharmony_ci    Args:
4725f9996aaSopenharmony_ci      output: Path or ZipFile instance to add files to.
4735f9996aaSopenharmony_ci      input_zips: Iterable of paths to zip files to merge.
4745f9996aaSopenharmony_ci      path_transform: Called for each entry path. Returns a new path, or None to
4755f9996aaSopenharmony_ci          skip the file.
4765f9996aaSopenharmony_ci    """
4775f9996aaSopenharmony_ci    options = None
4785f9996aaSopenharmony_ci    if merge_args:
4795f9996aaSopenharmony_ci        parser = optparse.OptionParser()
4805f9996aaSopenharmony_ci        parser.add_option('--stripDir',
4815f9996aaSopenharmony_ci                          action='append',
4825f9996aaSopenharmony_ci                          help='strip specific directory')
4835f9996aaSopenharmony_ci        parser.add_option('--stripFile',
4845f9996aaSopenharmony_ci                          action='append',
4855f9996aaSopenharmony_ci                          help='strip specific file.')
4865f9996aaSopenharmony_ci
4875f9996aaSopenharmony_ci        args = expand_file_args(merge_args)
4885f9996aaSopenharmony_ci        options, _ = parser.parse_args(args)
4895f9996aaSopenharmony_ci
4905f9996aaSopenharmony_ci    path_transform = path_transform or (lambda p: p)
4915f9996aaSopenharmony_ci    added_names = set()
4925f9996aaSopenharmony_ci
4935f9996aaSopenharmony_ci    output_is_already_open = not isinstance(output, str)
4945f9996aaSopenharmony_ci    if output_is_already_open:
4955f9996aaSopenharmony_ci        assert isinstance(output, zipfile.ZipFile)
4965f9996aaSopenharmony_ci        out_zip = output
4975f9996aaSopenharmony_ci    else:
4985f9996aaSopenharmony_ci        out_zip = zipfile.ZipFile(output, 'w')
4995f9996aaSopenharmony_ci
5005f9996aaSopenharmony_ci    try:
5015f9996aaSopenharmony_ci        for in_file in input_zips:
5025f9996aaSopenharmony_ci            with zipfile.ZipFile(in_file, 'r') as in_zip:
5035f9996aaSopenharmony_ci                # ijar creates zips with null CRCs.
5045f9996aaSopenharmony_ci                in_zip._expected_crc = None
5055f9996aaSopenharmony_ci                for info in in_zip.infolist():
5065f9996aaSopenharmony_ci                    # Ignore directories.
5075f9996aaSopenharmony_ci                    if info.filename[-1] == '/':
5085f9996aaSopenharmony_ci                        continue
5095f9996aaSopenharmony_ci                    dst_name = path_transform(info.filename)
5105f9996aaSopenharmony_ci                    if not dst_name:
5115f9996aaSopenharmony_ci                        continue
5125f9996aaSopenharmony_ci                    if _strip_dst_name(dst_name, options):
5135f9996aaSopenharmony_ci                        continue
5145f9996aaSopenharmony_ci                    already_added = dst_name in added_names
5155f9996aaSopenharmony_ci                    if not already_added:
5165f9996aaSopenharmony_ci                        add_to_zip_hermetic(
5175f9996aaSopenharmony_ci                            out_zip,
5185f9996aaSopenharmony_ci                            dst_name,
5195f9996aaSopenharmony_ci                            data=in_zip.read(info),
5205f9996aaSopenharmony_ci                            compress=info.compress_type != zipfile.ZIP_STORED)
5215f9996aaSopenharmony_ci                        added_names.add(dst_name)
5225f9996aaSopenharmony_ci    finally:
5235f9996aaSopenharmony_ci        if not output_is_already_open:
5245f9996aaSopenharmony_ci            out_zip.close()
5255f9996aaSopenharmony_ci
5265f9996aaSopenharmony_ci
5275f9996aaSopenharmony_cidef get_sorted_transitive_dependencies(top, deps_func):
5285f9996aaSopenharmony_ci    """Gets the list of all transitive dependencies in sorted order.
5295f9996aaSopenharmony_ci
5305f9996aaSopenharmony_ci    There should be no cycles in the dependency graph (crashes if cycles exist).
5315f9996aaSopenharmony_ci
5325f9996aaSopenharmony_ci    Args:
5335f9996aaSopenharmony_ci      top: A list of the top level nodes
5345f9996aaSopenharmony_ci      deps_func: A function that takes a node and returns a list of its direct
5355f9996aaSopenharmony_ci          dependencies.
5365f9996aaSopenharmony_ci    Returns:
5375f9996aaSopenharmony_ci      A list of all transitive dependencies of nodes in top, in order (a node
5385f9996aaSopenharmony_ci      will appear in the list at a higher index than all of its dependencies).
5395f9996aaSopenharmony_ci    """
5405f9996aaSopenharmony_ci    # Find all deps depth-first, maintaining original order in the case of ties.
5415f9996aaSopenharmony_ci    deps_map = collections.OrderedDict()
5425f9996aaSopenharmony_ci
5435f9996aaSopenharmony_ci    def discover(nodes):
5445f9996aaSopenharmony_ci        for node in nodes:
5455f9996aaSopenharmony_ci            if node in deps_map:
5465f9996aaSopenharmony_ci                continue
5475f9996aaSopenharmony_ci            deps = deps_func(node)
5485f9996aaSopenharmony_ci            discover(deps)
5495f9996aaSopenharmony_ci            deps_map[node] = deps
5505f9996aaSopenharmony_ci
5515f9996aaSopenharmony_ci    discover(top)
5525f9996aaSopenharmony_ci    return list(deps_map.keys())
5535f9996aaSopenharmony_ci
5545f9996aaSopenharmony_ci
5555f9996aaSopenharmony_cidef _compute_python_dependencies():
5565f9996aaSopenharmony_ci    """Gets the paths of imported non-system python modules.
5575f9996aaSopenharmony_ci
5585f9996aaSopenharmony_ci    A path is assumed to be a "system" import if it is outside of chromium's
5595f9996aaSopenharmony_ci    src/. The paths will be relative to the current directory.
5605f9996aaSopenharmony_ci    """
5615f9996aaSopenharmony_ci    _force_lazy_modules_to_load()
5625f9996aaSopenharmony_ci    module_paths = (m.__file__ for m in sys.modules.values()
5635f9996aaSopenharmony_ci                    if m is not None and hasattr(m, '__file__') and m.__file__)
5645f9996aaSopenharmony_ci    abs_module_paths = list(map(os.path.abspath, module_paths))
5655f9996aaSopenharmony_ci
5665f9996aaSopenharmony_ci    assert os.path.isabs(DIR_SOURCE_ROOT)
5675f9996aaSopenharmony_ci    non_system_module_paths = [
5685f9996aaSopenharmony_ci        p for p in abs_module_paths if p.startswith(DIR_SOURCE_ROOT)
5695f9996aaSopenharmony_ci    ]
5705f9996aaSopenharmony_ci
5715f9996aaSopenharmony_ci    def convert_pyc_to_py(s):
5725f9996aaSopenharmony_ci        if s.endswith('.pyc'):
5735f9996aaSopenharmony_ci            return s[:-1]
5745f9996aaSopenharmony_ci        return s
5755f9996aaSopenharmony_ci
5765f9996aaSopenharmony_ci    non_system_module_paths = list(
5775f9996aaSopenharmony_ci        map(convert_pyc_to_py, non_system_module_paths))
5785f9996aaSopenharmony_ci    non_system_module_paths = list(
5795f9996aaSopenharmony_ci        map(os.path.relpath, non_system_module_paths))
5805f9996aaSopenharmony_ci    return sorted(set(non_system_module_paths))
5815f9996aaSopenharmony_ci
5825f9996aaSopenharmony_ci
5835f9996aaSopenharmony_cidef _force_lazy_modules_to_load():
5845f9996aaSopenharmony_ci    """Forces any lazily imported modules to fully load themselves.
5855f9996aaSopenharmony_ci
5865f9996aaSopenharmony_ci    Inspecting the modules' __file__ attribute causes lazily imported modules
5875f9996aaSopenharmony_ci    (e.g. from email) to get fully imported and update sys.modules. Iterate
5885f9996aaSopenharmony_ci    over the values until sys.modules stabilizes so that no modules are missed.
5895f9996aaSopenharmony_ci    """
5905f9996aaSopenharmony_ci    while True:
5915f9996aaSopenharmony_ci        num_modules_before = len(list(sys.modules.keys()))
5925f9996aaSopenharmony_ci        for m in list(sys.modules.values()):
5935f9996aaSopenharmony_ci            if m is not None and hasattr(m, '__file__'):
5945f9996aaSopenharmony_ci                _ = m.__file__
5955f9996aaSopenharmony_ci        num_modules_after = len(list(sys.modules.keys()))
5965f9996aaSopenharmony_ci        if num_modules_before == num_modules_after:
5975f9996aaSopenharmony_ci            break
5985f9996aaSopenharmony_ci
5995f9996aaSopenharmony_ci
6005f9996aaSopenharmony_cidef add_depfile_option(parser):
6015f9996aaSopenharmony_ci    if hasattr(parser, 'add_option'):
6025f9996aaSopenharmony_ci        func = parser.add_option
6035f9996aaSopenharmony_ci    else:
6045f9996aaSopenharmony_ci        func = parser.add_argument
6055f9996aaSopenharmony_ci    func('--depfile', help='Path to depfile (refer to `gn help depfile`)')
6065f9996aaSopenharmony_ci
6075f9996aaSopenharmony_ci
6085f9996aaSopenharmony_cidef write_depfile(depfile_path, first_gn_output, inputs=None, add_pydeps=True):
6095f9996aaSopenharmony_ci    assert depfile_path != first_gn_output  # http://crbug.com/646165
6105f9996aaSopenharmony_ci    inputs = inputs or []
6115f9996aaSopenharmony_ci    if add_pydeps:
6125f9996aaSopenharmony_ci        inputs = _compute_python_dependencies() + inputs
6135f9996aaSopenharmony_ci    inputs = sorted(inputs)
6145f9996aaSopenharmony_ci    make_directory(os.path.dirname(depfile_path))
6155f9996aaSopenharmony_ci    # Ninja does not support multiple outputs in depfiles.
6165f9996aaSopenharmony_ci    with open(depfile_path, 'w') as depfile:
6175f9996aaSopenharmony_ci        depfile.write(first_gn_output.replace(' ', '\\ '))
6185f9996aaSopenharmony_ci        depfile.write(': ')
6195f9996aaSopenharmony_ci        depfile.write(' '.join(i.replace(' ', '\\ ') for i in inputs))
6205f9996aaSopenharmony_ci        depfile.write('\n')
6215f9996aaSopenharmony_ci
6225f9996aaSopenharmony_ci
6235f9996aaSopenharmony_cidef expand_file_args(args):
6245f9996aaSopenharmony_ci    """Replaces file-arg placeholders in args.
6255f9996aaSopenharmony_ci
6265f9996aaSopenharmony_ci    These placeholders have the form:
6275f9996aaSopenharmony_ci      @FileArg(filename:key1:key2:...:keyn)
6285f9996aaSopenharmony_ci
6295f9996aaSopenharmony_ci    The value of such a placeholder is calculated by reading 'filename' as json.
6305f9996aaSopenharmony_ci    And then extracting the value at [key1][key2]...[keyn].
6315f9996aaSopenharmony_ci
6325f9996aaSopenharmony_ci    Note: This intentionally does not return the list of files that appear in
6335f9996aaSopenharmony_ci    such placeholders. An action that uses file-args *must* know the paths of
6345f9996aaSopenharmony_ci    those files prior to the parsing of the arguments (typically by explicitly
6355f9996aaSopenharmony_ci    listing them in the action's inputs in build files).
6365f9996aaSopenharmony_ci    """
6375f9996aaSopenharmony_ci    new_args = list(args)
6385f9996aaSopenharmony_ci    file_jsons = dict()
6395f9996aaSopenharmony_ci    r = re.compile(r'@FileArg\((.*?)\)')
6405f9996aaSopenharmony_ci    for i, arg in enumerate(args):
6415f9996aaSopenharmony_ci        match = r.search(arg)
6425f9996aaSopenharmony_ci        if not match:
6435f9996aaSopenharmony_ci            continue
6445f9996aaSopenharmony_ci
6455f9996aaSopenharmony_ci        if match.end() != len(arg):
6465f9996aaSopenharmony_ci            raise Exception(
6475f9996aaSopenharmony_ci                'Unexpected characters after FileArg: {}'.format(arg))
6485f9996aaSopenharmony_ci
6495f9996aaSopenharmony_ci        lookup_path = match.group(1).split(':')
6505f9996aaSopenharmony_ci        file_path = lookup_path[0]
6515f9996aaSopenharmony_ci        if file_path not in file_jsons:
6525f9996aaSopenharmony_ci            with open(file_path) as f:
6535f9996aaSopenharmony_ci                file_jsons[file_path] = json.load(f)
6545f9996aaSopenharmony_ci
6555f9996aaSopenharmony_ci        expansion = file_jsons[file_path]
6565f9996aaSopenharmony_ci
6575f9996aaSopenharmony_ci        for k in lookup_path[1:]:
6585f9996aaSopenharmony_ci            if k in expansion:
6595f9996aaSopenharmony_ci                expansion = expansion[k]
6605f9996aaSopenharmony_ci            else:
6615f9996aaSopenharmony_ci                expansion = ""
6625f9996aaSopenharmony_ci                print("WARNNING", lookup_path[1:], "is not in metadata file, set default ''")
6635f9996aaSopenharmony_ci        # This should match parse_gn_list. The output is either a GN-formatted list
6645f9996aaSopenharmony_ci        # or a literal (with no quotes).
6655f9996aaSopenharmony_ci        if isinstance(expansion, list):
6665f9996aaSopenharmony_ci            new_args[i] = arg[:match.start()] + gn_helpers.to_gn_string(
6675f9996aaSopenharmony_ci                expansion)
6685f9996aaSopenharmony_ci        else:
6695f9996aaSopenharmony_ci            new_args[i] = arg[:match.start()] + str(expansion)
6705f9996aaSopenharmony_ci
6715f9996aaSopenharmony_ci    return new_args
6725f9996aaSopenharmony_ci
6735f9996aaSopenharmony_ci
6745f9996aaSopenharmony_cidef read_sources_list(sources_list_file_name):
6755f9996aaSopenharmony_ci    """Reads a GN-written file containing list of file names and returns a list.
6765f9996aaSopenharmony_ci
6775f9996aaSopenharmony_ci    Note that this function should not be used to parse response files.
6785f9996aaSopenharmony_ci    """
6795f9996aaSopenharmony_ci    with open(sources_list_file_name) as f:
6805f9996aaSopenharmony_ci        return [file_name.strip() for file_name in f]
6815f9996aaSopenharmony_ci
6825f9996aaSopenharmony_ci
6835f9996aaSopenharmony_cidef call_and_write_depfile_if_stale(function,
6845f9996aaSopenharmony_ci                                    options,
6855f9996aaSopenharmony_ci                                    record_path=None,
6865f9996aaSopenharmony_ci                                    input_paths=None,
6875f9996aaSopenharmony_ci                                    input_strings=None,
6885f9996aaSopenharmony_ci                                    output_paths=None,
6895f9996aaSopenharmony_ci                                    force=False,
6905f9996aaSopenharmony_ci                                    pass_changes=False,
6915f9996aaSopenharmony_ci                                    depfile_deps=None,
6925f9996aaSopenharmony_ci                                    add_pydeps=True):
6935f9996aaSopenharmony_ci    """Wraps md5_check.call_and_record_if_stale() and writes a depfile if applicable.
6945f9996aaSopenharmony_ci
6955f9996aaSopenharmony_ci    Depfiles are automatically added to output_paths when present in the
6965f9996aaSopenharmony_ci    |options| argument. They are then created after |function| is called.
6975f9996aaSopenharmony_ci
6985f9996aaSopenharmony_ci    By default, only python dependencies are added to the depfile. If there are
6995f9996aaSopenharmony_ci    other input paths that are not captured by GN deps, then they should be
7005f9996aaSopenharmony_ci    listed in depfile_deps. It's important to write paths to the depfile that
7015f9996aaSopenharmony_ci    are already captured by GN deps since GN args can cause GN deps to change,
7025f9996aaSopenharmony_ci    and such changes are not immediately reflected in depfiles
7035f9996aaSopenharmony_ci    (http://crbug.com/589311).
7045f9996aaSopenharmony_ci    """
7055f9996aaSopenharmony_ci    if not output_paths:
7065f9996aaSopenharmony_ci        raise Exception('At least one output_path must be specified.')
7075f9996aaSopenharmony_ci    input_paths = list(input_paths or [])
7085f9996aaSopenharmony_ci    input_strings = list(input_strings or [])
7095f9996aaSopenharmony_ci    output_paths = list(output_paths or [])
7105f9996aaSopenharmony_ci
7115f9996aaSopenharmony_ci    python_deps = None
7125f9996aaSopenharmony_ci    if hasattr(options, 'depfile') and options.depfile:
7135f9996aaSopenharmony_ci        python_deps = _compute_python_dependencies()
7145f9996aaSopenharmony_ci        input_paths += python_deps
7155f9996aaSopenharmony_ci        output_paths += [options.depfile]
7165f9996aaSopenharmony_ci
7175f9996aaSopenharmony_ci    def on_stale_md5(changes):
7185f9996aaSopenharmony_ci        args = (changes, ) if pass_changes else ()
7195f9996aaSopenharmony_ci        function(*args)
7205f9996aaSopenharmony_ci        if python_deps is not None:
7215f9996aaSopenharmony_ci            all_depfile_deps = list(python_deps) if add_pydeps else []
7225f9996aaSopenharmony_ci            if depfile_deps:
7235f9996aaSopenharmony_ci                all_depfile_deps.extend(depfile_deps)
7245f9996aaSopenharmony_ci            write_depfile(options.depfile,
7255f9996aaSopenharmony_ci                          output_paths[0],
7265f9996aaSopenharmony_ci                          all_depfile_deps,
7275f9996aaSopenharmony_ci                          add_pydeps=False)
7285f9996aaSopenharmony_ci
7295f9996aaSopenharmony_ci    md5_check.call_and_record_if_stale(on_stale_md5,
7305f9996aaSopenharmony_ci                                       record_path=record_path,
7315f9996aaSopenharmony_ci                                       input_paths=input_paths,
7325f9996aaSopenharmony_ci                                       input_strings=input_strings,
7335f9996aaSopenharmony_ci                                       output_paths=output_paths,
7345f9996aaSopenharmony_ci                                       force=force,
7355f9996aaSopenharmony_ci                                       pass_changes=True)
7365f9996aaSopenharmony_ci
7375f9996aaSopenharmony_ci
7385f9996aaSopenharmony_cidef get_all_files(base, follow_symlinks=False):
7395f9996aaSopenharmony_ci    """Returns a list of all the files in |base|. Each entry is relative to the
7405f9996aaSopenharmony_ci    last path entry of |base|.
7415f9996aaSopenharmony_ci    """
7425f9996aaSopenharmony_ci    result = []
7435f9996aaSopenharmony_ci    for root, _, files in os.walk(base, followlinks=follow_symlinks):
7445f9996aaSopenharmony_ci        result.extend([os.path.join(root, f) for f in files])
7455f9996aaSopenharmony_ci
7465f9996aaSopenharmony_ci    return result
7475f9996aaSopenharmony_ci
7485f9996aaSopenharmony_ci
7495f9996aaSopenharmony_cidef rebase_path(path_to_rebase, new_base=None, current_base="."):
7505f9996aaSopenharmony_ci    if new_base:
7515f9996aaSopenharmony_ci        return os.path.relpath(os.path.join(current_base, path_to_rebase), new_base)
7525f9996aaSopenharmony_ci    else:
7535f9996aaSopenharmony_ci        return os.path.realpath(os.path.join(current_base, path_to_rebase))
754