use {
crate::starlark::{
code_signing::{handle_signable_event, SigningAction, SigningContext},
file_content::{FileContentValue, FileContentWrapper},
},
anyhow::anyhow,
log::warn,
simple_file_manifest::FileManifest,
starlark::{
environment::TypeValues,
eval::call_stack::CallStack,
values::{
error::{RuntimeError, ValueError},
none::NoneType,
{Mutable, TypedValue, Value, ValueResult},
},
{
starlark_fun, starlark_module, starlark_parse_param_type, starlark_signature,
starlark_signature_extraction, starlark_signatures,
},
},
starlark_dialect_build_targets::{
get_context_value, optional_str_arg, EnvironmentContext, ResolvedTarget,
ResolvedTargetValue, RunMode,
},
std::{
path::{Path, PathBuf},
sync::{Arc, Mutex, MutexGuard},
},
tugger_code_signing::SigningDestination,
};
fn error_context<F, T>(label: &str, f: F) -> Result<T, ValueError>
where
F: FnOnce() -> anyhow::Result<T>,
{
f().map_err(|e| {
ValueError::Runtime(RuntimeError {
code: "SIMPLE_FILE_MANIFEST",
message: format!("{:?}", e),
label: label.to_string(),
})
})
}
fn post_materialize_signing_checks(
label: &'static str,
type_values: &TypeValues,
call_stack: &mut CallStack,
action: SigningAction,
installed_paths: &[PathBuf],
) -> Result<(), ValueError> {
for path in installed_paths {
let filename = path.file_name().ok_or_else(|| {
ValueError::Runtime(RuntimeError {
code: "TUGGER_FILE_RESOURCE",
message: "unable to resolve filename of path (this should never happen)"
.to_string(),
label: label.to_string(),
})
})?;
let candidate = path.as_path().into();
let mut context = SigningContext::new(label, action, filename, &candidate);
context.set_path(path);
context.set_signing_destination(SigningDestination::File(path.clone()));
handle_signable_event(type_values, call_stack, context)?;
}
Ok(())
}
#[derive(Clone, Debug)]
pub struct FileManifestValue {
inner: Arc<Mutex<FileManifest>>,
pub run_path: Option<PathBuf>,
}
impl TypedValue for FileManifestValue {
type Holder = Mutable<FileManifestValue>;
const TYPE: &'static str = "FileManifest";
fn values_for_descendant_check_and_freeze(&self) -> Box<dyn Iterator<Item = Value>> {
Box::new(std::iter::empty())
}
}
impl FileManifestValue {
pub fn new_from_args() -> ValueResult {
Self::new_from_manifest(FileManifest::default())
}
pub fn new_from_manifest(manifest: FileManifest) -> ValueResult {
Ok(Value::new(Self {
inner: Arc::new(Mutex::new(manifest)),
run_path: None,
}))
}
pub fn inner(&self, label: &str) -> Result<MutexGuard<FileManifest>, ValueError> {
self.inner.try_lock().map_err(|e| {
ValueError::Runtime(RuntimeError {
code: "SIMPLE_FILE_MANIFEST",
message: format!("error obtaining lock: {}", e),
label: label.to_string(),
})
})
}
fn build(
&self,
type_values: &TypeValues,
call_stack: &mut CallStack,
target: String,
) -> ValueResult {
const LABEL: &str = "FileManifest.build()";
let context_value = get_context_value(type_values)?;
let context = context_value
.downcast_ref::<EnvironmentContext>()
.ok_or(ValueError::IncorrectParameterType)?;
let output_path = context.target_build_path(&target);
let inner = self.inner(LABEL)?;
let installed_paths = error_context(LABEL, || {
warn!("installing files to {}", output_path.display());
inner
.materialize_files_with_replace(&output_path)
.map_err(anyhow::Error::new)
})?;
post_materialize_signing_checks(
LABEL,
type_values,
call_stack,
SigningAction::FileManifestInstall,
&installed_paths,
)?;
let run_mode = if let Some(default) = &self.run_path {
RunMode::Path {
path: output_path.join(default),
}
} else {
let exes = inner
.iter_entries()
.filter(|(_, c)| c.is_executable())
.collect::<Vec<_>>();
if exes.len() == 1 {
RunMode::Path {
path: output_path.join(exes[0].0),
}
} else {
RunMode::None
}
};
Ok(Value::new(ResolvedTargetValue {
inner: ResolvedTarget {
run_mode,
output_path,
},
}))
}
pub fn add_manifest(&mut self, other: FileManifestValue) -> ValueResult {
const LABEL: &str = "FileManifest.add_manifest()";
let mut inner = self.inner(LABEL)?;
let other_inner = other.inner(LABEL)?;
error_context(LABEL, || {
inner.add_manifest(&other_inner).map_err(anyhow::Error::new)
})?;
Ok(Value::new(NoneType::None))
}
pub fn add_file(
&mut self,
content: FileContentValue,
path: Value,
directory: Value,
) -> ValueResult {
const LABEL: &str = "FileManifest.add_file()";
let path = optional_str_arg("path", &path)?;
let directory = optional_str_arg("directory", &directory)?;
let mut inner = self.inner(LABEL)?;
let content_inner = content.inner(LABEL)?;
error_context(LABEL, || {
if path.is_some() && directory.is_some() {
return Err(anyhow!(
"at most 1 of `path` and `directory` must be specified"
));
}
let path = if let Some(path) = path {
PathBuf::from(path)
} else if let Some(directory) = directory {
PathBuf::from(directory).join(&content_inner.filename)
} else {
PathBuf::from(&content_inner.filename)
};
inner.add_file_entry(path, content_inner.content.clone())?;
Ok(())
})?;
Ok(Value::new(NoneType::None))
}
pub fn add_path(
&mut self,
path: String,
strip_prefix: String,
force_read: bool,
) -> ValueResult {
const LABEL: &str = "FileManifest.add_path()";
let mut inner = self.inner(LABEL)?;
error_context(LABEL, || {
let path = Path::new(&path);
let strip_prefix = Path::new(&strip_prefix);
if force_read {
inner.add_path_memory(path, strip_prefix)
} else {
inner.add_path(path, strip_prefix)
}
.map_err(anyhow::Error::new)
})?;
Ok(Value::new(NoneType::None))
}
pub fn get_file(&self, path: String) -> ValueResult {
const LABEL: &str = "FileManifest.get_file()";
let (path, filename) = error_context(LABEL, || {
let path = PathBuf::from(path);
let filename = path
.file_name()
.ok_or_else(|| {
anyhow!("unable to resolve file name from path: {}", path.display())
})?
.to_string_lossy()
.to_string();
Ok((path, filename))
})?;
let inner = self.inner(LABEL)?;
if let Some(entry) = inner.get(path) {
Ok(FileContentWrapper {
content: entry.clone(),
filename,
}
.into())
} else {
Ok(Value::new(NoneType::None))
}
}
pub fn install(
&self,
type_values: &TypeValues,
call_stack: &mut CallStack,
path: String,
replace: bool,
) -> ValueResult {
const LABEL: &str = "FileManifest.install()";
let raw_context = get_context_value(type_values)?;
let context = raw_context
.downcast_ref::<EnvironmentContext>()
.ok_or(ValueError::IncorrectParameterType)?;
let inner = self.inner(LABEL)?;
let installed_paths = error_context(LABEL, || {
let dest_path = context.build_path().join(path);
if replace {
inner.materialize_files_with_replace(&dest_path)
} else {
inner.materialize_files(&dest_path)
}
.map_err(anyhow::Error::new)
})?;
post_materialize_signing_checks(
LABEL,
type_values,
call_stack,
SigningAction::FileManifestInstall,
&installed_paths,
)?;
Ok(Value::new(NoneType::None))
}
pub fn paths(&self) -> ValueResult {
const LABEL: &str = "FileManifest.paths()";
let inner = self.inner(LABEL)?;
let paths = inner
.iter_entries()
.map(|(path, _)| Value::from(format!("{}", path.display())))
.collect::<Vec<_>>();
Ok(Value::from(paths))
}
pub fn remove(&mut self, path: String) -> ValueResult {
const LABEL: &str = "FileManifest.remove()";
let (path, filename) = error_context(LABEL, || {
let path = PathBuf::from(path);
let filename = path
.file_name()
.ok_or_else(|| {
anyhow!("unable to resolve file name from path: {}", path.display())
})?
.to_string_lossy()
.to_string();
Ok((path, filename))
})?;
let mut inner = self.inner(LABEL)?;
if let Some(entry) = inner.remove(path) {
Ok(FileContentWrapper {
content: entry,
filename,
}
.into())
} else {
Ok(Value::new(NoneType::None))
}
}
}
starlark_module! { file_manifest_module =>
#[allow(non_snake_case)]
FileManifest(env _env) {
FileManifestValue::new_from_args()
}
FileManifest.add_manifest(this, other: FileManifestValue) {
let mut this = this.downcast_mut::<FileManifestValue>().unwrap().unwrap();
this.add_manifest(other)
}
FileManifest.add_file(
this,
content: FileContentValue,
path = NoneType::None,
directory = NoneType::None
) {
let mut this = this.downcast_mut::<FileManifestValue>().unwrap().unwrap();
this.add_file(content, path, directory)
}
FileManifest.add_path(this, path: String, strip_prefix: String, force_read: bool = false) {
let mut this = this.downcast_mut::<FileManifestValue>().unwrap().unwrap();
this.add_path(path, strip_prefix, force_read)
}
FileManifest.build(env env, call_stack cs, this, target: String) {
let this = this.downcast_ref::<FileManifestValue>().unwrap();
this.build(env, cs, target)
}
FileManifest.get_file(this, path: String) {
let this = this.downcast_ref::<FileManifestValue>().unwrap();
this.get_file(path)
}
FileManifest.install(env env, call_stack cs, this, path: String, replace: bool = true) {
let this = this.downcast_ref::<FileManifestValue>().unwrap();
this.install(env, cs, path, replace)
}
FileManifest.paths(this) {
let this = this.downcast_ref::<FileManifestValue>().unwrap();
this.paths()
}
FileManifest.remove(this, path: String) {
let mut this = this.downcast_mut::<FileManifestValue>().unwrap().unwrap();
this.remove(path)
}
}
#[cfg(test)]
mod tests {
use {
super::*, crate::starlark::testutil::*, anyhow::Result, simple_file_manifest::FileEntry,
tugger_common::testutil::*,
};
#[test]
fn test_new_file_manifest() {
let m = starlark_ok("FileManifest()");
assert_eq!(m.get_type(), "FileManifest");
let m = m.downcast_ref::<FileManifestValue>().unwrap();
assert_eq!(m.inner("ignored").unwrap().clone(), FileManifest::default());
}
#[test]
fn test_add_file_manifest() -> Result<()> {
let mut env = StarlarkEnvironment::new()?;
env.eval("m1 = FileManifest()")?;
env.eval("m2 = FileManifest()")?;
env.eval("m1.add_manifest(m2)")?;
Ok(())
}
#[test]
fn test_add_path() -> Result<()> {
let mut env = StarlarkEnvironment::new()?;
let manifest_value = env.eval("m = FileManifest(); m")?;
let res = env.eval("m.add_path('/does/not/exist', '/does/not')");
assert!(res.is_err());
let temp_file0 = DEFAULT_TEMP_DIR.path().join("test_add_path_0");
let temp_file1 = DEFAULT_TEMP_DIR.path().join("test_add_path_1");
std::fs::write(&temp_file0, vec![42])?;
std::fs::write(&temp_file1, vec![42, 42])?;
let parent = temp_file0.parent().unwrap();
env.eval(&format!(
"m.add_path('{}', '{}')",
temp_file0.display().to_string().escape_default(),
parent.display().to_string().escape_default()
))?;
env.eval(&format!(
"m.add_path('{}', '{}', force_read = True)",
temp_file1.display().to_string().escape_default(),
parent.display().to_string().escape_default()
))?;
let manifest = manifest_value.downcast_ref::<FileManifestValue>().unwrap();
{
let inner = manifest.inner("ignored").unwrap();
assert_eq!(inner.iter_files().count(), 2);
assert_eq!(
inner.get("test_add_path_0"),
Some(&FileEntry::new_from_path(temp_file0, false))
);
assert_eq!(inner.get("test_add_path_1"), Some(&vec![42, 42].into()),);
}
Ok(())
}
#[test]
fn add_file() -> Result<()> {
let mut env = StarlarkEnvironment::new()?;
env.eval("c = FileContent(filename = 'file', content = 'foo')")?;
env.eval("m = FileManifest()")?;
env.eval("m.add_file(c)")?;
let raw = env.eval("m")?;
let manifest = raw.downcast_ref::<FileManifestValue>().unwrap();
let inner = manifest.inner("ignored").unwrap();
let entries = inner.iter_entries().collect::<Vec<_>>();
assert_eq!(
entries,
vec![(&PathBuf::from("file"), &b"foo".as_ref().into())]
);
Ok(())
}
#[test]
fn add_file_path() -> Result<()> {
let mut env = StarlarkEnvironment::new()?;
env.eval("c = FileContent(filename = 'file', content = 'foo')")?;
env.eval("m = FileManifest()")?;
env.eval("m.add_file(c, path = 'foo/bar')")?;
let raw = env.eval("m")?;
let manifest = raw.downcast_ref::<FileManifestValue>().unwrap();
let inner = manifest.inner("ignored").unwrap();
let entries = inner.iter_entries().collect::<Vec<_>>();
assert_eq!(
entries,
vec![(&PathBuf::from("foo/bar"), &b"foo".as_ref().into())]
);
Ok(())
}
#[test]
fn add_file_directory() -> Result<()> {
let mut env = StarlarkEnvironment::new()?;
env.eval("c = FileContent(filename = 'file', content = 'foo')")?;
env.eval("m = FileManifest()")?;
env.eval("m.add_file(c, directory = 'dir')")?;
let raw = env.eval("m")?;
let manifest = raw.downcast_ref::<FileManifestValue>().unwrap();
let inner = manifest.inner("ignored").unwrap();
let entries = inner.iter_entries().collect::<Vec<_>>();
assert_eq!(
entries,
vec![(&PathBuf::from("dir/file"), &b"foo".as_ref().into())]
);
Ok(())
}
#[test]
fn get_file() -> Result<()> {
let mut env = StarlarkEnvironment::new()?;
env.eval("m = FileManifest()")?;
assert_eq!(env.eval("m.get_file('missing')")?.get_type(), "NoneType");
env.eval("m.add_file(FileContent(filename = 'file', content = 'foo'))")?;
assert_eq!(
env.eval("m.get_file('file')")?.get_type(),
FileContentValue::TYPE
);
Ok(())
}
#[test]
fn paths() -> Result<()> {
let mut env = StarlarkEnvironment::new()?;
env.eval("m = FileManifest()")?;
let v = env.eval("m.paths()")?;
assert_eq!(v.get_type(), "list");
assert_eq!(v.iter().unwrap().iter().count(), 0);
env.eval("m.add_file(FileContent(filename = 'file', content = 'foo'))")?;
let v = env.eval("m.paths()")?;
let values = v.iter().unwrap().to_vec();
assert_eq!(values.len(), 1);
assert_eq!(values[0].to_string(), "file");
Ok(())
}
#[test]
fn remove() -> Result<()> {
let mut env = StarlarkEnvironment::new()?;
env.eval("m = FileManifest()")?;
env.eval("m.add_file(FileContent(filename = 'file', content = 'foo'))")?;
assert_eq!(
env.eval("m.remove('file')")?.get_type(),
FileContentValue::TYPE
);
assert_eq!(env.eval("m.remove('file')")?.get_type(), "NoneType");
Ok(())
}
}