diff --git a/.github/workflows/release-rust.yml b/.github/workflows/release-rust.yml index 10baaf1e7..c9ffe525c 100644 --- a/.github/workflows/release-rust.yml +++ b/.github/workflows/release-rust.yml @@ -1,11 +1,14 @@ name: Release Rust Crates on: + workflow_dispatch: + pull_request: push: branches: [ master, develop ] - paths: - - 'source/ports/rs_port/Cargo.toml' - - 'source/ports/rs_port/inline/Cargo.toml' + # branches: [ master, develop ] + # paths: + # - 'source/ports/rs_port/Cargo.toml' + # - 'source/ports/rs_port/inline/Cargo.toml' concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} @@ -15,15 +18,44 @@ env: CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} jobs: - release: - name: Release Rust Port - runs-on: ubuntu-latest + test: + name: Rust Port Tests + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] steps: - name: Check out the repo uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Release the port + - name: Install MetaCall Unix + if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' + run: curl -sL https://raw.githubusercontent.com/metacall/install/master/install.sh | sh + - name: Install MetaCall Windows + if: matrix.os == 'windows-latest' + run: powershell -NoProfile -ExecutionPolicy Unrestricted -Command "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; &([scriptblock]::Create((Invoke-WebRequest -UseBasicParsing 'https://raw.githubusercontent.com/metacall/install/master/install.ps1')))" + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + - name: Build and Test the Rust Port + working-directory: source/ports/rs_port run: | - cd source/ports/rs_port - bash ./upload.sh + cargo build --verbose + cargo test --verbose + # release: + # name: Release Rust Port + # runs-on: ubuntu-latest + # needs: test + # steps: + # - name: Check out the repo + # uses: actions/checkout@v4 + # with: + # fetch-depth: 0 + # - name: Release the port + # run: | + # cd source/ports/rs_port + # bash ./upload.sh diff --git a/source/ports/rs_port/build.rs b/source/ports/rs_port/build.rs index 4b7bcf309..1c623ca2f 100644 --- a/source/ports/rs_port/build.rs +++ b/source/ports/rs_port/build.rs @@ -1,4 +1,147 @@ -use std::env; +use std::{ + env, fs, + path::{Path, PathBuf}, + vec, +}; + +// Search for MetaCall libraries in platform-specific locations +// Handle custom installation paths via environment variables +// Find configuration files recursively +// Provide helpful error messages when things aren't found + +/// Represents the install paths for a platform +struct InstallPath { + paths: Vec, + names: Vec<&'static str>, +} + +/// Find files recursively in a directory matching a pattern +fn find_files_recursively>( + root_dir: P, + filename: &str, + max_depth: Option, +) -> Result, Box> { + let mut matches = Vec::new(); + let mut stack = vec![(root_dir.as_ref().to_path_buf(), 0)]; + + while let Some((current_dir, depth)) = stack.pop() { + if let Some(max) = max_depth { + if depth > max { + continue; + } + } + + if let Ok(entries) = fs::read_dir(¤t_dir) { + for entry in entries.flatten() { + let path = entry.path(); + + if path.is_file() { + // Simple filename comparison instead of regex + if let Some(file_name) = path.file_name().and_then(|n| n.to_str()) { + if file_name == filename { + matches.push(path); + } + } + } else if path.is_dir() { + stack.push((path, depth + 1)); + } + } + } + } + + Ok(matches) +} + +fn platform_install_paths() -> Result> { + if cfg!(target_os = "windows") { + // Defaults to path: C:\Users\Default\AppData\Local + let local_app_data = env::var("LOCALAPPDATA") + .unwrap_or_else(|_| String::from("C:\\Users\\Default\\AppData\\Local")); + + Ok(InstallPath { + paths: vec![PathBuf::from(local_app_data) + .join("MetaCall") + .join("metacall")], + names: vec!["metacall.dll"], + }) + } else if cfg!(target_os = "macos") { + Ok(InstallPath { + paths: vec![ + PathBuf::from("/opt/homebrew/lib/"), + PathBuf::from("/usr/local/lib/"), + ], + names: vec!["metacall.dylib"], + }) + } else if cfg!(target_os = "linux") { + Ok(InstallPath { + paths: vec![PathBuf::from("/usr/local/lib/"), PathBuf::from("/gnu/lib/")], + names: vec!["libmetacall.so"], + }) + } else { + Err(format!("Platform {} not supported", env::consts::OS).into()) + } +} + +/// Get search paths, checking for custom installation path first +fn get_search_config() -> Result> { + // First, check if user specified a custom path + if let Ok(custom_path) = env::var("METACALL_INSTALL_PATH") { + // For custom paths, we need to search for any metacall library variant + return Ok(InstallPath { + paths: vec![PathBuf::from(custom_path)], + names: vec!["libmetacall.so", "libmetacalld.so", "libmetacall.dylib", "libmetacalld.dylib", "metacall.dll", "metacalld.dll"] + }); + } + + // Fall back to platform-specific paths + platform_install_paths() +} + +/// Find the MetaCall library +/// This orchestrates the search process +fn find_metacall_library() -> Result> { + let search_config = get_search_config()?; + + // Search in each configured path + for search_path in &search_config.paths { + for name in &search_config.names { + // Search with no limit in depth + match find_files_recursively(search_path, &name.to_string(), None) { + Ok(files) if !files.is_empty() => { + let found_lib = fs::canonicalize(&files[0])?; + + return Ok(found_lib.clone()); + } + Ok(_) => { + // No files found in this path, continue searching + continue; + } + Err(e) => { + eprintln!( + "Error searching in {}: {}", + search_path.display(), + e + ); + continue; + } + } + } + } + + // If we get here, library wasn't found + let search_paths: Vec = search_config + .paths + .iter() + .map(|p| p.display().to_string()) + .collect(); + + Err(format!( + "MetaCall library not found. Searched in: {}. \ + If you have it installed elsewhere, set METACALL_INSTALL_PATH environment variable.", + search_paths.join(", ") + ) + .into()) +} fn main() { // When running tests from CMake @@ -21,16 +164,21 @@ fn main() { } } } else { - // When building from Cargo - let profile = env::var("PROFILE").unwrap(); - match profile.as_str() { - // "debug" => { - // println!("cargo:rustc-link-lib=dylib=metacalld"); - // } - "debug" | "release" => { - println!("cargo:rustc-link-lib=dylib=metacall") + // When building from Cargo, try to find MetaCall + match find_metacall_library() { + Ok(lib_path) => { + // Extract the directory containing the library + if let Some(lib_dir) = lib_path.parent() { + println!("cargo:rustc-link-search=native={}", lib_dir.display()); + println!("cargo:rustc-link-lib=dylib=metacall"); + } } - _ => { + Err(e) => { + // Print the error + eprintln!("Failed to find MetaCall library with: {e} \ + Still trying to link in case the library is in system paths"); + + // Still try to link in case the library is in system paths println!("cargo:rustc-link-lib=dylib=metacall") } }