Skip to content

Commit

Permalink
twoliter: Dedup cargo make logic
Browse files Browse the repository at this point in the history
  • Loading branch information
ecpullen committed Oct 6, 2023
1 parent c7af698 commit d81edc6
Show file tree
Hide file tree
Showing 4 changed files with 232 additions and 135 deletions.
213 changes: 213 additions & 0 deletions twoliter/src/cargo_make.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
use std::path::PathBuf;

use anyhow::{bail, Result};
use log::trace;
use tokio::process::Command;

use crate::common::exec;
use crate::docker::ImageArchUri;
use crate::project::Project;

fn require_sdk(project: &Project, arch: &str) -> Result<(ImageArchUri, ImageArchUri)> {
match (project.sdk(arch), project.toolchain(arch)) {
(Some(s), Some(t)) => Ok((s, t)),
_ => bail!(
"When using twoliter make, it is required that the SDK and toolchain be specified in \
Twoliter.toml"
),
}
}

#[derive(Debug, Clone, Default)]
pub(crate) struct CargoMake {
makefile_path: Option<PathBuf>,
project_dir: Option<PathBuf>,
args: Vec<String>,
}

impl CargoMake {
pub(crate) fn new<S>(project: &Project, arch: S) -> Result<Self>
where
S: Into<String>,
{
let (sdk, toolchain) = require_sdk(project, &arch.into())?;
Ok(Self::default()
.env("TLPRIVATE_SDK_IMAGE", sdk)
.env("TLPRIVATE_TOOLCHAIN", toolchain))
}

pub(crate) fn makefile<P>(mut self, makefile_path: P) -> Self
where
P: Into<PathBuf>,
{
self.makefile_path = Some(makefile_path.into());
self
}

pub(crate) fn project_dir<P>(mut self, project_dir: P) -> Self
where
P: Into<PathBuf>,
{
self.project_dir = Some(project_dir.into());
self
}

pub(crate) fn env<S1, S2>(mut self, key: S1, value: S2) -> Self
where
S1: Into<String>,
S2: Into<String>,
{
self.args
.push(format!("-e={}={}", key.into(), value.into()));
self
}

pub(crate) fn _envs<S1, S2, V>(mut self, env_vars: V) -> Self
where
S1: Into<String>,
S2: Into<String>,
V: Into<Vec<(S1, S2)>>,
{
for (key, value) in env_vars.into() {
self.args
.push(format!("-e={}={}", key.into(), value.into()));
}
self
}

pub(crate) fn _arg<S>(mut self, arg: S) -> Self
where
S: Into<String>,
{
self.args.push(arg.into());
self
}

pub(crate) fn _args<V, S>(mut self, args: V) -> Self
where
S: Into<String>,
V: Into<Vec<S>>,
{
self.args.extend(args.into().into_iter().map(Into::into));
self
}

pub(crate) async fn _exec<S>(&self, task: S) -> Result<()>
where
S: Into<String>,
{
self.exec_with_args(task, Vec::<String>::new()).await
}

pub(crate) async fn exec_with_args<S1, S2, V>(&self, task: S1, args: V) -> Result<()>
where
S1: Into<String>,
S2: Into<String>,
V: Into<Vec<S2>>,
{
exec(
Command::new("cargo")
.arg("make")
.arg("--disable-check-for-updates")
.args(
self.makefile_path.iter().flat_map(|path| {
vec!["--makefile".to_string(), path.display().to_string()]
}),
)
.args(
self.makefile_path
.iter()
.flat_map(|path| vec!["--cwd".to_string(), path.display().to_string()]),
)
.args(build_system_env_vars()?)
.args(&self.args)
.arg(task.into())
.args(args.into().into_iter().map(Into::into)),
)
.await
}
}

fn build_system_env_vars() -> Result<Vec<String>> {
let mut args = Vec::new();
for (key, val) in std::env::vars() {
if is_build_system_env(key.as_str()) {
trace!("Passing env var {} to cargo make", key);
args.push("-e".to_string());
args.push(format!("{}={}", key, val));
}

// To avoid confusion, environment variables whose values have been moved to
// Twoliter.toml are expressly disallowed here.
check_for_disallowed_var(&key)?;
}
Ok(args)
}

