Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bindings/java/plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ SPDX-FileCopyrightText: © 2025 Sysand contributors <opensource@sensmetry.com>
<showVersion>true</showVersion>
<streamLogs>true</streamLogs>
<filterPom>true</filterPom>
<postBuildHookScript>verify</postBuildHookScript>
</configuration>
<executions>
<execution>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"projects": [
{
"path": "project1",
"iris": ["urn:kpar:project1"]
}
],
"presets": {
"kerml": {
"project": {
"version": "1.0.0"
},
"meta": {
"metamodel": "https://www.omg.org/spec/KerML/20250201"
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ SPDX-FileCopyrightText: © 2025 Sysand contributors <opensource@sensmetry.com>
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.sensmetry.sysand.it</groupId>
<artifactId>workspace-metamodel-project</artifactId>
<artifactId>workspace-inherit-group-project</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>workspace-metamodel-project</name>
<name>workspace-inherit-group-project</name>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"index": {
"A": "a.sysml"
},
"created": "2025-10-31T17:01:00.414506000Z",
"metamodel": { "preset": "kerml" },
"checksum": {
"a.sysml": {
"value": "",
"algorithm": "NONE"
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "project1",
"version": "0.0.1",
"version": { "preset": "kerml" },
"usage": []
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,24 @@
import java.util.zip.ZipFile
import groovy.json.JsonSlurper

def kparFile = new File(basedir, "output/project1-0.0.1.kpar")
def kparFile = new File(basedir, "output/project1-1.0.0.kpar")
assert kparFile.exists() : "Expected kpar file not found: ${kparFile}"

def zip = new ZipFile(kparFile)
try {
def infoEntry = zip.getEntry(".project.json")
assert infoEntry != null : ".project.json entry not found in kpar"

def infoJson = new JsonSlurper().parse(zip.getInputStream(infoEntry))
assert infoJson.version == "1.0.0" :
"Expected version '1.0.0' but got '${infoJson.version}'"

def metaEntry = zip.getEntry(".meta.json")
assert metaEntry != null : ".meta.json entry not found in kpar"

def metaJson = new JsonSlurper().parse(zip.getInputStream(metaEntry))
assert metaJson.metamodel == "https://www.omg.org/spec/SysML/20250201" :
"Expected metamodel 'https://www.omg.org/spec/SysML/20250201' but got '${metaJson.metamodel}'"
assert metaJson.metamodel == "https://www.omg.org/spec/KerML/20250201" :
"Expected metamodel 'https://www.omg.org/spec/KerML/20250201' but got '${metaJson.metamodel}'"
} finally {
zip.close()
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"iris": ["urn:kpar:project1"]
}
],
"meta": {
"metamodel": "https://www.omg.org/spec/SysML/20250201"
"project": {
"version": "2.0.0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
invoker.goals = -B -V package
invoker.buildResult = success
53 changes: 53 additions & 0 deletions bindings/java/plugin/src/it/workspace-inherit-version/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<!--
SPDX-License-Identifier: MIT OR Apache-2.0
SPDX-FileCopyrightText: © 2025 Sysand contributors <opensource@sensmetry.com>
-->

<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.sensmetry.sysand.it</groupId>
<artifactId>workspace-inherit-version-project</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>workspace-inherit-version-project</name>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<profiles>
<!-- maven.compiler.release is only available in Java 9+, but it is required
to disallow usage of Java >8 APIs when building with Java >8 -->
<profile>
<id>java-8-api</id>
<activation>
<jdk>[9,)</jdk>
</activation>
<properties>
<maven.compiler.release>8</maven.compiler.release>
</properties>
</profile>
</profiles>
<build>
<plugins>
<plugin>
<groupId>com.sensmetry</groupId>
<artifactId>sysand-maven-plugin</artifactId>
<version>@project.version@</version>
<executions>
<execution>
<goals>
<goal>build-kpar</goal>
</goals>
</execution>
</executions>
<configuration>
<workspacePath>${project.basedir}</workspacePath>
<outputPath>${project.basedir}/output</outputPath>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "project1",
"version": { "preset": "default" },
"usage": []
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package A;
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
// SPDX-FileCopyrightText: © 2025 Sysand contributors <opensource@sensmetry.com>

import java.util.zip.ZipFile
import groovy.json.JsonSlurper

def kparFile = new File(basedir, "output/project1-2.0.0.kpar")
assert kparFile.exists() : "Expected kpar file not found: ${kparFile}"

def zip = new ZipFile(kparFile)
try {
def infoEntry = zip.getEntry(".project.json")
assert infoEntry != null : ".project.json entry not found in kpar"

def infoJson = new JsonSlurper().parse(zip.getInputStream(infoEntry))
assert infoJson.version == "2.0.0" :
"Expected version '2.0.0' but got '${infoJson.version}'"
} finally {
zip.close()
}
14 changes: 13 additions & 1 deletion bindings/java/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,12 @@ pub extern "system" fn Java_com_sensmetry_sysand_Sysand_init<'local>(
LocalSrcError::MissingMeta => {
env.throw_exception(ExceptionKind::SysandException, suberror.to_string())
}
LocalSrcError::WorkspaceInheritance(_) => {
env.throw_exception(ExceptionKind::SysandException, suberror.to_string())
}
LocalSrcError::WorkspaceRead(_) => {
env.throw_exception(ExceptionKind::SysandException, suberror.to_string())
}
},
},
}
Expand Down Expand Up @@ -175,6 +181,12 @@ pub extern "system" fn Java_com_sensmetry_sysand_Sysand_env<'local>(
LocalWriteError::AddProject(subsuberror) => {
env.throw_exception(ExceptionKind::IOError, subsuberror.to_string())
}
LocalWriteError::WorkspaceInheritance(_) => {
env.throw_exception(ExceptionKind::SysandException, suberror.to_string())
}
LocalWriteError::WorkspaceRead(_) => {
env.throw_exception(ExceptionKind::SysandException, suberror.to_string())
}
},
},
}
Expand Down Expand Up @@ -431,7 +443,7 @@ fn handle_build_error(env: &mut JNIEnv<'_>, error: KParBuildError<LocalSrcError>
),
);
}
KParBuildError::WorkspaceMetamodelConflict { .. } => {
KParBuildError::WorkspaceInheritance(_) => {
env.throw_exception(ExceptionKind::SysandException, error.to_string());
}
}
Expand Down
8 changes: 5 additions & 3 deletions bindings/py/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ fn do_init_py_local_file(
PyValueError::new_err(error.to_string())
}
LocalSrcError::MissingMeta => PyFileNotFoundError::new_err(err.to_string()),
LocalSrcError::WorkspaceInheritance(_) => PyValueError::new_err(err.to_string()),
LocalSrcError::WorkspaceRead(_) => PyValueError::new_err(err.to_string()),
},
},
)?;
Expand Down Expand Up @@ -108,6 +110,8 @@ fn do_env_py_local_dir(path: String) -> PyResult<()> {
}
LocalWriteError::MissingMeta => PyFileNotFoundError::new_err(werr.to_string()),
LocalWriteError::AddProject(error) => PyIOError::new_err(error.to_string()),
LocalWriteError::WorkspaceInheritance(_) => PyValueError::new_err(werr.to_string()),
LocalWriteError::WorkspaceRead(_) => PyValueError::new_err(werr.to_string()),
},
})?;

