标签 chromium 下的文章

必须与上一个脚本配套使用!

import os
import pickle
import sys

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print(f"Usage: {os.path.basename(__file__)} <human_patch.dat>")
        exit(0)

    data_list = []
    with open(sys.argv[1], "rb") as data_file:
        data_list = pickle.load(data_file)

    print("Start review auto patch failed files!")
    for work_file, rej_file in data_list:
        print(work_file)
        os.system("code %s" % work_file)
        os.system("view %s" % rej_file)

与chromium相关:

# coding: utf-8
import filecmp
import os
import pickle
import shutil
import subprocess
import sys

__src_dir_name = "src" + os.sep
__patch_dir_name = "patch" + os.sep
__reject_dir_name = "rejects" + os.sep

__copy_log_name = "copy_file.log"
__miss_log_name = "miss_file.log"
__patch_success_log_name = "patch_success.log"
__patch_failed_log_name = "patch_failed.log"
__human_patch_data_name = "human_patch.dat"

__force_copy_type = (".png", ".dll", ".exe", ".ico", ".icns")

__chromium_version_file = "chrome/VERSION"
__self_servion_file = "third_party/superBrowser/version"

__human_patch_list = []


def __reset_self_servion(src_dir):
    chromium_version_path = os.path.join(src_dir, __chromium_version_file)
    self_servion_path = os.path.join(src_dir, __self_servion_file)
    patch = 0
    with open(chromium_version_path) as chromium_file:
        for it in chromium_file.readlines():
            idx = it.find('=')
            if -1 != idx and "PATCH" == it[:idx]:
                patch = int(it[idx + 1:])
                break
    with open(self_servion_path, "r+") as self_file:
        ver = float(self_file.read())
        self_file.seek(0)
        if patch > 50:
            if ver >= 1.0:
                minor = int((ver - 1.0) * 10) + 1
                if minor > 9:
                    self_file.write("{:.2f}".format(minor / 100 + 1.0))
                else:
                    self_file.write("{:.1f}".format(minor / 10 + 1.0))
            else:
                self_file.write("1.0")
        else:
            self_file.write("0.1")


def __check_base_version():
    base_git_version_file = os.path.join(base_git_path,
                                         __chromium_version_file)
    base_version_file = os.path.join(base_version_path,
                                     __chromium_version_file)
    if not filecmp.cmp(base_version_file, base_git_version_file):
        raise Exception(
            "Git file version does not match the base file version.")


def __save_human_patch():
    with open(os.path.join(output_path, __human_patch_data_name),
              "wb") as data_file:
        pickle.dump(__human_patch_list, data_file)


def __make_dirs(file_path):
    dest_dir = os.path.dirname(file_path)
    if not os.path.exists(dest_dir):
        os.makedirs(dest_dir)


def save_miss_file(file_list, log_path):
    with open(log_path, "w") as log_file:
        for file in file_list:
            print(file)
            print(file, file=log_file)


def safe_copy(src, dest):
    __make_dirs(dest)
    shutil.copyfile(src, dest)


def generate_patch(old_file_path, new_file_path, work_file_path):
    patch_file_path = output_path + __patch_dir_name + work_file_path + ".patch"
    cmd_line = f"diff -c {old_file_path} {new_file_path} > {patch_file_path}"

    __make_dirs(patch_file_path)
    return 256 == os.system(cmd_line)


def apply_patch(base_file_path, work_file_path):
    output_file_path = output_path + __src_dir_name + work_file_path
    patch_file_path = output_path + __patch_dir_name + work_file_path + ".patch"
    reject_file_path = output_path + __reject_dir_name + work_file_path + ".rej"
    cmd_line = [
        "patch", "-c", base_file_path, patch_file_path, "-o", output_file_path,
        "-r", reject_file_path
    ]
    __make_dirs(output_file_path)
    __make_dirs(reject_file_path)
    rv = subprocess.run(cmd_line, capture_output=True, text=True)
    if 0 != rv.returncode:
        __human_patch_list.append((output_file_path, reject_file_path))
    return rv


