/* * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see * . * * Copyright (C) 2023 Red Hat, Inc. */ mod common; use std::collections::HashSet; use std::fs::read_dir; use std::fs::read_to_string; use std::path::PathBuf; use pkg_config::get_variable; use regex::Regex; use serde::{Deserialize, Serialize}; use serde_xml_rs::from_str; /* Until we have 100% API coverage, we gradually enforce * coverage for certain areas of code, unless the feature * flag 'api-coverage' is set */ #[cfg(not(feature = "api_coverage"))] const ENFORCED_FUNC_PREFIXES: &[&str] = &["virSecret", "virStoragePool", "virStorageVol"]; #[cfg(not(feature = "api_coverage"))] const ENFORCED_MACRO_PREFIXES: &[&str] = &[]; #[cfg(not(feature = "api_coverage"))] const ENFORCED_ENUM_PREFIXES: &[&str] = &["VIR_FROM", "VIR_ERR"]; const IGNORE_FUNCS: &[&str] = &[ /* Not thread safe, so not to be exposed */ "virConnCopyLastError", "virConnGetLastError", "virConnResetLastError", "virConnSetErrorFunc", /* Only needed at C level */ "virCopyLastError", "virFreeError", "virGetLastErrorMessage", "virGetLastErrorCode", "virGetLastErrorDomain", "virResetLastError", "virSaveLastError", "virDefaultErrorFunc", /* No direct exposure of virTypesParams at Rust level */ "virTypedParamsAddBoolean", "virTypedParamsAddDouble", "virTypedParamsAddFromString", "virTypedParamsAddInt", "virTypedParamsAddLLong", "virTypedParamsAddString", "virTypedParamsAddStringList", "virTypedParamsAddUInt", "virTypedParamsAddULLong", "virTypedParamsClear", "virTypedParamsFree", "virTypedParamsGet", "virTypedParamsGetBoolean", "virTypedParamsGetDouble", "virTypedParamsGetInt", "virTypedParamsGetLLong", "virTypedParamsGetString", "virTypedParamsGetUInt", "virTypedParamsGetULLong", /* Deprecated in favour of virDomainCreateXML */ "virDomainCreateLinux", ]; const IGNORE_MACROS: &[&str] = &[ /* Can't be used as they contain a C format string * that is not supported in rust */ "VIR_DOMAIN_TUNABLE_CPU_IOTHREADSPIN", "VIR_DOMAIN_TUNABLE_CPU_VCPUPIN", /* Compat defines for obsolete types */ "_virBlkioParameter", "_virMemoryParameter", "_virSchedParameter", /* Obsoleted by VIR_TYPED_PARAM_FIELD_LENGTH */ "VIR_DOMAIN_BLKIO_FIELD_LENGTH", "VIR_DOMAIN_BLOCK_STATS_FIELD_LENGTH", "VIR_DOMAIN_MEMORY_FIELD_LENGTH", "VIR_DOMAIN_SCHED_FIELD_LENGTH", "VIR_NODE_CPU_STATS_FIELD_LENGTH", "VIR_NODE_MEMORY_STATS_FIELD_LENGTH", /* Not relevant at Rust API level */ "LIBVIR_CHECK_VERSION", "LIBVIR_VERSION_NUMBER", "VIR_GET_CPUMAP", "VIR_UNUSE_CPU", "VIR_USE_CPU", "VIR_UUID_BUFLEN", "VIR_UUID_STRING_BUFLEN", ]; const IGNORE_ENUMS: &[&str] = &[ // Deprecated in favour of VIR_TYPED_PARAM_* "VIR_DOMAIN_BLKIO_PARAM_BOOLEAN", "VIR_DOMAIN_BLKIO_PARAM_DOUBLE", "VIR_DOMAIN_BLKIO_PARAM_INT", "VIR_DOMAIN_BLKIO_PARAM_LLONG", "VIR_DOMAIN_BLKIO_PARAM_UINT", "VIR_DOMAIN_BLKIO_PARAM_ULLONG", "VIR_DOMAIN_MEMORY_PARAM_BOOLEAN", "VIR_DOMAIN_MEMORY_PARAM_DOUBLE", "VIR_DOMAIN_MEMORY_PARAM_INT", "VIR_DOMAIN_MEMORY_PARAM_LLONG", "VIR_DOMAIN_MEMORY_PARAM_UINT", "VIR_DOMAIN_MEMORY_PARAM_ULLONG", "VIR_DOMAIN_SCHED_FIELD_BOOLEAN", "VIR_DOMAIN_SCHED_FIELD_DOUBLE", "VIR_DOMAIN_SCHED_FIELD_INT", "VIR_DOMAIN_SCHED_FIELD_LLONG", "VIR_DOMAIN_SCHED_FIELD_UINT", "VIR_DOMAIN_SCHED_FIELD_ULLONG", /* Not relevant to expose */ "VIR_TYPED_PARAM_STRING_OKAY", ]; #[derive(Debug, Serialize, Deserialize, PartialEq)] struct ApiExport { #[serde(rename = "type")] ctype: String, symbol: String, } #[derive(Debug, Serialize, Deserialize, PartialEq)] struct ApiFile { name: String, exports: Vec, } #[derive(Debug, Serialize, Deserialize, PartialEq)] struct ApiFiles { file: Vec, } #[derive(Debug, Serialize, Deserialize, PartialEq)] struct Api { files: ApiFiles, } fn get_api_symbols( api: Api, funcs: &mut HashSet, macros: &mut HashSet, enums: &mut HashSet, ) { for file in api.files.file { for export in file.exports { if export.ctype == "function" { funcs.insert(export.symbol.to_string()); } else if export.ctype == "enum" { if !export.symbol.ends_with("_LAST") { enums.insert(export.symbol); } } else if export.ctype == "macro" { macros.insert(export.symbol); } } } } fn load_file( src: PathBuf, funcs: &mut HashSet, macros: &mut HashSet, enums: &mut HashSet, ) { let code = read_to_string(src.clone()).unwrap(); let re = Regex::new("sys::(vir|VIR_)[_a-zA-Z0-9]+").unwrap(); for m in re.find_iter(&code) { let sym = m.as_str()[5..].to_string(); if funcs.contains(&sym) { funcs.remove(&sym); } if macros.contains(&sym) { macros.remove(&sym); } if enums.contains(&sym) { enums.remove(&sym); } } } fn report_missing(symbols: HashSet, symtype: &str) -> bool { let mut syms = symbols.iter().collect::>(); syms.sort(); for name in syms { println!("Missing {}: {}", symtype, name); } !symbols.is_empty() } fn enforce(symname: &str, want: &[&str]) -> bool { for prefix in want { if symname.starts_with(prefix) { return true; } } false } fn load_mod( modname: &str, varname: &str, funcs: &mut HashSet, macros: &mut HashSet, enums: &mut HashSet, ) { let path = get_variable(modname, varname).unwrap(); let data = read_to_string(path).unwrap(); let api = from_str(&data).unwrap(); get_api_symbols(api, funcs, macros, enums); } fn do_test_api( want_func_prefixes: &[&str], want_macro_prefixes: &[&str], want_enum_prefixes: &[&str], ) { let mut funcs: HashSet = HashSet::new(); let mut macros: HashSet = HashSet::new(); let mut enums: HashSet = HashSet::new(); load_mod( "libvirt", "libvirt_api", &mut funcs, &mut macros, &mut enums, ); load_mod( "libvirt-lxc", "libvirt_lxc_api", &mut funcs, &mut macros, &mut enums, ); load_mod( "libvirt-qemu", "libvirt_qemu_api", &mut funcs, &mut macros, &mut enums, ); load_mod( "libvirt-admin", "libvirt_admin_api", &mut funcs, &mut macros, &mut enums, ); for name in IGNORE_FUNCS { funcs.remove(*name); } for name in IGNORE_MACROS { macros.remove(*name); } for name in IGNORE_ENUMS { enums.remove(*name); } funcs.retain(|sym| enforce(sym, want_func_prefixes)); macros.retain(|sym| enforce(sym, want_macro_prefixes)); enums.retain(|sym| enforce(sym, want_enum_prefixes)); for src in read_dir("src") .unwrap() .map(|res| res.unwrap().path()) .filter_map(|path| { if path .extension() .map_or(false, |ext| ext.to_str().unwrap() == "rs") && !path.file_name().map_or(false, |pre| { pre.to_str().map_or(false, |pre| pre.starts_with('.')) }) { Some(path) } else { None } }) .collect::>() { load_file(src, &mut funcs, &mut macros, &mut enums); } let mut missing = false; missing |= report_missing(funcs, "function"); missing |= report_missing(macros, "macro"); missing |= report_missing(enums, "enum"); assert!(!missing, "no missing symbols") } #[cfg(not(feature = "api_coverage"))] #[test] fn test_api_partial() { do_test_api( ENFORCED_FUNC_PREFIXES, ENFORCED_MACRO_PREFIXES, ENFORCED_ENUM_PREFIXES, ) } #[cfg(feature = "api_coverage")] #[test] fn test_api_full() { do_test_api(&[""], &[""], &[""]) }