You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
malachite/src/build.rs

189 lines
5.0 KiB
Rust

use std::fs;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
use fs_extra::dir::{self, CopyOptions};
use futures_util::StreamExt;
use futures_util::TryStreamExt;
use glob::glob;
use names::{Generator, Name};
use podman_api::opts::ContainerCreateOpts;
use podman_api::opts::ImageBuildOpts;
use podman_api::Podman;
use crate::errors::AppResult;
use crate::utils::{uid, ShellCommand};
pub const BUILDFILE: &str = include_str!("../Buildfile");
#[derive(Clone, Debug)]
pub enum BuildKind {
Podman { image: String },
Host,
}
impl BuildKind {
pub fn image(&self) -> Option<&str> {
match self {
Self::Podman { image } => Some(image),
Self::Host => None,
}
}
}
pub struct Build {
pub kind: BuildKind,
pub name: String,
pub pkg: String,
pub out: PathBuf,
pub flags: Vec<String>,
}
impl Build {
pub fn new<S: ToString, P: Into<PathBuf>>(
kind: BuildKind,
pkg: S,
out: P,
flags: Vec<String>,
) -> AppResult<Self> {
let mut generator = Generator::with_naming(Name::Numbered);
let pod = Self {
kind,
name: generator.next().unwrap(),
pkg: pkg.to_string(),
out: out.into(),
flags,
};
Ok(pod)
}
pub async fn build(&self) -> AppResult<()> {
fs::create_dir_all(&self.out)?;
match &self.kind {
BuildKind::Podman { .. } => self.podman_build().await?,
BuildKind::Host => self.host_build().await?,
}
Ok(())
}
async fn podman_build(&self) -> AppResult<()> {
let uid = uid();
let podman = Podman::new(format!("unix:///var/run/user/{}/podman/podman.sock", uid))?;
let image = self.kind.image().unwrap();
let buildfile = fs::read_to_string(".mlc/Buildfile")?;
let buildfile_template = liquid::ParserBuilder::with_stdlib()
.build()?
.parse(&buildfile)?;
let buildfile_values = liquid::object!({
"image": image,
"pkg": &self.pkg,
"flags": &self.flags.join(" "),
});
let buildfile = buildfile_template.render(&buildfile_values)?;
let build_path = std::env::temp_dir().join(&self.name);
fs::create_dir_all(&build_path)?;
fs::write(build_path.join("Buildfile"), buildfile)?;
dir::copy(&self.pkg, &build_path, &CopyOptions::new())?;
dir::copy(".mlc/store", &build_path, &CopyOptions::new())?;
let opts = &ImageBuildOpts::builder(build_path.to_string_lossy())
.dockerfile("Buildfile")
.tag(&self.name)
.build();
let mut log_file = File::create(&format!(".mlc/logs/{}-{}.log", &self.pkg, &self.name))?;
let images = podman.images();
match images.build(opts) {
Ok(mut build_stream) => {
while let Some(chunk) = build_stream.next().await {
match chunk {
Ok(chunk) => {
println!("{}", chunk.stream);
let _ = log_file.write(chunk.stream.as_bytes())?;
}
Err(e) => eprintln!("{}", e),
}
}
}
Err(e) => eprintln!("{}", e),
};
podman
.containers()
.create(
&ContainerCreateOpts::builder()
.image(&self.name)
.name(&self.name)
.build(),
)
.await?;
let bytes = podman
.containers()
.get(&self.name)
.copy_from("/out")
.try_concat()
.await?;
let mut archive = tar::Archive::new(&bytes[..]);
archive.unpack(self.out.parent().unwrap())?;
podman.images().get(&self.name).remove().await?;
fs::remove_dir_all(build_path)?;
Ok(())
}
async fn host_build(&self) -> AppResult<()> {
let build_path = std::env::temp_dir().join(&self.name);
fs::create_dir_all(&build_path)?;
dir::copy(&self.pkg, &build_path, &CopyOptions::new())?;
dir::copy(".mlc/store", &build_path, &CopyOptions::new())?;
ShellCommand::makepkg()
.args(["-s", "--noconfirm"])
.args(&self.flags)
.cwd(&build_path.join(&self.pkg))
.spawn()?
.wait()?;
let glob = glob(
&build_path
.join(&self.pkg)
.join("*.pkg.tar.*")
.to_string_lossy(),
)?;
for entry in glob {
match entry {
Ok(path) => {
fs_extra::copy_items(
&[path.to_string_lossy().to_string()],
&self.out,
&CopyOptions::new(),
)?;
}
Err(e) => eprintln!("{}", e),
}
}
fs::remove_dir_all(build_path)?;
Ok(())
}
}