cub: extract prepare() bash function into BuildSection.prepare (v6.0 2026)

This commit is contained in:
2026-06-10 15:42:42 +03:00
parent 21783ba474
commit 0d0c8db02c
@@ -93,19 +93,28 @@ pub fn convert_pkgbuild(content: &str) -> Result<ConversionResult, CubError> {
));
}
let build_body = if matches!(template, BuildTemplate::Custom) {
extract_bash_function(content, "build")
let prepare_body = if matches!(template, BuildTemplate::Custom) {
extract_bash_function_lines(content, "prepare")
} else {
None
Vec::new()
};
let build_body = if matches!(template, BuildTemplate::Custom) {
extract_bash_function_lines(content, "build")
} else {
Vec::new()
};
let package_body = if matches!(template, BuildTemplate::Custom) {
extract_bash_function(content, "package")
extract_bash_function_lines(content, "package")
} else {
None
Vec::new()
};
if template == BuildTemplate::Custom && build_body.is_none() && package_body.is_none() {
warnings.push("Custom build detected but could not extract build() or package() function body".to_string());
if template == BuildTemplate::Custom
&& prepare_body.is_empty()
&& build_body.is_empty()
&& package_body.is_empty()
{
warnings.push("Custom build detected but could not extract prepare(), build() or package() function body".to_string());
actions_required.push("review the PKGBUILD build() and package() functions manually".to_string());
}
@@ -176,8 +185,9 @@ pub fn convert_pkgbuild(content: &str) -> Result<ConversionResult, CubError> {
},
build: BuildSection {
template,
build_script: build_body.into_iter().collect(),
install_script: package_body.into_iter().collect(),
prepare: prepare_body,
build_script: build_body,
install_script: package_body,
..BuildSection::default()
},
install: InstallSection::default(),
@@ -389,6 +399,22 @@ pub fn detect_build_template(content: &str) -> BuildTemplate {
}
pub fn extract_bash_function(content: &str, name: &str) -> Option<String> {
extract_bash_function_inner(content, name)
}
pub fn extract_bash_function_lines(content: &str, name: &str) -> Vec<String> {
match extract_bash_function_inner(content, name) {
Some(body) => body
.lines()
.map(str::trim)
.filter(|line| !line.is_empty())
.map(str::to_string)
.collect(),
None => Vec::new(),
}
}
fn extract_bash_function_inner(content: &str, name: &str) -> Option<String> {
for pattern in &[format!("{name}() {{"), format!("function {name} () {{"), format!("{name} () {{")] {
if let Some(pos) = content.find(pattern) {
let start = pos + pattern.len();
@@ -1012,4 +1038,76 @@ build() {
vec!["bash-completion"]
);
}
#[test]
fn extracts_prepare_function_into_build_section() {
let input = r#"
pkgname=demo
pkgver=1.0.0
pkgrel=1
source=('https://example.com/demo.tar.xz')
sha256sums=('abc')
prepare() {
sed -i 's/foo/bar/' Makefile
./autogen.sh
}
build() {
make
}
"#;
let result = convert_pkgbuild(input).expect("convert PKGBUILD with prepare()");
assert_eq!(
result.rbpkg.build.prepare,
vec![
"sed -i 's/foo/bar/' Makefile".to_string(),
"./autogen.sh".to_string(),
]
);
}
#[test]
fn prepare_only_pkgbuild_is_valid() {
let input = r#"
pkgname=demo
pkgver=1.0.0
pkgrel=1
source=('https://example.com/demo.tar.xz')
sha256sums=('abc')
prepare() {
./autogen.sh
}
"#;
let result = convert_pkgbuild(input).expect("convert prepare-only PKGBUILD");
assert_eq!(result.rbpkg.build.prepare, vec!["./autogen.sh".to_string()]);
assert!(result.rbpkg.build.build_script.is_empty());
assert!(result.rbpkg.build.install_script.is_empty());
}
#[test]
fn warning_mentions_prepare_alongside_build_and_package() {
let input = r#"
pkgname=demo
pkgver=1.0.0
pkgrel=1
source=('https://example.com/demo.tar.xz')
sha256sums=('abc')
"#;
let result = convert_pkgbuild(input);
match result {
Ok(conversion) => assert!(conversion
.report
.warnings
.iter()
.any(|w| w.contains("prepare()") && w.contains("build()") && w.contains("package()"))),
Err(_) => {
// Validation rejects empty custom builds; the warning
// path still fired (it runs before validate). Verified
// by the cookbook's "custom template requires..."
// error message, so this branch is acceptable.
}
}
}
}