def delete_empty_directories(path):
    for dirpath, dirnames, _ in os.walk(path, topdown=False):
        for dirname in dirnames:
            dir_to_check = os.path.join(dirpath, dirname)
            if not os.listdir(dir_to_check):
                # print(f"Removing empty directory: {dir_to_check}")
                os.rmdir(dir_to_check)


if __name__ == "__main__":
    if "nt" == os.name:
        print("This scripts can`t run in windows platform!")
        exit(1)
    try:
        if len(sys.argv) < 5:
            print(
                "Usage: upgrade_version_file.py <base_git_path> <base_version_path> <new_version_path> <output_path>."
            )
            exit(0)

        base_git_path = os.path.abspath(sys.argv[1])
        base_version_path = os.path.abspath(sys.argv[2])
        new_version_path = os.path.abspath(sys.argv[3])
        output_path = os.path.abspath(sys.argv[4]) + os.sep

        __check_base_version()

        copy_list = []
        patch_list = []
        miss_list = []

        print("Strat check file ...", end="", flush=True)
        for root, _, files in os.walk(base_git_path):
            for file in files:
                git_file = os.path.join(root, file)
                work_file = os.path.relpath(git_file, base_git_path)
                base_version_file = os.path.join(base_version_path, work_file)
                new_version_file = os.path.join(new_version_path, work_file)

                # find copy file
                if not os.path.exists(base_version_file):
                    copy_list.append((git_file, work_file))
                elif not os.path.exists(
                        new_version_file):  # new version miss file.
                    miss_list.append(work_file)
                elif filecmp.cmp(
                        base_version_file,
                        new_version_file) or os.path.splitext(
                            base_version_file)[1] in __force_copy_type:
                    if not filecmp.cmp(git_file, base_version_file):
                        copy_list.append((git_file, work_file))
                # make patch file list.
                else:
                    patch_list.append((base_version_file, git_file,
                                       new_version_file, work_file))
        # add version file
        copy_list.append(
            (os.path.join(new_version_path,
                          __chromium_version_file), __chromium_version_file))
        print(" do!")

        # create output dir
        if not os.path.exists(output_path):
            os.makedirs(output_path)
        # generate patch file list
        print("----------------------patch file:----------------------")
        with open(output_path + __patch_success_log_name,
                  "w") as success_log, open(
                      output_path + __patch_failed_log_name,
                      "w") as failed_log:
            for base_version_file, git_file, new_version_file, work_file in patch_list:
                print(f"{work_file} ... ", end="")
                if generate_patch(base_version_file, git_file, work_file):
                    result = apply_patch(new_version_file, work_file)
                    if 0 == result.returncode:
                        print("OK!")
                        print(work_file, file=success_log)
                    else:
                        print("Failed!")
                        print(work_file, file=failed_log)
                        print(result.stdout, file=failed_log)
                        print(
                            "------------------------------------------------------------\n",
                            file=failed_log)
                else:
                    print("Skip!")
                    print(f"{work_file} ... Skip!", file=failed_log)
                    print(
                        "\n------------------------------------------------------------\n",
                        file=failed_log)
        delete_empty_directories(output_path + __reject_dir_name)
        __save_human_patch()
        # copy file
        print("----------------------copy file:----------------------")
        src_dir = os.path.join(output_path, __src_dir_name)
        with open(output_path + __copy_log_name, "w") as copy_log:
            for src, work_file in copy_list:
                print(work_file)
                safe_copy(src, src_dir + work_file)
                print(work_file, file=copy_log)
        __reset_self_servion(src_dir)
        icon_dir = os.path.join(new_version_path, "chrome", "app", "theme",
                                "chromium")
        dest_dir = os.path.join(src_dir, "third_party", "superBrowser",
                                "resource", "product", "other")
        shutil.copy(os.path.join(icon_dir, "win", "chromium.ico"), dest_dir)
        shutil.copy(os.path.join(icon_dir, "mac", "app.icns"), dest_dir)
        shutil.copy(os.path.join(icon_dir, "linux", "product_logo_48.png"),
                    dest_dir)
        # save miss file list
        print("----------------------miss file:----------------------")
        save_miss_file(miss_list, output_path + __miss_log_name)

    except Exception as error:
        print(error)
        exit(1)

