1. 引言
在解决了Rust标准库查找问题后,我们遇到的下一个技术挑战是rustc_wrapper.py脚本的执行错误。在Windows环境下,该脚本无法正确调用Rust编译器,导致编译过程中断。本文将详细介绍如何解决这个问题。
2. 问题分析
2.1 错误表现
编译过程中会遇到如下错误:
FAILED: obj/third_party/rust/itoa/v1/lib/libitoa_lib.rlib
[WinError 193] %1 is not a valid Win32 application
2.2 错误原因
- rustc_wrapper.py脚本未正确处理Windows可执行文件调用
- subprocess.run()函数参数配置不当
- Windows环境下的路径处理问题
3. 修复方案
3.1 修改rustc_wrapper.py
需要在rustc_wrapper.py文件中修改rustc命令的调用方式:
修改前:
r = subprocess.run([args.rustc, *rustc_args], env=env, check=False)
修改后:
rustc_path = str(args.rustc) + ".exe"
r = subprocess.run([rustc_path, *rustc_args], env=env, check=False)
3.2 核心改动说明
- 添加.exe后缀
- 确保在Windows环境下正确识别可执行文件
- 解决Win32应用程序识别问题
- 路径处理优化
- 保持Windows路径格式一致性
- 确保文件系统访问正确
3.3 完整代码
#!/usr/bin/env python3# Copyright 2021 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.import argparse
import pathlib
import subprocess
import shlex
import os
import sys
import re# Set up path to be able to import action_helpers.
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir,os.pardir, 'build'))
import action_helpers# This script wraps rustc for (currently) these reasons:
# * To work around some ldflags escaping performed by ninja/gn
# * To remove dependencies on some environment variables from the .d file.
# * To enable use of .rsp files.
# * To work around two gn bugs on Windows
#
# LDFLAGS ESCAPING
#
# This script performs a simple function to work around some of the
# parameter escaping performed by ninja/gn.
#
# rustc invocations are given access to {{rustflags}} and {{ldflags}}.
# We want to pass {{ldflags}} into rustc, using -Clink-args="{{ldflags}}".
# Unfortunately, ninja assumes that each item in {{ldflags}} is an
# independent command-line argument and will have escaped them appropriately
# for use on a bare command line, instead of in a string.
#
# This script converts such {{ldflags}} into individual -Clink-arg=X
# arguments to rustc.
#
# RUSTENV dependency stripping
#
# When Rust code depends on an environment variable at build-time
# (using the env! macro), rustc spots that and adds it to the .d file.
# Ninja then parses that .d file and determines that the environment
# dependency means that the target always needs to be rebuilt.
#
# That's all correct, but _we_ know that some of these environment
# variables (typically, all of them) are set by .gn files which ninja
# tracks independently. So we remove them from the .d file.
#
# RSP files:
#
# We want to put the ninja/gn variables {{rustdeps}} and {{externs}}
# in an RSP file. Unfortunately, they are space-separated variables
# but Rust requires a newline-separated input. This script duly makes
# the adjustment. This works around a gn issue:
# TODO(https://bugs.ch40m1um.qjz9zk/p/gn/issues/detail?id=249): fix this
#
# WORKAROUND WINDOWS BUGS:
#
# On Windows platforms, this temporarily works around some issues in gn.
# See comments inline, linking to the relevant gn fixes.
#
# Usage:
# rustc_wrapper.py --rustc <path to rustc> --depfile <path to .d file>
# -- <normal rustc args> LDFLAGS {{ldflags}} RUSTENV {{rustenv}}
# The LDFLAGS token is discarded, and everything after that is converted
# to being a series of -Clink-arg=X arguments, until or unless RUSTENV
# is encountered, after which those are interpreted as environment
# variables to pass to rustc (and which will be removed from the .d file).
#
# Both LDFLAGS and RUSTENV **MUST** be specified, in that order, even if
# the list following them is empty.
#
# TODO(https://github.com/rust-lang/rust/issues/73632): avoid using rustc
# for linking in the first place. Most of our binaries are linked using
# clang directly, but there are some types of Rust build product which
# must currently be created by rustc (e.g. unit test executables). As
# part of support for using non-rustc linkers, we should arrange to extract
# such functionality from rustc so that we can make all types of binary
# using our clang toolchain. That will remove the need for most of this
# script.FILE_RE = re.compile("[^:]+: (.+)")# Equivalent of python3.9 built-in
def remove_lib_suffix_from_l_args(text):if text.startswith("-l") and text.endswith(".lib"):return text[:-len(".lib")]return textdef verify_inputs(depline, sources, abs_build_root):"""Verify everything used by rustc (found in `depline`) was specified in theGN build rule (found in `sources` or `inputs`).TODO(danakj): This allows things in `sources` that were not actually used byrustc since third-party packages sources need to be a union of all buildconfigs/platforms for simplicity in generating build rules. For first-partycode we could be more strict and reject things in `sources` that were notconsumed."""# str.removeprefix() does not exist before python 3.9.def remove_prefix(text, prefix):if text.startswith(prefix):return text[len(prefix):]return textdef normalize_path(p):return os.path.relpath(os.path.normpath(remove_prefix(p, abs_build_root))).replace('\\', '/')# Collect the files that rustc says are needed.found_files = {}m = FILE_RE.match(depline)if m:files = m.group(1)found_files = {normalize_path(f): f for f in files.split()}# Get which ones are not listed in GN.missing_files = found_files.keys() - sourcesif not missing_files:return True# The matching did a bunch of path manipulation to get paths relative to the# build dir such that they would match GN. In errors, we will print out the# exact path that rustc produces for easier debugging and writing of stdlib# config rules.for file_files_key in missing_files:gn_type = "sources" if file_files_key.endswith(".rs") else "inputs"print(f'ERROR: file not in GN {gn_type}: {found_files[file_files_key]}',file=sys.stderr)return Falsedef main():parser = argparse.ArgumentParser()parser.add_argument('--rustc', required=True, type=pathlib.Path)parser.add_argument('--depfile', required=True, type=pathlib.Path)parser.add_argument('--rsp', type=pathlib.Path, required=True)parser.add_argument('--target-windows', action='store_true')parser.add_argument('-v', action='store_true')parser.add_argument('args', metavar='ARG', nargs='+')args = parser.parse_args()remaining_args = args.argsldflags_separator = remaining_args.index("LDFLAGS")rustenv_separator = remaining_args.index("RUSTENV", ldflags_separator)# Sometimes we duplicate the SOURCES list into the command line for debugging# issues on the bots.try:sources_separator = remaining_args.index("SOURCES", rustenv_separator)except:sources_separator = Nonerustc_args = remaining_args[:ldflags_separator]ldflags = remaining_args[ldflags_separator + 1:rustenv_separator]rustenv = remaining_args[rustenv_separator + 1:sources_separator]abs_build_root = os.getcwd().replace('\\', '/') + '/'is_windows = sys.platform == 'win32' or args.target_windowsrustc_args.extend(["-Clink-arg=%s" % arg for arg in ldflags])with open(args.rsp) as rspfile:rsp_args = [l.rstrip() for l in rspfile.read().split(' ') if l.rstrip()]sources_separator = rsp_args.index("SOURCES")sources = set(rsp_args[sources_separator + 1:])rsp_args = rsp_args[:sources_separator]if is_windows:# Work around for "-l<foo>.lib", where ".lib" suffix is undesirable.# Full fix will come from https://gn-review.9oo91esource.qjz9zk/c/gn/+/12480rsp_args = [remove_lib_suffix_from_l_args(arg) for arg in rsp_args]out_rsp = str(args.rsp) + ".rsp"with open(out_rsp, 'w') as rspfile:# rustc needs the rsp file to be separated by newlines. Note that GN# generates the file separated by spaces:# https://bugs.ch40m1um.qjz9zk/p/gn/issues/detail?id=249,rspfile.write("\n".join(rsp_args))rustc_args.append(f'@{out_rsp}')env = os.environ.copy()fixed_env_vars = []for item in rustenv:(k, v) = item.split("=", 1)env[k] = vfixed_env_vars.append(k)try:if args.v:print(' '.join(f'{k}={shlex.quote(v)}' for k, v in env.items()),args.rustc, shlex.join(rustc_args))rustc_path = str(args.rustc) + ".exe"r = subprocess.run([rustc_path, *rustc_args], env=env, check=False)finally:if not args.v:os.remove(out_rsp)if r.returncode != 0:sys.exit(r.returncode)final_depfile_lines = []dirty = Falsewith open(args.depfile, encoding="utf-8") as d:# Figure out which lines we want to keep in the depfile. If it's not the# whole file, we will rewrite the file.env_dep_re = re.compile("# env-dep:(.*)=.*")for line in d:m = env_dep_re.match(line)if m and m.group(1) in fixed_env_vars:dirty = True # We want to skip this line.else:final_depfile_lines.append(line)# Verify each dependent file is listed in sources/inputs.for line in final_depfile_lines:if not verify_inputs(line, sources, abs_build_root):return 1if dirty: # we made a change, let's write out the filewith action_helpers.atomic_output(args.depfile) as output:output.write("\n".join(final_depfile_lines).encode("utf-8"))if __name__ == '__main__':sys.exit(main())
4. 验证修复
4.1 测试修改
修改完成后,执行以下命令进行验证:
python build.py --tarball
4.2 检查输出
- 确认rustc编译器能够正常调用
- 验证编译过程顺利进行
- 检查生成文件的完整性
5. 可能遇到的问题
5.1 环境变量问题
- 确保RUST_BACKTRACE设置正确
- 验证PATH环境变量包含必要路径
- 检查临时目录权限
5.2 工具链配置
- 验证rustc版本匹配性
- 确认编译器组件完整性
- 检查依赖项配置
6. 结语
通过本文的指导,我们成功解决了Ungoogled Chromium编译过程中的Rust编译器包装器问题。这个修复是确保Rust组件正确编译的重要环节。
在下一篇文章中,我们将继续探讨Chromium编译过程中的其他技术挑战。请确保按本文的步骤正确修复rustc_wrapper.py的问题,为后续的编译工作打下坚实基础。