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.
189 lines
5.0 KiB
Rust
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(())
|
|
}
|
|
}
|