From bd6b4d28db1c485685417f1141b0a0eaaad24723 Mon Sep 17 00:00:00 2001 From: Windymelt Date: Mon, 10 Aug 2026 18:37:21 +0900 Subject: [PATCH 01/11] Move source to sbt layout under dev.capslock.comport package Relocate comport.scala to src/main/scala/dev/capslock/comport/Main.scala, strip scala-cli directives, and add the package declaration. Anchor the `comport`/`Comport` .gitignore patterns to the repo root so they do not match the new source directory. --- .gitignore | 4 ++-- .../main/scala/dev/capslock/comport/Main.scala | 7 +------ 2 files changed, 3 insertions(+), 8 deletions(-) rename comport.scala => src/main/scala/dev/capslock/comport/Main.scala (96%) diff --git a/.gitignore b/.gitignore index b135188..5a8af27 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,5 @@ .bsp/ .metals/ -comport -Comport +/comport +/Comport diff --git a/comport.scala b/src/main/scala/dev/capslock/comport/Main.scala similarity index 96% rename from comport.scala rename to src/main/scala/dev/capslock/comport/Main.scala index 32d7be6..9f27eb9 100644 --- a/comport.scala +++ b/src/main/scala/dev/capslock/comport/Main.scala @@ -1,9 +1,4 @@ -//> using platform native -//> using scala 3.8.2 -//> using options -no-indent -rewrite -//> using dep com.lihaoyi::mainargs::0.7.8 -//> using dep com.lihaoyi::os-lib::0.11.8 -//> using dep org.virtuslab::scala-yaml::0.3.3 +package dev.capslock.comport import mainargs.Flag import mainargs.ParserForMethods From 60c36d1426ac3ef99971d147b525f5ef004201f3 Mon Sep 17 00:00:00 2001 From: Windymelt Date: Mon, 10 Aug 2026 18:38:01 +0900 Subject: [PATCH 02/11] Add sbt 2 build definition with Scala Native plugin Introduce build.sbt, project/build.properties, and project/plugins.sbt to build comport with sbt 2.0.2 and sbt-scala-native 0.5.12 on Scala 3.8.2. The native config keeps the previous release-fast / full LTO / commix GC settings and appends whitespace-separated flags from the LINKFLAGS environment variable so the static-link path from the Alpine container build still works. --- build.sbt | 25 +++++++++++++++++++++++++ project/build.properties | 1 + project/plugins.sbt | 1 + 3 files changed, 27 insertions(+) create mode 100644 build.sbt create mode 100644 project/build.properties create mode 100644 project/plugins.sbt diff --git a/build.sbt b/build.sbt new file mode 100644 index 0000000..4d0eeac --- /dev/null +++ b/build.sbt @@ -0,0 +1,25 @@ +import scala.scalanative.build.* + +ThisBuild / scalaVersion := "3.8.2" +ThisBuild / organization := "dev.capslock" + +lazy val comport = (project in file(".")) + .enablePlugins(ScalaNativePlugin) + .settings( + name := "comport", + scalacOptions += "-no-indent", + libraryDependencies ++= Seq( + "com.lihaoyi" %% "mainargs" % "0.7.8", + "com.lihaoyi" %% "os-lib" % "0.11.8", + "org.virtuslab" %% "scala-yaml" % "0.3.3" + ), + nativeConfig ~= { cfg => + val extraLinkOpts = + sys.env.getOrElse("LINKFLAGS", "").split("\\s+").filter(_.nonEmpty).toSeq + cfg + .withMode(Mode.releaseFast) + .withLTO(LTO.full) + .withGC(GC.commix) + .withLinkingOptions(cfg.linkingOptions ++ extraLinkOpts) + } + ) diff --git a/project/build.properties b/project/build.properties new file mode 100644 index 0000000..3e39c25 --- /dev/null +++ b/project/build.properties @@ -0,0 +1 @@ +sbt.version=2.0.2 diff --git a/project/plugins.sbt b/project/plugins.sbt new file mode 100644 index 0000000..a4fc4a7 --- /dev/null +++ b/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.5.12") From 40165c022ae2c1d5d93463480d2e92cfb87e47b8 Mon Sep 17 00:00:00 2001 From: Windymelt Date: Mon, 10 Aug 2026 18:38:56 +0900 Subject: [PATCH 03/11] Switch build.sh to sbt nativeLink Replace the scala-cli invocation with `sbt nativeLink`; native config now lives in build.sbt, so build.sh just exports LINKFLAGS and copies the produced binary at target/scala-3.8.2/comport-out to ./comport for compatibility with existing callers (README, CI upload paths). --- build.sh | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/build.sh b/build.sh index 8627f65..c40421b 100755 --- a/build.sh +++ b/build.sh @@ -1,16 +1,18 @@ #!/bin/sh -# Build script using scala-cli. Overwrites `./comport` if exists. +# Build script using sbt. Overwrites `./comport` if exists. +# LINKFLAGS (whitespace-separated) is picked up by build.sbt and appended +# to nativeConfig.linkingOptions (used e.g. for `-static` in the Alpine +# container build). set -eux set pipefail LINKFLAGS=${LINKFLAGS:-} -COURSIER=${COURSIER:-cs} -SCALA_CLI_LAUNCH=${SCALA_CLI_LAUNCH:-scala-cli} +SBT_LAUNCH=${SBT_LAUNCH:-sbt} -${SCALA_CLI_LAUNCH} --power package . -o comport -f \ - --native-mode release-fast \ - --native-lto full \ - --native-gc commix \ - --native-linking "${LINKFLAGS}" +export LINKFLAGS + +${SBT_LAUNCH} -Dsbt.color=never nativeLink + +cp target/scala-3.8.2/comport-out ./comport From c0cd46c5dbb34567f402c12128739794355144a6 Mon Sep 17 00:00:00 2001 From: Windymelt Date: Mon, 10 Aug 2026 18:39:31 +0900 Subject: [PATCH 04/11] Install sbt instead of scala-cli in the Alpine builder Swap `coursier launch scala-cli` for `coursier install sbt` in the Dockerfile so the container ships a sbt wrapper on PATH, and drop the SCALA_CLI_LAUNCH override from build-with-container.sh (build.sh now defaults to `sbt`). JDK 17 headless is already installed, which meets sbt 2's runtime requirement. --- Dockerfile | 2 +- build-with-container.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0456b4d..1556f96 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,6 +3,6 @@ FROM alpine:latest # libc++-dev is for exception handling RUN apk add --no-cache bash curl clang libc++-dev alpine-sdk lld musl-dev openjdk17-jre-headless RUN curl -fLo /usr/local/bin/coursier https://github.com/coursier/launchers/raw/master/coursier.jar && chmod +x /usr/local/bin/coursier -RUN coursier launch scala-cli +RUN coursier install sbt ENV PATH="$PATH:/root/.local/share/coursier/bin" WORKDIR /work diff --git a/build-with-container.sh b/build-with-container.sh index 42924f8..aabb40f 100755 --- a/build-with-container.sh +++ b/build-with-container.sh @@ -4,4 +4,4 @@ set -eux set pipefail docker build -t comport-builder . -docker run --rm -v "$PWD":/work --rm -e SCALA_CLI_LAUNCH='coursier launch scala-cli --' -e LINKFLAGS=-static comport-builder:latest sh /work/build.sh +docker run --rm -v "$PWD":/work -e LINKFLAGS=-static comport-builder:latest sh /work/build.sh From d3b3c84ceae22ce958f1d4a6d5e34808491977a0 Mon Sep 17 00:00:00 2001 From: Windymelt Date: Mon, 10 Aug 2026 18:40:24 +0900 Subject: [PATCH 05/11] Switch GitHub Actions from scala-cli to sbt - Compile and macOS jobs: install Temurin 17 and sbt via actions/setup-java + sbt/setup-sbt, then run `sbt compile` / `./build.sh`. - Release Linux job: reuse `./build-with-container.sh` (Alpine + sbt image) instead of an inline `docker run` that installed scala-cli each time, matching the CI Linux path and removing the previous inline LTO=thin divergence (release now uses full LTO like build.sbt specifies). --- .github/workflows/ci.yml | 14 +++++++++++--- .github/workflows/release.yml | 22 +++++++--------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c6b2ea4..ab82d48 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,8 +11,12 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: VirtusLab/scala-cli-setup@v1 - - run: scala-cli compile . + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 17 + - uses: sbt/setup-sbt@v1 + - run: sbt compile build-linux: if: github.event_name == 'push' || github.event_name == 'pull_request' @@ -51,7 +55,11 @@ jobs: runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - - uses: VirtusLab/scala-cli-setup@v1 + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 17 + - uses: sbt/setup-sbt@v1 - name: Build native binary run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9270d64..cddba8f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,16 +24,7 @@ jobs: - name: Build in Alpine container run: | - docker run --rm -v "$PWD":/work -w /work alpine:latest sh -c ' - apk add --no-cache bash curl clang lld musl-dev openjdk17-jre-headless && - curl -fLo /usr/local/bin/coursier https://github.com/coursier/launchers/raw/master/coursier.jar && - chmod +x /usr/local/bin/coursier && - coursier launch scala-cli -- --power package . -o comport -f \ - --native-mode release-fast \ - --native-lto thin \ - --native-gc commix \ - --native-linking "-static" - ' + ./build-with-container.sh - name: Upload artifact uses: actions/upload-artifact@v4 @@ -52,7 +43,11 @@ jobs: runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - - uses: VirtusLab/scala-cli-setup@v1 + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 17 + - uses: sbt/setup-sbt@v1 - name: Extract version from tag id: version @@ -60,10 +55,7 @@ jobs: - name: Build native binary run: | - scala-cli --power package . -o comport -f \ - --native-mode release-fast \ - --native-lto full \ - --native-gc commix + ./build.sh - name: Upload artifact uses: actions/upload-artifact@v4 From efc36eebaf044c4820368531654c85c28b0ad7ef Mon Sep 17 00:00:00 2001 From: Windymelt Date: Mon, 10 Aug 2026 18:43:49 +0900 Subject: [PATCH 06/11] Fix nativeLink output path in build.sh sbt-scala-native 0.5.12 on sbt 2 puts the produced binary at `target/out/native0.5/scala-//`, not the sbt 1 `target/scala-/-out` layout that build.sh initially copied from. Verified locally that `./build.sh` now produces a working `./comport`. --- build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sh b/build.sh index c40421b..6031174 100755 --- a/build.sh +++ b/build.sh @@ -15,4 +15,4 @@ export LINKFLAGS ${SBT_LAUNCH} -Dsbt.color=never nativeLink -cp target/scala-3.8.2/comport-out ./comport +cp target/out/native0.5/scala-3.8.2/comport/comport ./comport From e1bf8abac67e0471eb48c4af3d6f7bc97fafbbad Mon Sep 17 00:00:00 2001 From: Windymelt Date: Mon, 10 Aug 2026 18:44:26 +0900 Subject: [PATCH 07/11] Update build-from-source docs to sbt Replace scala-cli instructions with `sbt run` for iteration and `./build.sh` for producing the native binary, matching the new build. --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d1128a8..d615f70 100644 --- a/README.md +++ b/README.md @@ -32,14 +32,15 @@ sudo mv comport /usr/local/bin/ ### Build from source -Requires [Scala CLI](https://scala-cli.virtuslab.org/). +Requires [sbt](https://www.scala-sbt.org/) (2.x), JDK 17+, and Clang/LLVM +for Scala Native linking. ```sh # Run directly -scala-cli run . +sbt run -# Build native binary -scala-cli --power package . -o comport --native-mode release-fast --native-gc commix +# Build native binary (produces ./comport) +./build.sh ``` ## Usage From d643e70d8849fedbf2e097ab5fa7d080c65d4328 Mon Sep 17 00:00:00 2001 From: Windymelt Date: Mon, 10 Aug 2026 18:46:10 +0900 Subject: [PATCH 08/11] Ignore sbt build directories Add `target/`, `project/target/`, and `project/project/` to .gitignore so sbt's build outputs stop showing up in `git status`. --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index 5a8af27..22c6cd9 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,10 @@ .bsp/ .metals/ +# sbt +target/ +project/target/ +project/project/ + /comport /Comport From 48c2df610cfaeaaed9c3ce087dca43ed97268a37 Mon Sep 17 00:00:00 2001 From: Windymelt Date: Mon, 10 Aug 2026 18:50:18 +0900 Subject: [PATCH 09/11] Pin GitHub Actions to commit SHAs Repository policy requires all actions be pinned to a full-length commit SHA rather than a tag reference. Resolve each `@vN` tag through the GitHub API and pin the workflows accordingly. Tag comments are preserved alongside so the intended version stays visible. --- .github/workflows/ci.yml | 18 +++++++++--------- .github/workflows/release.yml | 16 ++++++++-------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ab82d48..f479903 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,12 +10,12 @@ jobs: compile: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-java@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - uses: actions/setup-java@cf277c60eb25467037889841efdb72551f06f6c3 # v4 with: distribution: temurin java-version: 17 - - uses: sbt/setup-sbt@v1 + - uses: sbt/setup-sbt@8feba82adc7f01ddcf8165b86f778bdb5b82cebc # v1 - run: sbt compile build-linux: @@ -30,14 +30,14 @@ jobs: target: aarch64-linux runs-on: ${{ matrix.runner }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 - name: Build in Alpine container run: | ./build-with-container.sh - name: Upload artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: comport-${{ matrix.target }} path: comport @@ -54,19 +54,19 @@ jobs: target: aarch64-macos runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 - - uses: actions/setup-java@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - uses: actions/setup-java@cf277c60eb25467037889841efdb72551f06f6c3 # v4 with: distribution: temurin java-version: 17 - - uses: sbt/setup-sbt@v1 + - uses: sbt/setup-sbt@8feba82adc7f01ddcf8165b86f778bdb5b82cebc # v1 - name: Build native binary run: | ./build.sh - name: Upload artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: comport-${{ matrix.target }} path: comport diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cddba8f..0c91d50 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,7 +16,7 @@ jobs: target: aarch64-linux runs-on: ${{ matrix.runner }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 - name: Extract version from tag id: version @@ -27,7 +27,7 @@ jobs: ./build-with-container.sh - name: Upload artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: comport-${{ steps.version.outputs.version }}-${{ matrix.target }} path: comport @@ -42,12 +42,12 @@ jobs: target: aarch64-macos runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 - - uses: actions/setup-java@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - uses: actions/setup-java@cf277c60eb25467037889841efdb72551f06f6c3 # v4 with: distribution: temurin java-version: 17 - - uses: sbt/setup-sbt@v1 + - uses: sbt/setup-sbt@8feba82adc7f01ddcf8165b86f778bdb5b82cebc # v1 - name: Extract version from tag id: version @@ -58,7 +58,7 @@ jobs: ./build.sh - name: Upload artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: comport-${{ steps.version.outputs.version }}-${{ matrix.target }} path: comport @@ -74,7 +74,7 @@ jobs: run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT" - name: Download all artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: path: artifacts @@ -88,7 +88,7 @@ jobs: done - name: Create GitHub Release - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2 with: name: v${{ steps.version.outputs.version }} generate_release_notes: true From e9e5f16da17ebd17b429a53939fed4c0c35f50ab Mon Sep 17 00:00:00 2001 From: Windymelt Date: Mon, 10 Aug 2026 18:57:37 +0900 Subject: [PATCH 10/11] Force JVM sbt launcher with --server flag sbt 2's default launcher runs the sbtn thin client, whose distributed binary is glibc-linked and does not execute on Alpine (musl); the extraction path also picks unzip on non-linux-gnu OSTYPE, which is not installed in the builder image. Pass `--server` in build.sh so the launcher sets use_sbtn=0 and skips sbtn entirely, matching sbt 1 behavior. SBT_NATIVE_CLIENT is not usable here because the launcher only consults it in the enabling direction. --- build.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/build.sh b/build.sh index 6031174..76e62cb 100755 --- a/build.sh +++ b/build.sh @@ -13,6 +13,8 @@ SBT_LAUNCH=${SBT_LAUNCH:-sbt} export LINKFLAGS -${SBT_LAUNCH} -Dsbt.color=never nativeLink +# --server forces the JVM launcher and skips sbtn (the thin native client). +# sbt 2's sbtn is glibc-linked and does not run on Alpine/musl. +${SBT_LAUNCH} --server -Dsbt.color=never nativeLink cp target/out/native0.5/scala-3.8.2/comport/comport ./comport From dd3cdfeee8a62431cf7b2bb3a2225d7311ee55d5 Mon Sep 17 00:00:00 2001 From: Windymelt Date: Mon, 10 Aug 2026 19:09:43 +0900 Subject: [PATCH 11/11] Simplify build.sbt to sbt 2 bare style sbt 2 injects bare top-level settings into all subprojects (including the implicit root), which replaces the role ThisBuild used to play, and bare `enablePlugins(...)` applies to the implicit root project as well (pattern used by e.g. `sbt-app/src/sbt-test/nio/code-formatter/build.sbt` in the sbt 2.0.2 source tree). Drop the explicit `lazy val comport = project.in(file("."))` scaffolding and ThisBuild scoping accordingly. Verified locally with `sbt --server nativeLink` and `./build.sh`. --- build.sbt | 43 +++++++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/build.sbt b/build.sbt index 4d0eeac..de8b31b 100644 --- a/build.sbt +++ b/build.sbt @@ -1,25 +1,24 @@ import scala.scalanative.build.* -ThisBuild / scalaVersion := "3.8.2" -ThisBuild / organization := "dev.capslock" +enablePlugins(ScalaNativePlugin) -lazy val comport = (project in file(".")) - .enablePlugins(ScalaNativePlugin) - .settings( - name := "comport", - scalacOptions += "-no-indent", - libraryDependencies ++= Seq( - "com.lihaoyi" %% "mainargs" % "0.7.8", - "com.lihaoyi" %% "os-lib" % "0.11.8", - "org.virtuslab" %% "scala-yaml" % "0.3.3" - ), - nativeConfig ~= { cfg => - val extraLinkOpts = - sys.env.getOrElse("LINKFLAGS", "").split("\\s+").filter(_.nonEmpty).toSeq - cfg - .withMode(Mode.releaseFast) - .withLTO(LTO.full) - .withGC(GC.commix) - .withLinkingOptions(cfg.linkingOptions ++ extraLinkOpts) - } - ) +name := "comport" +organization := "dev.capslock" +scalaVersion := "3.8.2" +scalacOptions += "-no-indent" + +libraryDependencies ++= Seq( + "com.lihaoyi" %% "mainargs" % "0.7.8", + "com.lihaoyi" %% "os-lib" % "0.11.8", + "org.virtuslab" %% "scala-yaml" % "0.3.3" +) + +nativeConfig ~= { cfg => + val extraLinkOpts = + sys.env.getOrElse("LINKFLAGS", "").split("\\s+").filter(_.nonEmpty).toSeq + cfg + .withMode(Mode.releaseFast) + .withLTO(LTO.full) + .withGC(GC.commix) + .withLinkingOptions(cfg.linkingOptions ++ extraLinkOpts) +}