-
Notifications
You must be signed in to change notification settings - Fork 447
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Rust test targets now create a test launcher allow for setting env va…
…rs (#579) I was mistaken when I originally implemented #577 because I didn't realize that [std::env](https://doc.rust-lang.org/std/macro.env.html) loaded environment variables at compile time. The goal of this PR was to define environment variables at runtime. This appeared to be more complicated than I had thought. It seems there needs to be a launcher/runner that is produced by your rule and invokes your test executable to be able to guarantee the environment is configured for the test. This PR creates a small shell script that wraps `rust_test` targets to enable the use of the `env` attribute at runtime.
- Loading branch information
1 parent
6267c26
commit ab86e53
Showing
8 changed files
with
283 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
load("//rust:rust.bzl", "rust_binary") | ||
load(":installer.bzl", "installer") | ||
|
||
package(default_visibility = ["//visibility:public"]) | ||
|
||
rust_binary( | ||
name = "launcher", | ||
edition = "2018", | ||
srcs = ["launcher_main.rs"] | ||
) | ||
|
||
installer( | ||
name = "installer", | ||
src = select({ | ||
"//rust/platform:windows": "launcher_installer.bat", | ||
"//conditions:default": "launcher_installer.sh", | ||
}), | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
"""A module defining the installer rule for the rules_rust test launcher""" | ||
|
||
def _installer_impl(ctx): | ||
"""The `installer` rule's implementation | ||
Args: | ||
ctx (ctx): The rule's context object | ||
Returns: | ||
list: A list a DefaultInfo provider | ||
""" | ||
|
||
installer = ctx.actions.declare_file(ctx.file.src.basename) | ||
|
||
ctx.actions.expand_template( | ||
template = ctx.file.src, | ||
output = installer, | ||
substitutions = {}, | ||
is_executable = True, | ||
) | ||
|
||
return [DefaultInfo( | ||
files = depset([installer]), | ||
executable = installer, | ||
)] | ||
|
||
installer = rule( | ||
doc = "A rule which makes a native executable script available to other rules", | ||
implementation = _installer_impl, | ||
attrs = { | ||
"src": attr.label( | ||
allow_single_file = [".sh", ".bat"], | ||
), | ||
}, | ||
executable = True, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
@ECHO OFF | ||
@REM A native windows script for creating a `run` action output of the | ||
@REM the rules_rust launcher binary | ||
|
||
copy /v /y /b "%1" "%2" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
#!/bin/bash | ||
# A simple script for creating a `run` action output of the | ||
# the rules_rust launcher binary | ||
|
||
cp "$1" "$2" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
use std::collections::BTreeMap; | ||
use std::fs::File; | ||
use std::io::{BufReader, BufRead}; | ||
use std::path::PathBuf; | ||
use std::process::Command; | ||
use std::vec::Vec; | ||
|
||
#[cfg(target_family = "unix")] | ||
use std::os::unix::process::CommandExt; | ||
|
||
/// This string must match the one found in `_create_test_launcher` | ||
const LAUNCHFILES_ENV_PATH: &'static str = ".launchfiles/env"; | ||
|
||
/// Load environment variables from a uniquly formatted | ||
fn environ() -> BTreeMap<String, String> { | ||
let mut environ = BTreeMap::new(); | ||
|
||
let mut key: Option<String> = None; | ||
|
||
// Load the environment file into a map | ||
let env_path = std::env::args().nth(0).expect("arg 0 was not set") + LAUNCHFILES_ENV_PATH; | ||
let file = File::open(env_path).expect("Failed to load the environment file"); | ||
|
||
// Find all environment variables by reading pairs of lines as key/value pairs | ||
for line in BufReader::new(file).lines() { | ||
if key.is_none() { | ||
key = Some(line.expect("Failed to read line")); | ||
continue; | ||
} | ||
|
||
environ.insert( | ||
key.expect("Key is not set"), | ||
line.expect("Failed to read line"), | ||
); | ||
|
||
key = None; | ||
} | ||
|
||
environ | ||
} | ||
|
||
/// Locate the executable based on the name of the launcher executable | ||
fn executable() -> PathBuf { | ||
let mut exec_path = std::env::args().nth(0).expect("arg 0 was not set"); | ||
let stem_index = exec_path.rfind(".launcher").expect("This executable should always contain `.launcher`"); | ||
|
||
// Remove the substring from the exec path | ||
for _char in ".launcher".chars() { | ||
exec_path.remove(stem_index); | ||
} | ||
|
||
PathBuf::from(exec_path) | ||
} | ||
|
||
/// Parse the command line arguments but skip the first element which | ||
/// is the path to the test runner executable. | ||
fn args() -> Vec<String> { | ||
std::env::args().skip(1).collect() | ||
} | ||
|
||
/// Simply replace the current process with our test | ||
#[cfg(target_family = "unix")] | ||
fn exec(environ: BTreeMap<String, String>, executable: PathBuf, args: Vec<String>) { | ||
let error = Command::new(&executable) | ||
.envs(environ.iter()) | ||
.args(args) | ||
.exec(); | ||
|
||
panic!("Process failed to start: {:?} with {:?}", executable, error) | ||
} | ||
|
||
/// On windows, there is no way to replace the current process | ||
/// so instead we allow the command to run in a subprocess. | ||
#[cfg(target_family = "windows")] | ||
fn exec(environ: BTreeMap<String, String>, executable: PathBuf, args: Vec<String>) { | ||
let output = Command::new(executable) | ||
.envs(environ.iter()) | ||
.args(args) | ||
.output() | ||
.expect("Failed to run process"); | ||
|
||
std::process::exit(output.status.code().unwrap_or(1)); | ||
} | ||
|
||
/// Main entrypoint | ||
fn main() { | ||
// Gather environment variables | ||
let environ = environ(); | ||
|
||
// Gather arguments | ||
let args = args(); | ||
|
||
// Find executable | ||
let executable = executable(); | ||
|
||
// Replace the current process with the test target | ||
exec(environ, executable, args); | ||
|
||
// The call to exec should have exited the application. | ||
// This code should be unreachable. | ||
panic!("Process did not exit"); | ||
} |