Skip to content
Open
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
15 changes: 11 additions & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,12 @@ jobs:

- run: sudo apt-get update && sudo apt-get install socat ${{ matrix.packages }}
- run: mvn -P "${{ matrix.profile }}" --batch-mode
# The build rewrites the committed JNI header when SerialNativeInterface.java changed it (ant/build.xml)
- name: Check the JNI header is committed
run: git diff --exit-code -- src/main/cpp/jssc_SerialNativeInterface.h

macos:
runs-on: [macos-14]
runs-on: [macos-15]
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -81,13 +84,14 @@ jobs:
- name: Set SDK version
run: |
export MACOSX_DEPLOYMENT_TARGET=${{ matrix.macos-deployment-target }}
export SDKROOT=$XCODE_16_DEVELOPER_DIR/Platforms/MacOSX.platform/Developer/SDKs/${{ matrix.sdk-version }}
export CMAKE_OSX_SYSROOT=$SDKROOT
export CMAKE_OSX_SYSROOT=$XCODE_16_DEVELOPER_DIR/Platforms/MacOSX.platform/Developer/SDKs/${{ matrix.sdk-version }}
echo "MACOSX_DEPLOYMENT_TARGET=$MACOSX_DEPLOYMENT_TARGET" >> $GITHUB_ENV
echo "SDKROOT=$SDKROOT" >> $GITHUB_ENV
echo "CMAKE_OSX_SYSROOT=$CMAKE_OSX_SYSROOT" >> $GITHUB_ENV

- run: mvn -P "${{ matrix.profile }}" --batch-mode
# The build rewrites the committed JNI header when SerialNativeInterface.java changed it (ant/build.xml)
- name: Check the JNI header is committed
run: git diff --exit-code -- src/main/cpp/jssc_SerialNativeInterface.h

windows:
runs-on: [windows-latest]
Expand All @@ -107,3 +111,6 @@ jobs:
distribution: temurin

- run: mvn -P "${{ matrix.profile }}" --batch-mode
# The build rewrites the committed JNI header when SerialNativeInterface.java changed it (ant/build.xml)
- name: Check the JNI header is committed
run: git diff --exit-code -- src/main/cpp/jssc_SerialNativeInterface.h
22 changes: 17 additions & 5 deletions .github/workflows/cross-compile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ env:
GITHUB_BOT_NAME: github-actions
GITHUB_BOT_EMAIL: 41898282+github-actions[bot]@users.noreply.github.com
DOCK_CROSS_TAG: 20231208-4e4d3ad
# The targets the matrices below build, checked by PrecompiledNativesTest
NATIVE_TARGETS: linux_32,linux_64,linux_arm,linux_arm64,linux_ppc,linux_riscv32,linux_riscv64,osx_64,osx_arm64,windows_32,windows_64,windows_arm64

jobs:
create-branch:
Expand Down Expand Up @@ -135,7 +137,7 @@ jobs:
while git pull --rebase && ! git push; do sleep 5; done

