From 1c461ec71553d6faa49e56b540dd0a443bc2192e Mon Sep 17 00:00:00 2001 From: Mateusz Pietryga Date: Fri, 16 Oct 2026 23:11:52 +0200 Subject: [PATCH 1/4] Embed a build ident in every native library In order to be able to identify a binary library built for any architecture, and prevent errors when packaging during release, every native now carries one ident line in a section of its own: @(#)JSSC-IDENT version=${VERSION} rev=${REV} target=${LIB_DIR} origin=${ORIGIN} cross-compile.yml passes the revision and the repository it runs in (origin=java-native/jssc upstream, a fork reports its own name) through -Djssc.build.rev/-Djssc.build.origin. A local build takes the revision from git, appends -dirty when native sources have uncommitted changes, and says origin=local. Read it without loading the library: - grep -a -o 'JSSC-IDENT [[:print:]]*' (any platform) - readelf -p .jssc libjssc.so - otool -s __TEXT __jssc libjssc.dylib (or: what libjssc.dylib) - objdump -s -j .jssc jssc.dll --- .github/workflows/cross-compile.yml | 7 ++++- CMakeLists.txt | 28 +++++++++++++++++- ant/build.xml | 5 ++++ src/main/cpp/ident.cpp | 45 +++++++++++++++++++++++++++++ src/main/cpp/version.h.in | 1 + 5 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 src/main/cpp/ident.cpp diff --git a/.github/workflows/cross-compile.yml b/.github/workflows/cross-compile.yml index c9b354cb9..8ddc777cf 100644 --- a/.github/workflows/cross-compile.yml +++ b/.github/workflows/cross-compile.yml @@ -124,6 +124,8 @@ jobs: -Dos.target.name=${{ matrix.os_target_name }} \ -Dos.target.arch=${{ matrix.os_target_arch }} \ -Dos.target.bitness=${{ matrix.os_target_bitness }} \ + -Djssc.build.rev=${{ needs.create-branch.outputs.base_rev }} \ + -Djssc.build.origin=${{ github.repository }} \ ' - name: Push recompiled binaries @@ -177,7 +179,10 @@ jobs: echo "SDKROOT=$SDKROOT" >> $GITHUB_ENV echo "CMAKE_OSX_SYSROOT=$CMAKE_OSX_SYSROOT" >> $GITHUB_ENV - name: Build with Maven - run: mvn -B clean install -P ${{ matrix.profile }},update-resources-precompiled + run: | + mvn -B clean install -P ${{ matrix.profile }},update-resources-precompiled \ + -Djssc.build.rev=${{ needs.create-branch.outputs.base_rev }} \ + -Djssc.build.origin=${{ github.repository }} - name: Push recompiled binaries run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index 6181fa216..e2cac06a3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -179,9 +179,35 @@ if(NOT JSSC_VERSION) else() message(STATUS "Release JSSC version based on pom.xml: \"${JSSC_VERSION}\"") endif() +# Build ident (src/main/cpp/ident.cpp). ant/build.xml passes the revision and origin; when blank, +# the revision is taken from git ("-dirty" if native sources have uncommitted changes) and origin=local. +if(NOT JSSC_BUILD_REV) + set(JSSC_BUILD_REV "unknown") + find_package(Git QUIET) + if(GIT_FOUND) + execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + RESULT_VARIABLE GIT_RESULT OUTPUT_VARIABLE GIT_REV + OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET) + if(GIT_RESULT EQUAL 0) + set(JSSC_BUILD_REV "${GIT_REV}") + execute_process(COMMAND ${GIT_EXECUTABLE} status --porcelain -- src/main/cpp toolchain CMakeLists.txt + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + OUTPUT_VARIABLE GIT_DIRTY OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET) + if(GIT_DIRTY) + set(JSSC_BUILD_REV "${JSSC_BUILD_REV}-dirty") + endif() + endif() + endif() +endif() +if(NOT JSSC_BUILD_ORIGIN) + set(JSSC_BUILD_ORIGIN "local") +endif() +set(JSSC_IDENT "@(#)JSSC-IDENT version=${JSSC_VERSION} rev=${JSSC_BUILD_REV} target=${NATIVE_LIB_DIR} origin=${JSSC_BUILD_ORIGIN}") +message(STATUS "Native library ident: ${JSSC_IDENT}") configure_file(src/main/cpp/version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h @ONLY) -add_library(jssc SHARED src/main/cpp/${CPP_SOURCE}/jssc.cpp) +add_library(jssc SHARED src/main/cpp/${CPP_SOURCE}/jssc.cpp src/main/cpp/ident.cpp) # Fall-back header for when maven is not available list(APPEND JSSC_ADDITIONAL_INCLUDES src/main/cpp/) diff --git a/ant/build.xml b/ant/build.xml index d7c213710..13f0e3e35 100644 --- a/ant/build.xml +++ b/ant/build.xml @@ -259,6 +259,9 @@ + + + @@ -266,6 +269,8 @@ + + + Alexey Sokolov (scream3r) 2010-2014 and jSSC contributors + false false @@ -318,6 +336,8 @@ org.apache.maven.plugins maven-javadoc-plugin + + Copyright © ${jssc.copyright}. LGPL-3.0-or-later diff --git a/src/main/cpp/windows/jssc.rc.in b/src/main/cpp/windows/jssc.rc.in new file mode 100644 index 000000000..b515d8955 --- /dev/null +++ b/src/main/cpp/windows/jssc.rc.in @@ -0,0 +1,33 @@ +// Windows version resource, filled by CMake. Shown by Explorer (Properties > Details) and +// PowerShell: (Get-Item jssc.dll).VersionInfo +#include + +VS_VERSION_INFO VERSIONINFO + FILEVERSION @JSSC_VERSION_MAJOR@,@JSSC_VERSION_MINOR@,@JSSC_VERSION_PATCH@,0 + PRODUCTVERSION @JSSC_VERSION_MAJOR@,@JSSC_VERSION_MINOR@,@JSSC_VERSION_PATCH@,0 + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK + FILEFLAGS 0x0L + FILEOS VOS_NT_WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "CompanyName", "@JSSC_VENDOR@" + VALUE "FileDescription", "Java Simple Serial Connector native library (@NATIVE_LIB_DIR@)" + VALUE "FileVersion", "@JSSC_VERSION@" + VALUE "InternalName", "jssc" + VALUE "LegalCopyright", "Copyright (C) @JSSC_COPYRIGHT@. LGPL-3.0-or-later" + VALUE "OriginalFilename", "jssc.dll" + VALUE "ProductName", "jSSC" + VALUE "ProductVersion", "@JSSC_VERSION@" + VALUE "Comments", "@JSSC_BUILD_INFO@" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END From 5993c139c1786ad2021b6909a7e2c54a238b0ee5 Mon Sep 17 00:00:00 2001 From: Mateusz Pietryga Date: Wed, 21 Oct 2026 22:00:39 +0000 Subject: [PATCH 3/4] Read LibraryVersion from a filtered resource SerialNativeInterface.libVersion was hardcoded and only synced by a sed step in the cross-compile workflow, so after a release it lagged behind the pom version. Move it to a LibraryVersion class that reads the version from jssc/jssc-version.properties, which maven fills in through resource filtering. getLibraryVersion() reports the jar's version as is, qualifier included (2.10.5-SNAPSHOT). The test compares it with the version surefire passes from the pom, and is skipped when run outside maven. Drop the now unnecessary workflow step. --- .github/workflows/cross-compile.yml | 10 ------ pom.xml | 9 +++++ src/main/java/jssc/LibraryVersion.java | 33 +++++++++++++++++++ src/main/java/jssc/SerialNativeInterface.java | 4 +-- .../resources/jssc/jssc-version.properties | 2 ++ .../java/jssc/SerialNativeInterfaceTest.java | 8 +++++ 6 files changed, 54 insertions(+), 12 deletions(-) create mode 100644 src/main/java/jssc/LibraryVersion.java create mode 100644 src/main/resources/jssc/jssc-version.properties diff --git a/.github/workflows/cross-compile.yml b/.github/workflows/cross-compile.yml index 8ddc777cf..dcadfa505 100644 --- a/.github/workflows/cross-compile.yml +++ b/.github/workflows/cross-compile.yml @@ -15,16 +15,6 @@ jobs: - run: echo "precompiled_branch=$(git branch --show-current)-precompiled-natives" >> $GITHUB_ENV - name: Create branch for precompiled natives run: git checkout -b ${{ env.precompiled_branch }} - - name: Sync SerialNativeInterface version with the project pom version - run: | - FILE=src/main/java/jssc/SerialNativeInterface.java - RELEASE_VERSION=$(mvn validate help:evaluate -Dexpression=release.version -q -DforceStdout) - sed -i "s/private static final String libVersion =.*/private static final String libVersion = \"${RELEASE_VERSION}\";/" ${FILE} - if [ $(git ls-files --modified) ]; then - git config --global user.email "${GITHUB_BOT_EMAIL}" - git config --global user.name "${GITHUB_BOT_NAME}" - git commit ${FILE} --message "Update version in source files before building native libraries" - fi - run: echo "rev=$(git rev-parse --short HEAD)" >> $GITHUB_ENV - run: git push --force origin HEAD outputs: diff --git a/pom.xml b/pom.xml index 372abed6c..129f68768 100644 --- a/pom.xml +++ b/pom.xml @@ -126,6 +126,11 @@ ${project.basedir}/src/main/resources-precompiled + + + ${project.basedir}/src/main/resources + true + @@ -319,6 +324,10 @@ false + + + ${project.version} + ${maven.exclude.tests} diff --git a/src/main/java/jssc/LibraryVersion.java b/src/main/java/jssc/LibraryVersion.java new file mode 100644 index 000000000..e93e4095f --- /dev/null +++ b/src/main/java/jssc/LibraryVersion.java @@ -0,0 +1,33 @@ +package jssc; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +/** + * Reads the version maven filters into jssc-version.properties next to this class. + */ +final class LibraryVersion { + private static final String RESOURCE = "jssc-version.properties"; + private static final String UNKNOWN = "0.0.0-UNKNOWN"; + + private LibraryVersion() {} + + static String get() { + final InputStream in = LibraryVersion.class.getResourceAsStream(RESOURCE); + if (in == null) { + return UNKNOWN; + } + final Properties properties = new Properties(); + try { + try { + properties.load(in); + } finally { + in.close(); + } + } catch (IOException e) { + return UNKNOWN; + } + return properties.getProperty("version", UNKNOWN); + } +} diff --git a/src/main/java/jssc/SerialNativeInterface.java b/src/main/java/jssc/SerialNativeInterface.java index 242e4fe7e..81fcccfe4 100644 --- a/src/main/java/jssc/SerialNativeInterface.java +++ b/src/main/java/jssc/SerialNativeInterface.java @@ -34,7 +34,7 @@ */ public class SerialNativeInterface { - private static final String libVersion = "2.10.4"; + private static final String libVersion = LibraryVersion.get(); /** Linux **/ public static final int OS_LINUX = 0; @@ -148,7 +148,7 @@ public static int getOsType() { /** * Get library version * - * @return Full library version in "major.minor.patch" format. + * @return Full library version as in the jar's pom. * * @since 0.8 */ diff --git a/src/main/resources/jssc/jssc-version.properties b/src/main/resources/jssc/jssc-version.properties new file mode 100644 index 000000000..4d43e512e --- /dev/null +++ b/src/main/resources/jssc/jssc-version.properties @@ -0,0 +1,2 @@ +# Filtered by maven, read by jssc.LibraryVersion +version=${project.version} diff --git a/src/test/java/jssc/SerialNativeInterfaceTest.java b/src/test/java/jssc/SerialNativeInterfaceTest.java index 91adbbf1e..ffd789c99 100644 --- a/src/test/java/jssc/SerialNativeInterfaceTest.java +++ b/src/test/java/jssc/SerialNativeInterfaceTest.java @@ -49,6 +49,14 @@ public void testPrintVersion() { } + @Test + public void libraryVersionIsFilteredFromPom() { + // Set by surefire in pom.xml; absent when run outside maven, e.g. from an IDE + final String expectedVersion = System.getProperty("jssc.expected.version"); + Assume.assumeNotNull(expectedVersion); + assertThat(SerialNativeInterface.getLibraryVersion(), is(expectedVersion)); + } + @Test(expected = java.io.IOException.class) public void reportsWriteErrorsAsIOException() throws Exception { Assume.assumeFalse(SerialNativeInterface.getOsType() == SerialNativeInterface.OS_WINDOWS); From 047b0498c9fa62503dea4d6cfa3220ddf2b6eaf7 Mon Sep 17 00:00:00 2001 From: Mateusz Pietryga Date: Wed, 21 Oct 2026 21:06:02 +0000 Subject: [PATCH 4/4] Version the natives with the pom version as is, with a release switch The natives reported release.version (2.10.5-SNAPSHOT -> 2.10.5) from getNativeLibraryVersion(), in the ident and in VERSIONINFO, so a snapshot build could not be told apart from a release. They now get the pom version as is, from maven and from a plain cmake build alike, and match getLibraryVersion(). Two ant properties override it: - -Djssc.native.version=X sets it explicitly - -Djssc.native.release=true drops the qualifier (2.10.5) The release switch builds release natives while the pom still says -SNAPSHOT, ahead of release:prepare. cross-compile.yml exposes it as a "release" input, off by default. --- .github/workflows/cross-compile.yml | 12 ++++++++++-- CMakeLists.txt | 2 +- ant/build.xml | 7 ++++++- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cross-compile.yml b/.github/workflows/cross-compile.yml index dcadfa505..fc7c7d12d 100644 --- a/.github/workflows/cross-compile.yml +++ b/.github/workflows/cross-compile.yml @@ -1,5 +1,11 @@ name: Cross-compile native libraries -on: workflow_dispatch +on: + workflow_dispatch: + inputs: + release: + description: 'Release natives: version them without the -SNAPSHOT qualifier (2.10.5-SNAPSHOT -> 2.10.5)' + type: boolean + default: false env: MAVEN_OPTS: -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn @@ -116,6 +122,7 @@ jobs: -Dos.target.bitness=${{ matrix.os_target_bitness }} \ -Djssc.build.rev=${{ needs.create-branch.outputs.base_rev }} \ -Djssc.build.origin=${{ github.repository }} \ + -Djssc.native.release=${{ inputs.release }} \ ' - name: Push recompiled binaries @@ -172,7 +179,8 @@ jobs: run: | mvn -B clean install -P ${{ matrix.profile }},update-resources-precompiled \ -Djssc.build.rev=${{ needs.create-branch.outputs.base_rev }} \ - -Djssc.build.origin=${{ github.repository }} + -Djssc.build.origin=${{ github.repository }} \ + -Djssc.native.release=${{ inputs.release }} - name: Push recompiled binaries run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index daee836c1..b0dde9172 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -179,7 +179,7 @@ if(NOT JSSC_VERSION) endif() endforeach() else() - message(STATUS "Release JSSC version based on pom.xml: \"${JSSC_VERSION}\"") + message(STATUS "JSSC version passed in: \"${JSSC_VERSION}\"") endif() # Build ident (src/main/cpp/ident.cpp). ant/build.xml passes the revision and origin; when blank, # the revision is taken from git ("-dirty" if native sources have uncommitted changes) and origin=local. diff --git a/ant/build.xml b/ant/build.xml index 13f0e3e35..9d3c69689 100644 --- a/ant/build.xml +++ b/ant/build.xml @@ -262,13 +262,18 @@ + + + + - +