diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index b4c8a9fb..8d2e06c3 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -17,7 +17,7 @@ jobs: strategy: fail-fast: true matrix: - image: ["buildah", "finish", "go-toolset", "gradle-toolset", "helm", "sonar", "start", "pipeline-manager", "python-toolset", "node16-npm-toolset"] + image: ["buildah", "finish", "go-toolset", "gradle-toolset", "helm", "sonar", "start", "pipeline-manager", "python-toolset", "node16-npm-toolset", "sbt-toolset"] steps: - name: Checkout @@ -53,7 +53,7 @@ jobs: runs-on: ubuntu-latest needs: build-images env: - IMAGES: buildah finish go-toolset gradle-toolset helm sonar start pipeline-manager python-toolset node16-npm-toolset + IMAGES: buildah finish go-toolset gradle-toolset helm sonar start pipeline-manager python-toolset node16-npm-toolset sbt-toolset steps: - name: Download image artifacts diff --git a/build/package/Dockerfile.sbt-toolset b/build/package/Dockerfile.sbt-toolset new file mode 100644 index 00000000..ef2000d7 --- /dev/null +++ b/build/package/Dockerfile.sbt-toolset @@ -0,0 +1,53 @@ +FROM registry.access.redhat.com/ubi8/openjdk-11:1.14 + +SHELL ["/bin/bash", "-o", "pipefail", "-c"] + +ARG SBT_VERSION=1.7.2 +ARG SBT_SHA256=e9e3814b2a5a83734d02bf8f1dd8ac285620e601e2f9b1c0fa18c8b38d0dabe3 + +ENV LANG=en_US.UTF-8 \ + LC_ALL=en_US.UTF-8 \ + HOME=/home/jboss \ + COURSIER_CACHE=/home/jboss/.cache/coursier/v1 + +USER root + +COPY build/package/files/sbt/test /tmp/sbttest + +RUN microdnf install --nodocs openssl-${OPENSSL_VERSION}* git-${GIT_VERSION}* vim unzip && microdnf clean all + +RUN SBT_INSTALL_DIR=/tmp/sbt-install && \ + mkdir -p $SBT_INSTALL_DIR && \ + cd $SBT_INSTALL_DIR && \ + curl -LO https://github.com/sbt/sbt/releases/download/v${SBT_VERSION}/sbt-${SBT_VERSION}.zip && \ + echo $SBT_SHA256 sbt-${SBT_VERSION}.zip | sha256sum -c - && \ + unzip -d /opt sbt-${SBT_VERSION}.zip && \ + ln -s /opt/sbt/bin/sbt /usr/local/bin/sbt && \ + rm -rf $SBT_INSTALL_DIR + +COPY build/package/files/sbt/sbtopts /opt/sbt/conf/sbtopts +# Add scripts +COPY build/package/scripts/cache-build.sh /usr/local/bin/cache-build +COPY build/package/scripts/copy-build-if-cached.sh /usr/local/bin/copy-build-if-cached +COPY build/package/scripts/copy-artifacts.sh /usr/local/bin/copy-artifacts +COPY build/package/scripts/build-sbt.sh /usr/local/bin/build-sbt +COPY build/package/scripts/supply-sonar-project-properties-default.sh /usr/local/bin/supply-sonar-project-properties-default + +# Add sonar-project.properties +COPY build/package/sonar-project.properties.d/sbt.properties /usr/local/default-sonar-project.properties + +# TODO set sbt http proxy COPY build/package/scripts/set-sbt-proxy.sh /usr/local/bin/set-sbt-proxy +RUN cd /tmp/sbttest && \ + sbt 'set scalaVersion := "2.12.16"' compile && \ + sbt 'set scalaVersion := "2.13.10"' compile && \ + rm -rf /tmp/sbttest && \ + chmod +x /usr/local/bin/build-sbt && \ + chmod +x /usr/local/bin/cache-build && \ + chmod +x /usr/local/bin/copy-build-if-cached && \ + chmod +x /usr/local/bin/copy-artifacts && \ + chmod +x /usr/local/bin/supply-sonar-project-properties-default && \ + chown -R 1001:0 /home/jboss && \ + chown -R 1001:0 /tmp/.sbt +# TODO set sbt http proxy chmod +x /usr/local/bin/set-sbt-proxy # TODO set sbt http proxy + +USER 1001 diff --git a/build/package/files/sbt/sbtopts b/build/package/files/sbt/sbtopts new file mode 100644 index 00000000..a650ebc1 --- /dev/null +++ b/build/package/files/sbt/sbtopts @@ -0,0 +1,49 @@ +# ------------------------------------------------ # +# The SBT Configuration file. # +# ------------------------------------------------ # + + +# Disable ANSI color codes +# +-no-colors + +# Starts sbt even if the current directory contains no sbt project. +# +-sbt-create + +# Path to global settings/plugins directory (default: ~/.sbt) +# +-sbt-dir /home/jboss/.sbt + +# Path to shared boot directory (default: ~/.sbt/boot in 0.11 series) +# +-sbt-boot /home/jboss/.sbt/boot + +# Path to local Ivy repository (default: ~/.ivy2) +# +-ivy /home/jboss/.ivy2 + +# set memory options +# +#-mem + +# Use local caches for projects, no sharing. +# +#-no-share + +# Put SBT in offline mode. +# +#-offline + +# Sets the SBT version to use. +#-sbt-version 0.11.3 + +# Scala version (default: latest release) +# +#-scala-home +#-scala-version + +# java version (default: java from PATH, currently $(java -version |& grep version)) +# +#-java-home +-Duser.home=/home/jboss diff --git a/build/package/files/sbt/test/test.scala b/build/package/files/sbt/test/test.scala new file mode 100644 index 00000000..cbc2b638 --- /dev/null +++ b/build/package/files/sbt/test/test.scala @@ -0,0 +1,3 @@ +object Test { + val x = "nothing here" +} diff --git a/build/package/scripts/build-sbt.sh b/build/package/scripts/build-sbt.sh new file mode 100755 index 00000000..34e47d37 --- /dev/null +++ b/build/package/scripts/build-sbt.sh @@ -0,0 +1,78 @@ +#!/bin/bash +set -eu + +function timestamped() { + echo "$(date "+%Y/%m/%d %H:%M:%S") $1" +} + +OUTPUT_DIR="docker" +WORKING_DIR="." +ROOT_DIR=$(pwd) +export ARTIFACTS_DIR=$ROOT_DIR/.ods/artifacts + +# might be needed for several task executions that would publish to the same artefact path... +ARTIFACT_PREFIX= +DEBUG="${DEBUG:-false}" + +while [[ "$#" -gt 0 ]]; do + case $1 in + + --working-dir) WORKING_DIR="$2"; shift;; + --working-dir=*) WORKING_DIR="${1#*=}";; + + --output-dir) OUTPUT_DIR="$2"; shift;; + --output-dir=*) OUTPUT_DIR="${1#*=}";; + + *) echo "Unknown parameter passed: $1"; exit 1;; +esac; shift; done + +if [ "${WORKING_DIR}" != "." ]; then + WORKING_DIR="${ROOT_DIR}" + ARTIFACT_PREFIX="${WORKING_DIR/\//-}-" +fi + +if [ "${DEBUG}" == "true" ]; then + set -x +fi + +echo "Using NEXUS_URL=$NEXUS_URL" +echo "Using ARTIFACTS_DIR=$ARTIFACTS_DIR" + +echo +cd "${WORKING_DIR}" +echo "Working on SBT project in '${WORKING_DIR}'..." +echo +export ODS_OUTPUT_DIR=${OUTPUT_DIR} +echo "Exported env var 'ODS_OUTPUT_DIR' with value '${OUTPUT_DIR}'" +echo +echo "Building (Compile and Test) ..." +# shellcheck disable=SC2086 + +# check format of sbt and source files, activate coverage and test with coverage report +timestamped "run tests and coverage" +UNIT_TEST_ARTIFACTS_DIR="${ARTIFACTS_DIR}/xunit-reports" +CODE_COVERAGE_ARTIFACTS_DIR="${ARTIFACTS_DIR}/code-coverage" +export UNIT_TEST_RESULT_DIR="${UNIT_TEST_ARTIFACTS_DIR}/${ARTIFACT_PREFIX}" +export CODE_COVERAGE_TARGET_FILE="${CODE_COVERAGE_ARTIFACTS_DIR}/${ARTIFACT_PREFIX}scoverage.xml" +sbt -no-colors -v clean scalafmtSbtCheck scalafmtCheckAll coverage test coverageReport copyOdsReports clean stage + +timestamped "Verifying unit test report was generated ..." +if ls "${UNIT_TEST_RESULT_DIR}"*.xml >/dev/null 2>&1 ; then + timestamped "unit test results exist under ${UNIT_TEST_RESULT_DIR}" +else + timestamped "Build failed: no unit test results found in ${UNIT_TEST_RESULT_DIR}" + exit 1 +fi + +timestamped "Verifying unit test coverage report was generated ..." +if [ -f "${CODE_COVERAGE_TARGET_FILE}" ]; then + timestamped "unit test coverage report was found at ${CODE_COVERAGE_TARGET_FILE}" +else + timestamped "Build failed: no unit test coverage report was found at ${CODE_COVERAGE_TARGET_FILE}" + exit 1 +fi + +BUILD_DIR="target" +STAGING_DIR="${BUILD_DIR}/universal/stage" +timestamped "Copying contents of ${STAGING_DIR} to ${OUTPUT_DIR}/dist ..." +cp -r "${STAGING_DIR}/." "${OUTPUT_DIR}/dist" diff --git a/build/package/sonar-project.properties.d/sbt.properties b/build/package/sonar-project.properties.d/sbt.properties new file mode 100644 index 00000000..f2a9a2b9 --- /dev/null +++ b/build/package/sonar-project.properties.d/sbt.properties @@ -0,0 +1,5 @@ +# this is just a fallback sonar properties. A matching one should be provided by each project +sonar.sources=src +sonar.sourceEncoding=UTF-8 +sonar.scala.version=2.13 +sonar.scala.scapegoat.disable=true diff --git a/deploy/ods-pipeline/charts/images/templates/bc-ods-sbt-toolset.yaml b/deploy/ods-pipeline/charts/images/templates/bc-ods-sbt-toolset.yaml new file mode 100644 index 00000000..b2aa2e41 --- /dev/null +++ b/deploy/ods-pipeline/charts/images/templates/bc-ods-sbt-toolset.yaml @@ -0,0 +1,30 @@ +{{if or .Values.global.enabledTasks.buildSbt .Values.sbtToolset}} +kind: BuildConfig +apiVersion: build.openshift.io/v1 +metadata: + name: ods-sbt-toolset + labels: + {{- include "chart.labels" . | nindent 4}} +spec: + nodeSelector: null + output: + to: + kind: ImageStreamTag + name: 'ods-sbt-toolset:{{.Values.global.imageTag | default .Chart.AppVersion}}' + resources: {} + successfulBuildsHistoryLimit: 5 + failedBuildsHistoryLimit: 5 + postCommit: {} + strategy: + type: Docker + dockerStrategy: + buildArgs: + - name: imageTag + value: '{{.Values.global.imageTag | default .Chart.AppVersion}}' + - name: privateCertServer + value: '{{.Values.privateCertServer}}' + source: + dockerfile: |- + {{- .Files.Get "docker/Dockerfile.sbt-toolset" | nindent 6}} + runPolicy: Serial +{{end}} diff --git a/deploy/ods-pipeline/charts/images/templates/is-ods-sbt-toolset.yaml b/deploy/ods-pipeline/charts/images/templates/is-ods-sbt-toolset.yaml new file mode 100644 index 00000000..e9bbaa99 --- /dev/null +++ b/deploy/ods-pipeline/charts/images/templates/is-ods-sbt-toolset.yaml @@ -0,0 +1,10 @@ +{{if or .Values.global.enabledTasks.buildSbt .Values.sbtToolset}} +apiVersion: image.openshift.io/v1 +kind: ImageStream +metadata: + name: ods-sbt-toolset + labels: + {{- include "chart.labels" . | nindent 4}} + annotations: + "helm.sh/resource-policy": keep +{{end}} diff --git a/deploy/ods-pipeline/charts/tasks/templates/task-ods-build-sbt.yaml b/deploy/ods-pipeline/charts/tasks/templates/task-ods-build-sbt.yaml new file mode 100644 index 00000000..8f5a6d99 --- /dev/null +++ b/deploy/ods-pipeline/charts/tasks/templates/task-ods-build-sbt.yaml @@ -0,0 +1,122 @@ +{{if .Values.global.enabledTasks.buildSbt }} +apiVersion: tekton.dev/v1beta1 +kind: '{{default "Task" .Values.global.taskKind}}' +metadata: + name: '{{default "ods" .Values.taskPrefix}}-build-sbt{{- include "taskSuffix" .}}' + annotations: + "helm.sh/resource-policy": keep +spec: + description: | + sbt build - TBD + params: + - name: working-dir + description: | + Working directory. The path must be relative to the root of the repository, + without leading `./` and trailing `/`. + type: string + default: "." + - name: output-dir + description: >- + Path to the directory into which the resulting build artifact should be copied, relative to `working-dir`. + This directory may then later be used as Docker context for example. + type: string + default: docker + - name: cache-build + description: >- + If enabled tasks uses or populates cache with the output dir contents (and artifacts) so that + a build can be skipped if the `working-dir` contents did not change. + For single build repos enabling build caching has limited benefits. For multi build repos enabling this is recommended unless the build is dependant on files outside of the working directory. See ADR caching-build-tasks for more details and workarounds. + type: string + default: "false" + - name: build-script + description: >- + Build script to execute. The + link:https://github.com/opendevstack/ods-pipeline/blob/master/build/package/scripts/build-sbt.sh[default script] + is located in the container image. If you specify a relative path + instead, it will be resolved from the workspace. See the task definition + for details how the build script is invoked. + type: string + default: "/usr/local/bin/build-sbt" + - name: sonar-quality-gate + description: Whether the SonarQube quality gate needs to pass for the task to succeed. + type: string + default: "false" + - name: sonar-skip + description: Whether to skip SonarQube analysis or not. + type: string + default: "false" + results: + - description: The cache location that the build task used. If caching is not enabled this will be an empty string. + name: build-reused-from-location + {{- with ((.Values.sbt).sidecars) }} + sidecars: + {{- toYaml . | nindent 4 }} + {{- end }} + steps: + - name: build-sbt-binary + # Image is built from build/package/Dockerfile.sbt-toolset. + image: '{{.Values.registry}}/{{default .Release.Namespace .Values.namespace}}/ods-sbt-toolset:{{.Values.global.imageTag | default .Chart.AppVersion}}' + env: + - name: DEBUG + valueFrom: + configMapKeyRef: + key: debug + name: ods-pipeline + - name: HOME + value: '/tekton/home' + - name: CI + value: "true" + - name: NEXUS_URL + valueFrom: + configMapKeyRef: + key: url + name: ods-nexus + - name: NEXUS_USERNAME + valueFrom: + secretKeyRef: + key: username + name: ods-nexus-auth + - name: NEXUS_PASSWORD + valueFrom: + secretKeyRef: + key: password + name: ods-nexus-auth + resources: + {{- (.Values.sbt).resources | default dict | toYaml | nindent 8 }} + script: | + supply-sonar-project-properties-default + echo -n "" > $(results.build-reused-from-location.path) + cache_build_key=sbt + if copy-build-if-cached \ + --cache-build=$(params.cache-build) \ + --cache-build-key="$cache_build_key" \ + --cache-location-used-path=$(results.build-reused-from-location.path) \ + --working-dir=$(params.working-dir) \ + --output-dir=$(params.output-dir) \ + --debug=${DEBUG} ; then + exit 0 + fi + # Default build script is build/package/scripts/build-sbt.sh. + set +e + $(params.build-script) \ + --working-dir=$(params.working-dir) \ + --output-dir=$(params.output-dir) + build_exit=$? + set -e + copy-artifacts --debug=${DEBUG} + if [ $build_exit -ne 0 ]; then + exit $build_exit + fi + if [ "$(params.cache-build)" == "true" ]; then + cache-build \ + --cache-build-key="$cache_build_key" \ + --cache-location-used-path=$(results.build-reused-from-location.path) \ + --working-dir=$(params.working-dir) \ + --output-dir=$(params.output-dir) \ + --debug=${DEBUG} + fi + workingDir: $(workspaces.source.path) + {{- include "sonar-step" . | indent 4}} + workspaces: + - name: source +{{end}} diff --git a/deploy/ods-pipeline/charts/tasks/values.docs.yaml b/deploy/ods-pipeline/charts/tasks/values.docs.yaml index 585202c0..27b65505 100644 --- a/deploy/ods-pipeline/charts/tasks/values.docs.yaml +++ b/deploy/ods-pipeline/charts/tasks/values.docs.yaml @@ -7,6 +7,7 @@ global: buildGradle: true buildPython: true buildNPM: true + buildSbt: true packageImage: true deployHelm: true diff --git a/deploy/ods-pipeline/values.yaml b/deploy/ods-pipeline/values.yaml index 71e443db..2984ae2c 100644 --- a/deploy/ods-pipeline/values.yaml +++ b/deploy/ods-pipeline/values.yaml @@ -16,6 +16,7 @@ global: buildGradle: true buildPython: true buildNPM: true + buildSbt: true packageImage: true deployHelm: true diff --git a/test/tasks/ods-build-sbt_test.go b/test/tasks/ods-build-sbt_test.go new file mode 100644 index 00000000..d62402da --- /dev/null +++ b/test/tasks/ods-build-sbt_test.go @@ -0,0 +1,46 @@ +package tasks + +import ( + "path/filepath" + "testing" + "time" + + "github.com/opendevstack/pipeline/pkg/pipelinectxt" + + "github.com/opendevstack/pipeline/pkg/tasktesting" +) + +func TestTaskODSBuildSbt(t *testing.T) { + runTaskTestCases(t, + "ods-build-sbt", + []tasktesting.Service{ + tasktesting.Nexus, + tasktesting.SonarQube, + }, + map[string]tasktesting.TestCase{ + "task should build sbt sample app": { + Timeout: 10 * time.Minute, + WorkspaceDirMapping: map[string]string{"source": "sbt-sample-app"}, + PreRunFunc: func(t *testing.T, ctxt *tasktesting.TaskRunContext) { + wsDir := ctxt.Workspaces["source"] + ctxt.ODS = tasktesting.SetupGitRepo(t, ctxt.Namespace, wsDir) + ctxt.Params = map[string]string{ + "sonar-quality-gate": "true", + } + }, + WantRunSuccess: true, + PostRunFunc: func(t *testing.T, ctxt *tasktesting.TaskRunContext) { + wsDir := ctxt.Workspaces["source"] + + checkFilesExist(t, wsDir, + "docker/Dockerfile", + "docker/dist", + filepath.Join(pipelinectxt.XUnitReportsPath, "TEST-example.HelloSpec.xml"), + filepath.Join(pipelinectxt.CodeCoveragesPath, "scoverage.xml"), + filepath.Join(pipelinectxt.SonarAnalysisPath, "analysis-report.md"), + filepath.Join(pipelinectxt.SonarAnalysisPath, "issues-report.csv"), + ) + }, + }, + }) +} diff --git a/test/testdata/workspaces/sbt-sample-app/.gitignore b/test/testdata/workspaces/sbt-sample-app/.gitignore new file mode 100644 index 00000000..dce73038 --- /dev/null +++ b/test/testdata/workspaces/sbt-sample-app/.gitignore @@ -0,0 +1,9 @@ +logs +target +/.bsp +/.idea +/.idea_modules +/.classpath +/.project +/.settings +/RUNNING_PID diff --git a/test/testdata/workspaces/sbt-sample-app/.scalafmt.conf b/test/testdata/workspaces/sbt-sample-app/.scalafmt.conf new file mode 100644 index 00000000..f60df222 --- /dev/null +++ b/test/testdata/workspaces/sbt-sample-app/.scalafmt.conf @@ -0,0 +1,4 @@ +version = 3.5.8 +runner.dialect = scala213source3 +align.preset = more // For pretty alignment. +maxColumn = 100 // For my wide 30" display. \ No newline at end of file diff --git a/test/testdata/workspaces/sbt-sample-app/build.sbt b/test/testdata/workspaces/sbt-sample-app/build.sbt new file mode 100644 index 00000000..426f5984 --- /dev/null +++ b/test/testdata/workspaces/sbt-sample-app/build.sbt @@ -0,0 +1,15 @@ +import Dependencies._ + +ThisBuild / scalaVersion := "2.13.10" +ThisBuild / version := "0.1.0-SNAPSHOT" +ThisBuild / organization := "com.example" +ThisBuild / organizationName := "example" + +lazy val root = (project in file(".")) + .enablePlugins(JavaAppPackaging) + .settings( + name := "scala-seed", + libraryDependencies += scalaTest % Test + ) + +// See https://www.scala-sbt.org/1.x/docs/Using-Sonatype.html for instructions on how to publish to Sonatype. diff --git a/test/testdata/workspaces/sbt-sample-app/docker/Dockerfile b/test/testdata/workspaces/sbt-sample-app/docker/Dockerfile new file mode 100644 index 00000000..4d7e1967 --- /dev/null +++ b/test/testdata/workspaces/sbt-sample-app/docker/Dockerfile @@ -0,0 +1,13 @@ +FROM registry.access.redhat.com/ubi8/openjdk-11:1.13 + +WORKDIR /opt/sbt-sample-app +COPY dist /opt/sbt-sample-app + +USER root +RUN chown -R jboss: /opt/sbt-sample-app +USER jboss + +EXPOSE 9000 + +ENTRYPOINT ["/opt/sbt-sample-app/bin/sbt-sample-app"] +CMD [] diff --git a/test/testdata/workspaces/sbt-sample-app/project/Dependencies.scala b/test/testdata/workspaces/sbt-sample-app/project/Dependencies.scala new file mode 100644 index 00000000..c436f5b7 --- /dev/null +++ b/test/testdata/workspaces/sbt-sample-app/project/Dependencies.scala @@ -0,0 +1,5 @@ +import sbt._ + +object Dependencies { + lazy val scalaTest = "org.scalatest" %% "scalatest" % "3.2.14" +} diff --git a/test/testdata/workspaces/sbt-sample-app/project/OdsPipelinePlugin.scala b/test/testdata/workspaces/sbt-sample-app/project/OdsPipelinePlugin.scala new file mode 100644 index 00000000..5713dd57 --- /dev/null +++ b/test/testdata/workspaces/sbt-sample-app/project/OdsPipelinePlugin.scala @@ -0,0 +1,54 @@ +import sbt.Keys.streams +import sbt.io.IO +import sbt.plugins.JUnitXmlReportPlugin.autoImport.testReportsDirectory +import sbt.{AutoPlugin, Setting, Test, file, taskKey} +import scoverage.ScoverageKeys.coverageDataDir +import sbt._ + +object OdsPipelinePlugin extends AutoPlugin { + override def trigger = allRequirements + + object autoImport { + val copyOdsTestReports = taskKey[Unit]( + "copy test reports to the expected ods test report directory (UNIT_TEST_RESULT_DIR)" + ) + val copyOdsTestCoverageReport = taskKey[Unit]( + "copy test coverage report to the expected location (CODE_COVERAGE_TARGET_FILE)" + ) + val copyOdsReports = + taskKey[Unit]("copy all ods reports to the appropriate directories (defined by the envs)") + } + + import autoImport._ + + override lazy val projectSettings: Seq[Setting[_]] = Seq( + copyOdsTestReports := { + val log = streams.value.log + + sys.env.get("UNIT_TEST_RESULT_DIR") match { + case Some(targetPath) => { + val testReportDir = (Test / testReportsDirectory).value + log.info(s"copying ${testReportDir.listFiles().length} test report(s) to $targetPath") + IO.copyDirectory(testReportDir, file(targetPath)) + } + case None => log.info("no env (UNIT_TEST_RESULT_DIR) set, doing nothing ...") + } + }, + copyOdsTestCoverageReport := { + val log = streams.value.log + + sys.env.get("CODE_COVERAGE_TARGET_FILE") match { + case Some(targetFilePath) => { + val scoverageReportFile = coverageDataDir.value / "scoverage-report" / "scoverage.xml" + log.info(s"copying $scoverageReportFile to $targetFilePath") + IO.copyFile(scoverageReportFile, file(targetFilePath)) + } + case None => log.info("no env (CODE_COVERAGE_TARGET_FILE) set, doing nothing ...") + } + }, + copyOdsReports := { + copyOdsTestReports.value + copyOdsTestCoverageReport.value + } + ) +} diff --git a/test/testdata/workspaces/sbt-sample-app/project/build.properties b/test/testdata/workspaces/sbt-sample-app/project/build.properties new file mode 100644 index 00000000..563a014d --- /dev/null +++ b/test/testdata/workspaces/sbt-sample-app/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.7.2 diff --git a/test/testdata/workspaces/sbt-sample-app/project/plugins.sbt b/test/testdata/workspaces/sbt-sample-app/project/plugins.sbt new file mode 100644 index 00000000..3ba18c46 --- /dev/null +++ b/test/testdata/workspaces/sbt-sample-app/project/plugins.sbt @@ -0,0 +1,6 @@ +addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.9.4") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6") +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.5") + +// this fixes the problem with different versions of scala-xml in twirl and the scoverage sbt plugin :F +libraryDependencySchemes += "org.scala-lang.modules" %% "scala-xml" % VersionScheme.Always diff --git a/test/testdata/workspaces/sbt-sample-app/src/main/scala/example/Hello.scala b/test/testdata/workspaces/sbt-sample-app/src/main/scala/example/Hello.scala new file mode 100644 index 00000000..80ea40a9 --- /dev/null +++ b/test/testdata/workspaces/sbt-sample-app/src/main/scala/example/Hello.scala @@ -0,0 +1,9 @@ +package example + +object Hello extends Greeting with App { + println(greeting) +} + +trait Greeting { + lazy val greeting: String = "hello" +} diff --git a/test/testdata/workspaces/sbt-sample-app/src/test/scala/example/HelloSpec.scala b/test/testdata/workspaces/sbt-sample-app/src/test/scala/example/HelloSpec.scala new file mode 100644 index 00000000..a0069d92 --- /dev/null +++ b/test/testdata/workspaces/sbt-sample-app/src/test/scala/example/HelloSpec.scala @@ -0,0 +1,10 @@ +package example + +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +class HelloSpec extends AnyFlatSpec with Matchers { + "The Hello object" should "say hello" in { + Hello.greeting shouldEqual "hello" + } +}