diff options
author | Lars Wirzenius <liw@liw.fi> | 2023-09-01 14:06:14 +0300 |
---|---|---|
committer | Lars Wirzenius <liw@liw.fi> | 2023-09-01 15:33:06 +0300 |
commit | 905e0fd2ebb89c1edf1ea24e3b478ca0855c96d9 (patch) | |
tree | 77f588653b2b4849d326ff5837574faa072901ae | |
parent | 33f8b71100b1cfcf893ef989e0eaebb380a3f9b7 (diff) | |
download | ambient-run-905e0fd2ebb89c1edf1ea24e3b478ca0855c96d9.tar.gz |
feat: builds get a copy of the source code
Sponsored-by: author
-rw-r--r-- | ambient-run.md | 35 | ||||
-rw-r--r-- | src/bin/ambient-run.rs | 6 | ||||
-rw-r--r-- | src/project.rs | 57 | ||||
-rw-r--r-- | src/qemu.rs | 46 | ||||
-rw-r--r-- | src/vdrive.rs | 7 |
5 files changed, 134 insertions, 17 deletions
diff --git a/ambient-run.md b/ambient-run.md index 0387d0e..1690cce 100644 --- a/ambient-run.md +++ b/ambient-run.md @@ -184,13 +184,12 @@ _Stakeholder:_ Lars. ~~~scenario given an installed ambient-run -given file hello-project.yaml +given file smoke/project.yaml from smoke-project.yaml given image file image.qcow2 specified for test suite -when I run ambient-run build hello-project.yaml --log hello.log +when I run ambient-run build smoke/project.yaml --log hello.log then file hello.log contains "hello, world" ~~~ - -~~~{#hello-project.yaml .file .yaml} +~~~{#smoke-project.yaml .file .yaml} source: . shell: | #!/bin/bash @@ -199,6 +198,34 @@ shell: | image: image.qcow2 ~~~ +### Build gets source code + +_Requirement:_ The build gets a copy of the specified source code. + +_Justification:_ This is the simplest possible project build, and if +it works, then at least the very fundamental parts of `ambient-run` +work, and vice versa. + +_Stakeholder:_ Lars. + +~~~scenario +given an installed ambient-run +given file foo-project.yaml +given file foo/README.md from foo-project.yaml +given image file image.qcow2 specified for test suite +when I run ambient-run build foo-project.yaml --log foo.log +then file foo.log contains "README.md" +~~~ + + +~~~{#foo-project.yaml .file .yaml} +source: foo +shell: | + #!/bin/bash + ls -l +image: image.qcow2 +~~~ + ### Build is given dependencies ### Cache is persistent between builds ### Build gets the resources is demands diff --git a/src/bin/ambient-run.rs b/src/bin/ambient-run.rs index d0ca22b..e1f1b16 100644 --- a/src/bin/ambient-run.rs +++ b/src/bin/ambient-run.rs @@ -157,7 +157,7 @@ impl VDriveCreateCommand { if let Some(size) = self.size { builder = builder.size(size); } - builder.create()?; + builder.create(None)?; Ok(()) } } @@ -213,7 +213,9 @@ struct BuildCommand { impl BuildCommand { fn run(&self, _global: &Args, config: &Config) -> Result<(), AmbientRunError> { let project = Project::load(&self.filename, config)?; - let mut qemu = Qemu::new(project.image()).with_shell(project.shell()); + let mut qemu = Qemu::new(&project.image()) + .with_shell(project.shell()) + .with_source(&project.source()); if let Some(log) = &self.log { qemu = qemu.with_log(log); } diff --git a/src/project.rs b/src/project.rs index 341b5ad..8a182bc 100644 --- a/src/project.rs +++ b/src/project.rs @@ -7,12 +7,29 @@ use std::path::{Path, PathBuf}; /// Per-project build instructions. #[derive(Debug, Deserialize, Serialize)] pub struct Project { + #[serde(skip)] + filename: PathBuf, source: PathBuf, shell: String, image: PathBuf, } impl Project { + // Directory where project build instructions were loaded from. If + // we can't figure that out, default to root directory. + fn directory(&self) -> &Path { + if let Some(parent) = self.filename.parent() { + parent + } else { + Path::new("/") + } + } + + /// Return path relative to project build instruction directory. + pub fn join(&self, path: &Path) -> PathBuf { + self.directory().join(path) + } + /// Load build instructions from named file. pub fn load(filename: &Path, config: &Config) -> Result<Self, ProjectError> { let no_image = PathBuf::from("/no/image/specified"); @@ -22,6 +39,7 @@ impl Project { no_image.clone() }; let mut project = Self { + filename: filename.into(), source: PathBuf::from("."), shell: "".into(), image, @@ -36,8 +54,7 @@ impl Project { Ok(project) } - /// Load named project file, update self with values. - pub fn add_from(&mut self, filename: &Path) -> Result<(), ProjectError> { + fn add_from(&mut self, filename: &Path) -> Result<(), ProjectError> { let bytes = std::fs::read(filename).map_err(|e| ProjectError::Open(filename.into(), e))?; let text = String::from_utf8_lossy(&bytes); let snippet: ProjectSnippet = @@ -59,9 +76,14 @@ impl Project { serde_yaml::to_string(self).map_err(ProjectError::AsYaml) } + /// Source directory. + pub fn source(&self) -> PathBuf { + self.join(self.source.as_ref()) + } + /// VM image to use for this build. - pub fn image(&self) -> &Path { - self.image.as_ref() + pub fn image(&self) -> PathBuf { + self.image.clone() } /// Shell snippet to run to build the project. @@ -97,3 +119,30 @@ pub enum ProjectError { #[error("no image specified by project or config")] NoImage, } + +#[cfg(test)] +mod test { + use super::*; + use std::fs::write; + + #[test] + fn knows_its_directory() { + let config = Config::default(); + let tempdir = tempfile::tempdir().expect("create temporary directory"); + let filename = tempdir.path().join("project.yaml"); + write( + &filename, + r#"shell: | + #!/bin/bash + echo hello +image: nope +source: yup +"#, + ) + .expect("write project spec file"); + let p = Project::load(&filename, &config).expect("load build spec"); + assert_eq!(p.directory(), tempdir.path()); + assert_eq!(p.image(), Path::new("nope")); + assert_eq!(p.source(), tempdir.path().join("yup")); + } +} diff --git a/src/qemu.rs b/src/qemu.rs index 22b6864..c48213f 100644 --- a/src/qemu.rs +++ b/src/qemu.rs @@ -16,7 +16,7 @@ const OVMF_FD: &str = "/usr/share/ovmf/OVMF.fd"; pub struct Qemu { image: PathBuf, log: Option<PathBuf>, - source: Option<PathBuf>, + source: PathBuf, shell: Option<String>, } @@ -25,6 +25,7 @@ impl Qemu { pub fn new(image: &Path) -> Self { Self { image: image.into(), + source: PathBuf::from("."), ..Default::default() } } @@ -37,7 +38,7 @@ impl Qemu { /// Set source directory. pub fn with_source(mut self, source: &Path) -> Self { - self.source = Some(source.into()); + self.source = source.into(); self } @@ -49,25 +50,41 @@ impl Qemu { /// Run QEMU in the specified way. pub fn run(&self) -> Result<(), QemuError> { + eprintln!("qemu run"); let tmp = tempfile::tempdir().map_err(QemuError::TempDir)?; let empty = tempfile::tempdir().map_err(QemuError::TempDir)?; let image = tmp.path().join("vm.qcow2"); let vars = tmp.path().join("vars.fd"); + eprintln!("copy image and vars"); copy(&self.image, &image).map_err(|e| QemuError::Copy(self.image.clone(), e))?; copy(OVMF_FD, &vars).map_err(|e| QemuError::Copy(OVMF_FD.into(), e))?; + eprintln!("output drive"); let output_drive = Self::create_tar_with_size(tmp.path().join("output"), empty.path(), MAX_OUTPUT_SIZE)?; + + eprintln!("cache drive"); let cache_drive = Self::create_tar_with_size(tmp.path().join("cache"), empty.path(), MAX_OUTPUT_SIZE)?; + + eprintln!("deps drive"); let deps_drive = Self::create_tar(tmp.path().join("deps"), empty.path())?; + eprintln!("script"); + let script = empty.path().join("ambient-script"); if let Some(shell) = &self.shell { - write(empty.path().join(".ambient-script"), shell).map_err(QemuError::Write)?; + write(&script, shell).map_err(QemuError::Write)?; } - let source_drive = Self::create_tar(tmp.path().join("src"), empty.path())?; + + eprintln!("source drive"); + let source_drive = Self::create_tar_with_extra_file( + tmp.path().join("src"), + &self.source, + &script, + ".ambient-script", + )?; let args = QemuArgs::default() .with_valued_arg("-m", "16384") @@ -84,8 +101,10 @@ impl Qemu { .with_raw(deps_drive.filename(), true) .with_arg("-nodefaults"); + eprintln!("log file"); let log = Self::create_file(&self.log)?; + eprintln!("spawn qemu"); let child = Command::new("kvm") .args(args.iter()) .stdin(Stdio::null()) @@ -94,6 +113,7 @@ impl Qemu { .spawn() .map_err(QemuError::Run)?; + eprintln!("wait for qemu"); let output = child.wait_with_output().map_err(QemuError::Run)?; if output.status.success() { eprintln!("kvm OK"); @@ -120,7 +140,21 @@ impl Qemu { let tar = VirtualDriveBuilder::default() .filename(&tar_filename) .root_directory(dirname) - .create() + .create(None) + .map_err(|e| QemuError::Tar(dirname.into(), e))?; + Ok(tar) + } + + fn create_tar_with_extra_file( + tar_filename: PathBuf, + dirname: &Path, + extra: &Path, + name: &str, + ) -> Result<VirtualDrive, QemuError> { + let tar = VirtualDriveBuilder::default() + .filename(&tar_filename) + .root_directory(dirname) + .create(Some((extra, name))) .map_err(|e| QemuError::Tar(dirname.into(), e))?; Ok(tar) } @@ -134,7 +168,7 @@ impl Qemu { .filename(&tar_filename) .root_directory(dirname) .size(size) - .create() + .create(None) .map_err(|e| QemuError::Tar(dirname.into(), e))?; Ok(tar) } diff --git a/src/vdrive.rs b/src/vdrive.rs index e86eb9d..12ec823 100644 --- a/src/vdrive.rs +++ b/src/vdrive.rs @@ -79,7 +79,7 @@ impl VirtualDriveBuilder { } /// Create a virtual drive. - pub fn create(self) -> Result<VirtualDrive, VirtualDriveError> { + pub fn create(self, extra: Option<(&Path, &str)>) -> Result<VirtualDrive, VirtualDriveError> { let filename = self.filename.expect("filename has been set"); let file = File::create(&filename).map_err(|e| VirtualDriveError::Create(filename.clone(), e))?; @@ -93,6 +93,11 @@ impl VirtualDriveBuilder { .append_dir_all(".", &root) .map_err(VirtualDriveError::CreateTar)?; } + if let Some((path, name)) = extra { + builder + .append_path_with_name(path, name) + .map_err(VirtualDriveError::CreateTar)?; + } Ok(VirtualDrive { filename }) } |