macos:
runs-on: macos-14
runs-on: macos-15
needs: create-branch
strategy:
matrix:
Expand Down Expand Up @@ -170,10 +172,8 @@ jobs:
- name: Set SDK version
run: |
export MACOSX_DEPLOYMENT_TARGET=${{ matrix.macos-deployment-target }}
export SDKROOT=$XCODE_16_DEVELOPER_DIR/Platforms/MacOSX.platform/Developer/SDKs/${{ matrix.sdk-version }}
export CMAKE_OSX_SYSROOT=$SDKROOT
export CMAKE_OSX_SYSROOT=$XCODE_16_DEVELOPER_DIR/Platforms/MacOSX.platform/Developer/SDKs/${{ matrix.sdk-version }}
echo "MACOSX_DEPLOYMENT_TARGET=$MACOSX_DEPLOYMENT_TARGET" >> $GITHUB_ENV
echo "SDKROOT=$SDKROOT" >> $GITHUB_ENV
echo "CMAKE_OSX_SYSROOT=$CMAKE_OSX_SYSROOT" >> $GITHUB_ENV
- name: Build with Maven
run: |
Expand Down Expand Up @@ -204,4 +204,16 @@ jobs:
git reset --soft ${{ needs.create-branch.outputs.base_rev }}
git add src/main/resources-precompiled/**
git commit -m "Precompiled natives @${{ needs.create-branch.outputs.base_rev }}"
git push -f
- name: Set up JDK 11
uses: actions/setup-java@v3
with:
java-version: 11
distribution: temurin
# Same check as release:prepare (PrecompiledNativesTest): a missing or stale native fails here, before the squashed branch is pushed
- name: Verify the natives
run: |
mvn -B -P package test -Djssc.native.targets=${NATIVE_TARGETS} \
-Djssc.build.rev=${{ needs.create-branch.outputs.base_rev }} \
-Djssc.build.origin=${{ github.repository }} \
-Djssc.native.release=${{ inputs.release }}
- run: git push -f
15 changes: 13 additions & 2 deletions .github/workflows/release-new-version.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,20 @@ env:
JAVA_TOOL_OPTIONS: -Duser.name=io.github.java-native
GITHUB_BOT_NAME: github-actions
GITHUB_BOT_EMAIL: 41898282+github-actions[bot]@users.noreply.github.com
# The targets a release ships, checked by PrecompiledNativesTest; keep in step with cross-compile.yml
NATIVE_TARGETS: linux_32,linux_64,linux_arm,linux_arm64,linux_ppc,linux_riscv32,linux_riscv64,osx_64,osx_arm64,windows_32,windows_64,windows_arm64

jobs:
maven-central-release:
runs-on: ubuntu-latest
env:
# Passed through the environment, not substituted into the scripts, so the input cannot inject commands
REQUESTED_RELEASE_VERSION: ${{ github.event.inputs.releaseVersion }}
steps:
- uses: actions/checkout@v3
with:
# PrecompiledNativesTest, run by release:prepare, diffs the sources against the revision the natives were built from
fetch-depth: 0
- run: git config --global user.email "${GITHUB_BOT_EMAIL}"
- run: git config --global user.name "${GITHUB_BOT_NAME}"
- name: Set up JDK 11
Expand All @@ -30,9 +38,12 @@ jobs:
gpg-private-key: ${{ secrets.JAVA_NATIVE_PGP_KEY }}
- name: Set release version if provided as input
if: github.event.inputs.releaseVersion != ''
run: echo "VERSIONS=-DreleaseVersion=${{ github.event.inputs.releaseVersion }}" >> $GITHUB_ENV
run: echo "VERSIONS=-DreleaseVersion=${REQUESTED_RELEASE_VERSION}" >> $GITHUB_ENV
- name: Publish artifacts to Maven Central
run: mvn -B release:prepare release:perform -P package,maven-central-release ${VERSIONS}
# Only natives built by this repository for NATIVE_TARGETS may ship (PrecompiledNativesTest checks them)
run: >-
mvn -B release:prepare release:perform -P package,maven-central-release ${VERSIONS}
"-Djssc.release.arguments=-Djssc.build.origin=${{ github.repository }} -Djssc.native.targets=${NATIVE_TARGETS}"
env:
MAVEN_CENTRAL_PUBLISHING_TOKEN_USERNAME: ${{ secrets.MAVEN_CENTRAL_PUBLISHING_TOKEN_USERNAME }}
MAVEN_CENTRAL_PUBLISHING_TOKEN_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PUBLISHING_TOKEN_PASSWORD }}
Expand Down
6 changes: 6 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,12 @@ endif()
if(NOT JSSC_BUILD_ORIGIN)
set(JSSC_BUILD_ORIGIN "local")
endif()
# The ident is space separated key=value pairs of printable ASCII
foreach(IDENT_FIELD JSSC_VERSION JSSC_BUILD_REV JSSC_BUILD_ORIGIN)
if(NOT "${${IDENT_FIELD}}" MATCHES "^[A-Za-z0-9._+/-]+$")
message(FATAL_ERROR "${IDENT_FIELD} \"${${IDENT_FIELD}}\" must be printable ASCII without spaces or quotes")
endif()
endforeach()
set(JSSC_BUILD_INFO "version=${JSSC_VERSION} rev=${JSSC_BUILD_REV} target=${NATIVE_LIB_DIR} origin=${JSSC_BUILD_ORIGIN}")
set(JSSC_IDENT "@(#)JSSC-IDENT ${JSSC_BUILD_INFO}")
message(STATUS "Native library ident: ${JSSC_IDENT}")
Expand Down
91 changes: 66 additions & 25 deletions ant/build.xml
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
<project default="gather-props">
<project default="gather-props" xmlns:if="ant:if">
<!-- Properties for export to maven -->
<target name="gather-props" depends="main-props,msvc-props,xcode-props,gcc-props,unset-props,test-props">
<target name="gather-props" depends="main-props,msvc-props,xcode-props,gcc-props,unset-props,ident-props,test-props">
<propertyfile file="${ant.properties.file}">
<entry key="cmake.generator.nativelibdir" value="${cmake.generator.nativelibdir}"/>
<entry key="cmake.generator.arg" value="${cmake.generator.arg}"/>
<entry key="cmake.build.arg" value="${cmake.build.arg}"/>
<entry key="maven.nativelibdir.path" value="${maven.nativelibdir.path}"/>
<entry key="maven.assembly.id" value="${maven.assembly.id}"/>
<entry key="maven.test.skip" value="${maven.test.skip}"/>
<entry key="maven.include.tests" value="${maven.include.tests}"/>
<entry key="maven.exclude.tests" value="${maven.exclude.tests}"/>
<entry key="jssc.native.version" value="${jssc.native.version}"/>
<entry key="jssc.build.rev" value="${jssc.build.rev}"/>
<entry key="jssc.build.origin" value="${jssc.build.origin}"/>
</propertyfile>
</target>

Expand Down Expand Up @@ -159,8 +163,15 @@
<equals arg1="${os.target.arch}" arg2="arm_32"/>
</condition>

<!-- SDK from env CMAKE_OSX_SYSROOT, if set. Not SDKROOT: the xcrun shims (make, c++) read it too,
and newer Xcodes cannot resolve old SDKs -->
<property environment="env"/>
<condition property="xcode.sysroot.arg" value="-DCMAKE_OSX_SYSROOT=&quot;${env.CMAKE_OSX_SYSROOT}&quot;" else="">
<isset property="env.CMAKE_OSX_SYSROOT"/>
</condition>

<!-- Set cmake property "CMAKE_OSX_ARCHITECTURES" -->
<property name="cmake.generator.arg" value="-DCMAKE_OSX_ARCHITECTURES=${xcode.arch}"/>
<property name="cmake.generator.arg" value="-DCMAKE_OSX_ARCHITECTURES=${xcode.arch} ${xcode.sysroot.arg}"/>
</target>

<target name="gcc-props" if="use.gcc">
Expand Down Expand Up @@ -192,17 +203,23 @@
<property description="fallback value" name="cmake.generator.nativelibdir" value=""/>
</target>

<target name="ident-props">
<!-- Build ident: blank lets CMake take the revision from git and set origin "local" -->
<property description="fallback value" name="jssc.build.rev" value=""/>
<property description="fallback value" name="jssc.build.origin" value=""/>
<!-- Native version: jssc.native.version if given, else project.version,
without its qualifier if jssc.native.release=true -->
<condition property="jssc.native.version" value="${release.version}" else="${project.version}">
<istrue value="${jssc.native.release}"/>
</condition>
</target>

Comment on lines +206 to +216

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved this section from cmake-generate section below. These props are now exported back to maven and to java/junit.

<target name="test-props">
<!-- Calculate if tests will run -->
<condition property="maven.test.skip" value="false" else="true">
<!-- Honor existing flag if set -->
<and>
<not>
<isset property="maven.test.skip"/>
</not>
<!-- Run tests if detected system matches target system -->
<equals arg1="${os.target.classifier}" arg2="${os.detected.classifier}"/>
</and>
<!-- Tests run unless maven.test.skip or skipTests is set -->
<property name="maven.test.skip" value="false"/>
<!-- All tests if host matches target; otherwise only NativeIdentTest, which reads the library without loading it -->
<condition property="maven.include.tests" value="**/*Test.java" else="**/NativeIdentTest.java">
<equals arg1="${os.target.classifier}" arg2="${os.detected.classifier}"/>
</condition>