Expand Down Expand Up @@ -236,9 +240,7 @@ fn do_build_py(
KParBuildError::Serialize(..) => PyValueError::new_err(err.to_string()),
KParBuildError::WorkspaceRead(_) => PyRuntimeError::new_err(err.to_string()),
KParBuildError::PathUsage(_) => PyValueError::new_err(err.to_string()),
KParBuildError::WorkspaceMetamodelConflict { .. } => {
PyValueError::new_err(err.to_string())
}
KParBuildError::WorkspaceInheritance(_) => PyValueError::new_err(err.to_string()),
})
}

Expand Down
85 changes: 39 additions & 46 deletions core/src/commands/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::{
local_src::{LocalSrcError, LocalSrcProject},
utils::{FsIoError, ZipArchiveError},
},
workspace::{Workspace, WorkspaceReadError},
workspace::{ResolvedProject, Workspace, WorkspaceInheritanceError, WorkspaceReadError},
};

#[derive(Default, Copy, Clone, Debug, PartialEq, Eq)]
Expand Down Expand Up @@ -153,16 +153,8 @@ pub enum KParBuildError<ProjectReadError: ErrorBound> {
which is unlikely to be available on other computers at the same path"
)]
PathUsage(String),
#[error(
"workspace sets metamodel `{workspace_metamodel}`, but project `{project_path}` \
sets a different metamodel `{project_metamodel}` in `.meta.json`;\n\
remove the metamodel from the project's `.meta.json` or from `.workspace.json`"
)]
WorkspaceMetamodelConflict {
workspace_metamodel: String,
project_metamodel: String,
project_path: String,
},
#[error(transparent)]
WorkspaceInheritance(#[from] WorkspaceInheritanceError),
}

