blob: 0264e2841ac37b4889f9183010923fcf29ef447f [file] [log] [blame]
#!/usr/bin/env fuchsia-vendored-python
# Copyright 2022 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Tests for rustc_remote_wrapper."""
import contextlib
import io
import os
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path
from unittest import mock
import rustc_remote_wrapper
import rustc
import cl_utils
import fuchsia
import remote_action
from typing import Any, Iterable, Sequence
class ImmediateExit(Exception):
"""For mocking functions that do not return."""
pass
def _write_file_contents(path: Path, contents: str):
with open(path, "w") as f:
f.write(contents)
def _read_file_contents(path: Path) -> str:
with open(path, "r") as f:
return f.read()
def _strs(items: Sequence[Any]) -> Sequence[str]:
return [str(i) for i in items]
def _paths(items: Sequence[Any]) -> Sequence[Path]:
if isinstance(items, list):
return [Path(i) for i in items]
elif isinstance(items, set):
return {Path(i) for i in items}
elif isinstance(items, tuple):
return tuple(Path(i) for i in items)
t = type(items)
raise TypeError(f"Unhandled sequence type: {t}")
class AccompanyRlibWithSoTests(unittest.TestCase):
def test_not_rlib(self):
deps = Path("../path/to/foo.rs")
self.assertEqual(
list(rustc_remote_wrapper.accompany_rlib_with_so([deps])), [deps]
)
def test_rlib_without_so(self):
deps = Path("obj/build/foo.rlib")
with mock.patch.object(
Path, "is_file", return_value=False
) as mock_isfile:
self.assertEqual(
list(rustc_remote_wrapper.accompany_rlib_with_so([deps])),
[deps],
)
mock_isfile.assert_called_once()
def test_rlib_with_so(self):
deps = Path("obj/build/foo.rlib")
with mock.patch.object(
Path, "is_file", return_value=True
) as mock_isfile:
self.assertEqual(
list(rustc_remote_wrapper.accompany_rlib_with_so([deps])),
[deps, Path("obj/build/foo.so")],
)
mock_isfile.assert_called_once()
class ExpandDepsForRlibCompileTests(unittest.TestCase):
def test_proc_macro(self):
deps = list(
rustc_remote_wrapper.expand_deps_for_rlib_compile(
[Path("foo/barmac.so")]
)
)
self.assertEqual(deps, [Path("foo/barmac.so")])
def test_rlib(self):
deps = list(
rustc_remote_wrapper.expand_deps_for_rlib_compile(
[Path("foo/bar.rlib")]
)
)
self.assertEqual(deps, [Path("foo/bar.rlib")])
def test_rmeta_missing_rlib(self):
deps = list(
rustc_remote_wrapper.expand_deps_for_rlib_compile(
[Path("foo/bar.rmeta")]
)
)
self.assertEqual(deps, [Path("foo/bar.rmeta")])
def test_rmeta_with_coexisting_rlib(self):
with mock.patch.object(
Path, "exists", return_value=True
) as mock_rlib_exists:
deps = list(
rustc_remote_wrapper.expand_deps_for_rlib_compile(
[Path("foo/bar.rmeta")]
)
)
mock_rlib_exists.assert_called_once_with()
self.assertEqual(deps, [Path("foo/bar.rmeta"), Path("foo/bar.rlib")])
class RustRemoteActionPrepareTests(unittest.TestCase):
def generate_prepare_mocks(
self,
depfile_contents: Sequence[str] = None,
compiler_shlibs: Sequence[Path] = None,
clang_rt_libdir: Path = None,
libcxx_static: Path = None,
) -> Iterable[mock.patch.object]:
"""common mocks needed for RustRemoteAction.prepare()"""
# depfile only command
yield mock.patch.object(
rustc_remote_wrapper, "_make_local_depfile", return_value=0
)
if depfile_contents:
yield mock.patch.object(
rustc_remote_wrapper,
"_readlines_from_file",
return_value=[line + "\n" for line in depfile_contents],
)
# check compiler is executable
yield mock.patch.object(
rustc_remote_wrapper, "_tool_is_executable", return_value=True
)
if compiler_shlibs:
yield mock.patch.object(
rustc_remote_wrapper.RustRemoteAction,
"_remote_rustc_shlibs",
return_value=iter(compiler_shlibs),
)
yield mock.patch.object(
rustc_remote_wrapper, "_libcxx_isfile", return_value=True
)
if clang_rt_libdir:
yield mock.patch.object(
fuchsia, "clang_runtime_libdirs", return_value=[clang_rt_libdir]
)
if libcxx_static:
yield mock.patch.object(
fuchsia, "clang_libcxx_static", return_value=libcxx_static or ""
)
# expected to be called through _rust_stdlib_libunwind_inputs()
# when --sysroot is unspecified.
yield mock.patch.object(
rustc.RustAction,
"default_rust_sysroot",
return_value=Path("../some/random/sysroot"),
)
def test_prepare_basic(self):
exec_root = Path("/home/project")
working_dir = exec_root / "build-here"
compiler = Path("../tools/bin/rustc")
shlib = Path("tools/lib/librusteze.so")
shlib_abs = exec_root / shlib
shlib_rel = cl_utils.relpath(shlib_abs, start=working_dir)
source = Path("../foo/src/lib.rs")
rlib = Path("obj/foo.rlib")
deps = [Path("../foo/src/other.rs")]
depfile_contents = [str(d) + ":" for d in deps]
command = _strs([compiler, source, "-o", rlib])
r = rustc_remote_wrapper.RustRemoteAction(
["--"] + command,
exec_root=exec_root,
working_dir=working_dir,
auto_reproxy=False,
)
self.assertIsNone(r.clang_cxx_stdlibdir)
self.assertEqual(
r.dep_only_command,
list(r._rust_action.dep_only_command(r.local_depfile)),
)
mocks = self.generate_prepare_mocks(
depfile_contents=depfile_contents,
compiler_shlibs=[shlib_rel],
)
with contextlib.ExitStack() as stack:
for m in mocks:
stack.enter_context(m)
prepare_status = r.prepare()
self.assertEqual(prepare_status, 0) # success
a = r.remote_action
remote_inputs = set(a.inputs_relative_to_working_dir)
remote_output_files = set(a.output_files_relative_to_working_dir)
self.assertEqual(
remote_inputs, set([compiler, shlib_rel, source] + deps)
)
self.assertEqual(remote_output_files, {rlib})
def test_cxx_stdlibdir(self):
exec_root = Path("/home/project")
working_dir = exec_root / "build-here"
compiler = Path("../tools/bin/rustc")
shlib = Path("tools/lib/librusteze.so")
shlib_abs = exec_root / shlib
shlib_rel = cl_utils.relpath(shlib_abs, start=working_dir)
source = Path("../foo/src/lib.rs")
rlib = Path("obj/foo.rlib")
deps = [Path("../foo/src/other.rs")]
depfile_contents = [str(d) + ":" for d in deps]
command = _strs([compiler, source, "-o", rlib])
cxx_libdir = Path("../tools/clang/find/libcxx/here")
r = rustc_remote_wrapper.RustRemoteAction(
[f"--cxx-stdlibdir={cxx_libdir}", "--"] + command,
exec_root=exec_root,
working_dir=working_dir,
auto_reproxy=False,
)
self.assertEqual(r.clang_cxx_stdlibdir, cxx_libdir)
def test_prepare_with_rmeta_file_in_scanned_deps(self):
exec_root = Path("/home/project")
working_dir = exec_root / "build-here"
compiler = Path("../tools/bin/rustc")
shlib = Path("tools/lib/librusteze.so")
shlib_abs = exec_root / shlib
shlib_rel = cl_utils.relpath(shlib_abs, start=working_dir)
source = Path("../foo/src/lib.rs")
rlib = Path("obj/foo.rlib")
rmeta_dep = Path("obj/bar.rmeta")
implied_rlib_dep = Path("obj/bar.rlib")
deps = [rmeta_dep]
depfile_contents = [str(d) + ":" for d in deps]
with tempfile.TemporaryDirectory() as td:
tdp = Path(td)
command = _strs([compiler, source, "-o", rlib])
r = rustc_remote_wrapper.RustRemoteAction(
["--"] + command,
exec_root=exec_root,
working_dir=working_dir,
auto_reproxy=False,
)
mocks = self.generate_prepare_mocks(
depfile_contents=depfile_contents,
compiler_shlibs=[shlib_rel],
)
with contextlib.ExitStack() as stack:
for m in mocks:
stack.enter_context(m)
# 'expand_rmetas' checks for file existence
# specificially co-existence of .rlib with .rmeta
with mock.patch.object(
Path, "exists", return_value=True
) as mock_rlib_exists:
prepare_status = r.prepare()
mock_rlib_exists.assert_called()
self.assertEqual(prepare_status, 0) # success
a = r.remote_action
remote_inputs = set(a.inputs_relative_to_working_dir)
remote_output_files = set(a.output_files_relative_to_working_dir)
# The corresponding .rlib from the .rmeta dep is considered a remote input.
self.assertEqual(
remote_inputs,
set([compiler, shlib_rel, source, implied_rlib_dep] + deps),
)
self.assertEqual(remote_output_files, {rlib})
def test_prepare_with_response_file(self):
exec_root = Path("/home/project")
working_dir = exec_root / "build-here"
compiler = Path("../tools/bin/rustc")
shlib = Path("tools/lib/librusteze.so")
shlib_abs = exec_root / shlib
shlib_rel = cl_utils.relpath(shlib_abs, start=working_dir)
source = Path("../foo/src/lib.rs")
rlib = Path("obj/foo.rlib")
deps = [Path("../foo/src/other.rs")]
depfile_contents = [str(d) + ":" for d in deps]
with tempfile.TemporaryDirectory() as td:
tdp = Path(td)
rsp = tdp / "foo.rsp"
rsp.write_text(
"\n".join(_strs(["# expand these args", source, "-o", rlib]))
+ "\n"
)
command = _strs(
[compiler, f"@{rsp}"]
) # pass args through response file
r = rustc_remote_wrapper.RustRemoteAction(
["--"] + command,
exec_root=exec_root,
working_dir=working_dir,
auto_reproxy=False,
)
mocks = self.generate_prepare_mocks(
depfile_contents=depfile_contents,
compiler_shlibs=[shlib_rel],
)
with contextlib.ExitStack() as stack:
for m in mocks:
stack.enter_context(m)
prepare_status = r.prepare()
self.assertEqual(prepare_status, 0) # success
a = r.remote_action
remote_inputs = set(a.inputs_relative_to_working_dir)
remote_output_files = set(a.output_files_relative_to_working_dir)
self.assertEqual(
remote_inputs, set([compiler, shlib_rel, source, rsp] + deps)
)
self.assertEqual(remote_output_files, {rlib})
def test_dep_only_command_filtered(self):
exec_root = Path("/home/project")
working_dir = exec_root / "build-here"
compiler = Path("../tools/bin/rustc")
shlib = Path("tools/lib/librusteze.so")
shlib_abs = exec_root / shlib
shlib_rel = cl_utils.relpath(shlib_abs, start=working_dir)
source = Path("../foo/src/lib.rs")
rlib = Path("obj/foo.rlib")
deps = [Path("../foo/src/other.rs")]
depfile_path = Path("obj/foo.rlib.d")
depfile_contents = [str(d) + ":" for d in deps]
command = _strs(
[
compiler,
source,
"--local-only=--foo=bar",
"-o",
rlib,
f"--emit=dep-info={depfile_path}",
]
)
r = rustc_remote_wrapper.RustRemoteAction(
["--"] + command,
exec_root=exec_root,
working_dir=working_dir,
auto_reproxy=False,
)
self.assertIn("--foo=bar", r.dep_only_command)
self.assertNotIn("--local-only=--foo=bar", r.dep_only_command)
def test_prepare_depfile(self):
exec_root = Path("/home/project")
working_dir = exec_root / "build-here"
compiler = Path("../tools/bin/rustc")
shlib = Path("tools/lib/librusteze.so")
shlib_abs = exec_root / shlib
shlib_rel = cl_utils.relpath(shlib_abs, start=working_dir)
source = Path("../foo/src/lib.rs")
rlib = Path("obj/foo.rlib")
deps = [Path("../foo/src/other.rs")]
depfile_path = Path("obj/foo.rlib.d")
depfile_contents = [str(d) + ":" for d in deps]
command = _strs(
[compiler, source, "-o", rlib, f"--emit=dep-info={depfile_path}"]
)
r = rustc_remote_wrapper.RustRemoteAction(
["--"] + command,
exec_root=exec_root,
working_dir=working_dir,
auto_reproxy=False,
)
mocks = self.generate_prepare_mocks(
depfile_contents=depfile_contents,
compiler_shlibs=[shlib_rel],
)
with contextlib.ExitStack() as stack:
for m in mocks:
stack.enter_context(m)
prepare_status = r.prepare()
self.assertEqual(prepare_status, 0) # success
a = r.remote_action
remote_inputs = set(a.inputs_relative_to_working_dir)
remote_output_files = set(a.output_files_relative_to_working_dir)
self.assertEqual(
remote_inputs, set([compiler, shlib_rel, source] + deps)
)
self.assertEqual(remote_output_files, {rlib, depfile_path})
self.assertFalse(a.skipping_some_download)
# Even if download_outputs=false, the depfile is required locally.
self.assertEqual(set(a.expected_downloads), remote_output_files)
def test_prepare_depfile_download_false(self):
exec_root = Path("/home/project")
working_dir = exec_root / "build-here"
compiler = Path("../tools/bin/rustc")
shlib = Path("tools/lib/librusteze.so")
shlib_abs = exec_root / shlib
shlib_rel = cl_utils.relpath(shlib_abs, start=working_dir)
source = Path("../foo/src/lib.rs")
rlib = Path("obj/foo.rlib")
deps = [Path("../foo/src/other.rs")]
depfile_path = Path("obj/foo.rlib.d")
depfile_contents = [str(d) + ":" for d in deps]
# skip downloading the main output
command = _strs(
[
compiler,
source,
"-o",
rlib,
f"--emit=dep-info={depfile_path}",
"--remote-flag=--download_outputs=false",
]
)
r = rustc_remote_wrapper.RustRemoteAction(
["--"] + command,
exec_root=exec_root,
working_dir=working_dir,
auto_reproxy=False,
)
mocks = self.generate_prepare_mocks(
depfile_contents=depfile_contents,
compiler_shlibs=[shlib_rel],
)
with contextlib.ExitStack() as stack:
for m in mocks:
stack.enter_context(m)
prepare_status = r.prepare()
self.assertEqual(prepare_status, 0) # success
a = r.remote_action
remote_inputs = set(a.inputs_relative_to_working_dir)
remote_output_files = set(a.output_files_relative_to_working_dir)
self.assertEqual(
remote_inputs, set([compiler, shlib_rel, source] + deps)
)
self.assertEqual(remote_output_files, {rlib, depfile_path})
self.assertTrue(a.skipping_some_download)
# Even if download_outputs=false, the depfile is required locally.
self.assertEqual(set(a.expected_downloads), {depfile_path})
def test_prepare_depfile_failure(self):
exec_root = Path("/home/project")
working_dir = exec_root / "build-here"
compiler = Path("../tools/bin/rustc")
source = Path("../foo/src/lib.rs")
rlib = Path("obj/foo.rlib")
depfile_path = Path("obj/foo.rlib.d")
# mocking failure, no need for actual depfile contents
command = _strs(
[compiler, source, "-o", rlib, f"--emit=dep-info={depfile_path}"]
)
r = rustc_remote_wrapper.RustRemoteAction(
["--"] + command,
exec_root=exec_root,
working_dir=working_dir,
auto_reproxy=False,
)
# Make sure internal RuntimeError gets handled.
with mock.patch.object(
rustc_remote_wrapper, "_make_local_depfile", return_value=1
) as mock_deps:
prepare_status = r.prepare()
self.assertEqual(prepare_status, 1) # failure
def test_prepare_llvm_ir(self):
exec_root = Path("/home/project")
working_dir = exec_root / "build-here"
compiler = Path("../tools/bin/rustc")
shlib = Path("tools/lib/librusteze.so")
shlib_abs = exec_root / shlib
shlib_rel = cl_utils.relpath(shlib_abs, start=working_dir)
source = Path("../foo/src/lib.rs")
rlib = Path("obj/foo.rlib")
llvm_ir = Path("obj/foo.ll")
deps = [Path("../foo/src/other.rs")]
depfile_path = Path("obj/foo.rlib.d")
depfile_contents = [str(d) + ":" for d in deps]
command = _strs([compiler, source, "-o", rlib, f"--emit=llvm-ir"])
r = rustc_remote_wrapper.RustRemoteAction(
["--"] + command,
exec_root=exec_root,
working_dir=working_dir,
auto_reproxy=False,
)
mocks = self.generate_prepare_mocks(
depfile_contents=depfile_contents,
compiler_shlibs=[shlib_rel],
)
with contextlib.ExitStack() as stack:
for m in mocks:
stack.enter_context(m)
prepare_status = r.prepare()
self.assertEqual(prepare_status, 0) # success
a = r.remote_action
remote_inputs = set(a.inputs_relative_to_working_dir)
remote_output_files = set(a.output_files_relative_to_working_dir)
self.assertEqual(
remote_inputs, set([compiler, shlib_rel, source] + deps)
)
# Even if download_outputs=false, we want the non-primary outputs.
self.assertEqual(remote_output_files, {rlib, llvm_ir})
self.assertFalse(a.skipping_some_download)
self.assertEqual(set(a.expected_downloads), remote_output_files)
def test_prepare_llvm_ir_download_false(self):
exec_root = Path("/home/project")
working_dir = exec_root / "build-here"
compiler = Path("../tools/bin/rustc")
shlib = Path("tools/lib/librusteze.so")
shlib_abs = exec_root / shlib
shlib_rel = cl_utils.relpath(shlib_abs, start=working_dir)
source = Path("../foo/src/lib.rs")
rlib = Path("obj/foo.rlib")
llvm_ir = Path("obj/foo.ll")
deps = [Path("../foo/src/other.rs")]
depfile_path = Path("obj/foo.rlib.d")
depfile_contents = [str(d) + ":" for d in deps]
command = _strs(
[
compiler,
source,
"-o",
rlib,
f"--emit=llvm-ir",
"--remote-flag=--download_outputs=false",
]
)
r = rustc_remote_wrapper.RustRemoteAction(
["--"] + command,
exec_root=exec_root,
working_dir=working_dir,
auto_reproxy=False,
)
mocks = self.generate_prepare_mocks(
depfile_contents=depfile_contents,
compiler_shlibs=[shlib_rel],
)
with contextlib.ExitStack() as stack:
for m in mocks:
stack.enter_context(m)
prepare_status = r.prepare()
self.assertEqual(prepare_status, 0) # success
a = r.remote_action
remote_inputs = set(a.inputs_relative_to_working_dir)
remote_output_files = set(a.output_files_relative_to_working_dir)
self.assertEqual(
remote_inputs, set([compiler, shlib_rel, source] + deps)
)
# Even if download_outputs=false, we want the non-primary outputs.
self.assertEqual(remote_output_files, {rlib, llvm_ir})
self.assertTrue(a.skipping_some_download)
self.assertEqual(set(a.expected_downloads), {llvm_ir})
def test_prepare_externs(self):
exec_root = Path("/home/project")
working_dir = exec_root / "build-here"
compiler = Path("../tools/bin/rustc")
shlib = Path("tools/lib/librusteze.so")
shlib_abs = exec_root / shlib
shlib_rel = cl_utils.relpath(shlib_abs, start=working_dir)
source = Path("../foo/src/lib.rs")
rlib = Path("obj/foo.rlib")
deps = [Path("../foo/src/other.rs")]
depfile_contents = [str(d) + ":" for d in deps]
externs = _paths(
[
"obj/path/to/this.rlib",
"obj/path/to/that.rlib",
]
)
extern_flags = [
"--extern",
f"this={externs[0]}",
"--extern",
f"that={externs[1]}",
]
command = _strs([compiler, source, "-o", rlib]) + extern_flags
r = rustc_remote_wrapper.RustRemoteAction(
["--"] + command,
exec_root=exec_root,
working_dir=working_dir,
auto_reproxy=False,
)
mocks = self.generate_prepare_mocks(
depfile_contents=depfile_contents,
compiler_shlibs=[shlib_rel],
)
with contextlib.ExitStack() as stack:
for m in mocks:
stack.enter_context(m)
prepare_status = r.prepare()
self.assertEqual(prepare_status, 0) # success
a = r.remote_action
remote_inputs = set(a.inputs_relative_to_working_dir)
remote_output_files = set(a.output_files_relative_to_working_dir)
self.assertEqual(
remote_inputs, set([compiler, shlib_rel, source] + deps + externs)
)
self.assertEqual(remote_output_files, {rlib})
def test_prepare_link_arg_files(self):
exec_root = Path("/home/project")
working_dir = exec_root / "build-here"
compiler = Path("../tools/bin/rustc")
shlib = Path("tools/lib/librusteze.so")
shlib_abs = exec_root / shlib
shlib_rel = cl_utils.relpath(shlib_abs, start=working_dir)
source = Path("../foo/src/lib.rs")
rlib = Path("obj/foo.rlib")
deps = [Path("../foo/src/other.rs")]
depfile_contents = [str(d) + ":" for d in deps]
link_arg = Path("obj/some/random.a")
command = _strs(
[compiler, source, "-o", rlib, f"-Clink-arg={link_arg}"]
)
r = rustc_remote_wrapper.RustRemoteAction(
["--"] + command,
exec_root=exec_root,
working_dir=working_dir,
auto_reproxy=False,
)
mocks = self.generate_prepare_mocks(
depfile_contents=depfile_contents,
compiler_shlibs=[shlib_rel],
)
with contextlib.ExitStack() as stack:
for m in mocks:
stack.enter_context(m)
prepare_status = r.prepare()
self.assertEqual(prepare_status, 0) # success
a = r.remote_action
remote_inputs = set(a.inputs_relative_to_working_dir)
remote_output_files = set(a.output_files_relative_to_working_dir)
self.assertEqual(
remote_inputs,
set([compiler, shlib_rel, source] + deps + [link_arg]),
)
self.assertEqual(remote_output_files, {rlib})
def test_prepare_needs_linker(self):
exec_root = Path("/home/project")
working_dir = exec_root / "build-here"
exec_root_rel = cl_utils.relpath(exec_root, start=working_dir)
compiler = Path("../tools/bin/rustc")
shlib = Path("tools/lib/librusteze.so")
shlib_abs = exec_root / shlib
shlib_rel = cl_utils.relpath(shlib_abs, start=working_dir)
linker = Path("../tools/bin/linker")
lld = Path("../tools/bin/ld.lld")
clang_rt_libdir = Path("../fake/lib/clang/x86_64-unknown-linux")
libcxx = Path("fake/clang/libc++.a")
source = Path("../foo/src/lib.rs")
target = "x86_64-unknown-linux-gnu"
exe = Path("obj/foo.exe")
deps = [Path("../foo/src/other.rs")]
depfile_contents = [str(d) + ":" for d in deps]
command = _strs(
[
compiler,
source,
"-o",
exe,
"--crate-type",
"bin",
f"-Clinker={linker}",
f"--target={target}",
f"-Clink-arg=-fuse-ld=lld",
]
)
r = rustc_remote_wrapper.RustRemoteAction(
["--"] + command,
exec_root=exec_root,
working_dir=working_dir,
auto_reproxy=False,
)
self.assertEqual(r.linker, linker)
self.assertEqual(r.remote_ld_path, lld)
mocks = self.generate_prepare_mocks(
depfile_contents=depfile_contents,
compiler_shlibs=[shlib_rel],
clang_rt_libdir=clang_rt_libdir,
libcxx_static=exec_root_rel / libcxx,
)
with contextlib.ExitStack() as stack:
for m in mocks:
stack.enter_context(m)
prepare_status = r.prepare()
self.assertEqual(prepare_status, 0) # success
a = r.remote_action
remote_inputs = set(a.inputs_relative_to_working_dir)
remote_output_files = set(a.output_files_relative_to_working_dir)
self.assertEqual(
remote_inputs,
set(
_paths(
[compiler, shlib_rel, source]
+ deps
+ [linker, lld, clang_rt_libdir, exec_root_rel / libcxx]
)
),
)
self.assertEqual(remote_output_files, {exe})
def test_prepare_remote_option_forwarding(self):
exec_root = Path("/home/project")
working_dir = exec_root / "build-here"
compiler = Path("../tools/bin/rustc")
shlib = Path("tools/lib/librusteze.so")
shlib_abs = exec_root / shlib
shlib_rel = cl_utils.relpath(shlib_abs, start=working_dir)
source = Path("../foo/src/lib.rs")
rlib = Path("obj/foo.rlib")
deps = [Path("../foo/src/other.rs")]
depfile_contents = [str(d) + ":" for d in deps]
remote_flag = "--some_forwarded_rewrapper_flag=value" # not real
command = _strs(
[compiler, source, "-o", rlib, f"--remote-flag={remote_flag}"]
)
r = rustc_remote_wrapper.RustRemoteAction(
["--"] + command,
exec_root=exec_root,
working_dir=working_dir,
auto_reproxy=False,
)
mocks = self.generate_prepare_mocks(
depfile_contents=depfile_contents,
compiler_shlibs=[shlib_rel],
)
with contextlib.ExitStack() as stack:
for m in mocks:
stack.enter_context(m)
prepare_status = r.prepare()
self.assertEqual(prepare_status, 0) # success
a = r.remote_action
remote_inputs = set(a.inputs_relative_to_working_dir)
remote_output_files = set(a.output_files_relative_to_working_dir)
self.assertEqual(
remote_inputs, set([compiler, shlib_rel, source] + deps)
)
self.assertEqual(remote_output_files, {rlib})
# Position matters, not just presence.
# Could override the set of standard options.
self.assertEqual(r.remote_options[-1], remote_flag)
def test_prepare_environment_names_input_file(self):
exec_root = Path("/home/project")
working_dir = exec_root / "build-here"
compiler = Path("../tools/bin/rustc")
shlib = Path("tools/lib/librusteze.so")
shlib_abs = exec_root / shlib
shlib_rel = cl_utils.relpath(shlib_abs, start=working_dir)
source = Path("../foo/src/lib.rs")
rlib = Path("obj/foo.rlib")
deps = [Path("../foo/src/other.rs")]
depfile_contents = [str(d) + ":" for d in deps]
env_file = Path("../path/to/config.json")
command = _strs(
[
f"CONFIGURE_THINGY={env_file}",
compiler,
source,
"-o",
rlib,
f"--remote-inputs={env_file}",
]
)
r = rustc_remote_wrapper.RustRemoteAction(
["--"] + command,
exec_root=exec_root,
working_dir=working_dir,
auto_reproxy=False,
)
mocks = self.generate_prepare_mocks(
depfile_contents=depfile_contents,
compiler_shlibs=[shlib_rel],
)
with contextlib.ExitStack() as stack:
for m in mocks:
stack.enter_context(m)
# We are not scanning the environment variables automatically.
# Users should follow the above example command and
# explicitly pass --remote-inputs.
with mock.patch.object(
rustc_remote_wrapper, "_env_file_exists", return_value=True
) as mock_exists:
prepare_status = r.prepare()
mock_exists.assert_not_called()
self.assertEqual(prepare_status, 0) # success
a = r.remote_action
remote_inputs = set(a.inputs_relative_to_working_dir)
remote_output_files = set(a.output_files_relative_to_working_dir)
self.assertEqual(
remote_inputs,
set(_paths([compiler, shlib_rel, source, env_file] + deps)),
)
self.assertEqual(remote_output_files, {rlib})
def test_native_compile(self):
host_platform = fuchsia.REMOTE_PLATFORM
exec_root = Path("/home/project")
working_dir = exec_root / "build-here"
compiler = Path(f"../tools/{host_platform}/bin/rustc")
source = Path("../foo/src/lib.rs")
rlib = Path("obj/foo.rlib")
command = _strs([compiler, source, "-o", rlib])
r = rustc_remote_wrapper.RustRemoteAction(
["--"] + command,
exec_root=exec_root,
working_dir=working_dir,
host_platform=host_platform,
auto_reproxy=False,
)
self.assertTrue(r.host_matches_remote)
def test_target_linker_prefix(self):
for target, ld in [
("arm64-unknown-linux-gnu", "ld"),
("x86_64-unknown-linux-gnu", "ld"),
("x86_64-unknown-freebsd", "ld"),
("x86_64-apple-darwin", "ld64"),
("ppc64-apple-darwin", "ld64"),
]:
exec_root = Path("/home/project")
working_dir = exec_root / "build-here"
compiler = Path("../tools/host-platform/bin/rustc")
source = Path("../foo/src/lib.rs")
rlib = Path("obj/foo.rlib")
command = _strs(
[compiler, source, "-o", rlib, f"--target={target}"]
)
r = rustc_remote_wrapper.RustRemoteAction(
["--"] + command,
exec_root=exec_root,
working_dir=working_dir,
auto_reproxy=False,
)
self.assertEqual(r.target_linker_prefix, ld)
def test_remote_mode_ok_with_relative_c_sysroot(self):
exec_root = Path("/home/project")
working_dir = exec_root / "build-here"
compiler = Path("../tools/host-platform/bin/rustc")
source = Path("../foo/src/lib.rs")
rlib = Path("obj/foo.rlib")
c_sysroot = Path("generated/sys/root") # relative
target = "aarch64-unknown-linux-gnu"
command = _strs(
[
compiler,
source,
"-o",
rlib,
"--crate-type=bin",
f"--target={target}",
f"-Clink-arg=--sysroot={c_sysroot}",
]
)
r = rustc_remote_wrapper.RustRemoteAction(
["--"] + command,
exec_root=exec_root,
working_dir=working_dir,
auto_reproxy=False,
)
self.assertEqual(r.c_sysroot, c_sysroot)
self.assertFalse(r._c_sysroot_is_outside_exec_root)
self.assertTrue(r.needs_linker)
self.assertFalse(r._remote_disqualified())
self.assertFalse(r.local_only)
sysroot_files = list(r._c_sysroot_files())
self.assertNotEqual(sysroot_files, [])
def test_forced_local_mode_ok_with_external_absolute_c_sysroot(self):
exec_root = Path("/home/project")
working_dir = exec_root / "build-here"
compiler = Path("../tools/host-platform/bin/rustc")
source = Path("../foo/src/lib.rs")
rlib = Path("obj/foo.rlib")
c_sysroot = Path("/fancy/pants/local/SDK") # absolute, external
target = "aarch64-unknown-linux-gnu"
command = _strs(
[
compiler,
source,
"-o",
rlib,
"--crate-type=bin",
f"--target={target}",
f"-Clink-arg=--sysroot={c_sysroot}",
]
)
r = rustc_remote_wrapper.RustRemoteAction(
["--"] + command,
exec_root=exec_root,
working_dir=working_dir,
auto_reproxy=False,
)
self.assertEqual(r.c_sysroot, c_sysroot)
self.assertTrue(r._c_sysroot_is_outside_exec_root)
self.assertTrue(r.needs_linker)
self.assertTrue(r._remote_disqualified())
self.assertTrue(r.local_only)
with mock.patch.object(fuchsia, "c_sysroot_files") as mock_files:
sysroot_files = list(r._c_sysroot_files())
self.assertEqual(sysroot_files, [])
mock_files.assert_not_called()
def test_cdylib_rust_lld(self):
exec_root = Path("/home/project")
working_dir = exec_root / "build-here"
compiler = Path("../tools/mac-x64/bin/rustc")
remote_compiler = Path("../tools/linux-x64/bin/rustc")
remote_rust_lld = (
remote_compiler.parent / fuchsia._REMOTE_RUST_LLD_RELPATH
)
source = Path("../foo/src/lib.rs")
dylib = Path("obj/foo.dylib")
command = _strs(
[compiler, source, "--crate-type", "cdylib", "-o", dylib]
)
r = rustc_remote_wrapper.RustRemoteAction(
["--"] + command,
exec_root=exec_root,
working_dir=working_dir,
auto_reproxy=False,
)
mocks = self.generate_prepare_mocks()
with contextlib.ExitStack() as stack:
for m in mocks:
stack.enter_context(m)
with mock.patch.object(
rustc_remote_wrapper.RustRemoteAction, "_local_depfile_inputs"
) as mock_deps:
with mock.patch.object(
rustc_remote_wrapper.RustRemoteAction,
"_remote_rustc_shlibs",
) as mock_shlibs:
prepare_status = r.prepare()
mock_deps.assert_called_once()
mock_shlibs.assert_called_once()
a = r.remote_action
remote_inputs = set(a.inputs_relative_to_working_dir)
self.assertIn(remote_compiler, remote_inputs)
self.assertIn(remote_rust_lld, remote_inputs)
def test_prepare_remote_cross_compile(self):
host_platform = "mac-arm64"
remote_platform = fuchsia.REMOTE_PLATFORM
exec_root = Path("/home/project")
working_dir = exec_root / "build-here"
compiler = Path(f"../tools/{host_platform}/bin/rustc")
remote_compiler = Path(f"../tools/{remote_platform}/bin/rustc")
linker = Path(f"../tools/{host_platform}/bin/linker")
remote_linker = Path(f"../tools/{remote_platform}/bin/linker")
remote_ld_path = Path(f"../tools/{remote_platform}/bin/ld.lld")
shlib = Path("tools/lib/librusteze.so")
shlib_abs = exec_root / shlib
shlib_rel = cl_utils.relpath(shlib_abs, start=working_dir)
libcxx = Path("../std/tools/bin/../lib/libc++.a")
libcxx_norm = Path("../std/tools/lib/libc++.a")
target = "powerpc-unknown-freebsd"
source = Path("../foo/src/lib.rs")
rlib = Path("obj/foo.rlib")
deps = [Path("../foo/src/other.rs")]
depfile_contents = [str(d) + ":" for d in deps]
command = _strs(
[
compiler,
source,
"-o",
rlib,
f"--target={target}",
f"-Clinker={linker}",
f"-Clink-arg={libcxx}",
f"-Clink-arg=-fuse-ld=lld",
]
)
r = rustc_remote_wrapper.RustRemoteAction(
["--"] + command,
exec_root=exec_root,
working_dir=working_dir,
host_platform=host_platform,
auto_reproxy=False,
)
self.assertEqual(r.working_dir, working_dir)
self.assertFalse(r.host_matches_remote)
self.assertEqual(r.original_command, command)
self.assertEqual(r.linker, linker)
self.assertEqual(r.remote_linker, remote_linker)
self.assertEqual(r.remote_ld_path, remote_ld_path)
mocks = self.generate_prepare_mocks(
depfile_contents=depfile_contents,
compiler_shlibs=[shlib_rel],
)
with contextlib.ExitStack() as stack:
for m in mocks:
stack.enter_context(m)
prepare_status = r.prepare()
remote_command = list(r.remote_compile_command())
self.assertEqual(prepare_status, 0) # success
self.assertEqual(r.remote_compiler, remote_compiler)
a = r.remote_action
self.assertEqual(
remote_command,
_strs(
[
remote_compiler,
source,
"-o",
rlib,
f"--target={target}",
f"-Clinker={remote_linker}",
f"-Clink-arg={libcxx_norm}", # normalized
f"-Clink-arg=-fuse-ld=lld",
f"-Clink-arg=--ld-path={remote_ld_path}", # added by wrapper
f"--sysroot=../some/random/sysroot",
]
),
)
remote_inputs = set(a.inputs_relative_to_working_dir)
remote_output_files = set(a.output_files_relative_to_working_dir)
self.assertEqual(
remote_inputs,
set([remote_compiler, shlib_rel, libcxx, source] + deps),
)
self.assertEqual(remote_output_files, {rlib})
def test_run_local_downloads_inputs_if_needed(self):
exec_root = Path("/home/project")
working_dir = exec_root / "build-here"
compiler = Path("../tools/bin/rustc")
shlib = Path("tools/lib/librusteze.so")
shlib_abs = exec_root / shlib
shlib_rel = cl_utils.relpath(shlib_abs, start=working_dir)
source = Path("../foo/src/lib.rs")
rlib = Path("obj/foo.rlib")
input_rlib = Path("libintermediate.rlib")
deps = [Path("../foo/src/other.rs")]
depfile_path = Path("obj/foo.rlib.d")
depfile_contents = [str(d) + ":" for d in deps]
command = _strs(
[compiler, source, "-o", rlib, f"--emit=dep-info={depfile_path}"]
)
r = rustc_remote_wrapper.RustRemoteAction(
[f"--inputs={input_rlib}", "--"] + command,
exec_root=exec_root,
working_dir=working_dir,
auto_reproxy=False,
)
prepare_mocks = self.generate_prepare_mocks(
depfile_contents=depfile_contents,
compiler_shlibs=[shlib_rel],
)
with contextlib.ExitStack() as stack:
for m in prepare_mocks:
stack.enter_context(m)
prepare_status = r.prepare()
self.assertEqual(prepare_status, 0) # success
a = r.remote_action
remote_inputs = set(a.inputs_relative_to_working_dir)
remote_output_files = set(a.output_files_relative_to_working_dir)
self.assertEqual(
remote_inputs, set([compiler, shlib_rel, source, input_rlib] + deps)
)
self.assertEqual(remote_output_files, {rlib, depfile_path})
with mock.patch.object(
remote_action.RemoteAction, "download_inputs", return_value={}
) as mock_download_inputs:
with mock.patch.object(
subprocess, "call", return_value=0
) as mock_call:
run_status = r.run_local()
mock_call.assert_called_once()
self.assertEqual(run_status, 0)
mock_download_inputs.assert_called_once()
def test_post_run_actions(self):
exec_root = Path("/home/project")
working_dir = exec_root / "build-here"
compiler = Path("../tools/bin/rustc")
shlib = Path("tools/lib/librusteze.so")
shlib_abs = exec_root / shlib
shlib_rel = cl_utils.relpath(shlib_abs, start=working_dir)
source = Path("../foo/src/lib.rs")
rlib = Path("obj/foo.rlib")
deps = [Path("../foo/src/other.rs")]
depfile_path = Path("obj/foo.rlib.d")
depfile_contents = [str(d) + ":" for d in deps]
command = _strs(
[compiler, source, "-o", rlib, f"--emit=dep-info={depfile_path}"]
)
r = rustc_remote_wrapper.RustRemoteAction(
["--"] + command,
exec_root=exec_root,
working_dir=working_dir,
auto_reproxy=False,
)
prepare_mocks = self.generate_prepare_mocks(
depfile_contents=depfile_contents,
compiler_shlibs=[shlib_rel],
)
with contextlib.ExitStack() as stack:
for m in prepare_mocks:
stack.enter_context(m)
prepare_status = r.prepare()
self.assertEqual(prepare_status, 0) # success
a = r.remote_action
self.assertIsNotNone(a._post_remote_run_success_action)
remote_inputs = set(a.inputs_relative_to_working_dir)
remote_output_files = set(a.output_files_relative_to_working_dir)
self.assertEqual(
remote_inputs, set([compiler, shlib_rel, source] + deps)
)
self.assertEqual(remote_output_files, {rlib, depfile_path})
run_mocks = [
mock.patch.object(
remote_action.RemoteAction,
"_run_maybe_remotely",
return_value=cl_utils.SubprocessResult(0),
),
mock.patch.object(
rustc_remote_wrapper.RustRemoteAction,
"_depfile_exists",
return_value=True,
),
mock.patch.object(
rustc_remote_wrapper.RustRemoteAction, "_cleanup"
),
]
with contextlib.ExitStack() as stack:
for m in run_mocks:
stack.enter_context(m)
with mock.patch.object(
rustc_remote_wrapper.RustRemoteAction,
"_rewrite_remote_or_local_depfile",
) as mock_rewrite:
run_status = r.run()
mock_rewrite.assert_called_with()
def test_rewrite_remote_depfile(self):
compiler = Path("../tools/bin/rustc")
source = Path("../foo/src/lib.rs")
rlib = Path("obj/foo.rlib")
depfile_path = Path("obj/foo.rlib.d")
with tempfile.TemporaryDirectory() as td:
exec_root = Path(td)
working_dir = exec_root / "build-here"
command = _strs(
[
compiler,
source,
"-o",
rlib,
f"--emit=dep-info={depfile_path}",
]
)
r = rustc_remote_wrapper.RustRemoteAction(
["--canonicalize_working_dir=true", "--"] + command,
exec_root=exec_root,
working_dir=working_dir,
auto_reproxy=False,
)
prepare_mocks = self.generate_prepare_mocks()
with contextlib.ExitStack() as stack:
for m in prepare_mocks:
stack.enter_context(m)
with mock.patch.object(
rustc_remote_wrapper.RustRemoteAction,
"_remote_inputs",
return_value=iter([]),
) as mock_inputs:
prepare_status = r.prepare()
mock_inputs.assert_called_with()
self.assertEqual(prepare_status, 0) # success
remote_cwd = r.remote_action.remote_working_dir
depfile_abspath = working_dir / depfile_path
depfile_abspath.parent.mkdir(parents=True, exist_ok=True)
_write_file_contents(
depfile_abspath,
f"{remote_cwd}/obj/foo.rlib: {remote_cwd}/src/lib.rs\n",
)
r._rewrite_remote_or_local_depfile()
new_deps = _read_file_contents(depfile_abspath)
self.assertEqual(new_deps, "obj/foo.rlib: src/lib.rs\n")
class MainTests(unittest.TestCase):
def test_help_implicit(self):
# Just make sure help exits successfully, without any exceptions
# due to argument parsing.
stdout = io.StringIO()
with contextlib.redirect_stdout(stdout):
with mock.patch.object(
sys, "exit", side_effect=ImmediateExit
) as mock_exit:
with self.assertRaises(ImmediateExit):
rustc_remote_wrapper.main([])
mock_exit.assert_called_with(0)
def test_help_flag(self):
# Just make sure help exits successfully, without any exceptions
# due to argument parsing.
stdout = io.StringIO()
with contextlib.redirect_stdout(stdout):
with mock.patch.object(
sys, "exit", side_effect=ImmediateExit
) as mock_exit:
with self.assertRaises(ImmediateExit):
rustc_remote_wrapper.main(["--help"])
mock_exit.assert_called_with(0)
def test_auto_relaunched_with_reproxy(self):
argv = ["--", "rustc", "foo.rs", "-o", "foo.rlib"]
with mock.patch.object(
os.environ, "get", return_value=None
) as mock_env:
with mock.patch.object(
cl_utils, "exec_relaunch", side_effect=ImmediateExit
) as mock_relaunch:
with self.assertRaises(ImmediateExit):
rustc_remote_wrapper.main(argv)
mock_env.assert_called()
mock_relaunch.assert_called_once()
args, kwargs = mock_relaunch.call_args_list[0]
relaunch_cmd = args[0]
self.assertEqual(relaunch_cmd[0], str(fuchsia.REPROXY_WRAP))
cmd_slices = cl_utils.split_into_subsequences(relaunch_cmd[1:], "--")
reproxy_args, self_script, wrapped_command = cmd_slices
self.assertEqual(reproxy_args, ["-v"])
self.assertIn("python", self_script[0])
self.assertTrue(self_script[-1].endswith("rustc_remote_wrapper.py"))
self.assertEqual(wrapped_command, argv[1:])
if __name__ == "__main__":
unittest.main()