summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2023-09-01 14:06:14 +0300
committerLars Wirzenius <liw@liw.fi>2023-09-01 15:33:06 +0300
commit905e0fd2ebb89c1edf1ea24e3b478ca0855c96d9 (patch)
tree77f588653b2b4849d326ff5837574faa072901ae
parent33f8b71100b1cfcf893ef989e0eaebb380a3f9b7 (diff)
downloadambient-run-905e0fd2ebb89c1edf1ea24e3b478ca0855c96d9.tar.gz
feat: builds get a copy of the source code
Sponsored-by: author
-rw-r--r--ambient-run.md35
-rw-r--r--src/bin/ambient-run.rs6
-rw-r--r--src/project.rs57
-rw-r--r--src/qemu.rs46
-rw-r--r--src/vdrive.rs7
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 })
}