Skip to content
Merged
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
97 changes: 87 additions & 10 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,105 @@ name: CI
on:
push:
branches:
- main # Change to your default branch if different
- main
pull_request:

permissions:
contents: read

env:
JAVA_OPTS: "-Xmx4G -XX:+UseConcMarkSweepGC -XX:+CMSClassUnloadingEnabled"
SBT_OPTS: "-Xmx4G -XX:+UseG1GC"

jobs:
build:
native:
name: Native library (${{ matrix.platform }})
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
# platform must equal the directory name sbt-jni derives from `uname -sm`
include:
- runner: ubuntu-latest
platform: x86_64-linux
- runner: ubuntu-24.04-arm
platform: aarch64-linux
- runner: macos-latest
platform: arm64-darwin
- runner: macos-15-intel
platform: x86_64-darwin
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive

- name: Set up Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"

- name: Set up sbt
uses: sbt/setup-sbt@v1

- name: Silence ECOS prints and profiling
run: ./scripts/silence-ecos-logs

- name: Build the native library and run tests
run: sbt -Dsbt.log.noformat=true nativeCompile test

- name: Check the platform directory name
run: ls "target/native/${{ matrix.platform }}/bin/"

- name: Upload the native library
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.platform }}
path: target/native/${{ matrix.platform }}/bin/libecos0.*
if-no-files-found: error

package:
name: Package every platform into one jar
needs: native
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive

- name: Set up Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"

- name: Set up sbt
uses: sbt/setup-sbt@v1

- name: Silence ECOS prints and profiling
run: ./scripts/silence-ecos-logs

# Each artifact is named by platform, so this lands them at lib_native/<platform>/libecos0.*,
# the layout sbt-jni packages as native/<platform>/ in the jar. The host build of
# x86_64-linux maps to the same path and sbt-jni keeps one copy.
- name: Download the native libraries
uses: actions/download-artifact@v4
with:
path: lib_native

- name: Build Docker image
run: docker build -t ecos-java-scala .
- name: Build the jar
run: sbt -Dsbt.log.noformat=true test package

- name: Run tests
run: docker run --rm ecos-java-scala:latest sbt -Dsbt.log.noformat=true +test
- name: Check the jar holds every platform
run: ./scripts/check-jar-platforms

- name: Publish to Github Repositories
if: github.event_name == 'push' && github.ref == 'refs/heads/${{ github.event.repository.default_branch }}'
- name: Publish to GitHub Packages
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_ACTOR: ${{ github.actor }}
run: docker run --rm -e GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} -e GITHUB_ACTOR=${{ github.actor }} -e PUBLISH_TO_GITHUB=true ecos-java-scala:latest sbt -Dsbt.log.noformat=true +publish
run: sbt -Dsbt.log.noformat=true publish
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# ignore target anywhere in tree
target/

# native libraries collected from other platforms before packaging
lib_native/

# IntelliJ IDEA files
.idea/
*.iml
Expand Down
17 changes: 0 additions & 17 deletions Dockerfile

This file was deleted.

41 changes: 30 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ amd and ldl JNI libraries are licensed under LGPL honoring original Tim Davis's
### Prerequisites

* sbt
* cmake
* [optional] Java SDK 11 (for javah)
* cmake 3.10 or newer
* JDK 17
* [optional] `javah` from a JDK 11 or older, to regenerate the JNI header

### Clone submodules