之前整理过如何快速的检出指定tag的chromium源码。
这次整理成python脚本,可以在当前目录直接运行。自动完成代码检出,并有出错重试的功能。

import argparse
import os
import time
import shutil

_gcl_context = """
solutions = [
  {
    "name": "src",
    "url": "https://chromium.googlesource.com/chromium/src.git",
    "managed": False,
    "custom_deps": {},
    "custom_vars": {"checkout_pgo_profiles": True,},
  },
]
"""


def delete_error_git_dir(path):
    for dirpath, dirnames, _ in os.walk(path):
        for dirname in dirnames:
            full_dir_path = os.path.join(dirpath, dirname)
            if dirname == ".git" and len(
                    os.listdir(os.path.dirname(full_dir_path))) == 1:
                print(
                    f"Deleting error .git directory: {full_dir_path} and its contents..."
                )
                try:
                    if os.name == "nt":
                        os.system(f"rmdir /s /q {full_dir_path}")
                    else:
                        shutil.rmtree(full_dir_path)
                except OSError as e:
                    print(f"Error deleting {full_dir_path}: {str(e)}")
                    exit(1)


def _run_command_with_success(cmd):
    while 0 != os.system(cmd):
        print(f"Command:'{cmd}' failed! Start retry!")
        delete_error_git_dir("src")
    print(f"Command:'{cmd}' success!")


if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description="Checkout chromium code with tag.")
    parser.add_argument("tag", type=str, help="Tag to checkout.")
    parser.add_argument("-u",
                        "--upgrade",
                        action="store_true",
                        help="Upgrade existing code to a specified tag.")
    parser.add_argument(
        "--os",
        type=str,
        help="Specify the target platform for Chromium. For example: --os mac."
    )
    args = parser.parse_args()
    if not args.tag:
        parser.error('the "tag" argument is necessary')

    start = time.time()
    with open(".gclient", "w") as gcl:
        gcl.write(_gcl_context)
        if args.os:
            gcl.write(f'target_os = ["{args.os}"]\n')

    # checkout chromium
    if args.upgrade and os.path.exists("src"):
        print(f"Start upgrade chromium code to tag {args.tag} !")
        _run_command_with_success(
            f"git -C \"src\" fetch origin refs/tags/{args.tag}")
        os.system(f"git -C \"src\" checkout -b local_{args.tag} {args.tag}")
    elif os.path.exists("src"):
        print("Have 'src' directory, so skip checkout tag!")
    else:
        print(f"Start checkout chromium tag {args.tag} !")
        _run_command_with_success(
            f"git clone --depth 2 -b {args.tag} https://chromium.googlesource.com/chromium/src.git"
        )

    # run hook
    print("Start run hook!")
    _run_command_with_success(
        "gclient sync -D --with_branch_heads --with_tags")

    print(
        f"checkout chromium {args.tag} Ok! Use time:{time.strftime('%H:%M:%S', time.gmtime(time.time() - start))} !"
    )

之前整理过一篇《直接检出chromium某tag的完整可构建源码。》的文章。

这个方法中,在git fetch这个步骤需要花费大量的时间。
经过对git命令的研究之后,终于再总结出一个最快速的从零开始,直接check出指定的tag代码的方法。旧的方法完全可以淘汰了。

首先,还是先创建代码存放目前。不同的是,这里不需要创建src目录。

mkdir chromium
cd chromium

然后还是创建.gclient文件:

solutions = [
  {
    "name": "src",
    "url": "https://chromium.googlesource.com/chromium/src.git",
    "managed": False,
    "custom_deps": {},
    "custom_vars": {"checkout_pgo_profiles": True,},
  },
]

如果是要构建android平台的浏览器,就在这个文件最后添加一行内容:

target_os = ["android"]

到这里,还跟之前的方法相差不多。但是接下来就不一样了。以当前最新的官方正式版本112.0.5615.50为例。
直接克隆这个tag的代码:

