use crate::analyze::AnalyzedError;
use crate::buildsystem::{BuildSystem, DependencyCategory, Error};
use crate::dependencies::vague::VagueDependency;
use crate::dependency::Dependency;
use crate::dist_catcher::DistCatcher;
use crate::fix_build::BuildFixer;
use crate::installer::Error as InstallerError;
use crate::session::Session;
use std::path::{Path, PathBuf};
#[derive(Debug)]
pub struct Meson {
#[allow(dead_code)]
path: PathBuf,
}
#[derive(Debug, serde::Deserialize)]
#[allow(dead_code)]
struct MesonDependency {
pub name: String,
#[serde(deserialize_with = "version_as_vec")]
pub version: Vec<String>,
#[serde(default)]
pub required: bool,
#[serde(default)]
pub has_fallback: bool,
#[serde(default)]
pub conditional: bool,
}
fn version_as_vec<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::Deserialize;
#[derive(Deserialize)]
#[serde(untagged)]
enum StringOrVec {
String(String),
Vec(Vec<String>),
}
match StringOrVec::deserialize(deserializer)? {
StringOrVec::String(s) => Ok(if s.is_empty() { vec![] } else { vec![s] }),
StringOrVec::Vec(v) => Ok(v),
}
}
#[derive(Debug, serde::Deserialize)]
#[allow(dead_code)]
struct MesonTarget {
r#type: String,
installed: bool,
filename: Vec<PathBuf>,
}
impl Meson {
pub fn new(path: &Path) -> Self {
Self {
path: path.to_path_buf(),
}
}
fn setup(&self, session: &dyn Session) -> Result<(), Error> {
let project_dir = self
.path
.parent()
.expect("meson.build should have a parent directory");
let build_dir = project_dir.join("build");
if !session.exists(&build_dir) {
session.mkdir(&build_dir).unwrap();
}
session
.command(vec!["meson", "setup", "build"])
.cwd(project_dir)
.quiet(true)
.run_detecting_problems()?;
Ok(())
}
fn introspect<T: for<'a> serde::Deserialize<'a>>(
&self,
session: &dyn Session,
fixers: Option<&[&dyn BuildFixer<InstallerError>]>,
args: &[&str],
) -> Result<T, InstallerError> {
let project_dir = self
.path
.parent()
.expect("meson.build should have a parent directory");
let build_dir = project_dir.join("build");
let use_build_dir =
session.exists(&build_dir) && session.exists(&build_dir.join("build.ninja"));
let ret = if use_build_dir {
let build_dir_str = build_dir.to_string_lossy();
let introspect_args = [&["meson", "introspect", &build_dir_str], args].concat();
if let Some(fixers) = fixers {
session
.command(introspect_args)
.cwd(project_dir)
.quiet(true)
.run_fixing_problems::<_, Error>(fixers)
.unwrap()
} else {
session
.command(introspect_args)
.cwd(project_dir)
.quiet(true)
.run_detecting_problems()?
}
} else {
self.setup_temp_build_for_introspect(session, fixers, args)?
};
let text = ret.concat();
Ok(serde_json::from_str(&text).unwrap())
}
fn setup_temp_build_for_introspect(
&self,
session: &dyn Session,
fixers: Option<&[&dyn BuildFixer<InstallerError>]>,
args: &[&str],
) -> Result<Vec<String>, InstallerError> {
let project_dir = self
.path
.parent()
.expect("meson.build should have a parent directory");
let temp_build_dir = project_dir.join(".ognibuild-temp-build");
if session.exists(&temp_build_dir) {
session.rmtree(&temp_build_dir).ok();
}
session.mkdir(&temp_build_dir).map_err(|e| {
InstallerError::Other(format!("Failed to create temp build dir: {}", e))
})?;
let temp_build_str = temp_build_dir.to_string_lossy();
let setup_result = session
.command(vec!["meson", "setup", &temp_build_str])
.cwd(project_dir)
.quiet(true)
.run_detecting_problems();
match setup_result {
Ok(_) => {
let temp_build_str = temp_build_dir.to_string_lossy();
let introspect_args = [&["meson", "introspect", &temp_build_str], args].concat();
let result = if let Some(fixers) = fixers {
session
.command(introspect_args)
.cwd(project_dir)
.quiet(true)
.run_fixing_problems::<_, Error>(fixers)
.unwrap()
} else {
session
.command(introspect_args)
.cwd(project_dir)
.quiet(true)
.run_detecting_problems()?
};
session.rmtree(&temp_build_dir).ok();
Ok(result)
}
Err(e) => {
session.rmtree(&temp_build_dir).ok();
Err(e.into())
}
}
}
pub fn probe(path: &Path) -> Option<Box<dyn BuildSystem>> {
let path = path.join("meson.build");
if path.exists() {
log::debug!("Found meson.build, assuming meson package.");
Some(Box::new(Self::new(&path)))
} else {
None
}
}
}
impl BuildSystem for Meson {
fn name(&self) -> &str {
"meson"
}
fn dist(
&self,
session: &dyn Session,
_installer: &dyn crate::installer::Installer,
target_directory: &Path,
_quiet: bool,
) -> Result<std::ffi::OsString, Error> {
self.setup(session)?;
let project_dir = self
.path
.parent()
.expect("meson.build should have a parent directory");
let dc = DistCatcher::new(vec![
session.external_path(&project_dir.join("build/meson-dist"))
]);
match session
.command(vec!["ninja", "-C", "build", "dist"])
.cwd(project_dir)
.quiet(true)
.run_detecting_problems()
{
Ok(_) => {}
Err(AnalyzedError::Unidentified { lines, .. })
if lines.contains(
&"ninja: error: unknown target 'dist', did you mean 'dino'?".to_string(),
) =>
{
unimplemented!();
}
Err(e) => return Err(e.into()),
}
Ok(dc.copy_single(target_directory).unwrap().unwrap())
}
fn test(
&self,
session: &dyn Session,
_installer: &dyn crate::installer::Installer,
) -> Result<(), Error> {
self.setup(session)?;
let project_dir = self
.path
.parent()
.expect("meson.build should have a parent directory");
session
.command(vec!["ninja", "-C", "build", "test"])
.cwd(project_dir)
.quiet(true)
.run_detecting_problems()?;
Ok(())
}
fn build(
&self,
session: &dyn Session,
_installer: &dyn crate::installer::Installer,
) -> Result<(), Error> {
self.setup(session)?;
let project_dir = self
.path
.parent()
.expect("meson.build should have a parent directory");
session
.command(vec!["ninja", "-C", "build"])
.cwd(project_dir)
.quiet(true)
.run_detecting_problems()?;
Ok(())
}
fn clean(
&self,
session: &dyn Session,
_installer: &dyn crate::installer::Installer,
) -> Result<(), Error> {
self.setup(session)?;
let project_dir = self
.path
.parent()
.expect("meson.build should have a parent directory");
session
.command(vec!["ninja", "-C", "build", "clean"])
.cwd(project_dir)
.quiet(true)
.run_detecting_problems()?;
Ok(())
}
fn install(
&self,
session: &dyn Session,
_installer: &dyn crate::installer::Installer,
_install_target: &crate::buildsystem::InstallTarget,
) -> Result<(), Error> {
self.setup(session)?;
let project_dir = self
.path
.parent()
.expect("meson.build should have a parent directory");
session
.command(vec!["ninja", "-C", "build", "install"])
.cwd(project_dir)
.quiet(true)
.run_detecting_problems()?;
Ok(())
}
fn get_declared_dependencies(
&self,
session: &dyn Session,
fixers: Option<&[&dyn crate::fix_build::BuildFixer<crate::installer::Error>]>,
) -> Result<Vec<(crate::buildsystem::DependencyCategory, Box<dyn Dependency>)>, Error> {
let mut ret: Vec<(DependencyCategory, Box<dyn Dependency>)> = Vec::new();
let project_dir = self
.path
.parent()
.expect("meson.build should have a parent directory");
let meson_file_str = self.path.to_string_lossy();
let scan_args = vec![
"meson",
"introspect",
"--scan-dependencies",
&meson_file_str,
];
let output = if let Some(fixers) = fixers {
session
.command(scan_args)
.cwd(project_dir)
.quiet(true)
.run_fixing_problems::<_, Error>(fixers)
.map_err(|e| {
InstallerError::Other(format!("Failed to run scan-dependencies: {:?}", e))
})?
} else {
session
.command(scan_args)
.cwd(project_dir)
.quiet(true)
.run_detecting_problems()
.map_err(|e| {
InstallerError::Other(format!("Failed to run scan-dependencies: {:?}", e))
})?
};
let text = output.concat();
let resp: Vec<MesonDependency> = serde_json::from_str(&text).map_err(|e| {
InstallerError::Other(format!("Failed to parse scan-dependencies JSON: {}", e))
})?;
for entry in resp {
let mut minimum_version = None;
if entry.version.len() == 1 {
if let Some(rest) = entry.version[0].strip_prefix(">=") {
minimum_version = Some(rest.trim().to_string());
}
} else if entry.version.len() > 1 {
log::warn!("Unable to parse version constraints: {:?}", entry.version);
}
ret.push((
DependencyCategory::Universal,
Box::new(VagueDependency {
name: entry.name.to_string(),
minimum_version,
}),
));
}
Ok(ret)
}
fn get_declared_outputs(
&self,
session: &dyn Session,
fixers: Option<&[&dyn crate::fix_build::BuildFixer<crate::installer::Error>]>,
) -> Result<Vec<Box<dyn crate::output::Output>>, Error> {
let mut ret: Vec<Box<dyn crate::output::Output>> = Vec::new();
let resp = self.introspect::<Vec<MesonTarget>>(session, fixers, &["--targets"])?;
for entry in resp {
if !entry.installed {
continue;
}
if entry.r#type == "executable" {
for p in entry.filename {
ret.push(Box::new(crate::output::BinaryOutput::new(
p.file_name().unwrap().to_str().unwrap(),
)));
}
}
}
Ok(ret)
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::buildsystem::detect_buildsystems;
use crate::installer::NullInstaller;
use crate::session::plain::PlainSession;
use std::fs;
use tempfile::TempDir;
fn create_meson_project(dir: &Path) -> std::io::Result<()> {
fs::write(
dir.join("meson.build"),
r#"project('test-project', 'c',
version : '1.0.0',
license : 'MIT',
default_options : ['warning_level=2'])
# A simple dependency for testing
glib_dep = dependency('glib-2.0', required: false)
# Define a simple executable
executable('test-app',
'main.c',
dependencies : glib_dep,
install : true)
"#,
)?;
fs::write(
dir.join("main.c"),
r#"#include <stdio.h>
int main(int argc, char *argv[]) {
printf("Hello from Meson test!\n");
return 0;
}
"#,
)?;
Ok(())
}
#[test]
fn test_meson_detection() {
let temp_dir = TempDir::new().unwrap();
let project_dir = temp_dir.path();
let buildsystems = detect_buildsystems(project_dir);
assert!(
!buildsystems.iter().any(|bs| bs.name() == "meson"),
"Should not detect Meson without meson.build"
);
create_meson_project(project_dir).unwrap();
let buildsystems = detect_buildsystems(project_dir);
assert!(
buildsystems.iter().any(|bs| bs.name() == "meson"),
"Should detect Meson with meson.build"
);
if !buildsystems.is_empty() {
let first = &buildsystems[0];
assert_eq!(
first.name(),
"meson",
"Meson should be the primary build system"
);
}
}
#[test]
fn test_meson_introspect_with_different_cwd() {
let temp_dir = TempDir::new().unwrap();
let project_dir = temp_dir.path().join("my-project");
fs::create_dir(&project_dir).unwrap();
create_meson_project(&project_dir).unwrap();
let mut session = PlainSession::new();
session.chdir(temp_dir.path()).unwrap();
let buildsystems = detect_buildsystems(&project_dir);
assert!(!buildsystems.is_empty(), "Should detect Meson buildsystem");
let meson = buildsystems
.iter()
.find(|bs| bs.name() == "meson")
.expect("Should find Meson buildsystem");
match meson.get_declared_dependencies(&session, None) {
Ok(deps) => {
let _has_glib = deps
.iter()
.any(|(_, dep)| dep.family() == "glib" || dep.family() == "pkg-config");
log::debug!("Found {} dependencies", deps.len());
if !deps.is_empty() {
log::debug!("Dependencies: {:?}", deps);
}
}
Err(e) => {
let error_str = format!("{:?}", e);
assert!(
!error_str.contains("Missing Meson file"),
"Should not fail with 'Missing Meson file' error: {}",
error_str
);
assert!(
!error_str.contains("./meson.build"),
"Should not reference relative path './meson.build': {}",
error_str
);
}
}
}
#[test]
fn test_meson_with_nested_project_structure() {
let temp_dir = TempDir::new().unwrap();
let workspace_dir = temp_dir.path().join("workspace");
let project_dir = workspace_dir.join("subdir").join("project");
fs::create_dir_all(&project_dir).unwrap();
create_meson_project(&project_dir).unwrap();
let mut session = PlainSession::new();
session.chdir(&workspace_dir).unwrap();
let buildsystems = detect_buildsystems(&project_dir);
let meson = buildsystems
.iter()
.find(|bs| bs.name() == "meson")
.expect("Should find Meson buildsystem");
match meson.get_declared_dependencies(&session, None) {
Ok(_) => {
}
Err(e) => {
let error_str = format!("{:?}", e);
assert!(
!error_str.contains("Missing Meson file")
&& !error_str.contains("./meson.build"),
"Path handling error: {}",
error_str
);
}
}
}
#[test]
#[ignore] fn test_meson_build_operations() {
let temp_dir = TempDir::new().unwrap();
let project_dir = temp_dir.path().join("build-test");
fs::create_dir(&project_dir).unwrap();
create_meson_project(&project_dir).unwrap();
let mut session = PlainSession::new();
session.chdir(temp_dir.path()).unwrap();
let buildsystems = detect_buildsystems(&project_dir);
let meson = buildsystems
.iter()
.find(|bs| bs.name() == "meson")
.expect("Should find Meson buildsystem");
let installer = NullInstaller;
match meson.build(&session, &installer) {
Ok(_) => log::debug!("Build succeeded"),
Err(e) => {
let error_str = format!("{:?}", e);
assert!(
!error_str.contains("./meson.build"),
"Should not have path errors: {}",
error_str
);
}
}
match meson.clean(&session, &installer) {
Ok(_) => log::debug!("Clean succeeded"),
Err(e) => {
let error_str = format!("{:?}", e);
assert!(
!error_str.contains("./meson.build"),
"Should not have path errors: {}",
error_str
);
}
}
}
#[test]
fn test_meson_project_with_subprojects() {
let temp_dir = TempDir::new().unwrap();
let project_dir = temp_dir.path();
fs::write(
project_dir.join("meson.build"),
r#"project('main-project', 'c',
version : '2.0.0',
license : 'GPL-3.0')
# Subproject (would normally be in subprojects/ dir)
# This tests that we handle the main meson.build correctly
executable('main-app', 'main.c')
"#,
)
.unwrap();
fs::write(project_dir.join("main.c"), r#"int main() { return 0; }"#).unwrap();
let buildsystems = detect_buildsystems(project_dir);
assert!(
buildsystems.iter().any(|bs| bs.name() == "meson"),
"Should detect Meson for project with subprojects structure"
);
}
#[test]
fn test_meson_handles_symlinks() {
#[cfg(unix)]
{
use std::os::unix::fs::symlink;
let temp_dir = TempDir::new().unwrap();
let real_project = temp_dir.path().join("real-project");
let link_project = temp_dir.path().join("link-project");
fs::create_dir(&real_project).unwrap();
create_meson_project(&real_project).unwrap();
symlink(&real_project, &link_project).unwrap();
let buildsystems = detect_buildsystems(&link_project);
assert!(
buildsystems.iter().any(|bs| bs.name() == "meson"),
"Should detect Meson through symlink"
);
let mut session = PlainSession::new();
session.chdir(temp_dir.path()).unwrap();
let meson = buildsystems
.iter()
.find(|bs| bs.name() == "meson")
.expect("Should find Meson buildsystem");
match meson.get_declared_dependencies(&session, None) {
Ok(_) => {
}
Err(e) => {
let error_str = format!("{:?}", e);
assert!(
!error_str.contains("Missing Meson file"),
"Should handle symlinks correctly: {}",
error_str
);
}
}
}
}
#[test]
fn test_meson_introspect_unconfigured_project() {
let temp_dir = TempDir::new().unwrap();
let project_dir = temp_dir.path().join("unconfigured-project");
fs::create_dir(&project_dir).unwrap();
create_meson_project(&project_dir).unwrap();
let mut session = PlainSession::new();
session.chdir(temp_dir.path()).unwrap();
let buildsystems = detect_buildsystems(&project_dir);
let meson = buildsystems
.iter()
.find(|bs| bs.name() == "meson")
.expect("Should find Meson buildsystem");
match meson.get_declared_dependencies(&session, None) {
Ok(deps) => {
log::debug!(
"Successfully introspected unconfigured project with {} dependencies",
deps.len()
);
}
Err(e) => {
let error_str = format!("{:?}", e);
assert!(
!error_str.contains("Current directory is not a meson build directory"),
"Should not fail with build directory error for unconfigured projects: {}",
error_str
);
assert!(
!error_str.contains("Please specify a valid build dir"),
"Should not fail asking for build dir when introspecting source: {}",
error_str
);
log::debug!(
"Got acceptable error (likely meson not installed): {}",
error_str
);
}
}
}
#[test]
fn test_meson_introspect_scan_dependencies() {
let temp_dir = TempDir::new().unwrap();
let project_dir = temp_dir.path().join("scan-deps-test");
fs::create_dir(&project_dir).unwrap();
fs::write(
project_dir.join("meson.build"),
r#"project('scan-deps-test', 'c',
version : '1.0.0',
license : 'MIT')
# Test various types of dependencies
glib_dep = dependency('glib-2.0', required: false)
threads_dep = dependency('threads', required: false)
math_dep = declare_dependency(
dependencies: meson.get_compiler('c').find_library('m', required: false)
)
executable('test-app',
'main.c',
dependencies : [glib_dep, threads_dep, math_dep])
"#,
)
.unwrap();
fs::write(
project_dir.join("main.c"),
r#"#include <stdio.h>
#include <math.h>
int main() { printf("Result: %f\n", sqrt(16.0)); return 0; }"#,
)
.unwrap();
let mut session = PlainSession::new();
session.chdir(temp_dir.path()).unwrap();
let buildsystems = detect_buildsystems(&project_dir);
let meson = buildsystems
.iter()
.find(|bs| bs.name() == "meson")
.expect("Should find Meson buildsystem");
match meson.get_declared_dependencies(&session, None) {
Ok(deps) => {
log::debug!("Found {} dependencies", deps.len());
for (category, dep) in &deps {
let min_ver =
if let Some(vague_dep) = dep.as_any().downcast_ref::<VagueDependency>() {
vague_dep.minimum_version.as_deref().unwrap_or("any")
} else {
"any"
};
log::debug!(" {:?}: {} ({})", category, dep.family(), min_ver);
}
}
Err(e) => {
let error_str = format!("{:?}", e);
assert!(
!error_str.contains("No command specified"),
"Should not fail with 'No command specified' after fixing scan-dependencies usage: {}",
error_str
);
log::debug!("Got acceptable error: {}", error_str);
}
}
}
#[test]
fn test_meson_introspect_targets() {
let temp_dir = TempDir::new().unwrap();
let project_dir = temp_dir.path().join("targets-test");
fs::create_dir(&project_dir).unwrap();
fs::write(
project_dir.join("meson.build"),
r#"project('targets-test', 'c',
version : '1.0.0')
# Test various target types
executable('main-app',
'main.c',
install : true)
executable('test-util',
'test.c',
install : false)
static_library('helper',
'helper.c',
install : false)
"#,
)
.unwrap();
fs::write(project_dir.join("main.c"), "int main() { return 0; }").unwrap();
fs::write(project_dir.join("test.c"), "int main() { return 1; }").unwrap();
fs::write(project_dir.join("helper.c"), "void helper() {}").unwrap();
let mut session = PlainSession::new();
session.chdir(temp_dir.path()).unwrap();
let buildsystems = detect_buildsystems(&project_dir);
let meson = buildsystems
.iter()
.find(|bs| bs.name() == "meson")
.expect("Should find Meson buildsystem");
match meson.get_declared_outputs(&session, None) {
Ok(outputs) => {
log::debug!("Found {} outputs", outputs.len());
for output in &outputs {
log::debug!(" Output: {:?}", output);
}
}
Err(e) => {
let error_str = format!("{:?}", e);
assert!(
!error_str.contains("Missing Meson file")
&& !error_str.contains("./meson.build")
&& !error_str.contains("No command specified"),
"Should not have known path/command errors: {}",
error_str
);
log::debug!("Got acceptable error: {}", error_str);
}
}
}
#[test]
fn test_meson_introspect_complex_project() {
let temp_dir = TempDir::new().unwrap();
let project_dir = temp_dir.path().join("complex-test");
fs::create_dir_all(project_dir.join("src")).unwrap();
fs::create_dir_all(project_dir.join("include")).unwrap();
fs::write(
project_dir.join("meson.build"),
r#"project('complex-test', 'c',
version : '2.1.0',
license : 'GPL-2.0',
default_options : [
'warning_level=3',
'werror=true'
])
# Include directories
inc = include_directories('include')
# Dependencies with version constraints
json_dep = dependency('json-c', version: '>=0.13', required: false)
curl_dep = dependency('libcurl', version: '>=7.60', required: false)
zlib_dep = dependency('zlib', required: false)
# Subdirectory
subdir('src')
# Main executable
executable('complex-app',
sources: ['main.c', src_files],
include_directories: inc,
dependencies: [json_dep, curl_dep, zlib_dep],
install: true)
"#,
)
.unwrap();
fs::write(
project_dir.join("src/meson.build"),
"src_files = files('utils.c', 'parser.c')",
)
.unwrap();
fs::write(
project_dir.join("main.c"),
"#include <stdio.h>\nint main() { printf(\"Complex app\\n\"); return 0; }",
)
.unwrap();
fs::write(project_dir.join("src/utils.c"), "void utils_init() {}").unwrap();
fs::write(project_dir.join("src/parser.c"), "void parse() {}").unwrap();
fs::write(
project_dir.join("include/common.h"),
"#pragma once\nvoid utils_init();",
)
.unwrap();
let mut session = PlainSession::new();
session.chdir(temp_dir.path()).unwrap();
let buildsystems = detect_buildsystems(&project_dir);
let meson = buildsystems
.iter()
.find(|bs| bs.name() == "meson")
.expect("Should find Meson buildsystem");
match meson.get_declared_dependencies(&session, None) {
Ok(deps) => {
log::debug!("Complex project dependencies: {} found", deps.len());
let versioned_deps: Vec<_> = deps
.iter()
.filter(|(_, dep)| {
if let Some(vague_dep) = dep.as_any().downcast_ref::<VagueDependency>() {
vague_dep.minimum_version.is_some()
} else {
false
}
})
.collect();
if !versioned_deps.is_empty() {
log::debug!("Dependencies with version constraints:");
for (cat, dep) in versioned_deps {
let min_ver = if let Some(vague_dep) =
dep.as_any().downcast_ref::<VagueDependency>()
{
vague_dep.minimum_version.as_deref().unwrap_or("any")
} else {
"any"
};
log::debug!(" {:?}: {} >= {}", cat, dep.family(), min_ver);
}
}
}
Err(e) => {
let error_str = format!("{:?}", e);
assert!(
!error_str.contains("No command specified")
&& !error_str.contains("Missing Meson file")
&& !error_str.contains("./meson.build"),
"Should not have known errors on complex projects: {}",
error_str
);
log::debug!("Got acceptable error on complex project: {}", error_str);
}
}
match meson.get_declared_outputs(&session, None) {
Ok(outputs) => {
log::debug!("Complex project outputs: {} found", outputs.len());
}
Err(e) => {
let error_str = format!("{:?}", e);
assert!(
!error_str.contains("No command specified"),
"Should not have command errors: {}",
error_str
);
}
}
}
#[test]
fn test_meson_setup_command_with_source_and_build_dirs() {
let temp_dir = TempDir::new().unwrap();
let project_dir = temp_dir.path().join("setup-test");
fs::create_dir(&project_dir).unwrap();
create_meson_project(&project_dir).unwrap();
let mut session = PlainSession::new();
session.chdir(temp_dir.path()).unwrap();
let meson = Meson::new(&project_dir.join("meson.build"));
let result = meson.setup_temp_build_for_introspect(&session, None, &["--projectinfo"]);
match result {
Ok(_) => {
log::debug!("Setup with source and build dirs succeeded");
let temp_build = project_dir.join(".ognibuild-temp-build");
assert!(
!session.exists(&temp_build),
"Temporary build directory should be cleaned up"
);
}
Err(e) => {
let error_str = format!("{:?}", e);
assert!(
!error_str.contains("No command specified"),
"Setup should not fail with 'No command specified': {}",
error_str
);
log::debug!("Got acceptable setup error: {}", error_str);
}
}
}
#[test]
fn test_meson_commands_work_from_different_cwd_regression() {
let temp_dir = TempDir::new().unwrap();
let workspace = temp_dir.path().join("workspace");
let other_dir = temp_dir.path().join("other_directory");
let project_dir = workspace.join("projects").join("my-meson-project");
fs::create_dir_all(&workspace).unwrap();
fs::create_dir_all(&other_dir).unwrap();
fs::create_dir_all(&project_dir).unwrap();
create_meson_project(&project_dir).unwrap();
let mut session = PlainSession::new();
session.chdir(&other_dir).unwrap();
assert_ne!(session.pwd(), project_dir);
assert_ne!(session.pwd().parent(), Some(project_dir.as_path()));
let buildsystems = detect_buildsystems(&project_dir);
let meson = buildsystems
.iter()
.find(|bs| bs.name() == "meson")
.expect("Should find Meson buildsystem");
match meson.get_declared_dependencies(&session, None) {
Ok(deps) => {
let has_glib = deps
.iter()
.any(|(_, dep)| dep.family() == "glib-2.0" || dep.family().contains("glib"));
assert!(!deps.is_empty() || has_glib, "Should find dependencies");
}
Err(e) => {
let error_str = format!("{:?}", e);
assert!(
!error_str.contains("Missing Meson file")
&& !error_str.contains("nor build directory")
&& !error_str.contains("contain a meson.build"),
"BUG REPRODUCED: Meson command failed due to working directory issue: {}",
error_str
);
}
}
match meson.get_declared_outputs(&session, None) {
Ok(_outputs) => {
}
Err(e) => {
let error_str = format!("{:?}", e);
assert!(
!error_str.contains("Missing Meson file")
&& !error_str.contains("nor build directory")
&& !error_str.contains("contain a meson.build"),
"BUG REPRODUCED: Meson introspect failed due to working directory issue: {}",
error_str
);
}
}
let installer = NullInstaller;
match meson.build(&session, &installer) {
Ok(_) => {
}
Err(e) => {
let error_str = format!("{:?}", e);
assert!(
!error_str.contains("source directory") && !error_str.contains("meson.build"),
"BUG REPRODUCED: Meson setup failed due to working directory issue: {}",
error_str
);
}
}
let actual_pwd = session
.pwd()
.canonicalize()
.unwrap_or_else(|_| session.pwd().to_path_buf());
let expected_pwd = other_dir
.canonicalize()
.unwrap_or_else(|_| other_dir.clone());
assert_eq!(
actual_pwd, expected_pwd,
"Session cwd should not have changed"
);
}
#[test]
fn test_meson_error_handling_robustness() {
let temp_dir = TempDir::new().unwrap();
let project_dir = temp_dir.path().join("error-test");
fs::create_dir(&project_dir).unwrap();
fs::write(
project_dir.join("meson.build"),
r#"project('error-test', 'c', version : '1.0.0')
# Dependency that might not exist
missing_dep = dependency('this-probably-does-not-exist-anywhere',
required: false)
# Still define something so meson doesn't completely fail
executable('test', 'main.c', dependencies: missing_dep)
"#,
)
.unwrap();
fs::write(project_dir.join("main.c"), "int main() { return 0; }").unwrap();
let mut session = PlainSession::new();
session.chdir(temp_dir.path()).unwrap();
let buildsystems = detect_buildsystems(&project_dir);
let meson = buildsystems
.iter()
.find(|bs| bs.name() == "meson")
.expect("Should find Meson buildsystem");
match meson.get_declared_dependencies(&session, None) {
Ok(deps) => {
log::debug!(
"Handled potentially problematic project: {} deps",
deps.len()
);
let missing_deps: Vec<_> = deps
.iter()
.filter(|(_, dep)| dep.family().contains("this-probably-does-not-exist"))
.collect();
if !missing_deps.is_empty() {
log::debug!(
"Found declared but unavailable dependency: {:?}",
missing_deps[0].1.family()
);
}
}
Err(e) => {
let error_str = format!("{:?}", e);
assert!(
!error_str.contains("No command specified"),
"Should not get 'No command specified' after fixing scan-dependencies: {}",
error_str
);
log::debug!("Handled error case appropriately: {}", error_str);
}
}
}
}