def create_dir_hash(
    cmds: List[str],
    build_dir: str,
    filter_func: Callable[[str], bool] = is_source,
    debug=False,
    content_replacer: Callable[[str], Optional[bytes]] = None,
) -> str:
    """Create a hash of the (source file) contents of a build directory, used for
    creating a cache key of an entire directory along with the build commands.

    Args:
        cmds (List[str]): Build commands to be incorporated in hash key computation
        build_dir (str): Path to build directory ( not part of hash )
        filter_func (Callable[[str], bool], optional): Filter function which determines whether a given file is considered a source file or not. Defaults to is_source(path).
        debug (bool, optional): Whether to write a 'cache_key.log' file into the build directory, so that cache misses can be debugged more easily. Defaults to False.
        content_replacer (Callable[[Path], Optional[bytes]], optional): Content replacer is an optional function that may replace content of a file for hashing purposes. If None, or if this function returns None,
                                                                        then no content replacement is done   on the file.
    Returns:
        str: SHA256 Hash of the build directory contents in the form of a hexdigest string.
    """
    hash_log = None
    try:
        if not os.path.isdir(build_dir):
            return "empty_dir"
        if debug:
            hash_log = open(  # noqa: P201 - this is actually closed properly in the finally close below
                os.path.join(build_dir, "cache_key.log"), mode="a", encoding="utf8"
            )
            hash_log.write(f"Building dir hash of {build_dir}\n")
        basepath = Path(build_dir)
        files = [p.relative_to(basepath) for p in basepath.rglob("*") if not p.is_dir()]
        hash_object = hashlib.sha256()
        for cmd in cmds:
            _cmd = cmd.replace(
                build_dir, "${BUILD_DIR}"
            )  # Make sure we can cache regardless of the build directory location.
            hash_object.update(_cmd.encode("utf-8"))
            if debug:
                hash_log.write(f"\tCOMMAND: {_cmd} -> {hash_object.hexdigest()}\n")
        for fpath in sorted(files):
            if not filter_func(str(fpath)):
                continue
            hash_object.update(str(fpath).encode("utf-8"))
            fullpath = str(basepath / fpath)
            replaced_content = None
            if content_replacer is not None:
                replaced_content = content_replacer(fullpath)
            if replaced_content is not None:
                hash_object.update(replaced_content)
            else:
                with open(fullpath, "rb") as f:
                    # read file in chunks of 32kb
                    # in order to support large files ( constants.obj )
                    while True:
                        chunk = f.read(1024 * 32)
                        if not chunk:
                            break
                        hash_object.update(chunk)
            if debug:
                hash_log.write(f"\t{str(fpath)} -> {hash_object.hexdigest()}\n")
        if debug:
            hash_log.write(
                f"Final hash of {build_dir} is {hash_object.hexdigest().lower()}\n"
            )
        return hash_object.hexdigest().lower()
    finally:
        if hash_log:
            hash_log.close()
Create a hash of the (source file) contents of a build directory, used for
creating a cache key of an entire directory along with the build commands.

Args:
cmds (List[str]): Build commands to be incorporated in hash key computation
build_dir (str): Path to build directory ( not part of hash )
filter_func (Callable[[str], bool], optional): Filter function which determines whether a given file is considered a source file or not. Defaults to is_source(path).
debug (bool, optional): Whether to write a 'cache_key.log' file into the build directory, so that cache misses can be debugged more easily. Defaults to False.
content_replacer (Callable[[Path], Optional[bytes]], optional): Content replacer is an optional function that may replace content of a file for hashing purposes. If None, or if this function returns None,
then no content replacement is done   on the file.
Returns:
str: SHA256 Hash of the build directory contents in the form of a hexdigest string.
python/aitemplate/backend/build_cache_base.py