diff --git a/build/cli_wrapper/Cargo.lock b/build/cli_wrapper/Cargo.lock index 34b559df1..0fe889c8f 100644 --- a/build/cli_wrapper/Cargo.lock +++ b/build/cli_wrapper/Cargo.lock @@ -50,6 +50,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "clap" version = "4.5.4" @@ -102,6 +108,22 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "libloading" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" +dependencies = [ + "cfg-if", + "windows-targets", +] + [[package]] name = "proc-macro2" version = "1.0.81" @@ -125,6 +147,8 @@ name = "ribasim" version = "2024.7.0" dependencies = [ "clap", + "libc", + "libloading", ] [[package]] diff --git a/build/cli_wrapper/Cargo.toml b/build/cli_wrapper/Cargo.toml index 8b026fff0..82b19f5ba 100644 --- a/build/cli_wrapper/Cargo.toml +++ b/build/cli_wrapper/Cargo.toml @@ -7,3 +7,5 @@ edition = "2021" [dependencies] clap = { version = "4.5.4", features = ["derive"] } +libc = "0.2.153" +libloading = "0.8.3" diff --git a/build/cli_wrapper/src/main.rs b/build/cli_wrapper/src/main.rs index 39e9e87f8..b2b102361 100644 --- a/build/cli_wrapper/src/main.rs +++ b/build/cli_wrapper/src/main.rs @@ -1,6 +1,11 @@ -use std::{env, path::PathBuf}; +use std::{ + env::{self, consts::OS}, + ffi::CString, + path::PathBuf, +}; -use clap::{CommandFactory, Parser}; +use clap::Parser; +use libloading::{Library, Symbol}; use std::process::ExitCode; #[derive(Parser)] @@ -15,7 +20,7 @@ fn main() -> ExitCode { let exe_dir = env::current_exe().unwrap().parent().unwrap().to_owned(); // Set the appropriate environment variable for the current platform - if std::env::consts::OS == "windows" { + if OS == "windows" { env::set_var( "PATH", format!( @@ -26,7 +31,7 @@ fn main() -> ExitCode { ); } - // TODO: Do I need to set LD_LIBRARY_PATH on linux? + // TODO: Do we need to set LD_LIBRARY_PATH on linux? // Parse command line arguments let cli = Cli::parse(); @@ -36,6 +41,27 @@ fn main() -> ExitCode { return ExitCode::FAILURE; } - // Call ribasim shared library and check for errors - todo!() + let shared_lib_path = match OS { + "windows" => exe_dir.join("bin/libribasim.dll"), + "linux" => exe_dir.join("lib/libribasim.so"), + _ => unimplemented!(), + }; + + unsafe { + // Load the library + let lib = Library::new(shared_lib_path).unwrap(); + + // Load the function from the library + let execute: Symbol i32> = + lib.get(b"execute").unwrap(); + + // Convert the path to a CString + let toml_path_c = CString::new(cli.toml_path.to_str().unwrap()).unwrap(); + + // Call the function + let exit_code = execute(toml_path_c.as_ptr()); + + // Return with same exit code as `execute` did + ExitCode::from(exit_code as u8) + } } diff --git a/core/src/main.jl b/core/src/main.jl index 7e0735fc9..1818d1394 100644 --- a/core/src/main.jl +++ b/core/src/main.jl @@ -14,9 +14,6 @@ function run(config::Config)::Model return model end -main(toml_path::AbstractString)::Cint = main([toml_path]) -main()::Cint = main(ARGS) - """ main(toml_path::AbstractString)::Cint main(ARGS::Vector{String})::Cint @@ -26,7 +23,7 @@ This is the main entry point of the application. Performs argument parsing and sets up logging for both terminal and file. Calls Ribasim.run() and handles exceptions to convert to exit codes. """ -function main(toml_path::String)::Cint +function main(toml_path::AbstractString)::Cint try # show progress bar in terminal config = Config(toml_path) diff --git a/core/test/main_test.jl b/core/test/main_test.jl index 042751719..b94336808 100644 --- a/core/test/main_test.jl +++ b/core/test/main_test.jl @@ -1,12 +1,3 @@ -@testitem "version" begin - using IOCapture: capture - - (; value, output) = capture() do - Ribasim.main(["--version"]) - end - @test value == 0 - @test output == string(pkgversion(Ribasim)) -end @testitem "toml_path" begin using IOCapture: capture @@ -24,7 +15,7 @@ end @test ispath(toml_path) (; value, output, error, backtrace) = capture() do - Ribasim.main([toml_path]) + Ribasim.main(toml_path) end @test value == 0 if value != 0 @@ -34,23 +25,3 @@ end end @test occursin("version in the TOML config file does not match", output) end - -@testitem "too many arguments for main" begin - using IOCapture: capture - - (; value, output) = capture() do - Ribasim.main(["too", "many"]) - end - @test value == 1 - @test occursin("Exactly 1 argument expected, got 2", output) -end - -@testitem "non-existing file for main" begin - using IOCapture: capture - - (; value, output) = capture() do - Ribasim.main(["non-existing-file.toml"]) - end - @test value == 1 - @test occursin("File not found: non-existing-file.toml", output) -end diff --git a/python/ribasim_api/ribasim_api/ribasim_api.py b/python/ribasim_api/ribasim_api/ribasim_api.py index 341f1e7c4..74544e6f9 100644 --- a/python/ribasim_api/ribasim_api/ribasim_api.py +++ b/python/ribasim_api/ribasim_api/ribasim_api.py @@ -30,3 +30,6 @@ def shutdown_julia(self) -> None: def update_subgrid_level(self) -> None: self.lib.update_subgrid_level() + + def execute(self, config_file: str) -> None: + self._execute_function(self.lib.execute, config_file.encode()) diff --git a/python/ribasim_api/tests/conftest.py b/python/ribasim_api/tests/conftest.py index b69fe8167..7c57ed6bc 100644 --- a/python/ribasim_api/tests/conftest.py +++ b/python/ribasim_api/tests/conftest.py @@ -12,7 +12,7 @@ def libribasim_paths() -> tuple[Path, Path]: repo_root = Path(__file__).parents[3].resolve() lib_or_bin = "bin" if platform.system() == "Windows" else "lib" extension = ".dll" if platform.system() == "Windows" else ".so" - lib_folder = repo_root / "build" / "libribasim" / lib_or_bin + lib_folder = repo_root / "build" / "ribasim" / lib_or_bin lib_path = lib_folder / f"libribasim{extension}" return lib_path, lib_folder diff --git a/python/ribasim_api/tests/test_bmi.py b/python/ribasim_api/tests/test_bmi.py index 101bf7ed5..be1c9e340 100644 --- a/python/ribasim_api/tests/test_bmi.py +++ b/python/ribasim_api/tests/test_bmi.py @@ -132,3 +132,9 @@ def test_get_version(libribasim): config = tomli.load(fp) assert libribasim.get_version() == config["version"] + + +def test_execute(libribasim, basic, tmp_path): + basic.write(tmp_path / "ribasim.toml") + config_file = str(tmp_path / "ribasim.toml") + libribasim.execute(config_file)