/// A list of environment variables that don't conform to naming conventions but need to be passed
/// through to the `cargo make` invocation.
const ENV_VARS: [&str; 12] = [
"ALLOW_MISSING_KEY",
"AMI_DATA_FILE_SUFFIX",
"CARGO_MAKE_CARGO_ARGS",
"CARGO_MAKE_CARGO_LIMIT_JOBS",
"CARGO_MAKE_DEFAULT_TESTSYS_KUBECONFIG_PATH",
"CARGO_MAKE_TESTSYS_ARGS",
"CARGO_MAKE_TESTSYS_KUBECONFIG_ARG",
"MARK_OVA_AS_TEMPLATE",
"RELEASE_START_TIME",
"SSM_DATA_FILE_SUFFIX",
"VMWARE_IMPORT_SPEC_PATH",
"VMWARE_VM_NAME_DEFAULT",
];

const DISALLOWED_SDK_VARS: [&str; 4] = [
"BUILDSYS_SDK_NAME",
"BUILDSYS_SDK_VERSION",
"BUILDSYS_REGISTRY",
"BUILDSYS_TOOLCHAIN",
];

/// Returns `true` if `key` is an environment variable that needs to be passed to `cargo make`.
fn is_build_system_env(key: impl AsRef<str>) -> bool {
let key = key.as_ref();
key.starts_with("BUILDSYS_")
|| key.starts_with("PUBLISH_")
|| key.starts_with("REPO_")
|| key.starts_with("TESTSYS_")
|| key.starts_with("BOOT_CONFIG")
|| key.starts_with("AWS_")
|| ENV_VARS.contains(&key)
}

fn check_for_disallowed_var(key: &str) -> Result<()> {
if DISALLOWED_SDK_VARS.contains(&key) {
bail!(
"The environment variable '{}' can no longer be used. Specify the SDK in Twoliter.toml",
key
)
}
Ok(())
}

#[test]
fn test_is_build_system_env() {
assert!(is_build_system_env(
"CARGO_MAKE_DEFAULT_TESTSYS_KUBECONFIG_PATH"
));
assert!(is_build_system_env("BUILDSYS_PRETTY_NAME"));
assert!(is_build_system_env("PUBLISH_FOO_BAR"));
assert!(is_build_system_env("TESTSYS_!"));
assert!(is_build_system_env("BOOT_CONFIG!"));
assert!(is_build_system_env("BOOT_CONFIG_INPUT"));
assert!(is_build_system_env("AWS_REGION"));
assert!(!is_build_system_env("PATH"));
assert!(!is_build_system_env("HOME"));
assert!(!is_build_system_env("COLORTERM"));
}

#[test]
fn test_check_for_disallowed_var() {
assert!(check_for_disallowed_var("BUILDSYS_REGISTRY").is_err());
assert!(check_for_disallowed_var("BUILDSYS_PRETTY_NAME").is_ok());
}
147 changes: 12 additions & 135 deletions twoliter/src/cmd/make.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
use crate::common::exec;
use crate::docker::ImageArchUri;
use crate::cargo_make::CargoMake;
use crate::project;
use crate::project::Project;
use crate::tools::{install_tools, tools_tempdir};
use anyhow::{bail, ensure, Result};
use anyhow::Result;
use clap::Parser;
use log::trace;
use std::path::PathBuf;
use tokio::process::Command;

/// Run a cargo make command in Twoliter's build environment. Known Makefile.toml environment
/// variables will be passed-through to the cargo make invocation.
Expand All @@ -29,6 +25,9 @@ pub(crate) struct Make {
/// Uninspected arguments to be passed to cargo make after the target name. For example, --foo
/// in the following command : cargo make test --foo.
additional_args: Vec<String>,

#[clap(env = "BUILDSYS_ARCH")]
arch: String,
}

impl Make {
Expand All @@ -37,134 +36,12 @@ impl Make {
let tempdir = tools_tempdir()?;
install_tools(&tempdir).await?;
let makefile_path = tempdir.path().join("Makefile.toml");

let mut args = vec![
"make".to_string(),
"--disable-check-for-updates".to_string(),
"--makefile".to_string(),
makefile_path.display().to_string(),
"--cwd".to_string(),
project.project_dir().display().to_string(),
];

let mut arch = String::new();

for (key, val) in std::env::vars() {
if is_build_system_env(key.as_str()) {
trace!("Passing env var {} to cargo make", key);
args.push("-e".to_string());
args.push(format!("{}={}", key, val));
}

// To avoid confusion, environment variables whose values have been moved to
// Twoliter.toml are expressly disallowed here.
check_for_disallowed_var(&key)?;

if key == "BUILDSYS_ARCH" {
arch = val.clone();
}
}

ensure!(
!arch.is_empty(),
"It is required to pass a non-zero string as the value of environment variable \
'BUILDSYS_ARCH' when running twoliter make"
);

let (sdk, toolchain) = require_sdk(&project, &arch)?;

args.push(format!("-e=TLPRIVATE_SDK_IMAGE={}", sdk));
args.push(format!("-e=TLPRIVATE_TOOLCHAIN={}", toolchain));
args.push(format!("-e=CARGO_HOME={}", self.cargo_home.display()));
args.push(format!(
"-e=TWOLITER_TOOLS_DIR={}",
tempdir.path().display()
));

args.push(self.makefile_task.clone());

for cargo_make_arg in &self.additional_args {
args.push(cargo_make_arg.clone());
}

exec(Command::new("cargo").args(args)).await
}
}

