Identify outdated packages and smarter git

This commit is contained in:
Wildan M 2025-12-14 23:29:24 +07:00
parent bb8c13cf23
commit 81788a6fd1
No known key found for this signature in database
GPG Key ID: 01AC53185C679C79
8 changed files with 539 additions and 158 deletions

View File

@ -4,6 +4,7 @@ use cookbook::config::{CookConfig, get_config, init_config};
use cookbook::cook::cook_build::build;
use cookbook::cook::fetch::{fetch, fetch_offline};
use cookbook::cook::fs::{create_target_dir, run_command};
use cookbook::cook::ident;
use cookbook::cook::package::package;
use cookbook::cook::pty::{PtyOut, UnixSlavePty, flush_pty, setup_pty};
use cookbook::cook::script::KILL_ALL_PID;
@ -185,6 +186,9 @@ fn main_inner() -> anyhow::Result<()> {
}
let (config, command, recipe_names) = parse_args(args)?;
if command.is_building() {
ident::init_ident();
}
if command == CliCommand::Cook && config.cook.tui {
if let Some((name, e)) = run_tui_cook(config.clone(), recipe_names.clone())? {
let _ = stderr().write(e.as_bytes());
@ -534,10 +538,9 @@ fn handle_fetch(
allow_offline: bool,
logger: &PtyOut,
) -> anyhow::Result<PathBuf> {
let recipe_dir = &recipe.dir;
let source_dir = match config.cook.offline && allow_offline {
true => fetch_offline(recipe_dir, &recipe.recipe, logger),
false => fetch(recipe_dir, &recipe.recipe, logger),
true => fetch_offline(&recipe, logger),
false => fetch(&recipe, logger),
}
.map_err(|e| anyhow!("failed to fetch: {:?}", e))?;
@ -553,7 +556,7 @@ fn handle_cook(
) -> anyhow::Result<()> {
let recipe_dir = &recipe.dir;
let target_dir = create_target_dir(recipe_dir, recipe.target).map_err(|e| anyhow!(e))?;
let (stage_dir, auto_deps) = build(
let (stage_dirs, auto_deps) = build(
recipe_dir,
&source_dir,
&target_dir,
@ -565,15 +568,8 @@ fn handle_cook(
)
.map_err(|err| anyhow!("failed to build: {:?}", err))?;
package(
&stage_dir,
&target_dir,
&recipe.name,
&recipe.recipe,
&auto_deps,
logger,
)
.map_err(|err| anyhow!("failed to package: {:?}", err))?;
package(&recipe, &stage_dirs, &auto_deps, logger)
.map_err(|err| anyhow!("failed to package: {:?}", err))?;
Ok(())
}

View File

@ -1,10 +1,11 @@
use anyhow::anyhow;
use cookbook::WALK_DEPTH;
use cookbook::config::{get_config, init_config};
use cookbook::cook::package as cook_package;
use cookbook::cook::ident::{get_ident, init_ident};
use cookbook::cook::{fetch, package as cook_package};
use cookbook::recipe::CookRecipe;
use pkg::package::{Repository, SourceIdentifier};
use pkg::{Package, PackageName, recipes};
use std::collections::{BTreeMap, HashMap};
use std::collections::{BTreeMap, BTreeSet, HashMap};
use std::env;
use std::fs::{self, File};
use std::io::{Read, Write};
@ -27,7 +28,6 @@ fn is_newer(src: &Path, dst: &Path) -> bool {
#[derive(Clone)]
struct CliConfig {
repo_dir: PathBuf,
nonstop: bool,
appstream: bool,
recipe_list: Vec<String>,
}
@ -40,7 +40,6 @@ impl CliConfig {
.expect("Usage: repo_builder <REPO_DIR> <recipe1> <recipe2> ...");
Ok(CliConfig {
repo_dir: PathBuf::from(repo_dir),
nonstop: get_config().cook.nonstop,
appstream: env::var("COOKBOOK_APPSTREAM").ok().as_deref() == Some("true"),
recipe_list: args.collect(),
})
@ -48,7 +47,7 @@ impl CliConfig {
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
init_config();
init_ident();
let conf = CliConfig::parse_args()?;
Ok(publish_packages(&conf)?)
}
@ -65,7 +64,7 @@ fn publish_packages(config: &CliConfig) -> anyhow::Result<()> {
//
// The following adds the package dependencies of the recipes to the repo as
// well.
let recipe_list = Package::new_recursive(
let (recipe_list, recipe_map) = Package::new_recursive_nonstop(
&config
.recipe_list
.iter()
@ -73,18 +72,16 @@ fn publish_packages(config: &CliConfig) -> anyhow::Result<()> {
// Don't publish host packages
.filter(|pkg| pkg.as_ref().is_ok_and(|p| !p.is_host()))
.collect::<Result<Vec<_>, _>>()?,
config.nonstop,
WALK_DEPTH,
)?
.into_iter()
.map(|pkg| pkg.name.clone())
.collect::<Vec<_>>();
);
let mut appstream_sources: HashMap<String, PathBuf> = HashMap::new();
let mut packages: BTreeMap<String, String> = BTreeMap::new();
let mut outdated_packages: BTreeMap<String, SourceIdentifier> = BTreeMap::new();
// === 1. Push recipes in list ===
for recipe in &recipe_list {
for recipe_toml in &recipe_list {
let recipe = &recipe_toml.name;
let Some(recipe_path) = recipes::find(recipe.name()) else {
eprintln!("recipe {} not found", recipe);
continue;
@ -163,22 +160,68 @@ fn publish_packages(config: &CliConfig) -> anyhow::Result<()> {
}
}
// === 3. List outdated packages ===
for (recipe, e) in recipe_map
.into_iter()
.filter_map(|(k, v)| v.err().and_then(|e| Some((k, e))))
{
eprintln!(
"\x1b[0;91;49mrepo - marking {} as outdated:\x1b[0m {e}",
recipe
);
let Some(recipe_path) = recipes::find(recipe.name()) else {
eprintln!("recipe {} not found", recipe);
continue;
};
let Ok(cookbook_recipe) = CookRecipe::from_path(recipe_path, true, false) else {
eprintln!("recipe {} unable to read", recipe);
continue;
};
match fetch::fetch_get_source_info(&cookbook_recipe) {
Ok(source_ident) => {
outdated_packages.insert(recipe.name().to_string(), source_ident);
}
Err(e) => {
eprintln!(
"\x1b[0;91;49m source of {} is not identifiable:\x1b[0m {e}",
recipe
);
let ident = get_ident();
outdated_packages.insert(
recipe.name().to_string(),
SourceIdentifier {
source_identifier: "missing_source".to_string(),
commit_identifier: ident.commit.clone(),
time_identifier: ident.time.clone(),
},
);
}
};
}
eprintln!("\x1b[01;38;5;155mrepo - generating repo.toml\x1b[0m");
// === 3. Read and update repo.toml ===
// === 4. Read and update repo.toml ===
let repo_toml_path = repo_path.join("repo.toml");
if repo_toml_path.exists() {
let mut file = File::open(&repo_toml_path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
let parsed: Value = toml::from_str(&contents)?;
if let Some(pkg_table) = parsed.get("packages").and_then(|v| v.as_table()) {
for (k, v) in pkg_table {
if let Some(s) = v.as_str() {
packages.insert(k.clone(), format!("\"{}\"", s));
} else {
packages.insert(k.clone(), v.to_string());
let parsed: Repository = toml::from_str(&contents)?;
for (k, v) in parsed.packages {
packages.insert(k, v);
}
if parsed.outdated_packages.len() > 0 {
let built_packages: BTreeSet<String> = recipe_list
.iter()
.map(|p| p.name.name().to_string())
.collect();
for (k, v) in parsed.outdated_packages {
if outdated_packages.contains_key(&k) || !built_packages.contains(&k) {
outdated_packages.insert(k, v);
}
}
}
@ -203,21 +246,16 @@ fn publish_packages(config: &CliConfig) -> anyhow::Result<()> {
let version_str = parsed
.get("blake3")
.unwrap_or_else(|| parsed.get("version").unwrap_or_else(|| &empty_ver))
.to_string(); // includes quotes
.as_str()
.unwrap_or("");
let package_name = path.file_stem().unwrap().to_string_lossy().to_string();
packages.insert(package_name, version_str);
}
// FIXME: Use proper TOML serializer
let mut output = String::from("[packages]\n");
for (name, version) in &packages {
output.push_str(&if name.contains('.') {
format!("\"{name}\" = {version}\n")
} else {
format!("{name} = {version}\n")
});
packages.insert(package_name, version_str.to_string());
}
let output = toml::to_string(&Repository {
packages,
outdated_packages,
})?;
let mut output_file = File::create(&repo_toml_path)?;
output_file.write_all(output.as_bytes())?;

View File

@ -2,6 +2,7 @@
pub mod cook_build;
pub mod fetch;
pub mod fs;
pub mod ident;
pub mod package;
pub mod pty;
pub mod script;

View File

@ -265,6 +265,15 @@ pub fn build(
if stage_dir.is_dir() {
remove_all(&stage_dir)?;
}
// delete to let repo_builder know if this build fail later
let stage_file = stage_dir.with_added_extension("pkgar");
if stage_file.is_file() {
remove_all(&stage_file)?;
}
let stage_meta = stage_dir.with_added_extension("toml");
if stage_meta.is_file() {
remove_all(&stage_meta)?;
}
}
}
}

View File

@ -1,3 +1,5 @@
use pkg::package::SourceIdentifier;
use crate::REMOTE_PKG_SOURCE;
use crate::config::translate_mirror;
use crate::cook::fs::*;
@ -8,9 +10,11 @@ use crate::cook::script::*;
use crate::is_redox;
use crate::log_to_pty;
use crate::recipe::BuildKind;
use crate::recipe::Recipe;
use crate::recipe::CookRecipe;
use crate::{blake3, recipe::SourceRecipe};
use std::fs;
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::process::Command;
@ -30,27 +34,33 @@ pub(crate) fn get_blake3(path: &PathBuf, show_progress: bool) -> Result<String,
})
}
pub fn fetch_offline(
recipe_dir: &Path,
recipe: &Recipe,
logger: &PtyOut,
) -> Result<PathBuf, String> {
pub fn fetch_offline(recipe: &CookRecipe, logger: &PtyOut) -> Result<PathBuf, String> {
let recipe_dir = &recipe.dir;
let source_dir = recipe_dir.join("source");
if recipe.build.kind == BuildKind::None {
// the build function doesn't need source dir exists
return Ok(source_dir);
}
if recipe.build.kind == BuildKind::Remote {
fetch_remote(recipe_dir, recipe, true, logger)?;
return Ok(source_dir);
match recipe.recipe.build.kind {
BuildKind::None => {
// the build function doesn't need source dir exists
fetch_apply_source_info(recipe, "".to_string())?;
return Ok(source_dir);
}
BuildKind::Remote => {
fetch_remote(recipe_dir, recipe, true, logger)?;
return Ok(source_dir);
}
_ => {}
}
match &recipe.source {
let ident = match &recipe.recipe.source {
Some(SourceRecipe::Path { path: _ }) | None => {
return fetch(recipe_dir, recipe, logger);
fetch(recipe, logger)?;
"local_source".to_string()
}
Some(SourceRecipe::SameAs { same_as: _ }) => {
return fetch(recipe_dir, recipe, logger);
Some(SourceRecipe::SameAs { same_as }) => {
let recipe = fetch_resolve_canon(recipe_dir, &same_as, recipe.name.is_host())?;
// recursively fetch
fetch_offline(&recipe, logger)?;
fetch_make_symlink(&source_dir, &same_as)?;
fetch_get_source_info(&recipe)?.source_identifier
}
Some(SourceRecipe::Git {
git: _,
@ -62,6 +72,8 @@ pub fn fetch_offline(
shallow_clone: _,
}) => {
offline_check_exists(&source_dir)?;
let (head_rev, _) = get_git_head_rev(&source_dir)?;
head_rev
}
Some(SourceRecipe::Tar {
tar: _,
@ -93,29 +105,38 @@ pub fn fetch_offline(
offline_check_exists(&source_dir)?;
}
}
blake3.clone().unwrap_or("no_tar_blake3_hash_info".into())
}
}
};
fetch_apply_source_info(recipe, ident)?;
Ok(source_dir)
}
pub fn fetch(recipe_dir: &Path, recipe: &Recipe, logger: &PtyOut) -> Result<PathBuf, String> {
pub fn fetch(recipe: &CookRecipe, logger: &PtyOut) -> Result<PathBuf, String> {
let recipe_dir = &recipe.dir;
let source_dir = recipe_dir.join("source");
if recipe.build.kind == BuildKind::None {
// the build function doesn't need source dir exists
return Ok(source_dir);
}
if recipe.build.kind == BuildKind::Remote {
fetch_remote(recipe_dir, recipe, false, logger)?;
return Ok(source_dir);
match recipe.recipe.build.kind {
BuildKind::None => {
// the build function doesn't need source dir exists
fetch_apply_source_info(recipe, "".to_string())?;
return Ok(source_dir);
}
BuildKind::Remote => {
fetch_remote(recipe_dir, recipe, false, logger)?;
return Ok(source_dir);
}
_ => {}
}
match &recipe.source {
let ident = match &recipe.recipe.source {
Some(SourceRecipe::SameAs { same_as }) => {
let (canon_dir, recipe) = fetch_resolve_canon(recipe_dir, &same_as)?;
let recipe = fetch_resolve_canon(recipe_dir, &same_as, recipe.name.is_host())?;
// recursively fetch
fetch(&canon_dir, &recipe, logger)?;
fetch(&recipe, logger)?;
fetch_make_symlink(&source_dir, &same_as)?;
fetch_get_source_info(&recipe)?.source_identifier
}
Some(SourceRecipe::Path { path }) => {
if !source_dir.is_dir() || modified_dir(Path::new(&path))? > modified_dir(&source_dir)?
@ -135,6 +156,7 @@ pub fn fetch(recipe_dir: &Path, recipe: &Recipe, logger: &PtyOut) -> Result<Path
)
})?;
}
"local_source".to_string()
}
Some(SourceRecipe::Git {
git,
@ -147,7 +169,7 @@ pub fn fetch(recipe_dir: &Path, recipe: &Recipe, logger: &PtyOut) -> Result<Path
}) => {
//TODO: use libgit?
let shallow_clone = *shallow_clone == Some(true);
if !source_dir.is_dir() {
let can_skip_rebuild = if !source_dir.is_dir() {
// Create source.tmp
let source_dir_tmp = recipe_dir.join("source.tmp");
create_dir_clean(&source_dir_tmp)?;
@ -171,9 +193,10 @@ pub fn fetch(recipe_dir: &Path, recipe: &Recipe, logger: &PtyOut) -> Result<Path
// Move source.tmp to source atomically
rename(&source_dir_tmp, &source_dir)?;
false
} else {
let source_git_dir = source_dir.join(".git");
if !source_git_dir.is_dir() {
if !source_dir.join(".git").is_dir() {
return Err(format!(
"'{}' is not a git repository, but recipe indicated git source",
source_dir.display(),
@ -191,69 +214,96 @@ pub fn fetch(recipe_dir: &Path, recipe: &Recipe, logger: &PtyOut) -> Result<Path
command.arg("-C").arg(&source_dir);
command.arg("fetch").arg("origin");
run_command(command, logger)?;
}
if let Some(_upstream) = upstream {
//TODO: set upstream URL
// git remote set-url upstream "$GIT_UPSTREAM" &> /dev/null ||
// git remote add upstream "$GIT_UPSTREAM"
// git fetch upstream
}
let (head_rev, detached_rev) = get_git_head_rev(&source_dir)?;
if detached_rev {
if let Some(rev) = rev
&& let Ok(exp_rev) = get_git_tag_rev(&source_dir, &rev)
{
exp_rev == head_rev
} else {
false
}
} else {
let (_, remote_branch, remote_name, remote_url) =
get_git_remote_tracking(&source_dir)?;
// TODO: how to get default branch and compare it here?
if remote_name == "origin" && &remote_url == chop_dot_git(git) {
let fetch_rev =
get_git_fetch_rev(&source_dir, &remote_url, &remote_branch)?;
fetch_rev == head_rev
} else {
false
}
}
};
if let Some(rev) = rev {
// Check out specified revision
let mut command = Command::new("git");
command.arg("-C").arg(&source_dir);
command.arg("checkout").arg(rev);
run_command(command, logger)?;
} else if !is_redox() {
//If patches exists, we have to drop it
if patches.len() > 0 {
if !can_skip_rebuild {
if let Some(_upstream) = upstream {
//TODO: set upstream URL (is this needed?)
// git remote set-url upstream "$GIT_UPSTREAM" &> /dev/null ||
// git remote add upstream "$GIT_UPSTREAM"
// git fetch upstream
}
if let Some(rev) = rev {
// Check out specified revision
let mut command = Command::new("git");
command.arg("-C").arg(&source_dir);
command.arg("checkout").arg(rev);
run_command(command, logger)?;
} else if !is_redox() {
//If patches exists, we have to drop it
if patches.len() > 0 {
let mut command = Command::new("git");
command.arg("-C").arg(&source_dir);
command.arg("reset").arg("--hard");
run_command(command, logger)?;
}
//TODO: complicated stuff to check and reset branch to origin
//TODO: redox can't undestand this (got exit status 1)
let mut command = Command::new("bash");
command.arg("-c").arg(GIT_RESET_BRANCH);
if let Some(branch) = branch {
command.env("BRANCH", branch);
}
command.current_dir(&source_dir);
run_command(command, logger)?;
}
if !patches.is_empty() || script.is_some() {
// Hard reset
let mut command = Command::new("git");
command.arg("-C").arg(&source_dir);
command.arg("reset").arg("--hard");
run_command(command, logger)?;
}
//TODO: complicated stuff to check and reset branch to origin
//TODO: redox can't undestand this (got exit status 1)
let mut command = Command::new("bash");
command.arg("-c").arg(GIT_RESET_BRANCH);
if let Some(branch) = branch {
command.env("BRANCH", branch);
}
command.current_dir(&source_dir);
run_command(command, logger)?;
}
if !patches.is_empty() || script.is_some() {
// Hard reset
// Sync submodules URL
let mut command = Command::new("git");
command.arg("-C").arg(&source_dir);
command.arg("reset").arg("--hard");
command.arg("submodule").arg("sync").arg("--recursive");
run_command(command, logger)?;
// Update submodules
let mut command = Command::new("git");
command.arg("-C").arg(&source_dir);
command
.arg("submodule")
.arg("update")
.arg("--init")
.arg("--recursive");
if shallow_clone {
command.arg("--filter=tree:0");
}
run_command(command, logger)?;
fetch_apply_patches(recipe_dir, patches, script, &source_dir, logger)?;
}
// Sync submodules URL
let mut command = Command::new("git");
command.arg("-C").arg(&source_dir);
command.arg("submodule").arg("sync").arg("--recursive");
run_command(command, logger)?;
// Update submodules
let mut command = Command::new("git");
command.arg("-C").arg(&source_dir);
command
.arg("submodule")
.arg("update")
.arg("--init")
.arg("--recursive");
if shallow_clone {
command.arg("--filter=tree:0");
}
run_command(command, logger)?;
fetch_apply_patches(recipe_dir, patches, script, &source_dir, logger)?;
let (head_rev, _) = get_git_head_rev(&source_dir)?;
head_rev
}
Some(SourceRecipe::Tar {
tar,
@ -316,6 +366,7 @@ pub fn fetch(recipe_dir: &Path, recipe: &Recipe, logger: &PtyOut) -> Result<Path
// Move source.tmp to source atomically
rename(&source_dir_tmp, &source_dir)?;
}
blake3.clone().unwrap_or("no_tar_blake3_hash_info".into())
}
// Local Sources
None => {
@ -327,17 +378,20 @@ pub fn fetch(recipe_dir: &Path, recipe: &Recipe, logger: &PtyOut) -> Result<Path
);
create_dir(&source_dir)?;
}
"local_source".into()
}
}
};
if let BuildKind::Cargo {
package_path,
cargoflags: _,
} = &recipe.build.kind
} = &recipe.recipe.build.kind
{
fetch_cargo(&source_dir, package_path.as_ref(), logger)?;
}
fetch_apply_source_info(recipe, ident)?;
Ok(source_dir)
}
@ -367,7 +421,8 @@ pub(crate) fn fetch_make_symlink(source_dir: &PathBuf, same_as: &String) -> Resu
pub(crate) fn fetch_resolve_canon(
recipe_dir: &Path,
same_as: &String,
) -> Result<(PathBuf, Recipe), String> {
is_host: bool,
) -> Result<CookRecipe, String> {
let canon_dir = Path::new(recipe_dir).join(same_as);
if canon_dir
.to_str()
@ -382,12 +437,8 @@ pub(crate) fn fetch_resolve_canon(
if !canon_dir.exists() {
return Err(format!("'{dir}' is not exists.", dir = canon_dir.display()));
}
let recipe_path = canon_dir.join("recipe.toml");
let recipe_str = fs::read_to_string(&recipe_path)
.map_err(|e| format!("unable to read {path}: {e}", path = recipe_path.display()))?;
let recipe: Recipe = toml::from_str(&recipe_str)
.map_err(|e| format!("Unable to parse {path}: {e}", path = recipe_path.display()))?;
Ok((canon_dir, recipe))
CookRecipe::from_path(canon_dir.as_path(), true, is_host)
.map_err(|e| format!("Unable to load {dir}: {e:?}", dir = canon_dir.display()))
}
pub(crate) fn fetch_extract_tar(
@ -455,13 +506,11 @@ fn get_pubkey_url() -> String {
pub fn fetch_remote(
recipe_dir: &Path,
recipe: &Recipe,
recipe: &CookRecipe,
offline_mode: bool,
logger: &PtyOut,
) -> Result<(), String> {
// TODO: allow download to host target (waiting for build server to have them)
let target = redoxer::target();
let target_dir = create_target_dir(recipe_dir, target)?;
let target_dir = create_target_dir(recipe_dir, recipe.target)?;
let source_pubkey = target_dir.join("id_ed25519.pub.toml");
if !offline_mode {
download_wget(&get_pubkey_url(), &source_pubkey, logger)?;
@ -469,7 +518,7 @@ pub fn fetch_remote(
offline_check_exists(&source_pubkey)?;
}
let packages = recipe.get_packages_list();
let packages = recipe.recipe.get_packages_list();
let name = recipe_dir
.file_name()
@ -493,6 +542,28 @@ pub fn fetch_remote(
offline_check_exists(&source_pkgar)?;
offline_check_exists(&source_toml)?;
}
// guaranteed to exist once
if package.is_none() {
let mut file = File::open(&source_toml)
.map_err(|e| format!("Unable to open source.toml: {e:?}"))?;
let mut contents = String::new();
file.read_to_string(&mut contents)
.map_err(|e| format!("Unable to read source.toml: {e:?}"))?;
let pkg_toml = pkg::Package::from_toml(&contents)
.map_err(|e| format!("Unable to parse source.toml: {e:?}"))?;
fetch_apply_source_info_from_remote(
recipe,
&SourceIdentifier {
commit_identifier: pkg_toml.commit_identifier.clone(),
source_identifier: pkg_toml.source_identifier.clone(),
time_identifier: pkg_toml.time_identifier.clone(),
..Default::default()
},
)?;
}
}
Ok(())
@ -563,3 +634,37 @@ pub(crate) fn fetch_apply_patches(
)?;
})
}
pub(crate) fn fetch_apply_source_info(
recipe: &CookRecipe,
source_identifier: String,
) -> Result<(), String> {
let ident = crate::cook::ident::get_ident();
let info = pkg::package::SourceIdentifier {
commit_identifier: ident.commit.to_string(),
time_identifier: ident.time.to_string(),
source_identifier: source_identifier,
};
fetch_apply_source_info_from_remote(&recipe, &info)
}
pub(crate) fn fetch_apply_source_info_from_remote(
recipe: &CookRecipe,
info: &pkg::package::SourceIdentifier,
) -> Result<(), String> {
let target_dir = create_target_dir(&recipe.dir, recipe.target)?;
let source_toml_path = target_dir.join("source_info.toml");
serialize_and_write(&source_toml_path, &info)?;
Ok(())
}
pub fn fetch_get_source_info(recipe: &CookRecipe) -> Result<SourceIdentifier, String> {
let target_dir = recipe.target_dir();
let source_toml_path = target_dir.join("source_info.toml");
let toml_content = fs::read_to_string(source_toml_path)
.map_err(|e| format!("Unable to read source_info.toml: {:?}", e))?;
let parsed = toml::from_str(&toml_content)
.map_err(|e| format!("Unable to parse source_info.toml: {:?}", e))?;
Ok(parsed)
}

View File

@ -285,3 +285,186 @@ pub fn download_wget(url: &str, dest: &PathBuf, logger: &PtyOut) -> Result<(), S
}
Ok(())
}
/// get commit rev and return if it's detached or not
pub fn get_git_head_rev(dir: &PathBuf) -> Result<(String, bool), String> {
let git_head = dir.join(".git/HEAD");
let head_str = fs::read_to_string(&git_head)
.map_err(|e| format!("unable to read {path}: {e}", path = git_head.display()))?;
if head_str.starts_with("ref: ") {
let git_ref = dir.join(".git").join(head_str["ref: ".len()..].trim_end());
let ref_str = fs::read_to_string(&git_ref)
.map_err(|e| format!("unable to read {path}: {e}", path = git_ref.display()))?;
Ok((ref_str.trim().to_string(), false))
} else {
Ok((head_str.trim().to_string(), true))
}
}
pub fn get_git_tag_rev(dir: &PathBuf, tag: &str) -> Result<String, String> {
if tag.len() == 40 && tag.chars().all(|f| f.is_ascii_hexdigit()) {
return Ok(tag.to_string());
}
let git_refs = dir.join(".git/packed-refs");
let refs_str = fs::read_to_string(&git_refs)
.map_err(|e| format!("unable to read {path}: {e}", path = git_refs.display()))?;
let expected_comment_part = format!("refs/tags/{tag}");
for line in refs_str.lines() {
if line.contains(&expected_comment_part) {
let sha = line
.split_whitespace()
.next()
.ok_or_else(|| "packed-refs line is malformed.".to_string())?;
return Ok(sha.to_string());
}
}
Err(format!(
"Could not find a rev tag for {}",
expected_comment_part
))
}
/// get commit rev after fetch
pub fn get_git_fetch_rev(
dir: &PathBuf,
remote_url: &str,
remote_branch: &str,
) -> Result<String, String> {
let git_fetch_head = dir.join(".git/FETCH_HEAD");
let fetch_head_content = fs::read_to_string(&git_fetch_head).map_err(|e| {
format!(
"unable to read {path}: {e}",
path = git_fetch_head.display()
)
})?;
let expected_comment_part = format!("branch '{}' of {}", remote_branch, remote_url);
for line in fetch_head_content.lines() {
if line.contains(&expected_comment_part) && !line.contains("not-for-merge") {
let sha = line
.split_whitespace()
.next()
.ok_or_else(|| "FETCH_HEAD line is malformed.".to_string())?;
return Ok(sha.to_string());
}
}
Err(format!(
"Could not find a fetch target for tracking {}",
expected_comment_part
))
}
/// (local_branch_name, remote_branch, remote_name, remote_url)
/// -> ("fix_stuff", "master", "origin", "https://gitlab.redox-os.org/willnode/redox")
pub fn get_git_remote_tracking(dir: &PathBuf) -> Result<(String, String, String, String), String> {
let git_head = dir.join(".git/HEAD");
let git_config = dir.join(".git/config");
let head_content = fs::read_to_string(&git_head)
.map_err(|e| format!("unable to read {path}: {e}", path = git_head.display()))?;
if !head_content.starts_with("ref: ") {
let sha = head_content.trim_end().to_string();
return Ok((sha, "".to_string(), "".to_string(), "".to_string()));
}
let local_branch_path = head_content["ref: ".len()..].trim_end();
let local_branch_name = get_git_branch_name(local_branch_path)?;
let config_content = fs::read_to_string(&git_config)
.map_err(|e| format!("unable to read {path}: {e}", path = git_config.display()))?;
let branch_section = format!("[branch \"{}\"]", local_branch_name);
let mut remote_name: Option<String> = None;
let mut remote_branch: Option<String> = None;
let mut parsing_branch_section = false;
for line in config_content.lines().map(|l| l.trim()) {
if line.is_empty() {
continue;
}
if line == branch_section {
parsing_branch_section = true;
continue;
}
if parsing_branch_section {
if line.starts_with('[') {
break;
}
if line.starts_with("remote = ") {
remote_name = Some(line["remote = ".len()..].trim().to_string());
}
if line.starts_with("merge = ") {
remote_branch = Some(get_git_branch_name(line["merge = ".len()..].trim())?);
}
}
}
let remote_name_str = remote_name
.ok_or_else(|| format!("Branch '{}' is not tracking a remote.", local_branch_name))?;
let remote_branch_str = remote_branch.unwrap_or("".into());
let remote_section = format!("[remote \"{}\"]", remote_name_str);
let mut remote_url: Option<String> = None;
let mut parsing_remote_section = false;
for line in config_content.lines().map(|l| l.trim()) {
if line.is_empty() {
continue;
}
if line == remote_section {
parsing_remote_section = true;
continue;
}
if parsing_remote_section {
if line.starts_with('[') {
break;
}
if line.starts_with("url = ") {
let mut url = line["url = ".len()..].trim();
url = chop_dot_git(url);
remote_url = Some(url.to_string());
}
}
}
let remote_url_str = remote_url.ok_or_else(|| {
format!(
"Could not find URL for remote '{}' in .git/config.",
remote_name_str
)
})?;
Ok((
local_branch_name,
remote_branch_str,
remote_name_str,
remote_url_str,
))
}
pub(crate) fn chop_dot_git(url: &str) -> &str {
if url.ends_with(".git") {
return &url[..url.len() - ".git".len()];
}
url
}
fn get_git_branch_name(local_branch_path: &str) -> Result<String, String> {
// TODO: incorrectly handle branch with slashes
Ok(local_branch_path
.split('/')
.last()
.ok_or_else(|| format!("Failed to parse branch name of {:?}", local_branch_path))?
.to_string())
}

46
src/cook/ident.rs Normal file
View File

@ -0,0 +1,46 @@
use std::{
process::{Command, Stdio},
sync::OnceLock,
};
#[derive(Debug, Default)]
pub struct IdentifierConfig {
pub commit: String,
pub time: String,
}
impl IdentifierConfig {
fn new() -> Self {
let (commit, _) = crate::cook::fs::get_git_head_rev(
&std::env::current_dir().expect("unable to get $PWD"),
)
.expect("Can't read this repository commit");
// better than importing heavy deps like chrono
let time = String::from_utf8_lossy(
&Command::new("date")
.arg("-u")
.arg("+%Y-%m-%dT%H:%M:%SZ")
.stdout(Stdio::piped())
.output()
.expect("Failed to get current ISO-formatted time")
.stdout
.trim_ascii(),
)
.into();
IdentifierConfig { commit, time }
}
}
static IDENTIFIER_CONFIG: OnceLock<IdentifierConfig> = OnceLock::new();
pub fn get_ident() -> &'static IdentifierConfig {
IDENTIFIER_CONFIG
.get()
.expect("Identifier is not initialized")
}
pub fn init_ident() {
IDENTIFIER_CONFIG
.set(IdentifierConfig::new())
.expect("Identifier is initialized twice")
}

View File

@ -7,27 +7,26 @@ use pkg::{Package, PackageName};
use crate::{
blake3::hash_to_hex,
cook::{fs::*, pty::PtyOut},
cook::{fetch, fs::*, pty::PtyOut},
log_to_pty,
recipe::{BuildKind, OptionalPackageRecipe, Recipe},
recipe::{BuildKind, CookRecipe, OptionalPackageRecipe, Recipe},
};
pub fn package(
recipe: &CookRecipe,
stage_dirs: &Vec<PathBuf>,
target_dir: &Path,
name: &PackageName,
recipe: &Recipe,
auto_deps: &BTreeSet<PackageName>,
logger: &PtyOut,
) -> Result<(), String> {
if recipe.build.kind == BuildKind::None {
let name = &recipe.name;
let target_dir = &recipe.target_dir();
if recipe.recipe.build.kind == BuildKind::None {
// metapackages don't have stage dir and optional packages
package_toml(
target_dir.join("stage.toml"),
name,
recipe,
None,
recipe.package.dependencies.clone(),
recipe.recipe.package.dependencies.clone(),
&auto_deps,
)?;
return Ok(());
@ -50,7 +49,7 @@ pub fn package(
let stage_modified = modified_all(stage_dirs, modified_dir)?;
let packages = recipe.get_packages_list();
let packages = recipe.recipe.get_packages_list();
for package in packages {
let (stage_dir, package_file, package_meta) = package_stage_paths(package, target_dir);
@ -96,11 +95,10 @@ pub fn package(
}
})
.collect(),
None => recipe.package.dependencies.clone(),
None => recipe.recipe.package.dependencies.clone(),
};
package_toml(
package_meta,
&name,
recipe,
Some((Path::new(public_path), &package_file)),
package_deps,
@ -114,8 +112,7 @@ pub fn package(
pub fn package_toml(
toml_path: PathBuf,
name: &PackageName,
recipe: &Recipe,
recipe: &CookRecipe,
package_file: Option<(&Path, &PathBuf)>,
mut package_deps: Vec<PackageName>,
auto_deps: &BTreeSet<PackageName>,
@ -148,15 +145,21 @@ pub fn package_toml(
("".into(), 0)
};
let ident_source = fetch::fetch_get_source_info(recipe)?;
let package = Package {
name: name.without_host(),
version: package_version(recipe),
target: package_target(name).to_string(),
name: recipe.name.without_host(),
version: package_version(&recipe.recipe),
target: recipe.target.to_string(),
blake3: hash,
// this size will be different once pkgar supports compression
network_size: size,
storage_size: size,
depends: package_deps,
commit_identifier: ident_source.commit_identifier,
source_identifier: ident_source.source_identifier,
time_identifier: ident_source.time_identifier,
..Default::default()
};
serialize_and_write(&toml_path, &package)?;