use super::{C_STANDARD, CXX_STANDARD};
use crate::linker;
use regex::Regex;
use std::{
env,
path::{Path, PathBuf},
};
fn replace_in_file(file_path: &Path, pattern: &str, replacement: String, file_name: &str) -> bool {
let Ok(content) = std::fs::read_to_string(file_path) else {
println!("cargo:warning=Failed to read {}", file_name);
return false;
};
println!("Try to set: {}", &replacement);
let re = Regex::new(pattern).unwrap();
let modified = re.replace(&content, replacement);
if let Err(e) = std::fs::write(file_path, modified.as_ref()) {
println!("cargo:warning=Failed to set in {}: {}", file_name, e);
return false;
}
true
}
struct FileConfig {
file_path: &'static str,
file_name: &'static str,
pattern_prefix: &'static str,
pattern_suffix: &'static str,
replacement_prefix: &'static str,
replacement_suffix: &'static str,
replacement_end: &'static str,
warning_prefix: &'static str,
}
const DB_FORMAT_CONFIG: FileConfig = FileConfig {
file_path: "deps/leveldb/db/dbformat.h",
file_name: "dbformat.h",
pattern_prefix: r"static const int ",
pattern_suffix: r" = [^;]+;",
replacement_prefix: "static const int ",
replacement_suffix: " = ",
replacement_end: ";",
warning_prefix: "Set LevelDB format constant",
};
const OPTIONS_CONFIG: FileConfig = FileConfig {
file_path: "deps/leveldb/include/leveldb/options.h",
file_name: "options.h",
pattern_prefix: r"int ",
pattern_suffix: r" = [^;]+;",
replacement_prefix: "int ",
replacement_suffix: " = ",
replacement_end: ";",
warning_prefix: "Set LevelDB options default",
};
const VERSION_SET_CONFIG: FileConfig = FileConfig {
file_path: "deps/leveldb/db/version_set.cc",
file_name: "version_set.cc",
pattern_prefix: r"static_assert\(config::",
pattern_suffix: r" == [^,]+,",
replacement_prefix: "static_assert(config::",
replacement_suffix: " == ",
replacement_end: ",",
warning_prefix: "Set LevelDB version_set assert for",
};
fn configure_variable(variable_name: &str, env_var: &str, config: &FileConfig) {
println!("cargo:rerun-if-env-changed={}", env_var);
let Ok(value) = env::var(env_var) else {
return;
};
let pattern = format!(
"{}{}{}",
config.pattern_prefix, variable_name, config.pattern_suffix
);
let replacement = format!(
"{}{}{}{}{}",
config.replacement_prefix,
variable_name,
config.replacement_suffix,
value,
config.replacement_end
);
if replace_in_file(
std::path::Path::new(config.file_path),
&pattern,
replacement,
config.file_name,
) {
println!(
"cargo:warning={} {} to {}",
config.warning_prefix, variable_name, value
);
}
}
fn pre_configure() {
configure_variable(
"kNumLevels",
"LEVELDB_RS_BINDING_FORMAT_NUM_LEVELS",
&DB_FORMAT_CONFIG,
);
configure_variable(
"kNumLevels",
"LEVELDB_RS_BINDING_FORMAT_NUM_LEVELS",
&VERSION_SET_CONFIG,
);
configure_variable(
"kL0_CompactionTrigger",
"LEVELDB_RS_BINDING_FORMAT_L0_COMPACTION_TRIGGER",
&DB_FORMAT_CONFIG,
);
configure_variable(
"kL0_SlowdownWritesTrigger",
"LEVELDB_RS_BINDING_FORMAT_L0_SLOWDOWN_WRITES_TRIGGER",
&DB_FORMAT_CONFIG,
);
configure_variable(
"kL0_StopWritesTrigger",
"LEVELDB_RS_BINDING_FORMAT_L0_STOP_WRITES_TRIGGER",
&DB_FORMAT_CONFIG,
);
configure_variable(
"kMaxMemCompactLevel",
"LEVELDB_RS_BINDING_FORMAT_MAX_MEM_COMPACT_LEVEL",
&DB_FORMAT_CONFIG,
);
configure_variable(
"kReadBytesPeriod",
"LEVELDB_RS_BINDING_FORMAT_READ_BYTES_PERIOD",
&DB_FORMAT_CONFIG,
);
configure_variable(
"zstd_compression_level",
"LEVELDB_RS_BINDING_OPTIONS_ZSTD_DEFAULT_COMPRESSION_LEVEL",
&OPTIONS_CONFIG,
);
}
fn clear_code_changes() {
if env::var("LEVELDB_RS_BINDING_CLEAR_CODE_CHANGES").is_ok() {
match std::process::Command::new("git")
.args(["-C", "deps/leveldb", "reset", "--hard"])
.output()
{
Ok(output) if output.status.success() => {
println!("cargo:warning=Reset LevelDB source to original state");
}
Ok(output) => {
println!(
"cargo:warning=Failed to reset LevelDB: {}",
String::from_utf8_lossy(&output.stderr)
);
}
Err(e) => {
println!("cargo:warning=Failed to execute git reset: {}", e);
}
}
}
}
pub fn build(
install_dir: &Path,
build_type: &str,
snappy_prefix: Option<PathBuf>,
zstd_prefix: Option<PathBuf>,
) -> PathBuf {
println!("[leveldb] Building");
pre_configure();
let mut config = cmake::Config::new(std::path::Path::new("deps").join("leveldb"));
config
.profile(build_type)
.define("CMAKE_CXX_STANDARD", CXX_STANDARD)
.define("CMAKE_C_STANDARD", C_STANDARD)
.define("LEVELDB_BUILD_TESTS", "OFF")
.define("LEVELDB_BUILD_BENCHMARKS", "OFF")
.define("CMAKE_INSTALL_LIBDIR", install_dir);
crate::apply_cmake_flags(&mut config);
let mut lib_paths = Vec::new();
if let Some(snappy_prefix) = snappy_prefix {
lib_paths.push(
snappy_prefix
.join(install_dir.file_name().unwrap())
.display()
.to_string(),
);
config
.define("HAVE_SNAPPY", "ON")
.cflag(format!("-I{}", snappy_prefix.join("include").display()))
.cxxflag(format!("-I{}", snappy_prefix.join("include").display()));
} else {
config.define("HAVE_SNAPPY", "OFF");
}
if let Some(zstd_prefix) = zstd_prefix {
lib_paths.push(
zstd_prefix
.join(install_dir.file_name().unwrap())
.display()
.to_string(),
);
config
.define("HAVE_ZSTD", "ON")
.cflag(format!("-I{}", zstd_prefix.join("include").display()))
.cxxflag(format!("-I{}", zstd_prefix.join("include").display()));
} else {
config.define("HAVE_ZSTD", "OFF");
}
linker::set_ldflags(&lib_paths);
let dest_prefix = config.build();
assert_eq!(
dest_prefix.join(install_dir.file_name().unwrap()),
install_dir,
"CMake should build LevelDB in provided install_dir"
);
clear_code_changes();
dest_prefix
}