/// A list of environment variables that don't conform to naming conventions but need to be passed
/// through to the `cargo make` invocation.
const ENV_VARS: [&str; 12] = [
"ALLOW_MISSING_KEY",
"AMI_DATA_FILE_SUFFIX",
"CARGO_MAKE_CARGO_ARGS",
"CARGO_MAKE_CARGO_LIMIT_JOBS",
"CARGO_MAKE_DEFAULT_TESTSYS_KUBECONFIG_PATH",
"CARGO_MAKE_TESTSYS_ARGS",
"CARGO_MAKE_TESTSYS_KUBECONFIG_ARG",
"MARK_OVA_AS_TEMPLATE",
"RELEASE_START_TIME",
"SSM_DATA_FILE_SUFFIX",
"VMWARE_IMPORT_SPEC_PATH",
"VMWARE_VM_NAME_DEFAULT",
];

const DISALLOWED_SDK_VARS: [&str; 4] = [
"BUILDSYS_SDK_NAME",
"BUILDSYS_SDK_VERSION",
"BUILDSYS_REGISTRY",
"BUILDSYS_TOOLCHAIN",
];

/// Returns `true` if `key` is an environment variable that needs to be passed to `cargo make`.
fn is_build_system_env(key: impl AsRef<str>) -> bool {
let key = key.as_ref();
key.starts_with("BUILDSYS_")
|| key.starts_with("PUBLISH_")
|| key.starts_with("REPO_")
|| key.starts_with("TESTSYS_")
|| key.starts_with("BOOT_CONFIG")
|| key.starts_with("AWS_")
|| ENV_VARS.contains(&key)
}

fn check_for_disallowed_var(key: &str) -> Result<()> {
if DISALLOWED_SDK_VARS.contains(&key) {
bail!(
"The environment variable '{}' can no longer be used. Specify the SDK in Twoliter.toml",
key
)
CargoMake::new(&project, &self.arch)?
.env("CARGO_HOME", self.cargo_home.display().to_string())
.env("TWOLITER_TOOLS_DIR", tempdir.path().display().to_string())
.makefile(makefile_path)
.project_dir(project.project_dir())
.exec_with_args(&self.makefile_task, self.additional_args.clone())
.await
}
Ok(())
}

fn require_sdk(project: &Project, arch: &str) -> Result<(ImageArchUri, ImageArchUri)> {
match (project.sdk(arch), project.toolchain(arch)) {
(Some(s), Some(t)) => Ok((s, t)),
_ => bail!(
"When using twoliter make, it is required that the SDK and toolchain be specified in \
Twoliter.toml"
),
}
}

#[test]
fn test_is_build_system_env() {
assert!(is_build_system_env(
"CARGO_MAKE_DEFAULT_TESTSYS_KUBECONFIG_PATH"
));
assert!(is_build_system_env("BUILDSYS_PRETTY_NAME"));
assert!(is_build_system_env("PUBLISH_FOO_BAR"));
assert!(is_build_system_env("TESTSYS_!"));
assert!(is_build_system_env("BOOT_CONFIG!"));
assert!(is_build_system_env("BOOT_CONFIG_INPUT"));
assert!(is_build_system_env("AWS_REGION"));
assert!(!is_build_system_env("PATH"));
assert!(!is_build_system_env("HOME"));
assert!(!is_build_system_env("COLORTERM"));
}

#[test]
fn test_check_for_disallowed_var() {
assert!(check_for_disallowed_var("BUILDSYS_REGISTRY").is_err());
assert!(check_for_disallowed_var("BUILDSYS_PRETTY_NAME").is_ok());
}
6 changes: 6 additions & 0 deletions twoliter/src/docker/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ impl Display for ImageArchUri {
}
}

impl From<ImageArchUri> for String {
fn from(value: ImageArchUri) -> Self {
value.to_string()
}
}

#[test]
fn image_arch_uri_no_registry() {
let uri = ImageArchUri::new(None, "my-sdk", "i386", "v0.33.1");
Expand Down
Loading

0 comments on commit d81edc6

Please sign in to comment.