diff --git a/.github/workflows/cross-compile.yml b/.github/workflows/cross-compile.yml index c9b354cb9..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 @@ -15,16 +21,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: @@ -124,6 +120,9 @@ 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 }} \ + -Djssc.native.release=${{ inputs.release }} \ ' - name: Push recompiled binaries @@ -177,7 +176,11 @@ 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 }} \ + -Djssc.native.release=${{ inputs.release }} - name: Push recompiled binaries run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index 6181fa216..b0dde9172 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,6 +89,8 @@ endif() if(WIN32) set(CPP_SOURCE "windows") + # Version resource (jssc.rc.in) + enable_language(RC) # Remove "lib" prefix from windows binaries set(CMAKE_SHARED_LIBRARY_PREFIX "") # Disable optimization for Release builds (XP/Server 2003) @@ -177,11 +179,57 @@ 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. +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_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}") 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) + +if(WIN32) + # VERSIONINFO wants numbers: 2.10.5-SNAPSHOT becomes 2,10,5,0 + string(REGEX MATCH "^([0-9]+)\\.([0-9]+)\\.([0-9]+)" JSSC_VERSION_NUMERIC "${JSSC_VERSION}") + set(JSSC_VERSION_MAJOR ${CMAKE_MATCH_1}) + set(JSSC_VERSION_MINOR ${CMAKE_MATCH_2}) + set(JSSC_VERSION_PATCH ${CMAKE_MATCH_3}) + # Vendor and copyright are fixed in pom.xml, so read them from there for maven and plain cmake builds alike + file(READ ${CMAKE_CURRENT_SOURCE_DIR}/pom.xml POM_CONTENT) + string(REGEX MATCH "[ \t\r\n]*([^<]*)" POM_MATCH "${POM_CONTENT}") + set(JSSC_VENDOR "${CMAKE_MATCH_1}") + string(REGEX MATCH "([^<]*)" POM_MATCH "${POM_CONTENT}") + set(JSSC_COPYRIGHT "${CMAKE_MATCH_1}") + if(NOT JSSC_VENDOR OR NOT JSSC_COPYRIGHT) + message(WARNING "Could not read vendor or copyright from pom.xml, VERSIONINFO will be incomplete") + endif() + configure_file(src/main/cpp/windows/jssc.rc.in ${CMAKE_CURRENT_BINARY_DIR}/jssc.rc @ONLY) + target_sources(jssc PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/jssc.rc) +endif() # 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..9d3c69689 100644 --- a/ant/build.xml +++ b/ant/build.xml @@ -259,13 +259,23 @@ + + + + + + + - + + + + Alexey Sokolov (scream3r) 2010-2014 and jSSC contributors + false false @@ -108,6 +126,11 @@ ${project.basedir}/src/main/resources-precompiled + + + ${project.basedir}/src/main/resources + true + @@ -301,6 +324,10 @@ false + + + ${project.version} + ${maven.exclude.tests} @@ -318,6 +345,8 @@ org.apache.maven.plugins maven-javadoc-plugin + + Copyright © ${jssc.copyright}. LGPL-3.0-or-later diff --git a/src/main/cpp/ident.cpp b/src/main/cpp/ident.cpp new file mode 100644 index 000000000..447c026d2 --- /dev/null +++ b/src/main/cpp/ident.cpp @@ -0,0 +1,45 @@ +/* jSSC (Java Simple Serial Connector) - serial port communication library. + * + * This file is part of jSSC. + * + * jSSC is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * jSSC is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with jSSC. If not, see . + */ +#include "version.h" + +/* + * Build ident: version, git revision, target directory and build origin, e.g. + * + * @(#)JSSC-IDENT version=2.10.5 rev=77a3c9d target=linux_64 origin=java-native/jssc + * + * It sits in a section of its own and is never referenced, so it is marked as used to survive + * optimization, stripping and dead code elimination. 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 + */ +#if defined(_MSC_VER) + #pragma section(".jssc", read) + extern "C" __declspec(allocate(".jssc")) const char jssc_ident[] = JSSC_IDENT; + #if defined(_M_IX86) + #pragma comment(linker, "/include:_jssc_ident") + #else + #pragma comment(linker, "/include:jssc_ident") + #endif +#elif defined(__APPLE__) + extern "C" __attribute__((used, section("__TEXT,__jssc"))) const char jssc_ident[] = JSSC_IDENT; +#else + extern "C" __attribute__((used, section(".jssc"))) const char jssc_ident[] = JSSC_IDENT; +#endif diff --git a/src/main/cpp/version.h.in b/src/main/cpp/version.h.in index 07985fe1d..b9a0ca712 100644 --- a/src/main/cpp/version.h.in +++ b/src/main/cpp/version.h.in @@ -1 +1,2 @@ #cmakedefine JSSC_VERSION "@JSSC_VERSION@" +#define JSSC_IDENT "@JSSC_IDENT@" 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 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);