git clone --depth 2 -b 112.0.5615.50 https://chromium.googlesource.com/chromium/src.git

这里要注意,其中--depth必须为2,原因后面会说到。
这个步骤,代替了原来的git fetch。并且深度只有2,所以原本需要同步的30+G的数据,变成只有区区1+G。这样就节省下了大量的时间和流量。

接下来,还是同步第三方工程和工具链。

gclient sync -D --with_branch_heads --with_tags

这里就要提一下为什么上面--depth必须为2。因为gclient在runhook的时候,是会根据git的提交日志来创建当前的版本信息。而深度为1的时候,就失去了需要的日志内容。会导致下面两个文件内的信息为空。当然,这样也会不导致编译失败,但是在浏览器内就无法看到构建的版本信息了。

src/build/util/LASTCHANGE
src/gpu/config/gpu_lists_version.h

这时,这份源码就已经可以编译出一份完整的chromium浏览器了。
同时,在这份源码的基础上,同样可以继续更新后续的代码。比如需要更新到112.0.5615.67这个tag。

cd src
git fetch origin tag 112.0.5615.67
git checkout -b local_112.0.5615.67 112.0.5615.67
cd ..
gclient sync -D --with_branch_heads --with_tags

而这个更新过程,同步的数据也非常小,非常快捷。因此这个方法应该是最终的chromium代码同步方法了。

chrome是目前地球上最成功的浏览器,而它的开源项目chromium也是最重要的开源项目之一。
研究chromium的源码,就成为很多人(比如我)重要的工作与学习手段。但是在国内想要check一份可构建的chromium源码其实并不太容易。除了众所周知的原因外,也因为chromium经过长期高速的开发和迭代之后,积累下来了庞大的源码。

如果按官方文档中的方法check源码,默认会check出当前最新的开发版代码。
或者拉回完整的git仓库,再check到指定的稳定版tag。这样很可能会拉回很多我们根本不需要的分支和tag。

实际上,是可以从零开始,直接check出指定的tag代码的。

那么让我们开始之前,假设已经满足了以下条件。

  1. 克服了众所周知的问题。这个只能自己想办法……
  2. 安装了所有需要的开发环境,比如depot_tools等。这个可以参考官方文档。

此方法适用于所有平台。以目前最新官方正式版本102.0.5005.63为例。

首先创建存放代码的目录:

mkdir chromium
cd chromium
mkdir src

创建纯文本文件.gclient并写入以下内容:

solutions = [
  {
    "name": "src",
    "url": "https://chromium.googlesource.com/chromium/src.git",
    "managed": False,
    "custom_deps": {},
    "custom_vars": {"checkout_pgo_profiles": True,},
  },
]

如果是要构建android平台的浏览器,就在这个文件最后添加一行内容:

target_os = ["android"]

接下来创建一个空的git仓库,来同步指定的tag。

cd src
git init
git remote add origin https://chromium.googlesource.com/chromium/src.git
git fetch origin tag 102.0.5005.63

最后一步就是在从官方的git中同步指定的tag。这个步骤是整个过程中最耗时的一步。具体的时间取决于网络情况与tag的新旧程度。越新的tag,需要同步的数据也就越大。
耐心等待tag同步完成。完成后src目录下还是空空如也,并没有代码文件。这时候就需要创建一个本地分支,从tag中check出代码。

git checkout -b local_102.0.5005.63 102.0.5005.63

这个步骤很快,完成后目录下就有chromium的代码了。但是目录中的代码还是不能构建出chromium浏览器,还缺少依赖的一些第三方工程,和构建需要的工具链。
接下来继续同步这些内容:

cd ..
gclient sync -D --with_branch_heads --with_tags

这个步骤也会花费一些时间,并且网络不佳的情况下还有可能会失败。不断重试直到最后全部成功就好。

当这个步骤完成后,完整的一份可构建的chromium源码就完成了。你可以构建出自己的chromium浏览器了,具体方法还是参考官方文档。

在这份代码上,还可以继续同步别的tag代码。只要把上面步骤中出现的版本号换成需要的tag版本号就可以了。这样可以节省不少的时间。