From 46f1a221f7f1c2fc66ac3bb94e7d6368fbde7e7e Mon Sep 17 00:00:00 2001 From: "jonas.puksta.sensmetry" Date: Fri, 27 Feb 2026 09:28:21 +0200 Subject: [PATCH 01/16] add kpar compression options behind feature flags. java and python bindings are incomplete so far Signed-off-by: jonas.puksta.sensmetry --- bindings/java/src/lib.rs | 6 ++- bindings/py/src/lib.rs | 8 ++-- core/Cargo.toml | 7 +++- core/src/commands/build.rs | 8 ++-- core/src/model.rs | 42 ++++++++++++++++++++ core/src/project/local_kpar.rs | 3 +- docs/src/commands/build.md | 9 +++++ sysand/Cargo.toml | 7 +++- sysand/src/cli.rs | 65 +++++++++++++++++++++++++++++- sysand/src/commands/build.rs | 10 ++--- sysand/src/lib.rs | 6 +-- sysand/tests/cli_build.rs | 72 +++++++++++++++++++++++++++++++++- 12 files changed, 222 insertions(+), 21 deletions(-) diff --git a/bindings/java/src/lib.rs b/bindings/java/src/lib.rs index f85902e9..5218c2a9 100644 --- a/bindings/java/src/lib.rs +++ b/bindings/java/src/lib.rs @@ -327,6 +327,7 @@ pub extern "system" fn Java_com_sensmetry_sysand_Sysand_buildProject<'local>( _class: JClass<'local>, output_path: JString<'local>, project_path: JString<'local>, + compression: JString<'local> ) { let Some(output_path) = env.get_str(&output_path, "outputPath") else { return; @@ -338,7 +339,10 @@ pub extern "system" fn Java_com_sensmetry_sysand_Sysand_buildProject<'local>( nominal_path: None, project_path: Utf8PathBuf::from(project_path), }; - let command_result = sysand_core::commands::build::do_build_kpar(&project, &output_path, true); + let Some(compression) = env.get_str(&compression, "compression") else { + return; + }; + let command_result = sysand_core::commands::build::do_build_kpar(&project, &output_path, compression, true); match command_result { Ok(_) => {} Err(error) => handle_build_error(&mut env, error), diff --git a/bindings/py/src/lib.rs b/bindings/py/src/lib.rs index aa26c975..e67b5e48 100644 --- a/bindings/py/src/lib.rs +++ b/bindings/py/src/lib.rs @@ -28,7 +28,7 @@ use sysand_core::{ include::do_include, info::{InfoError, do_info, do_info_project}, init::InitError, - model::{InterchangeProjectInfoRaw, InterchangeProjectMetadataRaw}, + model::{InterchangeProjectInfoRaw, InterchangeProjectMetadataRaw, ZipCompressionMethod}, project::{ ProjectRead as _, local_kpar::LocalKParProject, @@ -177,9 +177,9 @@ fn do_info_py( #[pyfunction(name = "do_build_py")] #[pyo3( - signature = (output_path, project_path), + signature = (output_path, project_path, compression), )] -fn do_build_py(output_path: String, project_path: Option) -> PyResult<()> { +fn do_build_py(output_path: String, project_path: Option, compression: ZipCompressionMethod) -> PyResult<()> { let _ = pyo3_log::try_init(); let Some(current_project_path) = project_path else { @@ -190,7 +190,7 @@ fn do_build_py(output_path: String, project_path: Option) -> PyResult<() project_path: current_project_path.into(), }; - do_build_kpar(&project, &output_path, true) + do_build_kpar(&project, &output_path, compression, true) .map(|_| ()) .map_err(|err| match err { KParBuildError::ProjectRead(_) => PyRuntimeError::new_err(err.to_string()), diff --git a/core/Cargo.toml b/core/Cargo.toml index 32b84663..d2a2bf6e 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -22,6 +22,11 @@ python = ["dep:pyo3"] js = ["dep:wasm-bindgen"] filesystem = ["dep:camino-tempfile", "dep:dirs", "dep:zip"] networking = ["dep:reqwest", "dep:gix"] # "dep:reqwest-middleware", "dep:partialzip" +# Different compression methods for creating kpar +kpar-bzip2 = ["zip?/bzip2"] +kpar-zstd = ["zip?/zstd"] +kpar-xz = ["zip?/xz"] +kpar-ppmd = ["zip?/ppmd"] alltests = [] [dependencies] @@ -53,7 +58,7 @@ typed-path = { version = "0.12.0", default-features = false } walkdir = "2.5.0" # unicode-normalization = { version = "0.1.24", default-features = false } wasm-bindgen = { version = "0.2.106", default-features = false, optional = true } -zip = { version = "6.0.0", default-features = false, optional = true } +zip = { version = "6.0.0", default-features = false, optional = true, features = ["deflate"] } url = { version = "2.5.7", default-features = false } gix = { version = "0.77.0", default-features = false, optional = true, features = ["blocking-http-transport-reqwest", "blocking-network-client", "worktree-mutation"] } logos = "0.15.1" diff --git a/core/src/commands/build.rs b/core/src/commands/build.rs index 918c8c28..f5bd7cec 100644 --- a/core/src/commands/build.rs +++ b/core/src/commands/build.rs @@ -14,7 +14,7 @@ use crate::{ workspace::WorkspaceReadError, }; #[cfg(feature = "filesystem")] -use crate::{project::local_src::LocalSrcProject, workspace::Workspace}; +use crate::{model::ZipCompressionMethod, project::local_src::LocalSrcProject, workspace::Workspace}; use super::include::IncludeError; @@ -119,6 +119,7 @@ pub fn default_kpar_file_name( pub fn do_build_kpar, Pr: ProjectRead>( project: &Pr, path: P, + compression: ZipCompressionMethod, canonicalise: bool, ) -> Result> { use crate::project::local_src::LocalSrcProject; @@ -152,13 +153,14 @@ pub fn do_build_kpar, Pr: ProjectRead>( } } - Ok(LocalKParProject::from_project(&local_project, path)?) + Ok(LocalKParProject::from_project(&local_project, path, compression.into())?) } #[cfg(feature = "filesystem")] pub fn do_build_workspace_kpars>( workspace: &Workspace, path: P, + compression: ZipCompressionMethod, canonicalise: bool, ) -> Result, KParBuildError> { let mut result = Vec::new(); @@ -174,7 +176,7 @@ pub fn do_build_workspace_kpars>( }; let file_name = default_kpar_file_name(&project)?; let output_path = path.as_ref().join(file_name); - let kpar_project = do_build_kpar(&project, &output_path, canonicalise)?; + let kpar_project = do_build_kpar(&project, &output_path, compression, canonicalise)?; result.push(kpar_project); } Ok(result) diff --git a/core/src/model.rs b/core/src/model.rs index 88d7dd5b..278c06f9 100644 --- a/core/src/model.rs +++ b/core/src/model.rs @@ -349,6 +349,48 @@ impl KerMlChecksumAlg { } } +#[cfg(feature = "filesystem")] +#[derive(Default, Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "python", pyclass(eq))] +pub enum ZipCompressionMethod { + /// Store the files as is + Stored, + /// Compress the files using Deflate + #[default] + Deflated, + /// Compress the files using BZIP2 + #[cfg(feature = "kpar-bzip2")] + Bzip2, + /// Compress the files using ZStandard + #[cfg(feature = "kpar-zstd")] + Zstd, + /// Compress the files using XZ + #[cfg(feature = "kpar-xz")] + Xz, + /// Compress the files using PPMd + #[cfg(feature = "kpar-ppmd")] + Ppmd +} + +#[cfg(feature = "filesystem")] +impl From for zip::CompressionMethod { + fn from(value: ZipCompressionMethod) -> Self { + match value { + ZipCompressionMethod::Stored => zip::CompressionMethod::Stored, + ZipCompressionMethod::Deflated => zip::CompressionMethod::Deflated, + #[cfg(feature = "kpar-zstd")] + ZipCompressionMethod::Bzip2 => zip::CompressionMethod::Bzip2, + #[cfg(feature = "kpar-zstd")] + ZipCompressionMethod::Zstd => zip::CompressionMethod::Zstd, + #[cfg(feature = "kpar-xz")] + ZipCompressionMethod::Xz => zip::CompressionMethod::Xz, + #[cfg(feature = "kpar-ppmd")] + ZipCompressionMethod::Ppmd => zip::CompressionMethod::Ppmd + } + } +} + + #[derive(Eq, Clone, PartialEq, Serialize, Deserialize, Debug)] #[cfg_attr(feature = "python", derive(FromPyObject, IntoPyObject))] #[serde(rename_all = "camelCase")] diff --git a/core/src/project/local_kpar.rs b/core/src/project/local_kpar.rs index 3705068f..38093d83 100644 --- a/core/src/project/local_kpar.rs +++ b/core/src/project/local_kpar.rs @@ -199,12 +199,13 @@ impl LocalKParProject { pub fn from_project>( from: &Pr, path: P, + compression: zip::CompressionMethod ) -> Result> { let file = wrapfs::File::create(&path)?; let mut zip = zip::ZipWriter::new(file); let options = zip::write::SimpleFileOptions::default() - .compression_method(zip::CompressionMethod::Stored); + .compression_method(compression); let (info, meta) = from.get_project().map_err(IntoKparError::ProjectRead)?; let info = info.ok_or(IntoKparError::MissingInfo)?; diff --git a/docs/src/commands/build.md b/docs/src/commands/build.md index 3dc9f5f7..337e6ad6 100644 --- a/docs/src/commands/build.md +++ b/docs/src/commands/build.md @@ -26,4 +26,13 @@ if none is found uses the current directory instead. `/output/-.kpar` depending on whether the current project belongs to a workspace or not). +## Options + +- `-c`, `--compression`: Method to compress the files in zip archive. + Possible values: + - `stored`: Store the files as is + - `deflated`: Compress the files using Deflate + + [default: `deflated`] + {{#include ./partials/global_opts.md}} diff --git a/sysand/Cargo.toml b/sysand/Cargo.toml index 578b48bb..8601dbca 100644 --- a/sysand/Cargo.toml +++ b/sysand/Cargo.toml @@ -15,6 +15,11 @@ homepage.workspace = true default = ["std"] std = ["anyhow/std", "clap/std", "fluent-uri/std", "log/std", "toml/std", "typed-path/std"] alltests = [] +# Different compression methods for creating kpar +kpar-bzip2 = ["sysand-core/kpar-bzip2"] +kpar-zstd = ["sysand-core/kpar-zstd"] +kpar-xz = ["sysand-core/kpar-xz"] +kpar-ppmd = ["sysand-core/kpar-ppmd"] [dependencies] # General @@ -35,7 +40,7 @@ fluent-uri = { version = "0.4.1", default-features = false } typed-path = { version = "0.12.0", default-features = false } url = { version = "2.5.7", default-features = false } # Enables a bunch of additional compression/encryption features not enabled by default in sysand-core -zip = { version = "6.0.0" } +zip = { version = "6.0.0", default-features = false, features = ["deflate"] } pubgrub = { version = "0.3.0", default-features = false } indexmap = "2.12.1" tokio = { version = "1.48.0", default-features = false } diff --git a/sysand/src/cli.rs b/sysand/src/cli.rs index ccb96c2c..576a0ecd 100644 --- a/sysand/src/cli.rs +++ b/sysand/src/cli.rs @@ -11,6 +11,7 @@ use camino::Utf8PathBuf; use clap::{ValueEnum, builder::StyledStr, crate_authors}; use fluent_uri::Iri; use semver::VersionReq; +use sysand_core::model::ZipCompressionMethod; use crate::env_vars; @@ -161,6 +162,10 @@ pub enum Command { /// on whether the current project belongs to a workspace or not). #[clap(verbatim_doc_comment)] path: Option, + #[clap(verbatim_doc_comment)] + /// Method to compress the files in zip archive + #[arg(short = 'c', long, default_value_t, value_enum)] + compression: ZipCompressionMethodCli }, /// Create or update lockfile Lock { @@ -251,9 +256,67 @@ pub struct ProjectLocatorArgs { pub path: Option, } +#[derive(clap::ValueEnum, Default, Copy, Clone, Debug)] +#[clap(rename_all = "lowercase")] +pub enum ZipCompressionMethodCli { + /// Store the files as is + Stored, + /// Compress the files using Deflate + #[default] + Deflated, + /// Compress the files using BZIP2 + #[cfg(feature = "kpar-bzip2")] + Bzip2, + /// Compress the files using ZStandard + #[cfg(feature = "kpar-zstd")] + Zstd, + /// Compress the files using XZ + #[cfg(feature = "kpar-xz")] + Xz, + /// Compress the files using PPMd + #[cfg(feature = "kpar-ppmd")] + Ppmd +} + +impl From for ZipCompressionMethod{ + fn from(value: ZipCompressionMethodCli) -> Self { + match value { + ZipCompressionMethodCli::Stored => ZipCompressionMethod::Stored, + ZipCompressionMethodCli::Deflated => ZipCompressionMethod::Deflated, + #[cfg(feature = "kpar-zstd")] + ZipCompressionMethodCli::Bzip2 => ZipCompressionMethod::Bzip2, + #[cfg(feature = "kpar-zstd")] + ZipCompressionMethodCli::Zstd => ZipCompressionMethod::Zstd, + #[cfg(feature = "kpar-xz")] + ZipCompressionMethodCli::Xz => ZipCompressionMethod::Xz, + #[cfg(feature = "kpar-ppmd")] + ZipCompressionMethodCli::Ppmd => ZipCompressionMethod::Ppmd + } + } +} + +// This is implemented mainly so that if ZipCompressionMethod gets a new member +// and ZipCompressionMethodCli isn't updated it would give a compilation error +impl From for ZipCompressionMethodCli { + fn from(value: ZipCompressionMethod) -> Self { + match value { + ZipCompressionMethod::Stored => ZipCompressionMethodCli::Stored, + ZipCompressionMethod::Deflated => ZipCompressionMethodCli::Deflated, + #[cfg(feature = "kpar-zstd")] + ZipCompressionMethod::Bzip2 => ZipCompressionMethodCli::Bzip2, + #[cfg(feature = "kpar-zstd")] + ZipCompressionMethod::Zstd => ZipCompressionMethodCli::Zstd, + #[cfg(feature = "kpar-xz")] + ZipCompressionMethod::Xz => ZipCompressionMethodCli::Xz, + #[cfg(feature = "kpar-ppmd")] + ZipCompressionMethod::Ppmd => ZipCompressionMethodCli::Ppmd + } + } +} + #[derive(Clone, Debug)] struct InvalidCommand { - message: String, + message: String } fn invalid_command>(message: S) -> InvalidCommand { diff --git a/sysand/src/commands/build.rs b/sysand/src/commands/build.rs index 65ca1680..522c4ce4 100644 --- a/sysand/src/commands/build.rs +++ b/sysand/src/commands/build.rs @@ -4,22 +4,22 @@ use anyhow::Result; use camino::Utf8Path; use sysand_core::{ - build::{do_build_kpar, do_build_workspace_kpars}, - project::local_src::LocalSrcProject, - workspace::Workspace, + build::{do_build_kpar, do_build_workspace_kpars}, model::ZipCompressionMethod, project::local_src::LocalSrcProject, workspace::Workspace }; pub fn command_build_for_project>( path: P, + compression: ZipCompressionMethod, current_project: LocalSrcProject, ) -> Result<()> { - do_build_kpar(¤t_project, &path, true)?; + do_build_kpar(¤t_project, &path, compression, true)?; Ok(()) } pub fn command_build_for_workspace>( path: P, + compression: ZipCompressionMethod, workspace: Workspace, ) -> Result<()> { log::warn!( @@ -28,7 +28,7 @@ pub fn command_build_for_workspace>( releases. For the status of this feature, see\n\ https://github.com/sensmetry/sysand/issues/101." ); - do_build_workspace_kpars(&workspace, &path, true)?; + do_build_workspace_kpars(&workspace, &path, compression, true)?; Ok(()) } diff --git a/sysand/src/lib.rs b/sysand/src/lib.rs index a97a2b73..b6c308fa 100644 --- a/sysand/src/lib.rs +++ b/sysand/src/lib.rs @@ -588,7 +588,7 @@ pub fn run_cli(args: cli::Args) -> Result<()> { no_index_symbols, } => command_include(paths, add_checksum, !no_index_symbols, current_project), cli::Command::Exclude { paths } => command_exclude(paths, current_project), - cli::Command::Build { path } => { + cli::Command::Build { path, compression } => { if let Some(current_project) = current_project { // Even if we are in a workspace, the project takes precedence. let path = if let Some(path) = path { @@ -606,7 +606,7 @@ pub fn run_cli(args: cli::Args) -> Result<()> { output_dir.push(name); output_dir }; - command_build_for_project(path, current_project) + command_build_for_project(path, compression.into(), current_project) } else { // If the workspace is also missing, report an error about // missing project because that is what the user is more likely @@ -618,7 +618,7 @@ pub fn run_cli(args: cli::Args) -> Result<()> { if !wrapfs::is_dir(&output_dir)? { wrapfs::create_dir(&output_dir)?; } - command_build_for_workspace(output_dir, current_workspace) + command_build_for_workspace(output_dir, compression.into(), current_workspace) } } cli::Command::Sources { sources_opts } => { diff --git a/sysand/tests/cli_build.rs b/sysand/tests/cli_build.rs index 79d5ff7a..41cecd55 100644 --- a/sysand/tests/cli_build.rs +++ b/sysand/tests/cli_build.rs @@ -1,10 +1,12 @@ // SPDX-FileCopyrightText: © 2025 Sysand contributors // SPDX-License-Identifier: MIT OR Apache-2.0 -use std::io::Write; +use std::{io::{Read, Write}}; use assert_cmd::prelude::*; +use clap::ValueEnum; use predicates::prelude::*; +use sysand::cli::ZipCompressionMethodCli; use sysand_core::{ model::{InterchangeProjectChecksumRaw, KerMlChecksumAlg}, project::{ProjectRead, local_kpar::LocalKParProject}, @@ -156,3 +158,71 @@ fn test_workspace_build() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_compression_methods() -> Result<(), Box> { + let compressions = ZipCompressionMethodCli::value_variants(); + test_compression_method(None)?; + for compression in compressions { + test_compression_method(Some(compression.to_possible_value().unwrap().get_name()))?; + } + Ok(()) +} + +fn test_compression_method(compression: Option<&str>) -> Result<(), Box> { + let (_temp_dir, cwd, out) = + run_sysand(["init", "--version", "1.2.3", "--name", "test_build"], None)?; + + { + let mut sysml_file = std::fs::File::create(cwd.join("test.sysml"))?; + sysml_file.write_all(b"package P;\n")?; + } + + out.assert().success(); + + let out = run_sysand_in(&cwd, ["include", "--no-index-symbols", "test.sysml"], None)?; + + out.assert().success(); + + let out = match compression { + Some(compression) => run_sysand_in( + &cwd, + ["build", "--compression", compression, "./test_build.kpar"], + None, + )?, + None => run_sysand_in(&cwd, ["build", "./test_build.kpar"], None)?, + }; + + out.assert().success(); + + let out = run_sysand_in( + &cwd, + ["info", "--path", cwd.join("test_build.kpar").as_str()], + None, + )?; + + out.assert() + .success() + .stdout(predicate::str::contains("Name: test_build")) + .stdout(predicate::str::contains("Version: 1.2.3")); + + let kpar_project = LocalKParProject::new_guess_root(cwd.join("test_build.kpar"))?; + + let (Some(info), Some(meta)) = kpar_project.get_project()? else { + panic!("failed to get built project info/meta"); + }; + + assert_eq!(info.name, "test_build"); + assert_eq!(info.version, "1.2.3"); + + assert_eq!(meta.checksum.as_ref().unwrap().len(), 1); + assert_eq!(meta.index.len(), 1); + assert_eq!(meta.index.get("P").unwrap(), "test.sysml"); + let mut src = "".to_string(); + kpar_project + .read_source("test.sysml")? + .read_to_string(&mut src)?; + + assert_eq!(src, "package P;\n"); + Ok(()) +} From 0d61454028aed9bb5a3eb7c8e79346ee78a777ba Mon Sep 17 00:00:00 2001 From: "jonas.puksta.sensmetry" Date: Fri, 27 Feb 2026 16:30:01 +0200 Subject: [PATCH 02/16] add python and java bindings Signed-off-by: jonas.puksta.sensmetry --- .github/workflows/test.yml | 4 +- .vscode/settings.json | 21 ++++--- DEVELOPMENT.md | 4 +- bindings/java/Cargo.toml | 6 ++ .../java/com/sensmetry/sysand/BasicTest.java | 25 ++++++++ .../java/com/sensmetry/sysand/Sysand.java | 12 ++-- .../sysand/model/CompressionMethod.java | 16 +++++ .../org/sysand/maven/SysandBuildKParMojo.java | 21 +++++-- bindings/java/src/lib.rs | 38 ++++++++++-- bindings/py/Cargo.toml | 4 ++ bindings/py/python/sysand/__init__.py | 2 + bindings/py/python/sysand/_build.py | 12 +++- bindings/py/python/sysand/_model.py | 17 ++++++ bindings/py/src/lib.rs | 16 ++++- bindings/py/tests/test_basic.py | 30 ++++++++- core/src/model.rs | 61 ++++++++++++++++++- 16 files changed, 255 insertions(+), 34 deletions(-) create mode 100644 bindings/java/java/src/main/java/com/sensmetry/sysand/model/CompressionMethod.java diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6e7af381..8bdc9563 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -43,9 +43,9 @@ jobs: git config --global user.email "user@sysand.org" git config --global user.name "Test User" - name: Test Core - run: cargo test --package sysand-core --verbose --features filesystem,js,python,alltests + run: cargo test --package sysand-core --verbose --features filesystem,js,python,alltests,kpar-bzip2,kpar-zstd,kpar-xz,kpar-ppmd - name: Test CLI - run: cargo test --package sysand --verbose --features alltests + run: cargo test --package sysand --verbose --features alltests,kpar-bzip2,kpar-zstd,kpar-xz,kpar-ppmd build: strategy: diff --git a/.vscode/settings.json b/.vscode/settings.json index 3546edc2..ebd05564 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,14 +1,21 @@ { "cSpell.words": [ - "sysand", - "sysml", "kerml", "kpar", - "metamodel", "mdbook", - "thiserror", - "reqwest", + "metamodel", + "Ppmd", "pubgrub", - "pycache" - ], + "pycache", + "pyerr", + "pyfunction", + "pymodule", + "reqwest", + "strs", + "sysand", + "sysml", + "thiserror", + "werr", + "wrapfs" + ] } \ No newline at end of file diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index d924c6d7..952f650b 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -87,8 +87,8 @@ cargo build --release # optimized Run tests for main Rust crates. This excludes language bindings, because they have their own test suites: ```sh -cargo test -p sysand-core -F filesystem,js,python,alltests -cargo test -p sysand -F alltests +cargo test -p sysand-core -F filesystem,js,python,alltests,kpar-bzip2,kpar-zstd,kpar-xz,kpar-ppmd +cargo test -p sysand -F alltests,kpar-bzip2,kpar-zstd,kpar-xz,kpar-ppmd ``` Run tests for all crates and language bindings (requires bindings dependencies): diff --git a/bindings/java/Cargo.toml b/bindings/java/Cargo.toml index 840b63cf..6b67ee4f 100644 --- a/bindings/java/Cargo.toml +++ b/bindings/java/Cargo.toml @@ -15,6 +15,12 @@ homepage.workspace = true name = "sysand" crate-type = ["cdylib"] +[features] +kpar-bzip2 = ["sysand-core/kpar-bzip2"] +kpar-zstd = ["sysand-core/kpar-zstd"] +kpar-xz = ["sysand-core/kpar-xz"] +kpar-ppmd = ["sysand-core/kpar-ppmd"] + [dependencies] sysand-core = { path = "../../core", features = ["std", "filesystem", "networking"] } camino.workspace = true diff --git a/bindings/java/java-test/src/test/java/com/sensmetry/sysand/BasicTest.java b/bindings/java/java-test/src/test/java/com/sensmetry/sysand/BasicTest.java index c720d423..a9951710 100644 --- a/bindings/java/java-test/src/test/java/com/sensmetry/sysand/BasicTest.java +++ b/bindings/java/java-test/src/test/java/com/sensmetry/sysand/BasicTest.java @@ -6,6 +6,8 @@ import org.junit.jupiter.api.Test; +import com.sensmetry.sysand.model.CompressionMethod; + import static org.junit.jupiter.api.Assertions.*; import java.util.regex.Pattern; @@ -108,6 +110,29 @@ public void testBasicInfo() { } } + @Test + public void testProjectBuild() { + try { + java.nio.file.Path tempDir = java.nio.file.Files.createTempDirectory("sysand-test-build"); + com.sensmetry.sysand.Sysand.init("test_basic_info", "1.2.3", "MIT", tempDir); + + com.sensmetry.sysand.model.InterchangeProject project = com.sensmetry.sysand.Sysand.infoPath(tempDir); + assertExpectedProject(project); + + java.net.URI fileUri = tempDir.toUri(); + com.sensmetry.sysand.model.InterchangeProject[] projects = com.sensmetry.sysand.Sysand.info(fileUri, + tempDir); + assertEquals(projects.length, 1); + assertExpectedProject(projects[0]); + + com.sensmetry.sysand.Sysand.buildProject(tempDir.resolve("sysand-test-build.kpar"), tempDir, CompressionMethod.DEFLATED); + } catch (java.io.IOException e) { + fail("Failed during temporary directory operations or Sysand.info: " + e.getMessage()); + } catch (com.sensmetry.sysand.exceptions.SysandException e) { + fail("Failed during temporary directory operations or Sysand.info: " + e.getMessage()); + } + } + @Test public void testHttpInfo() { // TODO: Find a good mock server so that we can test this. diff --git a/bindings/java/java/src/main/java/com/sensmetry/sysand/Sysand.java b/bindings/java/java/src/main/java/com/sensmetry/sysand/Sysand.java index 7c2c7179..8da7b520 100644 --- a/bindings/java/java/src/main/java/com/sensmetry/sysand/Sysand.java +++ b/bindings/java/java/src/main/java/com/sensmetry/sysand/Sysand.java @@ -152,7 +152,7 @@ public static com.sensmetry.sysand.model.InterchangeProject[] info(java.net.URI * @param outputPath The path to the output file. * @param projectPath The path to the project. */ - public static native void buildProject(String outputPath, String projectPath) + private static native void buildProject(String outputPath, String projectPath, String compression) throws com.sensmetry.sysand.exceptions.SysandException; /** @@ -162,9 +162,9 @@ public static native void buildProject(String outputPath, String projectPath) * @param outputPath The path to the output file. * @param projectPath The path to the project. */ - public static void buildProject(java.nio.file.Path outputPath, java.nio.file.Path projectPath) + public static void buildProject(java.nio.file.Path outputPath, java.nio.file.Path projectPath, com.sensmetry.sysand.model.CompressionMethod compression) throws com.sensmetry.sysand.exceptions.SysandException { - buildProject(outputPath.toString(), projectPath.toString()); + buildProject(outputPath.toString(), projectPath.toString(), compression.toString()); } /** @@ -174,7 +174,7 @@ public static void buildProject(java.nio.file.Path outputPath, java.nio.file.Pat * @param outputPath The path to the output file. * @param workspacePath The path to the workspace. */ - public static native void buildWorkspace(String outputPath, String workspacePath) + private static native void buildWorkspace(String outputPath, String workspacePath, String compression) throws com.sensmetry.sysand.exceptions.SysandException; /** @@ -184,8 +184,8 @@ public static native void buildWorkspace(String outputPath, String workspacePath * @param outputPath The path to the output file. * @param workspacePath The path to the workspace. */ - public static void buildWorkspace(java.nio.file.Path outputPath, java.nio.file.Path workspacePath) + public static void buildWorkspace(java.nio.file.Path outputPath, java.nio.file.Path workspacePath, com.sensmetry.sysand.model.CompressionMethod compression) throws com.sensmetry.sysand.exceptions.SysandException { - buildWorkspace(outputPath.toString(), workspacePath.toString()); + buildWorkspace(outputPath.toString(), workspacePath.toString(), compression.toString()); } } diff --git a/bindings/java/java/src/main/java/com/sensmetry/sysand/model/CompressionMethod.java b/bindings/java/java/src/main/java/com/sensmetry/sysand/model/CompressionMethod.java new file mode 100644 index 00000000..9e211c14 --- /dev/null +++ b/bindings/java/java/src/main/java/com/sensmetry/sysand/model/CompressionMethod.java @@ -0,0 +1,16 @@ +package com.sensmetry.sysand.model; + +public enum CompressionMethod { + // Store the files as is + STORED, + // Compress the files using Deflate + DEFLATED, + /// Compress the files using BZIP2. Only available when sysand is compiled with feature kpar-bzip2 + BZIP2, + /// Compress the files using ZStandard. Only available when sysand is compiled with feature kpar-zstd + ZSTD, + /// Compress the files using XZ. Only available when sysand is compiled with feature kpar-xz + XZ, + /// Compress the files using PPMd. Only available when sysand is compiled with feature kpar-ppmd + PPMD, +} diff --git a/bindings/java/plugin/src/main/java/org/sysand/maven/SysandBuildKParMojo.java b/bindings/java/plugin/src/main/java/org/sysand/maven/SysandBuildKParMojo.java index f272cefe..c08a3ab1 100644 --- a/bindings/java/plugin/src/main/java/org/sysand/maven/SysandBuildKParMojo.java +++ b/bindings/java/plugin/src/main/java/org/sysand/maven/SysandBuildKParMojo.java @@ -4,12 +4,16 @@ package org.sysand.maven; +import java.nio.file.Paths; + import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; +import com.sensmetry.sysand.model.CompressionMethod; + @Mojo(name = "build-kpar", defaultPhase = LifecyclePhase.PACKAGE, threadSafe = false) public class SysandBuildKParMojo extends AbstractMojo { @@ -37,6 +41,14 @@ public class SysandBuildKParMojo extends AbstractMojo { @Parameter(property = "sysand.outputPath", required = true) private String outputPath; + /** + * Kpar compression method. Can be configured as + * {@code ...} or + * via {@code -Dsysand.compressionMethod=...}. + */ + @Parameter(property = "sysand.compressionMethod", required = true) + private String compressionMethod; + @Override public void execute() throws MojoExecutionException { if (projectPath == null && workspacePath == null) { @@ -46,15 +58,16 @@ public void execute() throws MojoExecutionException { if (outputPath == null || outputPath.trim().isEmpty()) { throw new MojoExecutionException("Parameter 'outputPath' must be provided and non-empty"); } + CompressionMethod compression = CompressionMethod.valueOf(compressionMethod.toUpperCase()); try { if (workspacePath == null) { - getLog().info("Invoking Sysand.buildProject on: " + projectPath + " to " + outputPath); - com.sensmetry.sysand.Sysand.buildProject(outputPath, projectPath); + getLog().info("Invoking Sysand.buildProject on: " + projectPath + " to " + outputPath + " with compression " + compressionMethod); + com.sensmetry.sysand.Sysand.buildProject(Paths.get(outputPath), Paths.get(projectPath), compression); getLog().info("Sysand.buildProject completed successfully."); } else { - getLog().info("Invoking Sysand.buildWorkspace on: " + workspacePath + " to " + outputPath); - com.sensmetry.sysand.Sysand.buildWorkspace(outputPath, workspacePath); + getLog().info("Invoking Sysand.buildWorkspace on: " + workspacePath + " to " + outputPath + " with compression " + compressionMethod); + com.sensmetry.sysand.Sysand.buildWorkspace(Paths.get(outputPath), Paths.get(workspacePath), compression); getLog().info("Sysand.buildWorkspace completed successfully."); } } catch (com.sensmetry.sysand.exceptions.SysandException e) { diff --git a/bindings/java/src/lib.rs b/bindings/java/src/lib.rs index 5218c2a9..929a6fe0 100644 --- a/bindings/java/src/lib.rs +++ b/bindings/java/src/lib.rs @@ -16,6 +16,7 @@ use sysand_core::{ env::local_directory::{self, LocalWriteError}, info::InfoError, init::InitError, + model::ZipCompressionMethod, project::{ local_src::{LocalSrcError, LocalSrcProject}, utils::wrapfs, @@ -321,13 +322,26 @@ fn handle_build_error(env: &mut JNIEnv<'_>, error: KParBuildError } } +fn compression_from_java_string( + env: &mut JNIEnv<'_>, + compression: String, +) -> Option { + match ZipCompressionMethod::try_from(compression) { + Ok(compression) => Some(compression), + Err(err) => { + env.throw_exception(ExceptionKind::SysandException, err.0); + None + } + } +} + #[unsafe(no_mangle)] pub extern "system" fn Java_com_sensmetry_sysand_Sysand_buildProject<'local>( mut env: JNIEnv<'local>, _class: JClass<'local>, output_path: JString<'local>, project_path: JString<'local>, - compression: JString<'local> + compression: JString<'local>, ) { let Some(output_path) = env.get_str(&output_path, "outputPath") else { return; @@ -342,7 +356,11 @@ pub extern "system" fn Java_com_sensmetry_sysand_Sysand_buildProject<'local>( let Some(compression) = env.get_str(&compression, "compression") else { return; }; - let command_result = sysand_core::commands::build::do_build_kpar(&project, &output_path, compression, true); + let Some(compression) = compression_from_java_string(&mut env, compression) else { + return; + }; + let command_result = + sysand_core::commands::build::do_build_kpar(&project, &output_path, compression, true); match command_result { Ok(_) => {} Err(error) => handle_build_error(&mut env, error), @@ -355,6 +373,7 @@ pub extern "system" fn Java_com_sensmetry_sysand_Sysand_buildWorkspace<'local>( _class: JClass<'local>, output_path: JString<'local>, workspace_path: JString<'local>, + compression: JString<'local>, ) { let Some(output_path) = env.get_str(&output_path, "outputPath") else { return; @@ -365,6 +384,12 @@ pub extern "system" fn Java_com_sensmetry_sysand_Sysand_buildWorkspace<'local>( let workspace = Workspace { workspace_path: Utf8PathBuf::from(workspace_path), }; + let Some(compression) = env.get_str(&compression, "compression") else { + return; + }; + let Some(compression) = compression_from_java_string(&mut env, compression) else { + return; + }; match wrapfs::create_dir_all(&output_path) { Ok(_) => {} Err(error) => { @@ -372,8 +397,13 @@ pub extern "system" fn Java_com_sensmetry_sysand_Sysand_buildWorkspace<'local>( return; } } - let command_result = - sysand_core::commands::build::do_build_workspace_kpars(&workspace, &output_path, true); + + let command_result = sysand_core::commands::build::do_build_workspace_kpars( + &workspace, + &output_path, + compression, + true, + ); match command_result { Ok(_) => {} Err(error) => handle_build_error(&mut env, error), diff --git a/bindings/py/Cargo.toml b/bindings/py/Cargo.toml index 8580bbe2..2ba37edb 100644 --- a/bindings/py/Cargo.toml +++ b/bindings/py/Cargo.toml @@ -15,6 +15,10 @@ homepage.workspace = true default = ["extension-module", "abi3"] extension-module = ["pyo3/extension-module"] abi3 = ["pyo3/abi3", "pyo3/abi3-py38"] +kpar-bzip2 = ["sysand-core/kpar-bzip2"] +kpar-zstd = ["sysand-core/kpar-zstd"] +kpar-xz = ["sysand-core/kpar-xz"] +kpar-ppmd = ["sysand-core/kpar-ppmd"] [dependencies] sysand-core = { path = "../../core", features = ["python", "filesystem", "networking"] } diff --git a/bindings/py/python/sysand/__init__.py b/bindings/py/python/sysand/__init__.py index ecf42c76..b1897bc8 100644 --- a/bindings/py/python/sysand/__init__.py +++ b/bindings/py/python/sysand/__init__.py @@ -7,6 +7,7 @@ InterchangeProjectInfo, InterchangeProjectChecksum, InterchangeProjectMetadata, + CompressionMethod, ) from ._info import info_path, info @@ -47,6 +48,7 @@ "InterchangeProjectInfo", "InterchangeProjectChecksum", "InterchangeProjectMetadata", + "CompressionMethod", ## Add "add", ## Remove diff --git a/bindings/py/python/sysand/_build.py b/bindings/py/python/sysand/_build.py index 2397ad83..ed49cff3 100644 --- a/bindings/py/python/sysand/_build.py +++ b/bindings/py/python/sysand/_build.py @@ -1,14 +1,22 @@ from __future__ import annotations +from sysand._model import CompressionMethod import sysand._sysand_core as sysand_rs # type: ignore from pathlib import Path -def build(output_path: str | Path, project_path: str | Path | None = None) -> None: +def build( + output_path: str | Path, + project_path: str | Path | None = None, + compression: CompressionMethod | None = None, +) -> None: if project_path is not None: project_path = str(project_path) - sysand_rs.do_build_py(str(output_path), project_path) + + # comp = None if compression is None else _convert_compression(compression) + comp = None if compression is None else compression.name + sysand_rs.do_build_py(str(output_path), project_path, comp) __all__ = [ diff --git a/bindings/py/python/sysand/_model.py b/bindings/py/python/sysand/_model.py index 4e84cae6..87a6651c 100644 --- a/bindings/py/python/sysand/_model.py +++ b/bindings/py/python/sysand/_model.py @@ -2,6 +2,7 @@ # # SPDX-License-Identifier: MIT OR Apache-2.0 +from enum import Enum, auto import typing import datetime @@ -36,9 +37,25 @@ class InterchangeProjectMetadata(typing.TypedDict): checksum: typing.Optional[typing.List[InterchangeProjectChecksum]] +class CompressionMethod(Enum): + STORED = auto() + """Store the files as is""" + DEFLATED = auto() + """Compress the files using Deflate""" + BZIP2 = auto() + """Compress the files using BZIP2. Only available when sysand is compiled with feature kpar-bzip2""" + ZSTD = auto() + """Compress the files using ZStandard. Only available when sysand is compiled with feature kpar-zstd""" + XZ = auto() + """Compress the files using XZ. Only available when sysand is compiled with feature kpar-xz""" + PPMD = auto() + """Compress the files using PPMd. Only available when sysand is compiled with feature kpar-ppmd""" + + __all__ = [ "InterchangeProjectUsage", "InterchangeProjectInfo", "InterchangeProjectChecksum", "InterchangeProjectMetadata", + "CompressionMethod", ] diff --git a/bindings/py/src/lib.rs b/bindings/py/src/lib.rs index e67b5e48..4d7591c2 100644 --- a/bindings/py/src/lib.rs +++ b/bindings/py/src/lib.rs @@ -179,7 +179,11 @@ fn do_info_py( #[pyo3( signature = (output_path, project_path, compression), )] -fn do_build_py(output_path: String, project_path: Option, compression: ZipCompressionMethod) -> PyResult<()> { +fn do_build_py( + output_path: String, + project_path: Option, + compression: Option, +) -> PyResult<()> { let _ = pyo3_log::try_init(); let Some(current_project_path) = project_path else { @@ -190,6 +194,14 @@ fn do_build_py(output_path: String, project_path: Option, compression: Z project_path: current_project_path.into(), }; + let compression = match compression { + Some(compression) => match ZipCompressionMethod::try_from(compression) { + Ok(compression) => compression, + Err(err) => return Err(PyValueError::new_err(err.0)), + }, + None => ZipCompressionMethod::default(), + }; + do_build_kpar(&project, &output_path, compression, true) .map(|_| ()) .map_err(|err| match err { @@ -564,6 +576,8 @@ pub fn sysand_py(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(do_include_py, m)?)?; m.add_function(wrap_pyfunction!(do_exclude_py, m)?)?; m.add_function(wrap_pyfunction!(do_env_install_path_py, m)?)?; + // Currently this interop is done with strings instead + // m.add_class::()?; m.add("DEFAULT_ENV_NAME", DEFAULT_ENV_NAME)?; Ok(()) diff --git a/bindings/py/tests/test_basic.py b/bindings/py/tests/test_basic.py index 7f0eb8e7..50a8009a 100644 --- a/bindings/py/tests/test_basic.py +++ b/bindings/py/tests/test_basic.py @@ -3,7 +3,7 @@ from pathlib import Path import re import os -from typing import List +from typing import List, Union import pytest from pytest_httpserver import HTTPServer @@ -170,7 +170,10 @@ def test_index_info(caplog: pytest.LogCaptureFixture, httpserver: HTTPServer) -> assert meta["checksum"] is None -def compare_sources(sources: List[str], expected_sources: List[str]) -> None: +def compare_sources( + sources: Union[List[Path], List[str]], + expected_sources: Union[List[Path], List[str]], +) -> None: assert len(sources) == len(expected_sources) for source, expected_source in zip(sources, expected_sources): assert os.path.samefile(source, expected_source), ( @@ -178,7 +181,7 @@ def compare_sources(sources: List[str], expected_sources: List[str]) -> None: ) -def test_end_to_end_install_sources(): +def test_end_to_end_install_sources() -> None: with tempfile.TemporaryDirectory() as tmp_main: with tempfile.TemporaryDirectory() as tmp_dep: tmp_main = Path(tmp_main).resolve() @@ -242,3 +245,24 @@ def test_end_to_end_install_sources(): ), ], ) + + +@pytest.mark.parametrize( + "compression", + [None, sysand.CompressionMethod.STORED, sysand.CompressionMethod.DEFLATED], +) +def test_build(compression: Union[sysand.CompressionMethod, None]) -> None: + with tempfile.TemporaryDirectory() as tmp_main: + tmp_main = Path(tmp_main).resolve() + sysand.new("test_build", "1.2.3", tmp_main) + + with open(tmp_main / "src.sysml", "w") as f: + f.write("package Src;") + + sysand.include(tmp_main, "src.sysml") + + sysand.build( + output_path=tmp_main / "test_build.kpar", + project_path=tmp_main, + compression=compression, + ) diff --git a/core/src/model.rs b/core/src/model.rs index 278c06f9..35d6ab9b 100644 --- a/core/src/model.rs +++ b/core/src/model.rs @@ -351,7 +351,11 @@ impl KerMlChecksumAlg { #[cfg(feature = "filesystem")] #[derive(Default, Copy, Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "python", pyclass(eq))] +// Currently python interop is done with strings instead +// in part to have less boilerplate, in part because the old +// Python we use doesn't have pattern matching which ensures +// all cases are covered +// #[cfg_attr(feature = "python", pyclass(eq))] pub enum ZipCompressionMethod { /// Store the files as is Stored, @@ -369,7 +373,7 @@ pub enum ZipCompressionMethod { Xz, /// Compress the files using PPMd #[cfg(feature = "kpar-ppmd")] - Ppmd + Ppmd, } #[cfg(feature = "filesystem")] @@ -385,11 +389,62 @@ impl From for zip::CompressionMethod { #[cfg(feature = "kpar-xz")] ZipCompressionMethod::Xz => zip::CompressionMethod::Xz, #[cfg(feature = "kpar-ppmd")] - ZipCompressionMethod::Ppmd => zip::CompressionMethod::Ppmd + ZipCompressionMethod::Ppmd => zip::CompressionMethod::Ppmd, } } } +#[cfg(feature = "filesystem")] +#[derive(Debug, Error)] +#[error("failed to parse checksum algorithm")] +pub struct CompressionMethodParseError(pub String); + +#[cfg(feature = "filesystem")] +impl TryFrom for ZipCompressionMethod { + type Error = CompressionMethodParseError; + + fn try_from(value: String) -> Result { + Self::try_from(value.as_str()) + } +} + +#[cfg(feature = "filesystem")] +impl TryFrom<&str> for ZipCompressionMethod { + type Error = CompressionMethodParseError; + fn try_from(value: &str) -> Result { + match value { + "STORED" => Ok(ZipCompressionMethod::Stored), + "DEFLATED" => Ok(ZipCompressionMethod::Deflated), + #[cfg(feature = "kpar-bzip2")] + "BZIP2" => Ok(ZipCompressionMethod::Bzip2), + #[cfg(not(feature = "kpar-bzip2"))] + "BZIP2" => { + Err(CompressionMethodParseError("Compile sysand with feature kpar-bzip2 to use BZIP2 compression".to_string())) + } + #[cfg(feature = "kpar-zstd")] + "ZSTD" => Ok(ZipCompressionMethod::Zstd), + #[cfg(not(feature = "kpar-zstd"))] + "ZSTD" => { + Err(CompressionMethodParseError("Compile sysand with feature kpar-zstd to use ZSTD compression".to_string())) + }, + #[cfg(feature = "kpar-xz")] + "XZ" => Ok(ZipCompressionMethod::Xz), + #[cfg(not(feature = "kpar-xz"))] + "XZ" => { + Err(CompressionMethodParseError("Compile sysand with feature kpar-xz to use XZ compression".to_string())) + } + #[cfg(feature = "kpar-ppmd")] + "PPMD" => Ok(ZipCompressionMethod::Ppmd), + #[cfg(not(feature = "kpar-ppmd"))] + "PPMD" => { + Err(CompressionMethodParseError("Compile sysand with feature kpar-ppmd to use PPMD compression".to_string())) + } + _ => { + Err(CompressionMethodParseError(format!("Compression method {value} is invalid"))) + } + } + } +} #[derive(Eq, Clone, PartialEq, Serialize, Deserialize, Debug)] #[cfg_attr(feature = "python", derive(FromPyObject, IntoPyObject))] From 13296b144775818ac9d928d12e8d41c51029573d Mon Sep 17 00:00:00 2001 From: "jonas.puksta.sensmetry" Date: Fri, 27 Feb 2026 16:48:14 +0200 Subject: [PATCH 03/16] fix incorrect feature gating Signed-off-by: jonas.puksta.sensmetry --- core/src/model.rs | 2 +- sysand/src/cli.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/model.rs b/core/src/model.rs index 35d6ab9b..03b54ef4 100644 --- a/core/src/model.rs +++ b/core/src/model.rs @@ -382,7 +382,7 @@ impl From for zip::CompressionMethod { match value { ZipCompressionMethod::Stored => zip::CompressionMethod::Stored, ZipCompressionMethod::Deflated => zip::CompressionMethod::Deflated, - #[cfg(feature = "kpar-zstd")] + #[cfg(feature = "kpar-bzip2")] ZipCompressionMethod::Bzip2 => zip::CompressionMethod::Bzip2, #[cfg(feature = "kpar-zstd")] ZipCompressionMethod::Zstd => zip::CompressionMethod::Zstd, diff --git a/sysand/src/cli.rs b/sysand/src/cli.rs index 576a0ecd..0cc598f5 100644 --- a/sysand/src/cli.rs +++ b/sysand/src/cli.rs @@ -283,7 +283,7 @@ impl From for ZipCompressionMethod{ match value { ZipCompressionMethodCli::Stored => ZipCompressionMethod::Stored, ZipCompressionMethodCli::Deflated => ZipCompressionMethod::Deflated, - #[cfg(feature = "kpar-zstd")] + #[cfg(feature = "kpar-bzip2")] ZipCompressionMethodCli::Bzip2 => ZipCompressionMethod::Bzip2, #[cfg(feature = "kpar-zstd")] ZipCompressionMethodCli::Zstd => ZipCompressionMethod::Zstd, @@ -302,7 +302,7 @@ impl From for ZipCompressionMethodCli { match value { ZipCompressionMethod::Stored => ZipCompressionMethodCli::Stored, ZipCompressionMethod::Deflated => ZipCompressionMethodCli::Deflated, - #[cfg(feature = "kpar-zstd")] + #[cfg(feature = "kpar-bzip2")] ZipCompressionMethod::Bzip2 => ZipCompressionMethodCli::Bzip2, #[cfg(feature = "kpar-zstd")] ZipCompressionMethod::Zstd => ZipCompressionMethodCli::Zstd, From a4e93dbbfb514eaf4cec49d0e3226727049957c3 Mon Sep 17 00:00:00 2001 From: "jonas.puksta.sensmetry" Date: Fri, 27 Feb 2026 16:59:14 +0200 Subject: [PATCH 04/16] fix bindings Cargo.toml file features Signed-off-by: jonas.puksta.sensmetry --- bindings/java/Cargo.toml | 8 ++++---- bindings/py/Cargo.toml | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/bindings/java/Cargo.toml b/bindings/java/Cargo.toml index 6b67ee4f..c5549690 100644 --- a/bindings/java/Cargo.toml +++ b/bindings/java/Cargo.toml @@ -16,10 +16,10 @@ name = "sysand" crate-type = ["cdylib"] [features] -kpar-bzip2 = ["sysand-core/kpar-bzip2"] -kpar-zstd = ["sysand-core/kpar-zstd"] -kpar-xz = ["sysand-core/kpar-xz"] -kpar-ppmd = ["sysand-core/kpar-ppmd"] +kpar-bzip2 = ["sysand-core/kpar-bzip2", "sysand/kpar-bzip2"] +kpar-zstd = ["sysand-core/kpar-zstd", "sysand/kpar-zstd"] +kpar-xz = ["sysand-core/kpar-xz", "sysand/kpar-xz"] +kpar-ppmd = ["sysand-core/kpar-ppmd", "sysand/kpar-ppmd"] [dependencies] sysand-core = { path = "../../core", features = ["std", "filesystem", "networking"] } diff --git a/bindings/py/Cargo.toml b/bindings/py/Cargo.toml index 2ba37edb..5d5bd528 100644 --- a/bindings/py/Cargo.toml +++ b/bindings/py/Cargo.toml @@ -15,10 +15,10 @@ homepage.workspace = true default = ["extension-module", "abi3"] extension-module = ["pyo3/extension-module"] abi3 = ["pyo3/abi3", "pyo3/abi3-py38"] -kpar-bzip2 = ["sysand-core/kpar-bzip2"] -kpar-zstd = ["sysand-core/kpar-zstd"] -kpar-xz = ["sysand-core/kpar-xz"] -kpar-ppmd = ["sysand-core/kpar-ppmd"] +kpar-bzip2 = ["sysand-core/kpar-bzip2", "sysand/kpar-bzip2"] +kpar-zstd = ["sysand-core/kpar-zstd", "sysand/kpar-zstd"] +kpar-xz = ["sysand-core/kpar-xz", "sysand/kpar-xz"] +kpar-ppmd = ["sysand-core/kpar-ppmd", "sysand/kpar-ppmd"] [dependencies] sysand-core = { path = "../../core", features = ["python", "filesystem", "networking"] } From 04f2953a4cf7ca23c87396a79a6f22eb5c9b97a6 Mon Sep 17 00:00:00 2001 From: "jonas.puksta.sensmetry" Date: Fri, 27 Feb 2026 17:00:42 +0200 Subject: [PATCH 05/16] fix java bindings Cargo.toml Signed-off-by: jonas.puksta.sensmetry --- bindings/java/Cargo.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bindings/java/Cargo.toml b/bindings/java/Cargo.toml index c5549690..6b67ee4f 100644 --- a/bindings/java/Cargo.toml +++ b/bindings/java/Cargo.toml @@ -16,10 +16,10 @@ name = "sysand" crate-type = ["cdylib"] [features] -kpar-bzip2 = ["sysand-core/kpar-bzip2", "sysand/kpar-bzip2"] -kpar-zstd = ["sysand-core/kpar-zstd", "sysand/kpar-zstd"] -kpar-xz = ["sysand-core/kpar-xz", "sysand/kpar-xz"] -kpar-ppmd = ["sysand-core/kpar-ppmd", "sysand/kpar-ppmd"] +kpar-bzip2 = ["sysand-core/kpar-bzip2"] +kpar-zstd = ["sysand-core/kpar-zstd"] +kpar-xz = ["sysand-core/kpar-xz"] +kpar-ppmd = ["sysand-core/kpar-ppmd"] [dependencies] sysand-core = { path = "../../core", features = ["std", "filesystem", "networking"] } From 8adce14ee112becb4a86108aa686c8a137127e71 Mon Sep 17 00:00:00 2001 From: "jonas.puksta.sensmetry" Date: Fri, 27 Feb 2026 17:10:19 +0200 Subject: [PATCH 06/16] formatting Signed-off-by: jonas.puksta.sensmetry --- core/src/commands/build.rs | 10 ++++++++-- core/src/model.rs | 30 +++++++++++++++--------------- core/src/project/local_kpar.rs | 5 ++--- sysand/src/cli.rs | 12 ++++++------ sysand/src/commands/build.rs | 5 ++++- sysand/tests/cli_build.rs | 2 +- 6 files changed, 36 insertions(+), 28 deletions(-) diff --git a/core/src/commands/build.rs b/core/src/commands/build.rs index f5bd7cec..3f33a20b 100644 --- a/core/src/commands/build.rs +++ b/core/src/commands/build.rs @@ -14,7 +14,9 @@ use crate::{ workspace::WorkspaceReadError, }; #[cfg(feature = "filesystem")] -use crate::{model::ZipCompressionMethod, project::local_src::LocalSrcProject, workspace::Workspace}; +use crate::{ + model::ZipCompressionMethod, project::local_src::LocalSrcProject, workspace::Workspace, +}; use super::include::IncludeError; @@ -153,7 +155,11 @@ pub fn do_build_kpar, Pr: ProjectRead>( } } - Ok(LocalKParProject::from_project(&local_project, path, compression.into())?) + Ok(LocalKParProject::from_project( + &local_project, + path, + compression.into(), + )?) } #[cfg(feature = "filesystem")] diff --git a/core/src/model.rs b/core/src/model.rs index 03b54ef4..f22cf7d0 100644 --- a/core/src/model.rs +++ b/core/src/model.rs @@ -418,30 +418,30 @@ impl TryFrom<&str> for ZipCompressionMethod { #[cfg(feature = "kpar-bzip2")] "BZIP2" => Ok(ZipCompressionMethod::Bzip2), #[cfg(not(feature = "kpar-bzip2"))] - "BZIP2" => { - Err(CompressionMethodParseError("Compile sysand with feature kpar-bzip2 to use BZIP2 compression".to_string())) - } + "BZIP2" => Err(CompressionMethodParseError( + "Compile sysand with feature kpar-bzip2 to use BZIP2 compression".to_string(), + )), #[cfg(feature = "kpar-zstd")] "ZSTD" => Ok(ZipCompressionMethod::Zstd), #[cfg(not(feature = "kpar-zstd"))] - "ZSTD" => { - Err(CompressionMethodParseError("Compile sysand with feature kpar-zstd to use ZSTD compression".to_string())) - }, + "ZSTD" => Err(CompressionMethodParseError( + "Compile sysand with feature kpar-zstd to use ZSTD compression".to_string(), + )), #[cfg(feature = "kpar-xz")] "XZ" => Ok(ZipCompressionMethod::Xz), #[cfg(not(feature = "kpar-xz"))] - "XZ" => { - Err(CompressionMethodParseError("Compile sysand with feature kpar-xz to use XZ compression".to_string())) - } + "XZ" => Err(CompressionMethodParseError( + "Compile sysand with feature kpar-xz to use XZ compression".to_string(), + )), #[cfg(feature = "kpar-ppmd")] "PPMD" => Ok(ZipCompressionMethod::Ppmd), #[cfg(not(feature = "kpar-ppmd"))] - "PPMD" => { - Err(CompressionMethodParseError("Compile sysand with feature kpar-ppmd to use PPMD compression".to_string())) - } - _ => { - Err(CompressionMethodParseError(format!("Compression method {value} is invalid"))) - } + "PPMD" => Err(CompressionMethodParseError( + "Compile sysand with feature kpar-ppmd to use PPMD compression".to_string(), + )), + _ => Err(CompressionMethodParseError(format!( + "Compression method {value} is invalid" + ))), } } } diff --git a/core/src/project/local_kpar.rs b/core/src/project/local_kpar.rs index 38093d83..10506ed7 100644 --- a/core/src/project/local_kpar.rs +++ b/core/src/project/local_kpar.rs @@ -199,13 +199,12 @@ impl LocalKParProject { pub fn from_project>( from: &Pr, path: P, - compression: zip::CompressionMethod + compression: zip::CompressionMethod, ) -> Result> { let file = wrapfs::File::create(&path)?; let mut zip = zip::ZipWriter::new(file); - let options = zip::write::SimpleFileOptions::default() - .compression_method(compression); + let options = zip::write::SimpleFileOptions::default().compression_method(compression); let (info, meta) = from.get_project().map_err(IntoKparError::ProjectRead)?; let info = info.ok_or(IntoKparError::MissingInfo)?; diff --git a/sysand/src/cli.rs b/sysand/src/cli.rs index 0cc598f5..f5479fd2 100644 --- a/sysand/src/cli.rs +++ b/sysand/src/cli.rs @@ -165,7 +165,7 @@ pub enum Command { #[clap(verbatim_doc_comment)] /// Method to compress the files in zip archive #[arg(short = 'c', long, default_value_t, value_enum)] - compression: ZipCompressionMethodCli + compression: ZipCompressionMethodCli, }, /// Create or update lockfile Lock { @@ -275,10 +275,10 @@ pub enum ZipCompressionMethodCli { Xz, /// Compress the files using PPMd #[cfg(feature = "kpar-ppmd")] - Ppmd + Ppmd, } -impl From for ZipCompressionMethod{ +impl From for ZipCompressionMethod { fn from(value: ZipCompressionMethodCli) -> Self { match value { ZipCompressionMethodCli::Stored => ZipCompressionMethod::Stored, @@ -290,7 +290,7 @@ impl From for ZipCompressionMethod{ #[cfg(feature = "kpar-xz")] ZipCompressionMethodCli::Xz => ZipCompressionMethod::Xz, #[cfg(feature = "kpar-ppmd")] - ZipCompressionMethodCli::Ppmd => ZipCompressionMethod::Ppmd + ZipCompressionMethodCli::Ppmd => ZipCompressionMethod::Ppmd, } } } @@ -309,14 +309,14 @@ impl From for ZipCompressionMethodCli { #[cfg(feature = "kpar-xz")] ZipCompressionMethod::Xz => ZipCompressionMethodCli::Xz, #[cfg(feature = "kpar-ppmd")] - ZipCompressionMethod::Ppmd => ZipCompressionMethodCli::Ppmd + ZipCompressionMethod::Ppmd => ZipCompressionMethodCli::Ppmd, } } } #[derive(Clone, Debug)] struct InvalidCommand { - message: String + message: String, } fn invalid_command>(message: S) -> InvalidCommand { diff --git a/sysand/src/commands/build.rs b/sysand/src/commands/build.rs index 522c4ce4..d895ed29 100644 --- a/sysand/src/commands/build.rs +++ b/sysand/src/commands/build.rs @@ -4,7 +4,10 @@ use anyhow::Result; use camino::Utf8Path; use sysand_core::{ - build::{do_build_kpar, do_build_workspace_kpars}, model::ZipCompressionMethod, project::local_src::LocalSrcProject, workspace::Workspace + build::{do_build_kpar, do_build_workspace_kpars}, + model::ZipCompressionMethod, + project::local_src::LocalSrcProject, + workspace::Workspace, }; pub fn command_build_for_project>( diff --git a/sysand/tests/cli_build.rs b/sysand/tests/cli_build.rs index 41cecd55..7d846656 100644 --- a/sysand/tests/cli_build.rs +++ b/sysand/tests/cli_build.rs @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: © 2025 Sysand contributors // SPDX-License-Identifier: MIT OR Apache-2.0 -use std::{io::{Read, Write}}; +use std::io::{Read, Write}; use assert_cmd::prelude::*; use clap::ValueEnum; From f798ed11493731116197b51f6e87a94ecaf74c90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Puk=C5=A1ta?= <146448971+Jonas-Puksta-Sensmetry@users.noreply.github.com> Date: Fri, 27 Feb 2026 17:18:02 +0200 Subject: [PATCH 07/16] Capitalization in comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Andrius Pukšta Signed-off-by: Jonas Pukšta <146448971+Jonas-Puksta-Sensmetry@users.noreply.github.com> Signed-off-by: jonas.puksta.sensmetry --- .../src/main/java/org/sysand/maven/SysandBuildKParMojo.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/java/plugin/src/main/java/org/sysand/maven/SysandBuildKParMojo.java b/bindings/java/plugin/src/main/java/org/sysand/maven/SysandBuildKParMojo.java index c08a3ab1..13687dc4 100644 --- a/bindings/java/plugin/src/main/java/org/sysand/maven/SysandBuildKParMojo.java +++ b/bindings/java/plugin/src/main/java/org/sysand/maven/SysandBuildKParMojo.java @@ -42,7 +42,7 @@ public class SysandBuildKParMojo extends AbstractMojo { private String outputPath; /** - * Kpar compression method. Can be configured as + * KPAR compression method. Can be configured as * {@code ...} or * via {@code -Dsysand.compressionMethod=...}. */ From 811c0a5b48dc51d5bdbf876db78f2b6b8f63841d Mon Sep 17 00:00:00 2001 From: "jonas.puksta.sensmetry" Date: Fri, 27 Feb 2026 18:15:37 +0200 Subject: [PATCH 08/16] add networking to tested features Signed-off-by: jonas.puksta.sensmetry --- .github/workflows/test.yml | 2 +- DEVELOPMENT.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8bdc9563..d8511204 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -43,7 +43,7 @@ jobs: git config --global user.email "user@sysand.org" git config --global user.name "Test User" - name: Test Core - run: cargo test --package sysand-core --verbose --features filesystem,js,python,alltests,kpar-bzip2,kpar-zstd,kpar-xz,kpar-ppmd + run: cargo test --package sysand-core --verbose --features filesystem,networking,js,python,alltests,kpar-bzip2,kpar-zstd,kpar-xz,kpar-ppmd - name: Test CLI run: cargo test --package sysand --verbose --features alltests,kpar-bzip2,kpar-zstd,kpar-xz,kpar-ppmd diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 952f650b..fd922d94 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -87,7 +87,7 @@ cargo build --release # optimized Run tests for main Rust crates. This excludes language bindings, because they have their own test suites: ```sh -cargo test -p sysand-core -F filesystem,js,python,alltests,kpar-bzip2,kpar-zstd,kpar-xz,kpar-ppmd +cargo test -p sysand-core -F filesystem,networking,js,python,alltests,kpar-bzip2,kpar-zstd,kpar-xz,kpar-ppmd cargo test -p sysand -F alltests,kpar-bzip2,kpar-zstd,kpar-xz,kpar-ppmd ``` From 14282852ef1334356f5c5db978d1bc59dfbe03d6 Mon Sep 17 00:00:00 2001 From: "jonas.puksta.sensmetry" Date: Fri, 27 Feb 2026 18:17:12 +0200 Subject: [PATCH 09/16] use kpar instead of zip in naming and small wording fixes Signed-off-by: jonas.puksta.sensmetry --- bindings/java/scripts/java-builder.py | 4 +- bindings/java/src/lib.rs | 8 +-- bindings/py/src/lib.rs | 10 +-- core/Cargo.toml | 2 +- core/src/commands/build.rs | 6 +- core/src/model.rs | 77 +++++++++++++---------- core/src/project/local_kpar.rs | 4 +- core/src/project/local_src.rs | 2 +- core/src/project/reqwest_kpar_download.rs | 2 +- core/src/resolve/reqwest_http.rs | 2 +- docs/src/commands/build.md | 2 +- sysand/src/cli.rs | 44 ++++++------- sysand/src/commands/build.rs | 6 +- sysand/tests/cli_build.rs | 6 +- 14 files changed, 93 insertions(+), 82 deletions(-) diff --git a/bindings/java/scripts/java-builder.py b/bindings/java/scripts/java-builder.py index 9366f50c..804c00aa 100755 --- a/bindings/java/scripts/java-builder.py +++ b/bindings/java/scripts/java-builder.py @@ -11,7 +11,7 @@ import platform import shutil import subprocess -from typing import Any +from typing import Any, Union ROOT_DIR = Path(__file__).absolute().parent.parent.parent.parent @@ -131,7 +131,7 @@ def compute_full_version(version: str, release_jar_version: bool) -> str: def build( use_release_build: bool, - use_existing_native_libs: Path | None, + use_existing_native_libs: Union[Path, None], sign_artifacts: bool, release_jar_version: bool, version: str, diff --git a/bindings/java/src/lib.rs b/bindings/java/src/lib.rs index 929a6fe0..18b9c9ba 100644 --- a/bindings/java/src/lib.rs +++ b/bindings/java/src/lib.rs @@ -16,7 +16,7 @@ use sysand_core::{ env::local_directory::{self, LocalWriteError}, info::InfoError, init::InitError, - model::ZipCompressionMethod, + model::KparCompressionMethod, project::{ local_src::{LocalSrcError, LocalSrcProject}, utils::wrapfs, @@ -325,11 +325,11 @@ fn handle_build_error(env: &mut JNIEnv<'_>, error: KParBuildError fn compression_from_java_string( env: &mut JNIEnv<'_>, compression: String, -) -> Option { - match ZipCompressionMethod::try_from(compression) { +) -> Option { + match KparCompressionMethod::try_from(compression) { Ok(compression) => Some(compression), Err(err) => { - env.throw_exception(ExceptionKind::SysandException, err.0); + env.throw_exception(ExceptionKind::SysandException, err.to_string()); None } } diff --git a/bindings/py/src/lib.rs b/bindings/py/src/lib.rs index 4d7591c2..99e0b5ae 100644 --- a/bindings/py/src/lib.rs +++ b/bindings/py/src/lib.rs @@ -28,7 +28,7 @@ use sysand_core::{ include::do_include, info::{InfoError, do_info, do_info_project}, init::InitError, - model::{InterchangeProjectInfoRaw, InterchangeProjectMetadataRaw, ZipCompressionMethod}, + model::{InterchangeProjectInfoRaw, InterchangeProjectMetadataRaw, KparCompressionMethod}, project::{ ProjectRead as _, local_kpar::LocalKParProject, @@ -195,11 +195,11 @@ fn do_build_py( }; let compression = match compression { - Some(compression) => match ZipCompressionMethod::try_from(compression) { + Some(compression) => match KparCompressionMethod::try_from(compression) { Ok(compression) => compression, - Err(err) => return Err(PyValueError::new_err(err.0)), + Err(err) => return Err(PyValueError::new_err(err.to_string())), }, - None => ZipCompressionMethod::default(), + None => KparCompressionMethod::default(), }; do_build_kpar(&project, &output_path, compression, true) @@ -577,7 +577,7 @@ pub fn sysand_py(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(do_exclude_py, m)?)?; m.add_function(wrap_pyfunction!(do_env_install_path_py, m)?)?; // Currently this interop is done with strings instead - // m.add_class::()?; + // m.add_class::()?; m.add("DEFAULT_ENV_NAME", DEFAULT_ENV_NAME)?; Ok(()) diff --git a/core/Cargo.toml b/core/Cargo.toml index d2a2bf6e..47d62dde 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -22,7 +22,7 @@ python = ["dep:pyo3"] js = ["dep:wasm-bindgen"] filesystem = ["dep:camino-tempfile", "dep:dirs", "dep:zip"] networking = ["dep:reqwest", "dep:gix"] # "dep:reqwest-middleware", "dep:partialzip" -# Different compression methods for creating kpar +# Different compression methods for creating KPARs kpar-bzip2 = ["zip?/bzip2"] kpar-zstd = ["zip?/zstd"] kpar-xz = ["zip?/xz"] diff --git a/core/src/commands/build.rs b/core/src/commands/build.rs index 3f33a20b..f789b097 100644 --- a/core/src/commands/build.rs +++ b/core/src/commands/build.rs @@ -15,7 +15,7 @@ use crate::{ }; #[cfg(feature = "filesystem")] use crate::{ - model::ZipCompressionMethod, project::local_src::LocalSrcProject, workspace::Workspace, + model::KparCompressionMethod, project::local_src::LocalSrcProject, workspace::Workspace, }; use super::include::IncludeError; @@ -121,7 +121,7 @@ pub fn default_kpar_file_name( pub fn do_build_kpar, Pr: ProjectRead>( project: &Pr, path: P, - compression: ZipCompressionMethod, + compression: KparCompressionMethod, canonicalise: bool, ) -> Result> { use crate::project::local_src::LocalSrcProject; @@ -166,7 +166,7 @@ pub fn do_build_kpar, Pr: ProjectRead>( pub fn do_build_workspace_kpars>( workspace: &Workspace, path: P, - compression: ZipCompressionMethod, + compression: KparCompressionMethod, canonicalise: bool, ) -> Result, KParBuildError> { let mut result = Vec::new(); diff --git a/core/src/model.rs b/core/src/model.rs index f22cf7d0..f470b92e 100644 --- a/core/src/model.rs +++ b/core/src/model.rs @@ -356,7 +356,7 @@ impl KerMlChecksumAlg { // Python we use doesn't have pattern matching which ensures // all cases are covered // #[cfg_attr(feature = "python", pyclass(eq))] -pub enum ZipCompressionMethod { +pub enum KparCompressionMethod { /// Store the files as is Stored, /// Compress the files using Deflate @@ -377,30 +377,37 @@ pub enum ZipCompressionMethod { } #[cfg(feature = "filesystem")] -impl From for zip::CompressionMethod { - fn from(value: ZipCompressionMethod) -> Self { +impl From for zip::CompressionMethod { + fn from(value: KparCompressionMethod) -> Self { match value { - ZipCompressionMethod::Stored => zip::CompressionMethod::Stored, - ZipCompressionMethod::Deflated => zip::CompressionMethod::Deflated, + KparCompressionMethod::Stored => zip::CompressionMethod::Stored, + KparCompressionMethod::Deflated => zip::CompressionMethod::Deflated, #[cfg(feature = "kpar-bzip2")] - ZipCompressionMethod::Bzip2 => zip::CompressionMethod::Bzip2, + KparCompressionMethod::Bzip2 => zip::CompressionMethod::Bzip2, #[cfg(feature = "kpar-zstd")] - ZipCompressionMethod::Zstd => zip::CompressionMethod::Zstd, + KparCompressionMethod::Zstd => zip::CompressionMethod::Zstd, #[cfg(feature = "kpar-xz")] - ZipCompressionMethod::Xz => zip::CompressionMethod::Xz, + KparCompressionMethod::Xz => zip::CompressionMethod::Xz, #[cfg(feature = "kpar-ppmd")] - ZipCompressionMethod::Ppmd => zip::CompressionMethod::Ppmd, + KparCompressionMethod::Ppmd => zip::CompressionMethod::Ppmd, } } } #[cfg(feature = "filesystem")] #[derive(Debug, Error)] -#[error("failed to parse checksum algorithm")] -pub struct CompressionMethodParseError(pub String); +pub enum CompressionMethodParseError { + #[error("Compile sysand with feature {feature} to use {compression} compression")] + SuggestFeature { + compression: String, + feature: String, + }, + #[error("{0}")] + Invalid(String), +} #[cfg(feature = "filesystem")] -impl TryFrom for ZipCompressionMethod { +impl TryFrom for KparCompressionMethod { type Error = CompressionMethodParseError; fn try_from(value: String) -> Result { @@ -409,38 +416,42 @@ impl TryFrom for ZipCompressionMethod { } #[cfg(feature = "filesystem")] -impl TryFrom<&str> for ZipCompressionMethod { +impl TryFrom<&str> for KparCompressionMethod { type Error = CompressionMethodParseError; fn try_from(value: &str) -> Result { match value { - "STORED" => Ok(ZipCompressionMethod::Stored), - "DEFLATED" => Ok(ZipCompressionMethod::Deflated), + "STORED" => Ok(KparCompressionMethod::Stored), + "DEFLATED" => Ok(KparCompressionMethod::Deflated), #[cfg(feature = "kpar-bzip2")] - "BZIP2" => Ok(ZipCompressionMethod::Bzip2), + "BZIP2" => Ok(KparCompressionMethod::Bzip2), #[cfg(not(feature = "kpar-bzip2"))] - "BZIP2" => Err(CompressionMethodParseError( - "Compile sysand with feature kpar-bzip2 to use BZIP2 compression".to_string(), - )), + "BZIP2" => Err(CompressionMethodParseError::SuggestFeature { + compression: value.into(), + feature: "kpar-bzip2".into(), + }), #[cfg(feature = "kpar-zstd")] - "ZSTD" => Ok(ZipCompressionMethod::Zstd), + "ZSTD" => Ok(KparCompressionMethod::Zstd), #[cfg(not(feature = "kpar-zstd"))] - "ZSTD" => Err(CompressionMethodParseError( - "Compile sysand with feature kpar-zstd to use ZSTD compression".to_string(), - )), + "ZSTD" => Err(CompressionMethodParseError::SuggestFeature { + compression: value.into(), + feature: "kpar-zstd".into(), + }), #[cfg(feature = "kpar-xz")] - "XZ" => Ok(ZipCompressionMethod::Xz), + "XZ" => Ok(KparCompressionMethod::Xz), #[cfg(not(feature = "kpar-xz"))] - "XZ" => Err(CompressionMethodParseError( - "Compile sysand with feature kpar-xz to use XZ compression".to_string(), - )), + "XZ" => Err(CompressionMethodParseError::SuggestFeature { + compression: value.into(), + feature: "kpar-xz".into(), + }), #[cfg(feature = "kpar-ppmd")] - "PPMD" => Ok(ZipCompressionMethod::Ppmd), + "PPMD" => Ok(KparCompressionMethod::Ppmd), #[cfg(not(feature = "kpar-ppmd"))] - "PPMD" => Err(CompressionMethodParseError( - "Compile sysand with feature kpar-ppmd to use PPMD compression".to_string(), - )), - _ => Err(CompressionMethodParseError(format!( - "Compression method {value} is invalid" + "PPMD" => Err(CompressionMethodParseError::SuggestFeature { + compression: value.into(), + feature: "kpar-ppmd".into(), + }), + _ => Err(CompressionMethodParseError::Invalid(format!( + "Compression method `{value}` is invalid" ))), } } diff --git a/core/src/project/local_kpar.rs b/core/src/project/local_kpar.rs index 10506ed7..50d2aff8 100644 --- a/core/src/project/local_kpar.rs +++ b/core/src/project/local_kpar.rs @@ -403,7 +403,7 @@ mod tests { assert_eq!(info.version, "1.2.3"); assert_eq!(meta.created, "123"); - let mut src = "".to_string(); + let mut src = String::new(); project .read_source("test.sysml")? .read_to_string(&mut src)?; @@ -446,7 +446,7 @@ mod tests { assert_eq!(info.version, "1.2.3"); assert_eq!(meta.created, "123"); - let mut src = "".to_string(); + let mut src = String::new(); project .read_source("test.sysml")? .read_to_string(&mut src)?; diff --git a/core/src/project/local_src.rs b/core/src/project/local_src.rs index 1b8150c5..eefee79a 100644 --- a/core/src/project/local_src.rs +++ b/core/src/project/local_src.rs @@ -24,7 +24,7 @@ use crate::{ use super::utils::{FsIoError, ProjectDeserializationError, ProjectSerializationError}; -/// Project stored in a local directory as an extracted kpar archive. +/// Project stored in a local directory as an extracted KPAR archive. /// Source file paths with (unix) segments `segment1/.../segmentN` are /// re-interpreted as filesystem-native paths relative to `project_path`. #[derive(Clone, Debug)] diff --git a/core/src/project/reqwest_kpar_download.rs b/core/src/project/reqwest_kpar_download.rs index baa2a48e..045ebfca 100644 --- a/core/src/project/reqwest_kpar_download.rs +++ b/core/src/project/reqwest_kpar_download.rs @@ -247,7 +247,7 @@ mod tests { assert_eq!(info.name, "test_basic_download_request"); assert_eq!(meta.created, "123"); - let mut src = "".to_string(); + let mut src = String::new(); project .read_source("test.sysml")? .read_to_string(&mut src)?; diff --git a/core/src/resolve/reqwest_http.rs b/core/src/resolve/reqwest_http.rs index e4ac004b..a80a0564 100644 --- a/core/src/resolve/reqwest_http.rs +++ b/core/src/resolve/reqwest_http.rs @@ -240,7 +240,7 @@ impl Iterator for HTTPProjects { } /// Tries treat IRIs as HTTP URLs, pointing either to source files stored remotely -/// or a KPar archive stored remotely. +/// or a KPAR archive stored remotely. /// /// If `prefer_ranged` is true, it attempts to poke the remote server to see if it /// appears to support HTTP Range requests. If successful, it uses `HTTPKparProjectRanged` diff --git a/docs/src/commands/build.md b/docs/src/commands/build.md index 337e6ad6..cc1e9aa5 100644 --- a/docs/src/commands/build.md +++ b/docs/src/commands/build.md @@ -28,7 +28,7 @@ if none is found uses the current directory instead. ## Options -- `-c`, `--compression`: Method to compress the files in zip archive. +- `-c`, `--compression`: Method to compress the files in the KPAR. Possible values: - `stored`: Store the files as is - `deflated`: Compress the files using Deflate diff --git a/sysand/src/cli.rs b/sysand/src/cli.rs index f5479fd2..ec3cad97 100644 --- a/sysand/src/cli.rs +++ b/sysand/src/cli.rs @@ -11,7 +11,7 @@ use camino::Utf8PathBuf; use clap::{ValueEnum, builder::StyledStr, crate_authors}; use fluent_uri::Iri; use semver::VersionReq; -use sysand_core::model::ZipCompressionMethod; +use sysand_core::model::KparCompressionMethod; use crate::env_vars; @@ -163,9 +163,9 @@ pub enum Command { #[clap(verbatim_doc_comment)] path: Option, #[clap(verbatim_doc_comment)] - /// Method to compress the files in zip archive + /// Method to compress the files in the KPAR #[arg(short = 'c', long, default_value_t, value_enum)] - compression: ZipCompressionMethodCli, + compression: KparCompressionMethodCli, }, /// Create or update lockfile Lock { @@ -258,7 +258,7 @@ pub struct ProjectLocatorArgs { #[derive(clap::ValueEnum, Default, Copy, Clone, Debug)] #[clap(rename_all = "lowercase")] -pub enum ZipCompressionMethodCli { +pub enum KparCompressionMethodCli { /// Store the files as is Stored, /// Compress the files using Deflate @@ -278,38 +278,38 @@ pub enum ZipCompressionMethodCli { Ppmd, } -impl From for ZipCompressionMethod { - fn from(value: ZipCompressionMethodCli) -> Self { +impl From for KparCompressionMethod { + fn from(value: KparCompressionMethodCli) -> Self { match value { - ZipCompressionMethodCli::Stored => ZipCompressionMethod::Stored, - ZipCompressionMethodCli::Deflated => ZipCompressionMethod::Deflated, + KparCompressionMethodCli::Stored => KparCompressionMethod::Stored, + KparCompressionMethodCli::Deflated => KparCompressionMethod::Deflated, #[cfg(feature = "kpar-bzip2")] - ZipCompressionMethodCli::Bzip2 => ZipCompressionMethod::Bzip2, + KparCompressionMethodCli::Bzip2 => KparCompressionMethod::Bzip2, #[cfg(feature = "kpar-zstd")] - ZipCompressionMethodCli::Zstd => ZipCompressionMethod::Zstd, + KparCompressionMethodCli::Zstd => KparCompressionMethod::Zstd, #[cfg(feature = "kpar-xz")] - ZipCompressionMethodCli::Xz => ZipCompressionMethod::Xz, + KparCompressionMethodCli::Xz => KparCompressionMethod::Xz, #[cfg(feature = "kpar-ppmd")] - ZipCompressionMethodCli::Ppmd => ZipCompressionMethod::Ppmd, + KparCompressionMethodCli::Ppmd => KparCompressionMethod::Ppmd, } } } -// This is implemented mainly so that if ZipCompressionMethod gets a new member -// and ZipCompressionMethodCli isn't updated it would give a compilation error -impl From for ZipCompressionMethodCli { - fn from(value: ZipCompressionMethod) -> Self { +// This is implemented mainly so that if KparCompressionMethod gets a new member +// and KparCompressionMethodCli isn't updated it would give a compilation error +impl From for KparCompressionMethodCli { + fn from(value: KparCompressionMethod) -> Self { match value { - ZipCompressionMethod::Stored => ZipCompressionMethodCli::Stored, - ZipCompressionMethod::Deflated => ZipCompressionMethodCli::Deflated, + KparCompressionMethod::Stored => KparCompressionMethodCli::Stored, + KparCompressionMethod::Deflated => KparCompressionMethodCli::Deflated, #[cfg(feature = "kpar-bzip2")] - ZipCompressionMethod::Bzip2 => ZipCompressionMethodCli::Bzip2, + KparCompressionMethod::Bzip2 => KparCompressionMethodCli::Bzip2, #[cfg(feature = "kpar-zstd")] - ZipCompressionMethod::Zstd => ZipCompressionMethodCli::Zstd, + KparCompressionMethod::Zstd => KparCompressionMethodCli::Zstd, #[cfg(feature = "kpar-xz")] - ZipCompressionMethod::Xz => ZipCompressionMethodCli::Xz, + KparCompressionMethod::Xz => KparCompressionMethodCli::Xz, #[cfg(feature = "kpar-ppmd")] - ZipCompressionMethod::Ppmd => ZipCompressionMethodCli::Ppmd, + KparCompressionMethod::Ppmd => KparCompressionMethodCli::Ppmd, } } } diff --git a/sysand/src/commands/build.rs b/sysand/src/commands/build.rs index d895ed29..873308da 100644 --- a/sysand/src/commands/build.rs +++ b/sysand/src/commands/build.rs @@ -5,14 +5,14 @@ use anyhow::Result; use camino::Utf8Path; use sysand_core::{ build::{do_build_kpar, do_build_workspace_kpars}, - model::ZipCompressionMethod, + model::KparCompressionMethod, project::local_src::LocalSrcProject, workspace::Workspace, }; pub fn command_build_for_project>( path: P, - compression: ZipCompressionMethod, + compression: KparCompressionMethod, current_project: LocalSrcProject, ) -> Result<()> { do_build_kpar(¤t_project, &path, compression, true)?; @@ -22,7 +22,7 @@ pub fn command_build_for_project>( pub fn command_build_for_workspace>( path: P, - compression: ZipCompressionMethod, + compression: KparCompressionMethod, workspace: Workspace, ) -> Result<()> { log::warn!( diff --git a/sysand/tests/cli_build.rs b/sysand/tests/cli_build.rs index 7d846656..c339cd42 100644 --- a/sysand/tests/cli_build.rs +++ b/sysand/tests/cli_build.rs @@ -6,7 +6,7 @@ use std::io::{Read, Write}; use assert_cmd::prelude::*; use clap::ValueEnum; use predicates::prelude::*; -use sysand::cli::ZipCompressionMethodCli; +use sysand::cli::KparCompressionMethodCli; use sysand_core::{ model::{InterchangeProjectChecksumRaw, KerMlChecksumAlg}, project::{ProjectRead, local_kpar::LocalKParProject}, @@ -161,7 +161,7 @@ fn test_workspace_build() -> Result<(), Box> { #[test] fn test_compression_methods() -> Result<(), Box> { - let compressions = ZipCompressionMethodCli::value_variants(); + let compressions = KparCompressionMethodCli::value_variants(); test_compression_method(None)?; for compression in compressions { test_compression_method(Some(compression.to_possible_value().unwrap().get_name()))?; @@ -218,7 +218,7 @@ fn test_compression_method(compression: Option<&str>) -> Result<(), Box Date: Fri, 27 Feb 2026 18:17:47 +0200 Subject: [PATCH 10/16] fix java error Signed-off-by: jonas.puksta.sensmetry --- .../src/main/java/org/sysand/maven/SysandBuildKParMojo.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bindings/java/plugin/src/main/java/org/sysand/maven/SysandBuildKParMojo.java b/bindings/java/plugin/src/main/java/org/sysand/maven/SysandBuildKParMojo.java index 13687dc4..8b22913c 100644 --- a/bindings/java/plugin/src/main/java/org/sysand/maven/SysandBuildKParMojo.java +++ b/bindings/java/plugin/src/main/java/org/sysand/maven/SysandBuildKParMojo.java @@ -46,7 +46,7 @@ public class SysandBuildKParMojo extends AbstractMojo { * {@code ...} or * via {@code -Dsysand.compressionMethod=...}. */ - @Parameter(property = "sysand.compressionMethod", required = true) + @Parameter(property = "sysand.compressionMethod", required = false) private String compressionMethod; @Override @@ -58,7 +58,8 @@ public void execute() throws MojoExecutionException { if (outputPath == null || outputPath.trim().isEmpty()) { throw new MojoExecutionException("Parameter 'outputPath' must be provided and non-empty"); } - CompressionMethod compression = CompressionMethod.valueOf(compressionMethod.toUpperCase()); + + CompressionMethod compression = compressionMethod == null ? CompressionMethod.DEFLATED : CompressionMethod.valueOf(compressionMethod.toUpperCase()); try { if (workspacePath == null) { From 3e794ce9729fe1436ef9aaa123c6d7661e5bf82d Mon Sep 17 00:00:00 2001 From: "jonas.puksta.sensmetry" Date: Fri, 27 Feb 2026 18:22:22 +0200 Subject: [PATCH 11/16] cosmetic changes Signed-off-by: jonas.puksta.sensmetry --- bindings/js/src/env/local_storage.rs | 2 +- core/src/project/gix_git_download.rs | 2 +- core/src/project/reqwest_src.rs | 2 +- core/tests/filesystem_env.rs | 2 +- core/tests/memory_env.rs | 2 +- sysand/Cargo.toml | 2 +- sysand/tests/cli_info.rs | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/bindings/js/src/env/local_storage.rs b/bindings/js/src/env/local_storage.rs index 7531225a..fc9ee43e 100644 --- a/bindings/js/src/env/local_storage.rs +++ b/bindings/js/src/env/local_storage.rs @@ -178,7 +178,7 @@ impl WriteEnvironment for LocalBrowserStorageEnvironment { .read_string(self.versions_path(&uri)) .map_err(Error::LocalStorage)?; - let mut kept_versions = "".to_string(); + let mut kept_versions = String::new(); let mut found = false; for current_version in current_versions.lines() { diff --git a/core/src/project/gix_git_download.rs b/core/src/project/gix_git_download.rs index 460d3763..8dc0923c 100644 --- a/core/src/project/gix_git_download.rs +++ b/core/src/project/gix_git_download.rs @@ -235,7 +235,7 @@ mod tests { assert_eq!(info.name, "basic_gix_access"); assert_eq!(meta.created, "123"); - let mut buf = "".to_string(); + let mut buf = String::new(); project .read_source("test.sysml")? .read_to_string(&mut buf)?; diff --git a/core/src/project/reqwest_src.rs b/core/src/project/reqwest_src.rs index c8cef939..570fd0dc 100644 --- a/core/src/project/reqwest_src.rs +++ b/core/src/project/reqwest_src.rs @@ -304,7 +304,7 @@ mod tests { assert_eq!(info.name, "test_basic_project_urls"); assert_eq!(meta.created, "0000-00-00T00:00:00.123456789Z"); - let mut src_buf = "".to_string(); + let mut src_buf = String::new(); project .read_source(Utf8UnixPath::new("Mekanïk/Kommandöh.sysml").to_path_buf())? .read_to_string(&mut src_buf)?; diff --git a/core/tests/filesystem_env.rs b/core/tests/filesystem_env.rs index 4d9ad276..91b0b2e3 100644 --- a/core/tests/filesystem_env.rs +++ b/core/tests/filesystem_env.rs @@ -138,7 +138,7 @@ mod filesystem_tests { assert_eq!(read_info, Some(info.clone())); assert_eq!(read_meta, Some(meta.clone())); - let mut read_source_code = "".to_string(); + let mut read_source_code = String::new(); target_project .read_source(source_path)? diff --git a/core/tests/memory_env.rs b/core/tests/memory_env.rs index 78468977..ca52d6c5 100644 --- a/core/tests/memory_env.rs +++ b/core/tests/memory_env.rs @@ -86,7 +86,7 @@ fn env_manual_install() -> Result<(), Box> { assert_eq!(target_project.info, Some(info.clone())); assert_eq!(target_project.meta, Some(meta.clone())); - let mut read_source_code = "".to_string(); + let mut read_source_code = String::new(); target_project .read_source(source_path)? diff --git a/sysand/Cargo.toml b/sysand/Cargo.toml index 8601dbca..9cca5565 100644 --- a/sysand/Cargo.toml +++ b/sysand/Cargo.toml @@ -15,7 +15,7 @@ homepage.workspace = true default = ["std"] std = ["anyhow/std", "clap/std", "fluent-uri/std", "log/std", "toml/std", "typed-path/std"] alltests = [] -# Different compression methods for creating kpar +# Different compression methods for creating KPARs kpar-bzip2 = ["sysand-core/kpar-bzip2"] kpar-zstd = ["sysand-core/kpar-zstd"] kpar-xz = ["sysand-core/kpar-xz"] diff --git a/sysand/tests/cli_info.rs b/sysand/tests/cli_info.rs index 31c01751..feb4fb03 100644 --- a/sysand/tests/cli_info.rs +++ b/sysand/tests/cli_info.rs @@ -1391,7 +1391,7 @@ fn info_detailed_verbs() -> Result<(), Box> { if expected { out.assert().success(); let skipped = index.parse::()? - 1; - let mut expected_output = "".to_string(); + let mut expected_output = String::new(); for (i, line) in before.lines().enumerate() { if i != skipped { expected_output.push_str(line); From 1ce5ab8849372c06e00488c4ad82b0690b7db189 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Mon, 2 Mar 2026 12:34:07 +0100 Subject: [PATCH 12/16] Remove a path prefix on windows Signed-off-by: Erik Sundell --- core/src/project/gix_git_download.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/core/src/project/gix_git_download.rs b/core/src/project/gix_git_download.rs index 8dc0923c..dd683bd8 100644 --- a/core/src/project/gix_git_download.rs +++ b/core/src/project/gix_git_download.rs @@ -223,10 +223,12 @@ mod tests { // sleep(Duration::from_millis(100)); - let project = GixDownloadedProject::new(format!( - "file://{}", - repo_dir.path().canonicalize()?.display() - ))?; + let canonical = repo_dir.path().canonicalize()?; + // On Windows, canonicalize() returns extended-length paths with a `\\?\` + // prefix that gix cannot parse as a valid file URL. Strip it. + let path = canonical.to_str().unwrap(); + let path = path.strip_prefix(r"\\?\").unwrap_or(path); + let project = GixDownloadedProject::new(format!("file://{path}"))?; let (Some(info), Some(meta)) = project.get_project()? else { panic!("expected info and meta"); From e05e5d172a044f3dd4fe487d94651b5906b0ad20 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 14:19:50 +0000 Subject: [PATCH 13/16] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/src/commands/build.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/commands/build.md b/docs/src/commands/build.md index cc1e9aa5..33133187 100644 --- a/docs/src/commands/build.md +++ b/docs/src/commands/build.md @@ -30,7 +30,7 @@ if none is found uses the current directory instead. - `-c`, `--compression`: Method to compress the files in the KPAR. Possible values: - - `stored`: Store the files as is + - `stored`: Store the files as is - `deflated`: Compress the files using Deflate [default: `deflated`] From e711c4d085a868680154c063544fc97fd9c18630 Mon Sep 17 00:00:00 2001 From: "jonas.puksta.sensmetry" Date: Thu, 12 Mar 2026 15:18:44 +0200 Subject: [PATCH 14/16] remove unnecessary #[cfg(feature = filesystem)] as the entire build module is conditional on filesystem feature Signed-off-by: jonas.puksta.sensmetry --- core/src/commands/build.rs | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/core/src/commands/build.rs b/core/src/commands/build.rs index f789b097..6475751c 100644 --- a/core/src/commands/build.rs +++ b/core/src/commands/build.rs @@ -1,24 +1,18 @@ -#[cfg(feature = "filesystem")] use camino::Utf8Path; use thiserror::Error; use crate::{ env::utils::{CloneError, ErrorBound}, - model::InterchangeProjectValidationError, + model::{InterchangeProjectValidationError, KparCompressionMethod}, project::{ ProjectRead, local_kpar::{IntoKparError, LocalKParProject}, - local_src::LocalSrcError, + local_src::{LocalSrcError, LocalSrcProject}, utils::{FsIoError, ZipArchiveError}, }, - workspace::WorkspaceReadError, + workspace::{WorkspaceReadError, Workspace}, + include::IncludeError }; -#[cfg(feature = "filesystem")] -use crate::{ - model::KparCompressionMethod, project::local_src::LocalSrcProject, workspace::Workspace, -}; - -use super::include::IncludeError; #[derive(Error, Debug)] pub enum KParBuildError { @@ -99,7 +93,6 @@ impl From> } } -#[cfg(feature = "filesystem")] pub fn default_kpar_file_name( project: &Pr, ) -> Result> { @@ -117,7 +110,6 @@ pub fn default_kpar_file_name( )) } -#[cfg(feature = "filesystem")] pub fn do_build_kpar, Pr: ProjectRead>( project: &Pr, path: P, @@ -162,7 +154,6 @@ pub fn do_build_kpar, Pr: ProjectRead>( )?) } -#[cfg(feature = "filesystem")] pub fn do_build_workspace_kpars>( workspace: &Workspace, path: P, From 4a9b228010030f1f8140ca5d5c45c0b0d74f8aa9 Mon Sep 17 00:00:00 2001 From: "jonas.puksta.sensmetry" Date: Thu, 12 Mar 2026 15:32:17 +0200 Subject: [PATCH 15/16] move KparCompressionMethod declaration to core/src/commands Signed-off-by: jonas.puksta.sensmetry --- bindings/java/src/lib.rs | 3 +- bindings/py/src/lib.rs | 4 +- core/src/commands/build.rs | 109 ++++++++++++++++++++++++++++++++++- core/src/model.rs | 108 ---------------------------------- sysand/src/cli.rs | 2 +- sysand/src/commands/build.rs | 3 +- 6 files changed, 111 insertions(+), 118 deletions(-) diff --git a/bindings/java/src/lib.rs b/bindings/java/src/lib.rs index 18b9c9ba..1d65cbaa 100644 --- a/bindings/java/src/lib.rs +++ b/bindings/java/src/lib.rs @@ -11,12 +11,11 @@ use jni::{ }; use sysand_core::{ auth::Unauthenticated, - build::KParBuildError, + build::{KParBuildError, KparCompressionMethod}, commands, env::local_directory::{self, LocalWriteError}, info::InfoError, init::InitError, - model::KparCompressionMethod, project::{ local_src::{LocalSrcError, LocalSrcProject}, utils::wrapfs, diff --git a/bindings/py/src/lib.rs b/bindings/py/src/lib.rs index 99e0b5ae..f5834b41 100644 --- a/bindings/py/src/lib.rs +++ b/bindings/py/src/lib.rs @@ -12,7 +12,7 @@ use semver::{Version, VersionReq}; use sysand_core::{ add::do_add, auth::Unauthenticated, - build::{KParBuildError, do_build_kpar}, + build::{KParBuildError, KparCompressionMethod, do_build_kpar}, commands::{ env::{EnvError, do_env_local_dir}, init::do_init_local_file, @@ -28,7 +28,7 @@ use sysand_core::{ include::do_include, info::{InfoError, do_info, do_info_project}, init::InitError, - model::{InterchangeProjectInfoRaw, InterchangeProjectMetadataRaw, KparCompressionMethod}, + model::{InterchangeProjectInfoRaw, InterchangeProjectMetadataRaw}, project::{ ProjectRead as _, local_kpar::LocalKParProject, diff --git a/core/src/commands/build.rs b/core/src/commands/build.rs index 6475751c..cadaf69a 100644 --- a/core/src/commands/build.rs +++ b/core/src/commands/build.rs @@ -3,17 +3,120 @@ use thiserror::Error; use crate::{ env::utils::{CloneError, ErrorBound}, - model::{InterchangeProjectValidationError, KparCompressionMethod}, + include::IncludeError, + model::InterchangeProjectValidationError, project::{ ProjectRead, local_kpar::{IntoKparError, LocalKParProject}, local_src::{LocalSrcError, LocalSrcProject}, utils::{FsIoError, ZipArchiveError}, }, - workspace::{WorkspaceReadError, Workspace}, - include::IncludeError + workspace::{Workspace, WorkspaceReadError}, }; +#[derive(Default, Copy, Clone, Debug, PartialEq, Eq)] +// Currently python interop is done with strings instead +// in part to have less boilerplate, in part because the old +// Python we use doesn't have pattern matching which ensures +// all cases are covered +// #[cfg_attr(feature = "python", pyclass(eq))] +pub enum KparCompressionMethod { + /// Store the files as is + Stored, + /// Compress the files using Deflate + #[default] + Deflated, + /// Compress the files using BZIP2 + #[cfg(feature = "kpar-bzip2")] + Bzip2, + /// Compress the files using ZStandard + #[cfg(feature = "kpar-zstd")] + Zstd, + /// Compress the files using XZ + #[cfg(feature = "kpar-xz")] + Xz, + /// Compress the files using PPMd + #[cfg(feature = "kpar-ppmd")] + Ppmd, +} + +impl From for zip::CompressionMethod { + fn from(value: KparCompressionMethod) -> Self { + match value { + KparCompressionMethod::Stored => zip::CompressionMethod::Stored, + KparCompressionMethod::Deflated => zip::CompressionMethod::Deflated, + #[cfg(feature = "kpar-bzip2")] + KparCompressionMethod::Bzip2 => zip::CompressionMethod::Bzip2, + #[cfg(feature = "kpar-zstd")] + KparCompressionMethod::Zstd => zip::CompressionMethod::Zstd, + #[cfg(feature = "kpar-xz")] + KparCompressionMethod::Xz => zip::CompressionMethod::Xz, + #[cfg(feature = "kpar-ppmd")] + KparCompressionMethod::Ppmd => zip::CompressionMethod::Ppmd, + } + } +} + +#[derive(Debug, Error)] +pub enum CompressionMethodParseError { + #[error("Compile sysand with feature {feature} to use {compression} compression")] + SuggestFeature { + compression: String, + feature: String, + }, + #[error("{0}")] + Invalid(String), +} + +impl TryFrom for KparCompressionMethod { + type Error = CompressionMethodParseError; + + fn try_from(value: String) -> Result { + Self::try_from(value.as_str()) + } +} + +impl TryFrom<&str> for KparCompressionMethod { + type Error = CompressionMethodParseError; + fn try_from(value: &str) -> Result { + match value { + "STORED" => Ok(KparCompressionMethod::Stored), + "DEFLATED" => Ok(KparCompressionMethod::Deflated), + #[cfg(feature = "kpar-bzip2")] + "BZIP2" => Ok(KparCompressionMethod::Bzip2), + #[cfg(not(feature = "kpar-bzip2"))] + "BZIP2" => Err(CompressionMethodParseError::SuggestFeature { + compression: value.into(), + feature: "kpar-bzip2".into(), + }), + #[cfg(feature = "kpar-zstd")] + "ZSTD" => Ok(KparCompressionMethod::Zstd), + #[cfg(not(feature = "kpar-zstd"))] + "ZSTD" => Err(CompressionMethodParseError::SuggestFeature { + compression: value.into(), + feature: "kpar-zstd".into(), + }), + #[cfg(feature = "kpar-xz")] + "XZ" => Ok(KparCompressionMethod::Xz), + #[cfg(not(feature = "kpar-xz"))] + "XZ" => Err(CompressionMethodParseError::SuggestFeature { + compression: value.into(), + feature: "kpar-xz".into(), + }), + #[cfg(feature = "kpar-ppmd")] + "PPMD" => Ok(KparCompressionMethod::Ppmd), + #[cfg(not(feature = "kpar-ppmd"))] + "PPMD" => Err(CompressionMethodParseError::SuggestFeature { + compression: value.into(), + feature: "kpar-ppmd".into(), + }), + _ => Err(CompressionMethodParseError::Invalid(format!( + "Compression method `{value}` is invalid" + ))), + } + } +} + #[derive(Error, Debug)] pub enum KParBuildError { #[error(transparent)] diff --git a/core/src/model.rs b/core/src/model.rs index f470b92e..88d7dd5b 100644 --- a/core/src/model.rs +++ b/core/src/model.rs @@ -349,114 +349,6 @@ impl KerMlChecksumAlg { } } -#[cfg(feature = "filesystem")] -#[derive(Default, Copy, Clone, Debug, PartialEq, Eq)] -// Currently python interop is done with strings instead -// in part to have less boilerplate, in part because the old -// Python we use doesn't have pattern matching which ensures -// all cases are covered -// #[cfg_attr(feature = "python", pyclass(eq))] -pub enum KparCompressionMethod { - /// Store the files as is - Stored, - /// Compress the files using Deflate - #[default] - Deflated, - /// Compress the files using BZIP2 - #[cfg(feature = "kpar-bzip2")] - Bzip2, - /// Compress the files using ZStandard - #[cfg(feature = "kpar-zstd")] - Zstd, - /// Compress the files using XZ - #[cfg(feature = "kpar-xz")] - Xz, - /// Compress the files using PPMd - #[cfg(feature = "kpar-ppmd")] - Ppmd, -} - -#[cfg(feature = "filesystem")] -impl From for zip::CompressionMethod { - fn from(value: KparCompressionMethod) -> Self { - match value { - KparCompressionMethod::Stored => zip::CompressionMethod::Stored, - KparCompressionMethod::Deflated => zip::CompressionMethod::Deflated, - #[cfg(feature = "kpar-bzip2")] - KparCompressionMethod::Bzip2 => zip::CompressionMethod::Bzip2, - #[cfg(feature = "kpar-zstd")] - KparCompressionMethod::Zstd => zip::CompressionMethod::Zstd, - #[cfg(feature = "kpar-xz")] - KparCompressionMethod::Xz => zip::CompressionMethod::Xz, - #[cfg(feature = "kpar-ppmd")] - KparCompressionMethod::Ppmd => zip::CompressionMethod::Ppmd, - } - } -} - -#[cfg(feature = "filesystem")] -#[derive(Debug, Error)] -pub enum CompressionMethodParseError { - #[error("Compile sysand with feature {feature} to use {compression} compression")] - SuggestFeature { - compression: String, - feature: String, - }, - #[error("{0}")] - Invalid(String), -} - -#[cfg(feature = "filesystem")] -impl TryFrom for KparCompressionMethod { - type Error = CompressionMethodParseError; - - fn try_from(value: String) -> Result { - Self::try_from(value.as_str()) - } -} - -#[cfg(feature = "filesystem")] -impl TryFrom<&str> for KparCompressionMethod { - type Error = CompressionMethodParseError; - fn try_from(value: &str) -> Result { - match value { - "STORED" => Ok(KparCompressionMethod::Stored), - "DEFLATED" => Ok(KparCompressionMethod::Deflated), - #[cfg(feature = "kpar-bzip2")] - "BZIP2" => Ok(KparCompressionMethod::Bzip2), - #[cfg(not(feature = "kpar-bzip2"))] - "BZIP2" => Err(CompressionMethodParseError::SuggestFeature { - compression: value.into(), - feature: "kpar-bzip2".into(), - }), - #[cfg(feature = "kpar-zstd")] - "ZSTD" => Ok(KparCompressionMethod::Zstd), - #[cfg(not(feature = "kpar-zstd"))] - "ZSTD" => Err(CompressionMethodParseError::SuggestFeature { - compression: value.into(), - feature: "kpar-zstd".into(), - }), - #[cfg(feature = "kpar-xz")] - "XZ" => Ok(KparCompressionMethod::Xz), - #[cfg(not(feature = "kpar-xz"))] - "XZ" => Err(CompressionMethodParseError::SuggestFeature { - compression: value.into(), - feature: "kpar-xz".into(), - }), - #[cfg(feature = "kpar-ppmd")] - "PPMD" => Ok(KparCompressionMethod::Ppmd), - #[cfg(not(feature = "kpar-ppmd"))] - "PPMD" => Err(CompressionMethodParseError::SuggestFeature { - compression: value.into(), - feature: "kpar-ppmd".into(), - }), - _ => Err(CompressionMethodParseError::Invalid(format!( - "Compression method `{value}` is invalid" - ))), - } - } -} - #[derive(Eq, Clone, PartialEq, Serialize, Deserialize, Debug)] #[cfg_attr(feature = "python", derive(FromPyObject, IntoPyObject))] #[serde(rename_all = "camelCase")] diff --git a/sysand/src/cli.rs b/sysand/src/cli.rs index b7037858..3a6786dc 100644 --- a/sysand/src/cli.rs +++ b/sysand/src/cli.rs @@ -11,7 +11,7 @@ use camino::Utf8PathBuf; use clap::{ValueEnum, builder::StyledStr, crate_authors}; use fluent_uri::Iri; use semver::VersionReq; -use sysand_core::model::KparCompressionMethod; +use sysand_core::build::KparCompressionMethod; use crate::env_vars; diff --git a/sysand/src/commands/build.rs b/sysand/src/commands/build.rs index 873308da..9e2c17a7 100644 --- a/sysand/src/commands/build.rs +++ b/sysand/src/commands/build.rs @@ -4,8 +4,7 @@ use anyhow::Result; use camino::Utf8Path; use sysand_core::{ - build::{do_build_kpar, do_build_workspace_kpars}, - model::KparCompressionMethod, + build::{KparCompressionMethod, do_build_kpar, do_build_workspace_kpars}, project::local_src::LocalSrcProject, workspace::Workspace, }; From 1f015466493d78e2f880413116b202d301fb4025 Mon Sep 17 00:00:00 2001 From: "jonas.puksta.sensmetry" Date: Fri, 13 Mar 2026 09:20:35 +0200 Subject: [PATCH 16/16] remove zip from cli dependencies as it's no longer needed Signed-off-by: jonas.puksta.sensmetry --- sysand/Cargo.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/sysand/Cargo.toml b/sysand/Cargo.toml index 9cca5565..426676ef 100644 --- a/sysand/Cargo.toml +++ b/sysand/Cargo.toml @@ -39,8 +39,6 @@ spdx = "0.13.2" fluent-uri = { version = "0.4.1", default-features = false } typed-path = { version = "0.12.0", default-features = false } url = { version = "2.5.7", default-features = false } -# Enables a bunch of additional compression/encryption features not enabled by default in sysand-core -zip = { version = "6.0.0", default-features = false, features = ["deflate"] } pubgrub = { version = "0.3.0", default-features = false } indexmap = "2.12.1" tokio = { version = "1.48.0", default-features = false }