mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-08-25 06:39:53 -04:00
433 lines
16 KiB
Rust
433 lines
16 KiB
Rust
use cmake::Config;
|
||
use std::fs;
|
||
use std::path::Path;
|
||
|
||
/// Represents a patch to be applied to the Monero codebase
|
||
struct EmbeddedPatch {
|
||
name: &'static str,
|
||
description: &'static str,
|
||
patch_unified: &'static str,
|
||
}
|
||
|
||
/// Macro to create embedded patches with compile-time file inclusion
|
||
macro_rules! embedded_patch {
|
||
($name:literal, $description:literal, $patch_file:literal) => {
|
||
EmbeddedPatch {
|
||
name: $name,
|
||
description: $description,
|
||
patch_unified: include_str!($patch_file),
|
||
}
|
||
};
|
||
}
|
||
|
||
/// Embedded patches applied at compile time
|
||
const EMBEDDED_PATCHES: &[EmbeddedPatch] = &[embedded_patch!(
|
||
"wallet2_api_allow_subtract_from_fee",
|
||
"Adds subtract_fee_from_outputs parameter to wallet2_api transaction creation methods",
|
||
"patches/wallet2_api_allow_subtract_from_fee.patch"
|
||
)];
|
||
|
||
fn main() {
|
||
let is_github_actions: bool = std::env::var("GITHUB_ACTIONS").is_ok();
|
||
let is_docker_build: bool = std::env::var("DOCKER_BUILD").is_ok();
|
||
|
||
// Eerun this when the bridge.rs or static_bridge.h file changes.
|
||
println!("cargo:rerun-if-changed=src/bridge.rs");
|
||
println!("cargo:rerun-if-changed=src/bridge.h");
|
||
|
||
// Rerun if this build script changes (since it contains embedded patches)
|
||
println!("cargo:rerun-if-changed=build.rs");
|
||
|
||
// Rerun if the patches directory or any patch files change
|
||
println!("cargo:rerun-if-changed=patches");
|
||
|
||
// Apply embedded patches before building
|
||
apply_embedded_patches().expect("Failed to apply embedded patches");
|
||
|
||
// Build with the monero library all dependencies required
|
||
let mut config = Config::new("monero");
|
||
|
||
let output_directory = config
|
||
.build_target("wallet_api")
|
||
// Builds currently fail in Release mode
|
||
// .define("CMAKE_BUILD_TYPE", "Release")
|
||
// .define("CMAKE_RELEASE_TYPE", "Release")
|
||
// Force building static libraries
|
||
.define("STATIC", "ON")
|
||
.define("BUILD_SHARED_LIBS", "OFF")
|
||
.define("BUILD_TESTS", "OFF")
|
||
.define("Boost_USE_STATIC_LIBS", "ON")
|
||
.define("Boost_USE_STATIC_RUNTIME", "ON")
|
||
//// Disable support for ALL hardware wallets
|
||
// Disable Trezor support completely
|
||
.define("USE_DEVICE_TREZOR", "OFF")
|
||
.define("USE_DEVICE_TREZOR_MANDATORY", "OFF")
|
||
.define("USE_DEVICE_TREZOR_PROTOBUF_TEST", "OFF")
|
||
.define("USE_DEVICE_TREZOR_LIBUSB", "OFF")
|
||
.define("USE_DEVICE_TREZOR_UDP_RELEASE", "OFF")
|
||
.define("USE_DEVICE_TREZOR_DEBUG", "OFF")
|
||
.define("TREZOR_DEBUG", "OFF")
|
||
// Prevent CMake from finding dependencies that could enable Trezor
|
||
.define("CMAKE_DISABLE_FIND_PACKAGE_LibUSB", "ON")
|
||
// Disable Ledger support
|
||
.define("USE_DEVICE_LEDGER", "OFF")
|
||
.define("CMAKE_DISABLE_FIND_PACKAGE_HIDAPI", "ON")
|
||
.define("GTEST_HAS_ABSL", "OFF")
|
||
// Use lightweight crypto library
|
||
.define("MONERO_WALLET_CRYPTO_LIBRARY", "cn")
|
||
.build_arg("-Wno-dev") // Disable warnings we can't fix anyway
|
||
.build_arg(match (is_github_actions, is_docker_build) {
|
||
(true, _) => "-j1",
|
||
(_, true) => "-j1",
|
||
(_, _) => "-j4",
|
||
})
|
||
.build();
|
||
|
||
let monero_build_dir = output_directory.join("build");
|
||
|
||
println!(
|
||
"cargo:debug=Build directory: {}",
|
||
output_directory.display()
|
||
);
|
||
|
||
// Add output directories to the link search path
|
||
println!(
|
||
"cargo:rustc-link-search=native={}",
|
||
monero_build_dir.join("lib").display()
|
||
);
|
||
|
||
// Add additional link search paths for libraries in different directories
|
||
println!(
|
||
"cargo:rustc-link-search=native={}",
|
||
monero_build_dir.join("contrib/epee/src").display()
|
||
);
|
||
println!(
|
||
"cargo:rustc-link-search=native={}",
|
||
monero_build_dir.join("external/easylogging++").display()
|
||
);
|
||
println!(
|
||
"cargo:rustc-link-search=native={}",
|
||
monero_build_dir
|
||
.join("external/db_drivers/liblmdb")
|
||
.display()
|
||
);
|
||
println!(
|
||
"cargo:rustc-link-search=native={}",
|
||
monero_build_dir.join("external/randomx").display()
|
||
);
|
||
println!("cargo:rustc-link-search=native=/usr/lib/x86_64-linux-gnu");
|
||
|
||
println!(
|
||
"cargo:rustc-link-search=native={}",
|
||
monero_build_dir.join("src").display()
|
||
);
|
||
println!(
|
||
"cargo:rustc-link-search=native={}",
|
||
monero_build_dir.join("src/crypto").display()
|
||
);
|
||
println!(
|
||
"cargo:rustc-link-search=native={}",
|
||
monero_build_dir.join("src/net").display()
|
||
);
|
||
println!(
|
||
"cargo:rustc-link-search=native={}",
|
||
monero_build_dir.join("src/ringct").display()
|
||
);
|
||
println!(
|
||
"cargo:rustc-link-search=native={}",
|
||
monero_build_dir.join("src/checkpoints").display()
|
||
);
|
||
println!(
|
||
"cargo:rustc-link-search=native={}",
|
||
monero_build_dir.join("src/multisig").display()
|
||
);
|
||
println!(
|
||
"cargo:rustc-link-search=native={}",
|
||
monero_build_dir.join("src/cryptonote_basic").display()
|
||
);
|
||
println!(
|
||
"cargo:rustc-link-search=native={}",
|
||
monero_build_dir.join("src/common").display()
|
||
);
|
||
println!(
|
||
"cargo:rustc-link-search=native={}",
|
||
monero_build_dir.join("src/cryptonote_core").display()
|
||
);
|
||
println!(
|
||
"cargo:rustc-link-search=native={}",
|
||
monero_build_dir.join("src/hardforks").display()
|
||
);
|
||
println!(
|
||
"cargo:rustc-link-search=native={}",
|
||
monero_build_dir.join("src/blockchain_db").display()
|
||
);
|
||
println!(
|
||
"cargo:rustc-link-search=native={}",
|
||
monero_build_dir.join("src/device").display()
|
||
);
|
||
// device_trezor search path (stub version when disabled)
|
||
println!(
|
||
"cargo:rustc-link-search=native={}",
|
||
monero_build_dir.join("src/device_trezor").display()
|
||
);
|
||
println!(
|
||
"cargo:rustc-link-search=native={}",
|
||
monero_build_dir.join("src/mnemonics").display()
|
||
);
|
||
println!(
|
||
"cargo:rustc-link-search=native={}",
|
||
monero_build_dir.join("src/rpc").display()
|
||
);
|
||
|
||
#[cfg(target_os = "macos")]
|
||
{
|
||
// Dynamically detect Homebrew installation prefix (works on both Apple Silicon and Intel Macs)
|
||
let brew_prefix = std::process::Command::new("brew")
|
||
.arg("--prefix")
|
||
.output()
|
||
.ok()
|
||
.and_then(|o| String::from_utf8(o.stdout).ok())
|
||
.map(|s| s.trim().to_string())
|
||
.unwrap_or_else(|| "/opt/homebrew".into());
|
||
|
||
// add homebrew search paths using dynamic prefix
|
||
println!("cargo:rustc-link-search=native={}/lib", brew_prefix);
|
||
println!(
|
||
"cargo:rustc-link-search=native={}/opt/unbound/lib",
|
||
brew_prefix
|
||
);
|
||
println!(
|
||
"cargo:rustc-link-search=native={}/opt/expat/lib",
|
||
brew_prefix
|
||
);
|
||
println!(
|
||
"cargo:rustc-link-search=native={}/Cellar/protobuf@21/21.12_1/lib/",
|
||
brew_prefix
|
||
);
|
||
|
||
// Add search paths for clang runtime libraries
|
||
println !("cargo:rustc-link-search=native=/Library/Developer/CommandLineTools/usr/lib/clang/15.0.0/lib/darwin");
|
||
println !("cargo:rustc-link-search=native=/Library/Developer/CommandLineTools/usr/lib/clang/16.0.0/lib/darwin");
|
||
println !("cargo:rustc-link-search=native=/Library/Developer/CommandLineTools/usr/lib/clang/17.0.0/lib/darwin");
|
||
println !("cargo:rustc-link-search=native=/Library/Developer/CommandLineTools/usr/lib/clang/18.0.0/lib/darwin");
|
||
}
|
||
|
||
// Link libwallet and libwallet_api statically
|
||
println!("cargo:rustc-link-lib=static=wallet");
|
||
println!("cargo:rustc-link-lib=static=wallet_api");
|
||
|
||
// Link targets of monero codebase statically
|
||
println!("cargo:rustc-link-lib=static=epee");
|
||
println!("cargo:rustc-link-lib=static=easylogging");
|
||
println!("cargo:rustc-link-lib=static=lmdb");
|
||
println!("cargo:rustc-link-lib=static=randomx");
|
||
println!("cargo:rustc-link-lib=static=cncrypto");
|
||
println!("cargo:rustc-link-lib=static=net");
|
||
println!("cargo:rustc-link-lib=static=ringct");
|
||
println!("cargo:rustc-link-lib=static=ringct_basic");
|
||
println!("cargo:rustc-link-lib=static=checkpoints");
|
||
println!("cargo:rustc-link-lib=static=multisig");
|
||
println!("cargo:rustc-link-lib=static=version");
|
||
println!("cargo:rustc-link-lib=static=cryptonote_basic");
|
||
println!("cargo:rustc-link-lib=static=cryptonote_format_utils_basic");
|
||
println!("cargo:rustc-link-lib=static=common");
|
||
println!("cargo:rustc-link-lib=static=cryptonote_core");
|
||
println!("cargo:rustc-link-lib=static=hardforks");
|
||
println!("cargo:rustc-link-lib=static=blockchain_db");
|
||
println!("cargo:rustc-link-lib=static=device");
|
||
// Link device_trezor (stub version when USE_DEVICE_TREZOR=OFF)
|
||
println!("cargo:rustc-link-lib=static=device_trezor");
|
||
println!("cargo:rustc-link-lib=static=mnemonics");
|
||
println!("cargo:rustc-link-lib=static=rpc_base");
|
||
|
||
// Static linking for boost
|
||
println!("cargo:rustc-link-lib=static=boost_serialization");
|
||
println!("cargo:rustc-link-lib=static=boost_filesystem");
|
||
println!("cargo:rustc-link-lib=static=boost_thread");
|
||
println!("cargo:rustc-link-lib=static=boost_chrono");
|
||
|
||
// Link libsodium statically
|
||
println!("cargo:rustc-link-lib=static=sodium");
|
||
|
||
// Link OpenSSL statically
|
||
println!("cargo:rustc-link-lib=static=ssl"); // This is OpenSSL (libsll)
|
||
println!("cargo:rustc-link-lib=static=crypto"); // This is OpenSSLs crypto library (libcrypto)
|
||
|
||
// Link unbound statically
|
||
println!("cargo:rustc-link-lib=static=unbound");
|
||
println!("cargo:rustc-link-lib=static=expat"); // Expat is required by unbound
|
||
println!("cargo:rustc-link-lib=static=nghttp2");
|
||
println!("cargo:rustc-link-lib=static=event");
|
||
|
||
// Link protobuf statically
|
||
println!("cargo:rustc-link-lib=static=protobuf");
|
||
|
||
#[cfg(target_os = "macos")]
|
||
{
|
||
// Static archive is always present, dylib only on some versions.
|
||
println!("cargo:rustc-link-lib=static=clang_rt.osx");
|
||
|
||
// Minimum OS version you already add:
|
||
println!("cargo:rustc-link-arg=-mmacosx-version-min=11.0");
|
||
}
|
||
|
||
// Build the CXX bridge
|
||
let mut build = cxx_build::bridge("src/bridge.rs");
|
||
|
||
#[cfg(target_os = "macos")]
|
||
{
|
||
build.flag_if_supported("-mmacosx-version-min=11.0");
|
||
}
|
||
|
||
build
|
||
.flag_if_supported("-std=c++17")
|
||
.include("src") // Include the bridge.h file
|
||
.include("monero/src") // Includes the monero headers
|
||
.include("monero/external/easylogging++") // Includes the easylogging++ headers
|
||
.include("monero/contrib/epee/include") // Includes the epee headers for net/http_client.h
|
||
.include("/opt/homebrew/include") // Homebrew include path for Boost
|
||
.flag("-fPIC"); // Position independent code
|
||
|
||
#[cfg(target_os = "macos")]
|
||
{
|
||
// Use the same dynamic brew prefix for include paths
|
||
let brew_prefix = std::process::Command::new("brew")
|
||
.arg("--prefix")
|
||
.output()
|
||
.ok()
|
||
.and_then(|o| String::from_utf8(o.stdout).ok())
|
||
.map(|s| s.trim().to_string())
|
||
.unwrap_or_else(|| "/opt/homebrew".into());
|
||
|
||
build.include(format!("{}/include", brew_prefix)); // Homebrew include path for Boost
|
||
}
|
||
|
||
build.compile("monero-sys");
|
||
}
|
||
|
||
/// Split a multi-file patch into individual file patches
|
||
fn split_patch_by_files(
|
||
patch_content: &str,
|
||
) -> Result<Vec<(String, String)>, Box<dyn std::error::Error>> {
|
||
let mut file_patches = Vec::new();
|
||
let lines: Vec<&str> = patch_content.lines().collect();
|
||
|
||
let mut current_file_patch = String::new();
|
||
let mut current_file_path: Option<String> = None;
|
||
let mut in_file_section = false;
|
||
|
||
for line in lines {
|
||
if line.starts_with("diff --git ") {
|
||
// Save previous file patch if we have one
|
||
if let Some(file_path) = current_file_path.take() {
|
||
if !current_file_patch.trim().is_empty() {
|
||
file_patches.push((file_path, current_file_patch.clone()));
|
||
}
|
||
}
|
||
|
||
// Start new file patch
|
||
current_file_patch.clear();
|
||
current_file_patch.push_str(line);
|
||
current_file_patch.push('\n');
|
||
|
||
// Extract file path from diff line (e.g., "diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp")
|
||
let parts: Vec<&str> = line.split_whitespace().collect();
|
||
if parts.len() >= 4 {
|
||
let file_path = parts[2].strip_prefix("a/").unwrap_or(parts[2]);
|
||
current_file_path = Some(file_path.to_string());
|
||
}
|
||
in_file_section = true;
|
||
} else if in_file_section {
|
||
current_file_patch.push_str(line);
|
||
current_file_patch.push('\n');
|
||
}
|
||
}
|
||
|
||
// Don't forget the last file
|
||
if let Some(file_path) = current_file_path {
|
||
if !current_file_patch.trim().is_empty() {
|
||
file_patches.push((file_path, current_file_patch));
|
||
}
|
||
}
|
||
|
||
Ok(file_patches)
|
||
}
|
||
|
||
fn apply_embedded_patches() -> Result<(), Box<dyn std::error::Error>> {
|
||
let monero_dir = Path::new("monero");
|
||
|
||
if !monero_dir.exists() {
|
||
return Err("Monero directory not found. Please ensure the monero submodule is initialized and present.".into());
|
||
}
|
||
|
||
for embedded in EMBEDDED_PATCHES {
|
||
println!(
|
||
"cargo:warning=Processing embedded patch: {} ({})",
|
||
embedded.name, embedded.description
|
||
);
|
||
|
||
// Split the patch into individual file patches
|
||
let file_patches = split_patch_by_files(embedded.patch_unified)
|
||
.map_err(|e| format!("Failed to split patch {}: {}", embedded.name, e))?;
|
||
|
||
if file_patches.is_empty() {
|
||
return Err(format!("No file patches found in patch {}", embedded.name).into());
|
||
}
|
||
|
||
println!(
|
||
"cargo:warning=Found {} file(s) in patch {}",
|
||
file_patches.len(),
|
||
embedded.name
|
||
);
|
||
|
||
// Apply each file patch individually
|
||
for (file_path, patch_content) in file_patches {
|
||
println!("cargo:warning=Applying patch to file: {}", file_path);
|
||
|
||
// Parse the individual file patch
|
||
let patch = diffy::Patch::from_str(&patch_content)
|
||
.map_err(|e| format!("Failed to parse patch for {}: {}", file_path, e))?;
|
||
|
||
let target_path = monero_dir.join(&file_path);
|
||
|
||
if !target_path.exists() {
|
||
return Err(format!("Target file {} not found!", file_path).into());
|
||
}
|
||
|
||
let current = fs::read_to_string(&target_path)
|
||
.map_err(|e| format!("Failed to read {}: {}", file_path, e))?;
|
||
|
||
let patched = match diffy::apply(¤t, &patch) {
|
||
Ok(p) => p,
|
||
Err(_) => {
|
||
// Try reversing the patch – if that succeeds the file already contains the changes
|
||
if diffy::apply(¤t, &patch.reverse()).is_ok() {
|
||
println!(
|
||
"cargo:warning=Patch for {} already applied – skipping",
|
||
file_path
|
||
);
|
||
continue;
|
||
} else {
|
||
return Err(format!(
|
||
"Failed to apply patch to {}: hunk mismatch (not already applied)",
|
||
file_path
|
||
)
|
||
.into());
|
||
}
|
||
}
|
||
};
|
||
|
||
fs::write(&target_path, patched)
|
||
.map_err(|e| format!("Failed to write {}: {}", file_path, e))?;
|
||
|
||
println!("cargo:warning=Successfully applied patch to: {}", file_path);
|
||
}
|
||
|
||
println!(
|
||
"cargo:warning=Successfully applied all file patches for: {} ({})",
|
||
embedded.name, embedded.description
|
||
);
|
||
}
|
||
|
||
Ok(())
|
||
}
|