<!-- Skip ManualBootLibraryPathTest test if we're not building a native lib -->
Expand All @@ -211,14 +228,20 @@
</condition>

<!-- Summarize host/target -->
<echo level="info">Tests will run only if the TARGET and HOST match:${line.separator}${line.separator}</echo>
<echo level="info">All tests will run only if the TARGET and HOST match:${line.separator}${line.separator}</echo>
<echo level="info">TARGET: ${os.target.classifier}</echo>
<echo level="info">DETECTED: ${os.detected.classifier}</echo>
<echo level="info"/>

<!-- Negate result for human readability -->
<condition property="maven.test.message" value="Tests will NOT run" else="Tests WILL run">
<equals arg1="${maven.test.skip}" arg2="true"/>
<condition property="maven.test.message" value="Tests will NOT run">
<or>
<istrue value="${maven.test.skip}"/>
<istrue value="${skipTests}"/>
</or>
</condition>
<condition property="maven.test.message" value="Tests WILL run" else="Only NativeIdentTest will run">
<equals arg1="${maven.include.tests}" arg2="**/*Test.java"/>
</condition>
<echo level="info">===== ${maven.test.message} =====</echo>
<echo level="info"/>
Expand Down Expand Up @@ -258,15 +281,7 @@
<echo level="info"></echo>
</target>