impl<ProjectReadError: ErrorBound> From<FsIoError> for KParBuildError<ProjectReadError> {
Expand Down Expand Up @@ -249,14 +241,7 @@ pub fn do_build_kpar<P: AsRef<Utf8Path>, Pr: ProjectRead>(
canonicalise: bool,
allow_path_usage: bool,
) -> Result<LocalKParProject, KParBuildError<Pr::Error>> {
do_build_kpar_inner(
project,
path,
compression,
canonicalise,
allow_path_usage,
None,
)
do_build_kpar_inner(project, path, compression, canonicalise, allow_path_usage)
}

fn do_build_kpar_inner<P: AsRef<Utf8Path>, Pr: ProjectRead>(
Expand All @@ -265,16 +250,14 @@ fn do_build_kpar_inner<P: AsRef<Utf8Path>, Pr: ProjectRead>(
compression: KparCompressionMethod,
canonicalise: bool,
allow_path_usage: bool,
workspace_metamodel: Option<&str>,
) -> Result<LocalKParProject, KParBuildError<Pr::Error>> {
use crate::project::local_src::LocalSrcProject;

let building = "Building";
let header = crate::style::get_style_config().header;
log::info!("{header}{building:>12}{header:#} kpar `{}`", path.as_ref());

let (_tmp, mut local_project, info, mut meta) =
LocalSrcProject::temporary_from_project(project)?;
let (_tmp, mut local_project, info, meta) = LocalSrcProject::temporary_from_project(project)?;
match semver::Version::parse(&info.version) {
Ok(_) => (),
Err(e) => log::warn!(
Expand Down Expand Up @@ -315,24 +298,6 @@ fn do_build_kpar_inner<P: AsRef<Utf8Path>, Pr: ProjectRead>(
}
}

if let Some(ws_metamodel) = workspace_metamodel {
if let Some(proj_metamodel) = &meta.metamodel {
if proj_metamodel != ws_metamodel {
return Err(KParBuildError::WorkspaceMetamodelConflict {
workspace_metamodel: ws_metamodel.to_string(),
project_metamodel: proj_metamodel.clone(),
project_path: path.as_ref().to_string(),
});
}
} else {
meta.metamodel = Some(ws_metamodel.to_string());
use crate::project::ProjectMut;
local_project
.put_meta(&meta, true)
.map_err(KParBuildError::from)?;
}
}

if canonicalise {
for path in meta.validate()?.source_paths(true) {
use crate::include::do_include;
Expand Down Expand Up @@ -421,24 +386,52 @@ pub fn do_build_workspace_kpars<P: AsRef<Utf8Path>>(
canonicalise: bool,
allow_path_usage: bool,
) -> Result<Vec<LocalKParProject>, KParBuildError<LocalSrcError>> {
let ws_metamodel = workspace.metamodel().map(|iri| iri.as_str());
use crate::workspace::{resolve_project_info, resolve_project_metadata};

let mut result = Vec::new();
for project_root in workspace.projects() {
for ws_project_info in workspace.projects() {
let project = LocalSrcProject {
nominal_path: None,
project_path: workspace.root_path().join(&project_root.path),
project_path: workspace.root_path().join(&ws_project_info.path),
};

let file_name = default_kpar_file_name(&project)?;
// Read .project.json and .meta.json with workspace-inheritance support.
let (raw_info, raw_meta) = project.get_project_with_inherit()?;
let raw_info = raw_info.ok_or(KParBuildError::MissingInfo)?;
let raw_meta = raw_meta.ok_or(KParBuildError::MissingMeta)?;

// Resolve workspace references.
let resolved_info = resolve_project_info(raw_info, workspace.info())?;
let resolved_meta =
resolve_project_metadata(raw_meta, workspace.info(), &resolved_info.name)?;

// Use resolved version for the output filename.
let file_name = format!(
"{}-{}.kpar",
resolved_info
.name
.chars()
.map(|c| if c.is_alphanumeric() { c } else { '_' })
.collect::<String>(),
resolved_info.version
);
let output_path = path.as_ref().join(file_name);

// Wrap the project so that `temporary_from_project` (called inside
// `do_build_kpar_inner`) reads the resolved values rather than the
// raw files that may contain workspace inheritance placeholders.
let resolved_project = ResolvedProject {
inner: &project,
info: resolved_info,
meta: resolved_meta,
};

let kpar_project = do_build_kpar_inner(
&project,
&resolved_project,
&output_path,
compression,
canonicalise,
allow_path_usage,
ws_metamodel,
)?;
result.push(kpar_project);
}
Expand Down
Loading
Loading