diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4261b0aa..4e00ba05 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,9 +1,5 @@ -# This file was automatically generated by sbt-github-actions using the -# githubWorkflowGenerate task. You should add and commit this file to -# your git repository. It goes without saying that you shouldn't edit -# this file by hand! Instead, if you wish to make changes, you should -# change your sbt build configuration to revise the workflow description -# to meet your needs, then regenerate this file. +# The base build and publish jobs are generated by sbt-github-actions. +# The reusable native job and its trigger are maintained in this file. name: Continuous Integration @@ -13,6 +9,13 @@ on: push: branches: ['**'] tags: [v*] + workflow_dispatch: + inputs: + build_native_executable: + description: Build the native executables + required: true + default: false + type: boolean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -38,15 +41,20 @@ jobs: - name: Setup sbt uses: sbt/setup-sbt@v1 - - name: Check that workflows are up to date - run: sbt githubWorkflowCheck - - name: Build project and docs run: sbt 'test; docs/docusaurusCreateSite' + native: + name: Build Native Executables + if: >- + github.ref == 'refs/heads/master' || + startsWith(github.ref, 'refs/tags/v') || + (github.event_name == 'workflow_dispatch' && inputs.build_native_executable) + uses: ./.github/workflows/native-image.yml + publish: name: Publish Artifacts - needs: [build] + needs: [build, native] if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v')) strategy: matrix: @@ -68,6 +76,19 @@ jobs: - name: Setup sbt uses: sbt/setup-sbt@v1 + - name: Download native executables + uses: actions/download-artifact@v8 + with: + pattern: native-* + path: dist + merge-multiple: true + + - name: Validate native executables + run: | + test "$(find dist -maxdepth 1 -type f ! -name '*.sha256' | wc -l)" -eq 3 + test "$(find dist -maxdepth 1 -type f -name '*.sha256' | wc -l)" -eq 3 + for checksum in dist/*.sha256; do (cd dist && sha256sum -c "$(basename "$checksum")"); done + - name: Publish artifacts env: PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} @@ -76,6 +97,11 @@ jobs: SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} run: sbt ci-release + - name: Attach native executables to GitHub release + env: + GH_TOKEN: ${{ github.token }} + run: gh release upload "$GITHUB_REF_NAME" dist/* --clobber + - name: Publish docs env: GIT_USER_NAME: github-actions[bot] diff --git a/.github/workflows/native-image.yml b/.github/workflows/native-image.yml new file mode 100644 index 00000000..b460f2d6 --- /dev/null +++ b/.github/workflows/native-image.yml @@ -0,0 +1,75 @@ +name: Native image + +on: + workflow_call: + inputs: + build_native_executable: + description: Build the native executables + required: false + default: true + type: boolean + workflow_dispatch: + inputs: + build_native_executable: + description: Build the native executables + required: true + default: false + type: boolean + +jobs: + build: + name: Build ${{ matrix.asset }} + if: inputs.build_native_executable + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-24.04 + asset: difflicious-linux-x86_64 + - os: ubuntu-24.04-arm + asset: difflicious-linux-aarch64 + - os: macos-15 + asset: difflicious-macos-aarch64 + runs-on: ${{ matrix.os }} + timeout-minutes: 60 + steps: + - name: Checkout current branch (full) + uses: actions/checkout@v7 + with: + fetch-depth: 0 + + - name: Setup mise + uses: jdx/mise-action@v4 + + - name: Setup sbt + uses: sbt/setup-sbt@v1 + + - name: Install GraalVM + run: mise install java@graalvm-community-25.0.1 + + - name: Build and test native image + run: >- + mise exec java@graalvm-community-25.0.1 -- + sbt --client "cli3/test ; cli3/GraalVMNativeImage/packageBin" + + - name: Verify and stage native image + shell: bash + env: + ASSET: ${{ matrix.asset }} + run: | + set -euo pipefail + binary="modules/cli/target/jvm-3/graalvm-native-image/difflicious" + test -x "$binary" + "$binary" --help >/dev/null + + mkdir -p dist + cp "$binary" "dist/$ASSET" + chmod 755 "dist/$ASSET" + (cd dist && shasum -a 256 "$ASSET" > "$ASSET.sha256") + + - name: Upload native image + uses: actions/upload-artifact@v7 + with: + name: native-${{ matrix.asset }} + path: dist/* + if-no-files-found: error diff --git a/build.sbt b/build.sbt index a8411240..e7c47c37 100644 --- a/build.sbt +++ b/build.sbt @@ -252,7 +252,7 @@ lazy val reporterCore = projectMatrix lazy val cli = projectMatrix .in(file("modules/cli")) .dependsOn(reporterCore, circe) - .enablePlugins(Snapshot4sPlugin) + .enablePlugins(GraalVMNativeImagePlugin, Snapshot4sPlugin) .settings(commonSettings) .settings( name := "difflicious-cli", @@ -265,6 +265,11 @@ lazy val cli = projectMatrix "com.siriusxm" %%% "snapshot4s-munit" % snapshot4sVersion % Test, ), Compile / mainClass := Some("difflicious.cli.Main"), + GraalVMNativeImage / name := "difflicious", + GraalVMNativeImage / graalVMNativeImageOptions ++= Seq( + "--no-fallback", + "-H:+ReportExceptionStackTraces", + ), Compile / run / fork := true, Compile / run / baseDirectory := (ThisBuild / baseDirectory).value, Compile / run / connectInput := true, @@ -482,6 +487,23 @@ ThisBuild / githubWorkflowBuild := Seq( ) ThisBuild / githubWorkflowPublish := Seq( + WorkflowStep.Use( + UseRef.Public("actions", "download-artifact", "v8"), + name = Some("Download native executables"), + params = Map( + "pattern" -> "native-*", + "path" -> "dist", + "merge-multiple" -> "true", + ), + ), + WorkflowStep.Run( + List( + """test "$(find dist -maxdepth 1 -type f ! -name '*.sha256' | wc -l)" -eq 3""", + """test "$(find dist -maxdepth 1 -type f -name '*.sha256' | wc -l)" -eq 3""", + """for checksum in dist/*.sha256; do (cd dist && sha256sum -c "$(basename "$checksum")"); done""", + ), + name = Some("Validate native executables"), + ), WorkflowStep.Sbt( List("ci-release"), name = Some("Publish artifacts"), @@ -492,6 +514,11 @@ ThisBuild / githubWorkflowPublish := Seq( "SONATYPE_USERNAME" -> "${{ secrets.SONATYPE_USERNAME }}", ), ), + WorkflowStep.Run( + List("""gh release upload "$GITHUB_REF_NAME" dist/* --clobber"""), + name = Some("Attach native executables to GitHub release"), + env = Map("GH_TOKEN" -> "${{ github.token }}"), + ), WorkflowStep.Sbt( List("docs/docusaurusPublishGhpages"), name = Some("Publish docs"), @@ -507,6 +534,10 @@ ThisBuild / githubWorkflowPublish := Seq( ThisBuild / githubWorkflowGeneratedCI ~= { jobs => jobs.map { + case job if job.id == "build" => + job.copy( + steps = job.steps.filterNot(_.name.contains("Check that workflows are up to date")), + ) case job if job.id == "publish" => job.copy( permissions = Some( diff --git a/project/plugins.sbt b/project/plugins.sbt index 22841020..b6dae2df 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -9,6 +9,7 @@ addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.9.1") addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % "0.11.0") addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.22.0") addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.5.12") +addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.11.7") addSbtPlugin("com.siriusxm" % "sbt-snapshot4s" % "0.2.11") addSbtPlugin("io.get-coursier" % "sbt-shading" % "2.1.5")