<target name="cmake-generate">
<!-- Build ident: blank lets CMake take the revision from git and say "local" (cross-compile.yml sets both) -->
<property description="fallback value" name="jssc.build.rev" value=""/>
<property description="fallback value" name="jssc.build.origin" value=""/>
<!-- Native version defaults to the pom version as is, unless jssc.native.version is given
or jssc.native.release=true is set which drops the qualifier (used in cross-compilation ahead of release) -->
<condition property="jssc.native.version" value="${release.version}" else="${project.version}">
<istrue value="${jssc.native.release}"/>
</condition>
<target name="cmake-generate" depends="ident-props">
<mkdir dir="${cmake.generated.directory}"/>
<exec executable="cmake" dir="${cmake.generated.directory}" failonerror="true">
<arg line="${ant.project.basedir}"/>
Expand All @@ -292,6 +307,32 @@
<copy todir="${cmake.generated.directory}" flatten="true" overwrite="true" verbose="true" failonerror="false" quiet="true">
<fileset dir="${cmake.generated.directory}/../nar/" includes="**/*.h"/>
</copy>
<!-- The committed JNI header follows the generated one, so a JNI change is a change in src/main/cpp.
Unlike the natives, the build writes it to the source tree: it is a source, and CI fails when a build
changed it (build.yml), so an updated header can't be left out of a commit -->
<property name="jni.header.generated" location="${cmake.generated.directory}/jssc_SerialNativeInterface.h"/>
<property name="jni.header.committed" location="${ant.project.basedir}/src/main/cpp/jssc_SerialNativeInterface.h"/>
<fail message="No JNI header was generated at ${jni.header.generated}">
<condition>
<not><available file="${jni.header.generated}"/></not>
</condition>
</fail>
<!-- The header as it is committed, the same on every OS and JDK: javac on Windows writes long constants
with MSVC's i64 suffix, which GCC and Clang reject, so they get LL as elsewhere -->
<property name="jni.header.expected" location="${cmake.generated.directory}/committed/jssc_SerialNativeInterface.h"/>
<copy file="${jni.header.generated}" tofile="${jni.header.expected}" overwrite="true">
<filterchain>
<fixcrlf eol="lf"/>
<tokenfilter><replaceregex pattern="^(#define \S+ -?[0-9]+)i64$" replace="\1LL"/></tokenfilter>
</filterchain>
</copy>
<!-- Line endings don't count, so a checkout with CRLF is left alone -->
<condition property="jni.header.outdated">
<not><filesmatch file1="${jni.header.expected}" file2="${jni.header.committed}" textfile="true"/></not>
</condition>
<copy if:set="jni.header.outdated" file="${jni.header.expected}" tofile="${jni.header.committed}" overwrite="true"/>
<echo if:set="jni.header.outdated" level="warning"
message="The JNI surface of SerialNativeInterface.java changed: updated ${jni.header.committed}, commit it"/>
Comment on lines +310 to +335

@pietrygamat pietrygamat Nov 1, 2026 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our java builds generate jni header and use it for compilation, so the committed one is unused. This part detects that the generated output is the same as the committed, and if not - replaces the committed one so the author of the change may commit it and keep in sync. I run tests against JDK 8, 11, 17, 21 and 25 and confirm, they all produce identical output, so this should not randomly change, but with one quirk:

The header generator distinguishes between Windows and other OSes, so on Windows the headers have 1 difference:

            case LONG:
                // Visual C++ supports the i64 suffix, not LL.
                valueStr = value.toString() + ((isWindows) ? "i64" : "LL");
                break;

This is a compatibility layer that to my research, is a relic of 1998 era of MSVC compilers, and the LL format works on any modern compiler including Windows. Our repo has a header with LL commited, and non java raw cmake build works.

// ours:     #define jssc_SerialNativeInterface_ERR_PORT_BUSY -1LL
// windows:  #define jssc_SerialNativeInterface_ERR_PORT_BUSY -1i64

We don't want Windows users see this as a change, and especially to make them commit i64 which I found is NOT accepted on Linux, so the code above replaces i64 -> LL - I could not find a pattern that would cause a false positive.

