diff --git a/.github/workflows/build-deb.yml b/.github/workflows/build-deb.yml new file mode 100644 index 000000000000..acbd52dd5693 --- /dev/null +++ b/.github/workflows/build-deb.yml @@ -0,0 +1,54 @@ +################################################################################################### +### THIS IS A REUSABLE WORKFLOW TO BUILD SCALA DEB ### +### HOW TO USE: ### +### - THE RELEASE WORKFLOW SHOULD CALL THIS WORKFLOW ### +### - IT WILL UPLOAD TO GITHUB THE DEB FILE FOR SCALA UNDER THE 'scala.deb' NAME ### +### ### +### NOTE: ### +### - SCALA 3.8.0+ IS DISTRIBUTED USING JAVA 17 ### +################################################################################################### + +name: Build the Debian Package + +on: + workflow_call: + outputs: + version: + description: 'The full Scala version, from sbt (scala3-compiler-bootstrapped/version)' + value: ${{ jobs.build.outputs.version }} + +env: + # Release only happens when triggering CI by pushing a tag + RELEASEBUILD: ${{ startsWith(github.event.ref, 'refs/tags/') && 'yes' || 'no' }} + +jobs: + build: + runs-on: ubuntu-latest + outputs: + version: ${{ steps.extract-version.outputs.version }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 + with: + distribution: temurin + java-version: 17 + cache: sbt + - uses: sbt/setup-sbt@bfea3c5f48abd221b04a6df4798aa5eb8b6a2baf # v1.5.6 + - name: Ensure Debian build tooling + run: | + command -v dpkg-deb >/dev/null && command -v fakeroot >/dev/null \ + || (sudo apt-get update && sudo apt-get install -y --no-install-recommends dpkg-dev fakeroot) + - name: Extract Scala version + id: extract-version + shell: bash + run: | + version=$(./project/scripts/sbt "print scala3-compiler-bootstrapped/version" | tail -n1) + echo "version=$version" >> "$GITHUB_OUTPUT" + echo "Extracted Scala version: $version" + - name: Build the .deb package + run: ./project/scripts/sbt "dist-linux-x86_64-deb/Debian/packageBin" + - name: Upload deb Artifact + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: scala.deb + path: ./dist/linux-x86_64-deb/target/scala.deb diff --git a/.github/workflows/build-msi.yml b/.github/workflows/build-msi.yml index 9082d6134e6f..c409defbaf72 100644 --- a/.github/workflows/build-msi.yml +++ b/.github/workflows/build-msi.yml @@ -5,7 +5,7 @@ ### - IT WILL UPLOAD TO GITHUB THE MSI FILE FOR SCALA UNDER THE 'scala.msi' NAME ### ### ### ### NOTE: ### -### - WE SHOULD BUILD SCALA USING JAVA 8 ### +### - SCALA 3.8.0+ IS DISTRIBUTED USING JAVA 17 ### ################################################################################################### name: Build the MSI Package @@ -14,14 +14,14 @@ on: workflow_call: outputs: version: - description: 'The developed Scala version (major.minor.patch) extracted from project/Build.scala' + description: 'The full Scala version, from sbt (scala3-compiler-bootstrapped/version)' value: ${{ jobs.build.outputs.version }} permissions: contents: read env: - # Release only happends when triggering CI by pushing tag + # Release only happens when triggering CI by pushing a tag RELEASEBUILD: ${{ startsWith(github.event.ref, 'refs/tags/') && 'yes' || 'no' }} jobs: @@ -40,7 +40,7 @@ jobs: id: extract-version shell: bash run: | - version=$(sed -n 's/.*val developedVersion = "\(.*\)".*/\1/p' project/Build.scala) + version=$(sbt -no-colors "print scala3-compiler-bootstrapped/version" | tr -d '\r' | tail -n1) echo "version=$version" >> "$GITHUB_OUTPUT" echo "Extracted Scala version: $version" - name: Build MSI package diff --git a/.github/workflows/build-rpm.yml b/.github/workflows/build-rpm.yml new file mode 100644 index 000000000000..55136b2275c1 --- /dev/null +++ b/.github/workflows/build-rpm.yml @@ -0,0 +1,53 @@ +################################################################################################### +### THIS IS A REUSABLE WORKFLOW TO BUILD SCALA RPM ### +### HOW TO USE: ### +### - THE RELEASE WORKFLOW SHOULD CALL THIS WORKFLOW ### +### - IT WILL UPLOAD TO GITHUB THE RPM FILE FOR SCALA UNDER THE 'scala.rpm' NAME ### +### ### +### NOTE: ### +### - SCALA 3.8.0+ IS DISTRIBUTED USING JAVA 17 ### +### - rpmbuild is provided by the `rpm` apt package (cross-distro build tool) ### +################################################################################################### + +name: Build the RPM Package + +on: + workflow_call: + outputs: + version: + description: 'The full Scala version, from sbt (scala3-compiler-bootstrapped/version)' + value: ${{ jobs.build.outputs.version }} + +env: + # Release only happens when triggering CI by pushing a tag + RELEASEBUILD: ${{ startsWith(github.event.ref, 'refs/tags/') && 'yes' || 'no' }} + +jobs: + build: + runs-on: ubuntu-latest + outputs: + version: ${{ steps.extract-version.outputs.version }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 + with: + distribution: temurin + java-version: 17 + cache: sbt + - uses: sbt/setup-sbt@bfea3c5f48abd221b04a6df4798aa5eb8b6a2baf # v1.5.6 + - name: Ensure RPM build tooling + run: command -v rpmbuild >/dev/null || (sudo apt-get update && sudo apt-get install -y rpm) + - name: Extract Scala version + id: extract-version + shell: bash + run: | + version=$(./project/scripts/sbt "print scala3-compiler-bootstrapped/version" | tail -n1) + echo "version=$version" >> "$GITHUB_OUTPUT" + echo "Extracted Scala version: $version" + - name: Build the .rpm package + run: ./project/scripts/sbt "dist-linux-x86_64-rpm/Rpm/packageBin" + - name: Upload rpm Artifact + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: scala.rpm + path: ./dist/linux-x86_64-rpm/target/rpm/scala.rpm diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e12c8692a023..6f379a131cc9 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -222,6 +222,30 @@ jobs: mv scala.msi "${msiInstaller}" sha256sum "${msiInstaller}" > "${msiInstaller}.sha256" + - name: Download deb package + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: scala.deb + path: . + - name: Prepare deb package + shell: bash + run: | + debInstaller="scala3-${{ env.RELEASE_TAG }}-x86_64-pc-linux.deb" + mv scala.deb "${debInstaller}" + sha256sum "${debInstaller}" > "${debInstaller}.sha256" + + - name: Download rpm package + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: scala.rpm + path: . + - name: Prepare rpm package + shell: bash + run: | + rpmInstaller="scala3-${{ env.RELEASE_TAG }}-x86_64-pc-linux.rpm" + mv scala.rpm "${rpmInstaller}" + sha256sum "${rpmInstaller}" > "${rpmInstaller}.sha256" + - name: Install GH CLI uses: dev-hanz-ops/install-gh-cli-action@af38ce09b1ec248aeb08eea2b16bbecea9e059f8 # v0.2.1 with: @@ -251,6 +275,8 @@ jobs: --verify-tag ${{ env.RELEASE_TAG }} \ scala3-${{ env.RELEASE_TAG }}*.zip \ scala3-${{ env.RELEASE_TAG }}*.tar.gz \ + scala3-${{ env.RELEASE_TAG }}*.deb \ + scala3-${{ env.RELEASE_TAG }}*.rpm \ scala3-${{ env.RELEASE_TAG }}*.sha256 \ scala3-${{ env.RELEASE_TAG }}.msi @@ -379,6 +405,30 @@ jobs: version: ${{ needs.build-msi-package.outputs.version }} java-version: 17 + build-deb-package: + uses: ./.github/workflows/build-deb.yml + if : + (github.event_name == 'pull_request' && contains(github.event.pull_request.body, '[test_deb]')) || + (github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/')) + + test-deb-package: + uses: ./.github/workflows/test-deb.yml + needs: [build-deb-package] + with: + version: ${{ needs.build-deb-package.outputs.version }} + + build-rpm-package: + uses: ./.github/workflows/build-rpm.yml + if : + (github.event_name == 'pull_request' && contains(github.event.pull_request.body, '[test_rpm]')) || + (github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/')) + + test-rpm-package: + uses: ./.github/workflows/test-rpm.yml + needs: [build-rpm-package] + with: + version: ${{ needs.build-rpm-package.outputs.version }} + build-sdk-package: uses: ./.github/workflows/build-sdk.yml permissions: diff --git a/.github/workflows/test-deb.yml b/.github/workflows/test-deb.yml new file mode 100644 index 000000000000..7a26275027cc --- /dev/null +++ b/.github/workflows/test-deb.yml @@ -0,0 +1,82 @@ +################################################################################################### +### THIS IS A REUSABLE WORKFLOW TO TEST SCALA WITH DEB RUNNER ### +### HOW TO USE: ### +### `version` is the expected Scala version; the installed scala/scalac/scaladoc ### +### are asserted to report it, then a program is compiled and run. ### +### NOTE: Requires `scala.deb` artifact uploaded within the same run ### +### ### +################################################################################################### + +name: Test 'scala' Debian Package +run-name: Test 'scala' (${{ inputs.version }}) Debian Package + +on: + workflow_call: + inputs: + version: + required: true + type: string + +jobs: + test: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + image: + - debian:12 + - debian:13 + - ubuntu:22.04 + - ubuntu:24.04 + container: ${{ matrix.image }} + env: + VERSION: ${{ inputs.version }} + steps: + - name: Download deb artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: scala.deb + path: . + + - name: Install Scala Runner + run: | + apt-get update + apt-get install -y ./scala.deb + + - name: Verify installation layout + run: | + set -e + echo "Checking /usr/bin/scala symlink..." + test -L /usr/bin/scala + test "$(readlink -f /usr/bin/scala)" = /usr/share/scala3/bin/scala + + echo "Checking VERSION file..." + test -f /usr/share/scala3/VERSION + cat /usr/share/scala3/VERSION + + echo "Checking scala-cli launcher..." + test -x /usr/share/scala3/libexec/scala-cli + + echo "Checking maven2 directory..." + test -d /usr/share/scala3/maven2 + + - name: Test Scala Runner + run: | + out="$(scala --version)"; echo "$out" + echo "$out" | grep -qF "Scala version (default): $VERSION" + - name: Test the `scalac` command + run: | + out="$(scalac --version)"; echo "$out" + echo "$out" | grep -qF "Scala compiler version $VERSION" + - name: Test the `scaladoc` command + run: | + out="$(scaladoc --version)"; echo "$out" + echo "$out" | grep -qF "Scaladoc version $VERSION" + + - name: Smoke test - compile and run + run: | + echo '@main def test() = println("Hello from Scala")' > test.scala + scala run --server=false test.scala + + - name: Uninstall the `scala` package + run: apt-get remove -y scala3 diff --git a/.github/workflows/test-rpm.yml b/.github/workflows/test-rpm.yml new file mode 100644 index 000000000000..f3d1a88ffd58 --- /dev/null +++ b/.github/workflows/test-rpm.yml @@ -0,0 +1,95 @@ +################################################################################################### +### THIS IS A REUSABLE WORKFLOW TO TEST SCALA WITH RPM RUNNER ### +### HOW TO USE: ### +### `version` is the expected Scala version; the installed scala/scalac/scaladoc ### +### are asserted to report it, then a program is compiled and run. ### +### NOTE: Requires `scala.rpm` artifact uploaded within the same run ### +### ### +################################################################################################### + +name: Test 'scala' RPM Package +run-name: Test 'scala' (${{ inputs.version }}) RPM Package + +on: + workflow_call: + inputs: + version: + required: true + type: string + +jobs: + test: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + image: + - fedora:43 + - fedora:latest + - almalinux:9 + - almalinux:10 + - opensuse/leap:16.0 + - opensuse/tumbleweed + container: ${{ matrix.image }} + env: + VERSION: ${{ inputs.version }} + steps: + - name: Download rpm artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: scala.rpm + path: . + + - name: Install Scala Runner + run: | + if command -v dnf >/dev/null; then + dnf install -y ./scala.rpm + elif command -v zypper >/dev/null; then + zypper --non-interactive --gpg-auto-import-keys refresh + zypper --non-interactive install --allow-unsigned-rpm ./scala.rpm + else + echo "No supported package manager (dnf/zypper) found"; exit 1 + fi + + - name: Verify installation layout + run: | + set -e + echo "Checking /usr/bin/scala symlink..." + test -L /usr/bin/scala + test "$(readlink -f /usr/bin/scala)" = /usr/share/scala3/bin/scala + + echo "Checking VERSION file..." + test -f /usr/share/scala3/VERSION + cat /usr/share/scala3/VERSION + + echo "Checking scala-cli launcher..." + test -x /usr/share/scala3/libexec/scala-cli + + echo "Checking maven2 directory..." + test -d /usr/share/scala3/maven2 + + - name: Test Scala Runner + run: | + out="$(scala --version)"; echo "$out" + echo "$out" | grep -qF "Scala version (default): $VERSION" + - name: Test the `scalac` command + run: | + out="$(scalac --version)"; echo "$out" + echo "$out" | grep -qF "Scala compiler version $VERSION" + - name: Test the `scaladoc` command + run: | + out="$(scaladoc --version)"; echo "$out" + echo "$out" | grep -qF "Scaladoc version $VERSION" + + - name: Smoke test - compile and run + run: | + echo '@main def test() = println("Hello from Scala")' > test.scala + scala run --server=false test.scala + + - name: Uninstall the `scala` package + run: | + if command -v dnf >/dev/null; then + dnf remove -y scala3 + elif command -v zypper >/dev/null; then + zypper --non-interactive remove scala3 + fi diff --git a/build.sbt b/build.sbt index f34c3ddb5c5a..71b1353eea52 100644 --- a/build.sbt +++ b/build.sbt @@ -42,6 +42,8 @@ val `dist-mac-x86_64` = Build.`dist-mac-x86_64` val `dist-mac-aarch64` = Build.`dist-mac-aarch64` val `dist-win-x86_64` = Build.`dist-win-x86_64` val `dist-linux-x86_64` = Build.`dist-linux-x86_64` +val `dist-linux-x86_64-deb` = Build.`dist-linux-x86_64-deb` +val `dist-linux-x86_64-rpm` = Build.`dist-linux-x86_64-rpm` val `dist-linux-aarch64` = Build.`dist-linux-aarch64` val `community-build` = Build.`community-build` val `scala3-presentation-compiler` = Build.`scala3-presentation-compiler` diff --git a/dist/libexec/common-shared b/dist/libexec/common-shared index 48476c11b63e..7e076da72c7f 100644 --- a/dist/libexec/common-shared +++ b/dist/libexec/common-shared @@ -57,14 +57,14 @@ case "`uname`" in if [ -z "$JAVA_HOME" ] ; then JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/${JAVA_VERSION}/Home fi - JAVACMD="`which java`" + JAVACMD="`command -v java`" ;; esac unset CYGPATHCMD if [[ ${cygwin-} || ${mingw-} || ${msys-} ]]; then # cygpath is used by various windows shells: cygwin, git-sdk, gitbash, msys, etc. - CYGPATHCMD=`which cygpath 2>/dev/null` + CYGPATHCMD=`command -v cygpath 2>/dev/null` case "$TERM" in rxvt* | xterm* | cygwin*) [ $isterminal -eq 1 ] && stty -icanon min 1 -echo @@ -75,10 +75,10 @@ fi # Resolve JAVA_HOME from javac command path if [ -z "$JAVA_HOME" ]; then - javaExecutable="`which javac`" + javaExecutable="`command -v javac`" if [ -n "$javaExecutable" -a -f "$javaExecutable" -a ! "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then # readlink(1) is not available as standard on Solaris 10. - readLink=`which readlink` + readLink=`command -v readlink` if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then javaExecutable="`readlink -f \"$javaExecutable\"`" javaHome="`dirname \"$javaExecutable\"`" @@ -98,7 +98,7 @@ if [ -z "${JAVACMD-}" ] ; then JAVACMD="$JAVA_HOME/bin/java" fi else - JAVACMD="`which java`" + JAVACMD="`command -v java`" fi fi diff --git a/project/Build.scala b/project/Build.scala index c58ce08e7473..ed9fb9492bfe 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -10,6 +10,11 @@ import com.typesafe.sbt.packager.universal.UniversalPlugin import com.typesafe.sbt.packager.universal.UniversalPlugin.autoImport.Universal import com.typesafe.sbt.packager.windows.WindowsPlugin import com.typesafe.sbt.packager.windows.WindowsPlugin.autoImport.Windows +import com.typesafe.sbt.packager.debian.DebianPlugin +import com.typesafe.sbt.packager.debian.DebianPlugin.autoImport.Debian +import com.typesafe.sbt.packager.linux.LinuxPlugin.autoImport.Linux +import com.typesafe.sbt.packager.rpm.RpmPlugin +import com.typesafe.sbt.packager.rpm.RpmPlugin.autoImport.Rpm import sbt.Package.ManifestAttributes import sbt.PublishBinPlugin.autoImport._ import dotty.tools.sbtplugin._ @@ -627,6 +632,8 @@ object Build { `dist-mac-aarch64`, `dist-win-x86_64`, `dist-linux-x86_64`, + `dist-linux-x86_64-deb`, + `dist-linux-x86_64-rpm`, `dist-linux-aarch64`, ) @@ -2680,6 +2687,78 @@ object Build { ("scala-cli" -> s"gz+https://github.com/VirtusLab/scala-cli/releases/download/v${Dependencies.scalaCliLauncherVersion}/scala-cli-x86_64-pc-linux.gz") ) + lazy val `dist-linux-x86_64-deb` = project.in(file("dist/linux-x86_64-deb")).asDist + .enablePlugins(DebianPlugin) // TO GENERATE THE `.deb` package + .settings(packageName := (dist / packageName).value + "-x86_64-pc-linux") + .settings( + republishLibexecDir := (dist / republishLibexecDir).value, + republishLibexecOverrides += (dist / baseDirectory).value / "libexec-native-overrides", + republishFetchCoursier := (dist / republishFetchCoursier).value, + republishLaunchers += + ("scala-cli" -> s"gz+https://github.com/VirtusLab/scala-cli/releases/download/v${Dependencies.scalaCliLauncherVersion}/scala-cli-x86_64-pc-linux.gz") + ) + .settings( + Linux / packageName := "scala3", + Debian / name := "scala3", + // Debian version ordering: + // `-` is used for "debian revision", which we're not using, so we replace it + // `~` sorts BEFORE empty string (so x.y.z~RC is older than x.y.z) + // `+` is just an ordinary character, so we use it instead of `-` when we're not using `~` + // For reference: https://www.debian.org/doc/debian-policy/ch-controlfields.html#version + Debian / version := version.value + .replaceFirst("-RC", "~RC").replaceFirst("-bin-", "~bin-").replace("-", "+"), + Debian / packageArchitecture := "amd64", + // java17-runtime-headless - virtual package, satisfied by any existing java 17+ package + // If it's not available, we try openjdk-17-jre-headless and then openjdk-21-jre-headless (for systems without openjdk 17, e.g. Debian 13 Trixie) + debianPackageDependencies := Seq("java17-runtime-headless | openjdk-17-jre-headless | openjdk-21-jre-headless"), + maintainer := "The Scala Programming Language", + packageSummary := s"Scala $dottyVersion", + packageDescription := "The Scala Programming Language", + // Emit a fixed-name `scala.deb` so CI can reference it without a glob + // Debian packaging doesn't seem to respect `artifactPath` + Debian / packageBin := { + val built = (Debian / packageBin).dependsOn(republish).value + val fixed = built.getParentFile / "scala.deb" + IO.copyFile(built, fixed) + fixed + }, + ) + + lazy val `dist-linux-x86_64-rpm` = project.in(file("dist/linux-x86_64-rpm")).asDist + .enablePlugins(RpmPlugin) // TO GENERATE THE `.rpm` package + .settings(packageName := (dist / packageName).value + "-x86_64-pc-linux") + .settings( + republishLibexecDir := (dist / republishLibexecDir).value, + republishLibexecOverrides += (dist / baseDirectory).value / "libexec-native-overrides", + republishFetchCoursier := (dist / republishFetchCoursier).value, + republishLaunchers += + ("scala-cli" -> s"gz+https://github.com/VirtusLab/scala-cli/releases/download/v${Dependencies.scalaCliLauncherVersion}/scala-cli-x86_64-pc-linux.gz") + ) + .settings( + Linux / packageName := "scala3", + // No `Rpm / name := "scala3"` here unlike debian - the name is derived from packageName + // RPM version ordering (very similar to Debian): + // `-` is used for RPM release/revision, which is set automatically, so we replace it + // `~` sorts BEFORE empty string (so x.y.z~RC is older than x.y.z) + // `+` is just an ordinary character, so we use it instead of `-` when we're not using `~` + // For reference: https://rpm.org/docs/6.0.x/man/rpm-version.7 + Rpm / version := version.value + .replaceFirst("-RC", "~RC").replaceFirst("-bin-", "~bin-").replace("-", "+"), + Rpm / packageArchitecture := "x86_64", + rpmVendor := "The Scala Programming Language", + rpmLicense := Some("Apache-2.0"), + rpmAutoreq := "no", + rpmAutoprov := "no", + // `java-headless` for Fedora/RHEL/CentOS, `java-17-headless` for openSUSE + rpmRequirements := Seq("(java-headless >= 1:17 or java-17-headless)"), + maintainer := "The Scala Programming Language", + packageSummary := s"Scala $dottyVersion", + packageDescription := "The Scala Programming Language", + // Emit a fixed-name `scala.rpm` so CI can reference it without a glob + Rpm / packageBin / artifactPath := (Rpm / target).value / "scala.rpm", + Rpm / packageBin := (Rpm / packageBin).dependsOn(republish).value, + ) + lazy val `dist-linux-aarch64` = project.in(file("dist/linux-aarch64")).asDist .settings(packageName := (dist / packageName).value + "-aarch64-pc-linux") .settings(