Expand All @@ -47,12 +48,34 @@ To publish the Scala 2.13 version to local repository invoke:
./local_dev/publish_local_with_lowered_log_levels.sh
```

The above will set the ecos log level configurtions to sane levels, compile native ecos library and put that into jar. Platform is added as classifier to the published jar.
For Mac OS below files will be generated:
The script lowers the ECOS log levels and builds the native library for this machine.
Then sbt packs that library into the jar and writes the jar to:
```
${HOME}/.ivy2/local/io.citrine/ecos_2.13/X.X.X/jars/ecos_2.13-Mac_OS_X.jar
${HOME}/.ivy2/local/io.citrine/ecos_2.13/X.X.X/jars/ecos_2.13.jar
```

A local jar holds one native library, the one for this machine, under `native/<arch>-<kernel>/`.
On an Apple Silicon Mac that directory is `native/arm64-darwin/`.
The local ivy repository is first in sbt's resolver order, so mithril builds against the local jar at once.

### Continuous integration and publishing

The CI workflow builds the native library on four runners, one per platform:

| Runner | Platform directory |
|--------|--------------------|
| `ubuntu-latest` | `native/x86_64-linux/` |
| `ubuntu-24.04-arm` | `native/aarch64-linux/` |
| `macos-latest` | `native/arm64-darwin/` |
| `macos-15-intel` | `native/x86_64-darwin/` |

A final job collects the four libraries into one jar and checks that each directory is present.
On a push to `main` that job publishes the jar to GitHub Packages.
That jar has no classifier.
At runtime sbt-jni reads `uname -sm` and loads the library from the directory with that name.

GitHub Packages rejects a second publish of the same version, so bump `ThisBuild / version` in `build.sbt` for every PR that changes the library.

### Troubleshooting

For certain combinations of Java, CMake, and XCode the `FindJNI` module does not work properly, leading to the following error during compilation:
Expand Down Expand Up @@ -80,13 +103,9 @@ set(JAVA_AWT_INCLUDE_PATH "${JAVA_HOME}/include")

## Usage in Scala project

Add library dependency including classifier, e.g.:
Add the library dependency:
```
private val osNameClassifier = System.getProperty("os.name").replace(' ', '_').trim
private val osArchitecture = System.getProperty("os.arch").replace(' ', '_').trim
private val artifactClassifier = osNameClassifier + "_" + osArchitecture
[...]
libraryDependencies += "io.citrine" %% "ecos" % "X.X.X" classifier artifactClassifier
libraryDependencies += "io.citrine" %% "ecos" % "X.X.X"
```

Native library will be loaded when instance of `NativeECOS` is created.
Expand Down
11 changes: 5 additions & 6 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
import Dependencies._

ThisBuild / scalaVersion := "2.13.15"
ThisBuild / version := "0.0.14"
ThisBuild / version := "0.0.15"
ThisBuild / versionScheme := Some("early-semver")
ThisBuild / organization := "io.citrine"
ThisBuild / organizationName := "Citrine Informatics"
ThisBuild / artifactClassifier := Some(osNameClassifier + "_" + osArchitecture)

// Publish versions based on OS name/cpu architecture
lazy val osNameClassifier = System.getProperty("os.name").replace(' ', '_').trim
lazy val osArchitecture = System.getProperty("os.arch").replace(' ', '_').trim

ThisBuild / credentials += Credentials(
"GitHub Package Registry",
Expand All @@ -22,6 +17,10 @@ lazy val githubRepository = "GitHub Package Registry" at s"https://maven.pkg.git

lazy val commonSettings = Seq(
javah / target := sourceDirectory.value / "native" / "include",
// CI drops libraries from other runners into lib_native/<platform>/ before packaging.
// sbt-jni would also map lib_native/ under the host platform; turn that second mapping off.
Compile / unmanagedPlatformDependentNativeDirectories := Seq.empty,
Test / unmanagedPlatformDependentNativeDirectories := Seq.empty,
crossPaths := true,
packageDoc / publishArtifact := false,
publishTo := Some(githubRepository)
Expand Down
6 changes: 1 addition & 5 deletions local_dev/publish_local_with_lowered_log_levels.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@ set -o pipefail # don't hide errors within pipes
git submodule init
git submodule update

## This is a hack to set the global logging levels down to a reasonable level
## Note that this differs from the commands in the Dockerfile due
## to the differences in GNU and BSD sed. See https://stackoverflow.com/questions/4247068/sed-command-with-i-option-failing-on-mac-but-works-on-linux
sed -i '' 's/PRINTLEVEL (2)/PRINTLEVEL (0)/g' ecos/include/glblopts.h
sed -i '' 's/PROFILING (1)/PROFILING (0)/g' ecos/include/glblopts.h
./scripts/silence-ecos-logs

sbt publishLocal
12 changes: 12 additions & 0 deletions scripts/check-jar-platforms
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env bash
# Fail unless the packaged jar holds a native library for every supported platform.
# Prerequisite: `sbt package` has run.
set -euo pipefail

cd "$(dirname "$0")/.."

jar=$(ls target/scala-2.13/ecos_2.13-*.jar)
unzip -l "$jar" | grep 'native/'
for platform in x86_64-linux aarch64-linux arm64-darwin x86_64-darwin; do
unzip -l "$jar" | grep -q "native/$platform/libecos0" || { echo "missing native/$platform/"; exit 1; }
done
11 changes: 11 additions & 0 deletions scripts/silence-ecos-logs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env bash
# Turn off ECOS console prints and profiling before the native build.
# Prerequisite: the ecos submodule is checked out (git submodule update --init).
# Side effect: edits ecos/include/glblopts.h in the working tree.
set -euo pipefail

cd "$(dirname "$0")/.."

# -i.bak works with both GNU and BSD sed.
sed -i.bak 's/PRINTLEVEL (2)/PRINTLEVEL (0)/; s/PROFILING (1)/PROFILING (0)/' ecos/include/glblopts.h
rm ecos/include/glblopts.h.bak
2 changes: 1 addition & 1 deletion src/native/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# add/modify/remove settings to build your specific library. #
################################################################

cmake_minimum_required(VERSION 3.0.0)
cmake_minimum_required(VERSION 3.10)

# Define project and related variables
# (required by sbt-jni) please use semantic versioning
Expand Down
Loading