mirror of
https://gitlab.redox-os.org/redox-os/redox.git
synced 2026-06-30 16:48:45 +08:00
This deduplicates code as well as forces names to be sanitized. The API for both redox-pkg and cookbook need some reimagining due to the reshuffling, but this patch is not concerned with that as yet.
300 lines
9.0 KiB
Rust
300 lines
9.0 KiB
Rust
use std::{convert::TryInto, fs, path::PathBuf};
|
|
|
|
use pkg::{recipes, PackageName};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
/// Specifies how to download the source for a recipe
|
|
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
|
#[serde(untagged)]
|
|
pub enum SourceRecipe {
|
|
/// Reuse the source directory of another package
|
|
///
|
|
/// This is useful when a single source repo contains multiple projects which each have their
|
|
/// own recipe to build them.
|
|
SameAs {
|
|
/// Relative path to the package for which to reuse the source dir
|
|
same_as: String,
|
|
},
|
|
/// Path source
|
|
Path {
|
|
/// The path to the source
|
|
path: String,
|
|
},
|
|
/// A git repository source
|
|
Git {
|
|
/// The URL for the git repository, such as https://gitlab.redox-os.org/redox-os/ion.git
|
|
git: String,
|
|
/// The URL for an upstream repository
|
|
upstream: Option<String>,
|
|
/// The optional branch of the git repository to track, such as master. Please specify to
|
|
/// make updates to the rev easier
|
|
branch: Option<String>,
|
|
/// The optional revision of the git repository to use for builds. Please specify for
|
|
/// reproducible builds
|
|
rev: Option<String>,
|
|
/// A list of patch files to apply to the source
|
|
#[serde(default)]
|
|
patches: Vec<String>,
|
|
/// Optional script to run to prepare the source
|
|
script: Option<String>,
|
|
},
|
|
/// A tar file source
|
|
Tar {
|
|
/// The URL of a tar source
|
|
tar: String,
|
|
/// The optional blake3 sum of the tar file. Please specify this to make reproducible
|
|
/// builds more reliable
|
|
blake3: Option<String>,
|
|
/// A list of patch files to apply to the source
|
|
#[serde(default)]
|
|
patches: Vec<String>,
|
|
/// Optional script to run to prepare the source, such as ./autogen.sh
|
|
script: Option<String>,
|
|
},
|
|
}
|
|
|
|
/// Specifies how to build a recipe
|
|
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
|
#[serde(tag = "template")]
|
|
pub enum BuildKind {
|
|
/// Will build and install using cargo
|
|
#[serde(rename = "cargo")]
|
|
Cargo {
|
|
#[serde(default)]
|
|
package_path: Option<String>,
|
|
|
|
#[serde(default)]
|
|
cargoflags: String,
|
|
},
|
|
/// Will build and install using configure and make
|
|
#[serde(rename = "configure")]
|
|
Configure,
|
|
/// Will build and install using custom commands
|
|
#[serde(rename = "custom")]
|
|
Custom { script: String },
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
|
pub struct BuildRecipe {
|
|
#[serde(flatten)]
|
|
pub kind: BuildKind,
|
|
#[serde(default)]
|
|
pub dependencies: Vec<PackageName>,
|
|
}
|
|
|
|
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
|
|
pub struct PackageRecipe {
|
|
#[serde(default)]
|
|
pub dependencies: Vec<PackageName>,
|
|
}
|
|
|
|
/// Everything required to build a Redox package
|
|
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
|
pub struct Recipe {
|
|
/// Specifies how to download the source for this recipe
|
|
pub source: Option<SourceRecipe>,
|
|
/// Specifies how to build this recipe
|
|
pub build: BuildRecipe,
|
|
/// Specifies how to package this recipe
|
|
#[serde(default)]
|
|
pub package: PackageRecipe,
|
|
}
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
pub struct CookRecipe {
|
|
pub name: PackageName,
|
|
pub dir: PathBuf,
|
|
pub recipe: Recipe,
|
|
}
|
|
|
|
impl CookRecipe {
|
|
pub fn new(name: &str) -> Result<Self, String> {
|
|
let name: PackageName = name
|
|
.try_into()
|
|
.map_err(|e| format!("Invalid package name: {e}"))?;
|
|
let dir = recipes::find(name.as_str());
|
|
if dir.is_none() {
|
|
return Err(format!("failed to find recipe directory '{}'", name));
|
|
}
|
|
let dir = dir.unwrap();
|
|
let file = dir.join("recipe.toml");
|
|
if !file.is_file() {
|
|
return Err(format!("failed to find recipe file '{}'", file.display()));
|
|
}
|
|
|
|
let toml = fs::read_to_string(&file).map_err(|err| {
|
|
format!(
|
|
"failed to read recipe file '{}': {}\n{:#?}",
|
|
file.display(),
|
|
err,
|
|
err
|
|
)
|
|
})?;
|
|
|
|
let recipe: Recipe = toml::from_str(&toml).map_err(|err| {
|
|
format!(
|
|
"failed to parse recipe file '{}': {}\n{:#?}",
|
|
file.display(),
|
|
err,
|
|
err
|
|
)
|
|
})?;
|
|
|
|
let dir = dir.to_path_buf();
|
|
Ok(Self { name, dir, recipe })
|
|
}
|
|
|
|
pub fn new_recursive(names: &[PackageName], recursion: usize) -> Result<Vec<Self>, String> {
|
|
if recursion == 0 {
|
|
return Err(format!(
|
|
"recursion limit while processing build dependencies: {:#?}",
|
|
names
|
|
));
|
|
}
|
|
|
|
let mut recipes = Vec::new();
|
|
for name in names {
|
|
let recipe = Self::new(name.as_str())?;
|
|
|
|
let dependencies =
|
|
Self::new_recursive(&recipe.recipe.build.dependencies, recursion - 1).map_err(
|
|
|err| format!("{}: failed on loading build dependencies:\n{}", name, err),
|
|
)?;
|
|
|
|
for dependency in dependencies {
|
|
if !recipes.contains(&dependency) {
|
|
recipes.push(dependency);
|
|
}
|
|
}
|
|
|
|
if !recipes.contains(&recipe) {
|
|
recipes.push(recipe);
|
|
}
|
|
}
|
|
|
|
Ok(recipes)
|
|
}
|
|
|
|
pub fn get_package_deps_recursive(
|
|
names: &[PackageName],
|
|
recursion: usize,
|
|
) -> Result<Vec<PackageName>, String> {
|
|
if recursion == 0 {
|
|
return Err(format!(
|
|
"recursion limit while processing package dependencies: {:#?}",
|
|
names
|
|
));
|
|
}
|
|
|
|
let mut recipes: Vec<PackageName> = Vec::new();
|
|
for name in names {
|
|
let recipe = Self::new(name.as_str())?;
|
|
|
|
let dependencies = Self::get_package_deps_recursive(
|
|
&recipe.recipe.package.dependencies,
|
|
recursion - 1,
|
|
)
|
|
.map_err(|err| format!("{}: failed on loading package dependencies:\n{}", name, err))?;
|
|
|
|
for dependency in dependencies {
|
|
if !recipes.contains(&dependency) {
|
|
recipes.push(dependency);
|
|
}
|
|
}
|
|
|
|
if !recipes.contains(name) {
|
|
recipes.push(name.clone());
|
|
}
|
|
}
|
|
|
|
Ok(recipes)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
#[test]
|
|
fn git_cargo_recipe() {
|
|
use crate::recipe::{BuildKind, BuildRecipe, PackageRecipe, Recipe, SourceRecipe};
|
|
|
|
let recipe: Recipe = toml::from_str(
|
|
r#"
|
|
[source]
|
|
git = "https://gitlab.redox-os.org/redox-os/acid.git"
|
|
branch = "master"
|
|
rev = "06344744d3d55a5ac9a62a6059cb363d40699bbc"
|
|
|
|
[build]
|
|
template = "cargo"
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
|
|
assert_eq!(
|
|
recipe,
|
|
Recipe {
|
|
source: Some(SourceRecipe::Git {
|
|
git: "https://gitlab.redox-os.org/redox-os/acid.git".to_string(),
|
|
upstream: None,
|
|
branch: Some("master".to_string()),
|
|
rev: Some("06344744d3d55a5ac9a62a6059cb363d40699bbc".to_string()),
|
|
patches: Vec::new(),
|
|
script: None,
|
|
}),
|
|
build: BuildRecipe {
|
|
kind: BuildKind::Cargo {
|
|
package_path: None,
|
|
cargoflags: String::new(),
|
|
},
|
|
dependencies: Vec::new(),
|
|
},
|
|
package: PackageRecipe {
|
|
dependencies: Vec::new(),
|
|
},
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn tar_custom_recipe() {
|
|
use crate::recipe::{BuildKind, BuildRecipe, PackageRecipe, Recipe, SourceRecipe};
|
|
|
|
let recipe: Recipe = toml::from_str(
|
|
r#"
|
|
[source]
|
|
tar = "http://downloads.xiph.org/releases/ogg/libogg-1.3.3.tar.xz"
|
|
blake3 = "8220c0e4082fa26c07b10bfe31f641d2e33ebe1d1bb0b20221b7016bc8b78a3a"
|
|
|
|
[build]
|
|
template = "custom"
|
|
script = "make"
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
|
|
assert_eq!(
|
|
recipe,
|
|
Recipe {
|
|
source: Some(SourceRecipe::Tar {
|
|
tar: "http://downloads.xiph.org/releases/ogg/libogg-1.3.3.tar.xz".to_string(),
|
|
blake3: Some(
|
|
"8220c0e4082fa26c07b10bfe31f641d2e33ebe1d1bb0b20221b7016bc8b78a3a"
|
|
.to_string()
|
|
),
|
|
patches: Vec::new(),
|
|
script: None,
|
|
}),
|
|
build: BuildRecipe {
|
|
kind: BuildKind::Custom {
|
|
script: "make".to_string()
|
|
},
|
|
dependencies: Vec::new(),
|
|
},
|
|
package: PackageRecipe {
|
|
dependencies: Vec::new(),
|
|
},
|
|
}
|
|
);
|
|
}
|
|
}
|