Anyway, the header that current build generates is still one that the compiler uses, so the replacement is a form of tracking changes in the repo rather than a rewrite of the compilation process.

<exec executable="cmake" dir="${cmake.generated.directory}" failonerror="true">
<arg line="--build"/>
<arg line="."/>
Expand Down
55 changes: 54 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@
<javah.skip>false</javah.skip>
<jar.dependencies.skip>true</jar.dependencies.skip>
<update-resources-precompiled.skip>true</update-resources-precompiled.skip>
<jssc.verify.natives.skip>true</jssc.verify.natives.skip>
<!-- The targets verify-natives checks, comma separated; the caller names them -->
<jssc.native.targets></jssc.native.targets>
<!-- Extra arguments for the builds forked by release:prepare and release:perform -->
<jssc.release.arguments></jssc.release.arguments>

<!-- dependency versions a-z -->
<dependency.junit.version>4.13.2</dependency.junit.version>
Expand Down Expand Up @@ -327,12 +332,42 @@
<!-- Checked against LibraryVersion in SerialNativeInterfaceTest -->
<systemPropertyVariables>
<jssc.expected.version>${project.version}</jssc.expected.version>
<!-- Expected JSSC-IDENT values, from ant/build.xml (NativeIdentTest, PrecompiledNativesTest) -->
<!--suppress UnresolvedMavenProperty -->
<jssc.expected.native.dir>${maven.nativelibdir.path}</jssc.expected.native.dir>
<!--suppress UnresolvedMavenProperty -->
<jssc.expected.native.version>${jssc.native.version}</jssc.expected.native.version>
<!--suppress UnresolvedMavenProperty -->
<jssc.expected.build.rev>${jssc.build.rev}</jssc.expected.build.rev>
<!--suppress UnresolvedMavenProperty -->
<jssc.expected.build.origin>${jssc.build.origin}</jssc.expected.build.origin>
<jssc.expected.native.targets>${jssc.native.targets}</jssc.expected.native.targets>
<jssc.native.build.skip>${cmake.build.skip}</jssc.native.build.skip>
</systemPropertyVariables>
<!-- Only NativeIdentTest when cross-compiling, see test-props in ant/build.xml -->
<includes>
<!--suppress UnresolvedMavenProperty -->
<include>${maven.include.tests}</include>
</includes>
<excludes>
<!--suppress UnresolvedMavenProperty -->
<exclude>${maven.exclude.tests}</exclude>
<!-- Runs on request only, in verify-natives -->
<exclude>**/PrecompiledNativesTest.java</exclude>
</excludes>
</configuration>
<executions>
<!-- Checks the precompiled natives a release ships: -Djssc.native.targets=... (or -Djssc.verify.natives.skip=false, as release:prepare does, which fails without the targets) -->
<execution>
<id>verify-natives</id>
<goals><goal>test</goal></goals>
<configuration>
<skipTests>${jssc.verify.natives.skip}</skipTests>
<!-- overrides includes and excludes -->
<test>PrecompiledNativesTest</test>
</configuration>
</execution>
</executions>
</plugin>

<plugin>
Expand Down Expand Up @@ -500,12 +535,26 @@
</properties>
</profile>

<!-- Naming the targets requests verify-natives: -Djssc.native.targets=linux_64,osx_arm64 -->
<profile>
<id>verify-natives</id>
<activation>
<property>
<name>jssc.native.targets</name>
</property>
</activation>
<properties>
<jssc.verify.natives.skip>false</jssc.verify.natives.skip>
</properties>
</profile>

<!-- collect existing native libraries for a distribution; skips native compilation -->
<profile>
<id>package</id>
<properties>
<javah.skip>true</javah.skip>
<maven.test.skip>true</maven.test.skip>
<!-- not maven.test.skip: the tests are compiled for verify-natives -->
<skipTests>true</skipTests>
<cmake.build.skip>true</cmake.build.skip>
<cmake.generate.skip>true</cmake.generate.skip>
</properties>
Expand Down Expand Up @@ -714,6 +763,10 @@
<pushChanges>false</pushChanges>
<localCheckout>true</localCheckout>
<goals>deploy</goals>
<!-- Refuse precompiled natives not built for this version from these sources (PrecompiledNativesTest) -->
<arguments>-Djssc.verify.natives.skip=false ${jssc.release.arguments}</arguments>
<!-- release:perform runs it again in its checkout, which needs the history back to the natives' rev -->
<scmShallowClone>false</scmShallowClone>
</configuration>
</plugin>
<plugin>
Expand Down
Loading
Loading