diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5caa3380..8294974d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -49,13 +49,13 @@ jobs: run: sudo systemctl start unit.service # - name: Run Mill Plugin Tests # run: ./mill --disable-ticker snunit-mill-plugin-itest.__.test - - name: Build and Test Sbt plugin - run: | - ( - cd sbt-plugin - sudo systemctl start unit.service - sbt scalafmtCheckAll scalafmtSbtCheck scripted - ) + # - name: Build and Test Sbt plugin + # run: | + # ( + # cd sbt-plugin + # sudo systemctl start unit.service + # sbt scalafmtCheckAll scalafmtSbtCheck scripted + # ) check-binary-compatibility: runs-on: ubuntu-22.04 steps: @@ -103,9 +103,10 @@ jobs: PGP_SECRET: ${{ secrets.PGP_SECRET }} SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} - - name: Publish Sbt Plugin - run: | - if [[ $(git tag --points-at HEAD) != '' ]]; then - cd sbt-plugin - sbt publishSigned sonatypeBundleRelease - fi + # TODO: Support again Sbt plugin once Mill plugin is stable + # - name: Publish Sbt Plugin + # run: | + # if [[ $(git tag --points-at HEAD) != '' ]]; then + # cd sbt-plugin + # sbt publishSigned sonatypeBundleRelease + # fi diff --git a/build.mill.scala b/build.mill.scala index d5d13a27..3a5d88ff 100644 --- a/build.mill.scala +++ b/build.mill.scala @@ -2,7 +2,6 @@ package build import $ivy.`com.goyeau::mill-scalafix::0.3.1` import $ivy.`io.chris-kipp::mill-ci-release::0.1.9` -import $ivy.`de.tototec::de.tobiasroeser.mill.integrationtest::0.7.1` import $ivy.`com.github.lolgab::mill-crossplatform::0.2.3` import $ivy.`com.lihaoyi::mill-contrib-buildinfo:` import $ivy.`com.github.lolgab::mill-mima::0.1.0` @@ -11,7 +10,6 @@ import mill._, mill.scalalib._, mill.scalanativelib._, mill.scalanativelib.api._ import mill.scalalib.publish._ import com.goyeau.mill.scalafix.ScalafixModule import io.kipp.mill.ci.release.CiReleaseModule -import de.tobiasroeser.mill.integrationtest._ import mill.contrib.buildinfo.BuildInfo import com.github.lolgab.mill.mima._ import com.github.lolgab.mill.crossplatform._ @@ -158,13 +156,13 @@ trait SNUnitTapirModule extends Common.Cross with Publish { // } def caskSources = Task { - val dest = T.dest + val dest = Task.dest os.proc("git", "clone", "--branch", Versions.cask, "--depth", "1", "https://github.com/com-lihaoyi/cask", dest).call() os.proc("git", "apply", T.workspace / "cask.patch").call(cwd = dest / "cask") PathRef(dest) } def castorSources = Task { - val dest = T.dest + val dest = Task.dest os.proc("git", "clone", "--branch", Versions.castor, "--depth", "1", "https://github.com/com-lihaoyi/castor", dest) .call() PathRef(dest) @@ -263,29 +261,41 @@ object integration extends ScalaModule { } } -object `snunit-plugins-shared` extends Cross[SnunitPluginsShared](mill.main.BuildInfo.scalaVersion, Versions.scala212) -trait SnunitPluginsShared extends CrossScalaModule with Publish with BuildInfo { +object `snunit-mill-plugin` extends ScalaModule with Publish with BuildInfo { def buildInfoMembers = Seq( BuildInfo.Value("snunitVersion", publishVersion()) ) def buildInfoPackageName = "snunit.plugin.internal" - object test extends ScalaTests with TestModule.Utest { - def ivyDeps = super.ivyDeps() ++ Agg( - utest, - osLib - ) - } -} -object `snunit-mill-plugin` extends ScalaModule with Publish { def artifactName = s"mill-snunit_mill${Versions.mill011.split('.').take(2).mkString(".")}" - def moduleDeps = Seq(`snunit-plugins-shared`(mill.main.BuildInfo.scalaVersion)) def scalaVersion = mill.main.BuildInfo.scalaVersion def compileIvyDeps = super.compileIvyDeps() ++ Agg( - ivy"com.lihaoyi::mill-scalanativelib:${Versions.mill011}" + ivy"com.lihaoyi::mill-scalanativelib:${mill.main.BuildInfo.millVersion}" ) -} -object `snunit-mill-plugin-itest` extends MillIntegrationTestModule { - def millTestVersion = Versions.mill011 - def pluginsUnderTest = Seq(`snunit-mill-plugin`) - def temporaryIvyModules = Seq(`snunit-plugins-shared`(mill.main.BuildInfo.scalaVersion), snunit(Versions.scala3)) + + object test extends ScalaTests with TestModule.Utest with BuildInfo { + def ivyDeps = Agg( + ivy"com.lihaoyi::mill-testkit:${mill.main.BuildInfo.millVersion}", + ivy"com.lihaoyi::mill-scalanativelib:${mill.main.BuildInfo.millVersion}" + ) + def forkEnv = Map("MILL_EXECUTABLE_PATH" -> millExecutable.assembly().path.toString) + def buildInfoMembers = Seq( + BuildInfo.Value("scalaNativeVersion", versions.Versions.scalaNative), + BuildInfo.Value("scalaVersion", versions.Versions.scala3) + ) + def buildInfoPackageName = "snunit.plugin" + object millExecutable extends JavaModule { + def ivyDeps = Agg( + ivy"com.lihaoyi:mill-dist:${mill.main.BuildInfo.millVersion}" + ) + def mainClass = Some("mill.runner.client.MillClientMain") + def resources = Task { + // make sure snunit is published + snunit(versions.Versions.scala3).publishLocal()() + + val p = Task.dest / "mill/local-test-overrides" / s"com.lihaoyi-${`snunit-mill-plugin`.artifactId()}" + os.write(p, `snunit-mill-plugin`.localClasspath().map(_.path).mkString("\n"), createFolders = true) + Seq(PathRef(Task.dest)) + } + } + } } diff --git a/mill b/mill index 6e2d6530..ebc8bbd1 100755 --- a/mill +++ b/mill @@ -1,256 +1,49 @@ #!/usr/bin/env sh # This is a wrapper script, that automatically download mill from GitHub release pages -# You can give the required mill version with --mill-version parameter +# You can give the required mill version with MILL_VERSION env variable # If no version is given, it falls back to the value of DEFAULT_MILL_VERSION -# -# Original Project page: https://github.com/lefou/millw -# Script Version: 0.4.12 -# -# If you want to improve this script, please also contribute your changes back! -# -# Licensed under the Apache License, Version 2.0 +DEFAULT_MILL_VERSION=0.10.8 set -e -if [ -z "${DEFAULT_MILL_VERSION}" ] ; then - DEFAULT_MILL_VERSION="0.11.4" -fi - - -if [ -z "${GITHUB_RELEASE_CDN}" ] ; then - GITHUB_RELEASE_CDN="" -fi - - -MILL_REPO_URL="https://github.com/com-lihaoyi/mill" - -if [ -z "${CURL_CMD}" ] ; then - CURL_CMD=curl -fi - -# Explicit commandline argument takes precedence over all other methods -if [ "$1" = "--mill-version" ] ; then - shift - if [ "x$1" != "x" ] ; then - MILL_VERSION="$1" - shift - else - echo "You specified --mill-version without a version." 1>&2 - echo "Please provide a version that matches one provided on" 1>&2 - echo "${MILL_REPO_URL}/releases" 1>&2 - false - fi -fi - -# Please note, that if a MILL_VERSION is already set in the environment, -# We reuse it's value and skip searching for a value. - -# If not already set, read .mill-version file -if [ -z "${MILL_VERSION}" ] ; then +if [ -z "$MILL_VERSION" ] ; then if [ -f ".mill-version" ] ; then - MILL_VERSION="$(tr '\r' '\n' < .mill-version | head -n 1 2> /dev/null)" - elif [ -f ".config/mill-version" ] ; then - MILL_VERSION="$(tr '\r' '\n' < .config/mill-version | head -n 1 2> /dev/null)" - fi -fi - -MILL_USER_CACHE_DIR="${XDG_CACHE_HOME:-${HOME}/.cache}/mill" - -if [ -z "${MILL_DOWNLOAD_PATH}" ] ; then - MILL_DOWNLOAD_PATH="${MILL_USER_CACHE_DIR}/download" -fi - -# If not already set, try to fetch newest from Github -if [ -z "${MILL_VERSION}" ] ; then - # TODO: try to load latest version from release page - echo "No mill version specified." 1>&2 - echo "You should provide a version via '.mill-version' file or --mill-version option." 1>&2 - - mkdir -p "${MILL_DOWNLOAD_PATH}" - LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" 2>/dev/null || ( - # we might be on OSX or BSD which don't have -d option for touch - # but probably a -A [-][[hh]mm]SS - touch "${MILL_DOWNLOAD_PATH}/.expire_latest"; touch -A -010000 "${MILL_DOWNLOAD_PATH}/.expire_latest" - ) || ( - # in case we still failed, we retry the first touch command with the intention - # to show the (previously suppressed) error message - LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" - ) - - # POSIX shell variant of bash's -nt operator, see https://unix.stackexchange.com/a/449744/6993 - # if [ "${MILL_DOWNLOAD_PATH}/.latest" -nt "${MILL_DOWNLOAD_PATH}/.expire_latest" ] ; then - if [ -n "$(find -L "${MILL_DOWNLOAD_PATH}/.latest" -prune -newer "${MILL_DOWNLOAD_PATH}/.expire_latest")" ]; then - # we know a current latest version - MILL_VERSION=$(head -n 1 "${MILL_DOWNLOAD_PATH}"/.latest 2> /dev/null) - fi - - if [ -z "${MILL_VERSION}" ] ; then - # we don't know a current latest version - echo "Retrieving latest mill version ..." 1>&2 - LANG=C ${CURL_CMD} -s -i -f -I ${MILL_REPO_URL}/releases/latest 2> /dev/null | grep --ignore-case Location: | sed s'/^.*tag\///' | tr -d '\r\n' > "${MILL_DOWNLOAD_PATH}/.latest" - MILL_VERSION=$(head -n 1 "${MILL_DOWNLOAD_PATH}"/.latest 2> /dev/null) - fi - - if [ -z "${MILL_VERSION}" ] ; then - # Last resort - MILL_VERSION="${DEFAULT_MILL_VERSION}" - echo "Falling back to hardcoded mill version ${MILL_VERSION}" 1>&2 + MILL_VERSION="$(head -n 1 .mill-version 2> /dev/null)" + elif [ -f "mill" ] && [ "$0" != "mill" ] ; then + MILL_VERSION=$(grep -F "DEFAULT_MILL_VERSION=" "mill" | head -n 1 | cut -d= -f2) else - echo "Using mill version ${MILL_VERSION}" 1>&2 + MILL_VERSION=$DEFAULT_MILL_VERSION fi fi -MILL_NATIVE_SUFFIX="-native" -case "$MILL_VERSION" in - *"$MILL_NATIVE_SUFFIX") - MILL_VERSION=${MILL_VERSION%"$MILL_NATIVE_SUFFIX"} - if [ "$(expr substr $(uname -s) 1 5 2>/dev/null)" = "Linux" ]; then - ARTIFACT_SUFFIX="-native-linux-amd64" - elif [ "$(uname)" = "Darwin" ]; then - ARTIFACT_SUFFIX="-native-mac-aarch64" - else - echo "This native mill launcher supports only Linux and macOS." 1>&2 - exit 1 - fi -esac - - -MILL="${MILL_DOWNLOAD_PATH}/${MILL_VERSION}" - -try_to_use_system_mill() { - if [ "$(uname)" != "Linux" ]; then - return 0 - fi - - MILL_IN_PATH="$(command -v mill || true)" - - if [ -z "${MILL_IN_PATH}" ]; then - return 0 - fi - - SYSTEM_MILL_FIRST_TWO_BYTES=$(head --bytes=2 "${MILL_IN_PATH}") - if [ "${SYSTEM_MILL_FIRST_TWO_BYTES}" = "#!" ]; then - # MILL_IN_PATH is (very likely) a shell script and not the mill - # executable, ignore it. - return 0 - fi - - SYSTEM_MILL_PATH=$(readlink -e "${MILL_IN_PATH}") - SYSTEM_MILL_SIZE=$(stat --format=%s "${SYSTEM_MILL_PATH}") - SYSTEM_MILL_MTIME=$(stat --format=%y "${SYSTEM_MILL_PATH}") - - if [ ! -d "${MILL_USER_CACHE_DIR}" ]; then - mkdir -p "${MILL_USER_CACHE_DIR}" - fi - - SYSTEM_MILL_INFO_FILE="${MILL_USER_CACHE_DIR}/system-mill-info" - if [ -f "${SYSTEM_MILL_INFO_FILE}" ]; then - parseSystemMillInfo() { - LINE_NUMBER="${1}" - # Select the line number of the SYSTEM_MILL_INFO_FILE, cut the - # variable definition in that line in two halves and return - # the value, and finally remove the quotes. - sed -n "${LINE_NUMBER}p" "${SYSTEM_MILL_INFO_FILE}" |\ - cut -d= -f2 |\ - sed 's/"\(.*\)"/\1/' - } - - CACHED_SYSTEM_MILL_PATH=$(parseSystemMillInfo 1) - CACHED_SYSTEM_MILL_VERSION=$(parseSystemMillInfo 2) - CACHED_SYSTEM_MILL_SIZE=$(parseSystemMillInfo 3) - CACHED_SYSTEM_MILL_MTIME=$(parseSystemMillInfo 4) - - if [ "${SYSTEM_MILL_PATH}" = "${CACHED_SYSTEM_MILL_PATH}" ] \ - && [ "${SYSTEM_MILL_SIZE}" = "${CACHED_SYSTEM_MILL_SIZE}" ] \ - && [ "${SYSTEM_MILL_MTIME}" = "${CACHED_SYSTEM_MILL_MTIME}" ]; then - if [ "${CACHED_SYSTEM_MILL_VERSION}" = "${MILL_VERSION}" ]; then - MILL="${SYSTEM_MILL_PATH}" - return 0 - else - return 0 - fi - fi - fi - - SYSTEM_MILL_VERSION=$(${SYSTEM_MILL_PATH} --version | head -n1 | sed -n 's/^Mill.*version \(.*\)/\1/p') - - cat < "${SYSTEM_MILL_INFO_FILE}" -CACHED_SYSTEM_MILL_PATH="${SYSTEM_MILL_PATH}" -CACHED_SYSTEM_MILL_VERSION="${SYSTEM_MILL_VERSION}" -CACHED_SYSTEM_MILL_SIZE="${SYSTEM_MILL_SIZE}" -CACHED_SYSTEM_MILL_MTIME="${SYSTEM_MILL_MTIME}" -EOF - - if [ "${SYSTEM_MILL_VERSION}" = "${MILL_VERSION}" ]; then - MILL="${SYSTEM_MILL_PATH}" - fi -} -try_to_use_system_mill - -# If not already downloaded, download it -if [ ! -s "${MILL}" ] ; then - - # support old non-XDG download dir - MILL_OLD_DOWNLOAD_PATH="${HOME}/.mill/download" - OLD_MILL="${MILL_OLD_DOWNLOAD_PATH}/${MILL_VERSION}" - if [ -x "${OLD_MILL}" ] ; then - MILL="${OLD_MILL}" - else - case $MILL_VERSION in - 0.0.* | 0.1.* | 0.2.* | 0.3.* | 0.4.* ) - DOWNLOAD_SUFFIX="" - DOWNLOAD_FROM_MAVEN=0 - ;; - 0.5.* | 0.6.* | 0.7.* | 0.8.* | 0.9.* | 0.10.* | 0.11.0-M* ) - DOWNLOAD_SUFFIX="-assembly" - DOWNLOAD_FROM_MAVEN=0 - ;; - *) - DOWNLOAD_SUFFIX="-assembly" - DOWNLOAD_FROM_MAVEN=1 - ;; - esac - - DOWNLOAD_FILE=$(mktemp mill.XXXXXX) - - if [ "$DOWNLOAD_FROM_MAVEN" = "1" ] ; then - DOWNLOAD_URL="https://repo1.maven.org/maven2/com/lihaoyi/mill-dist${ARTIFACT_SUFFIX}/${MILL_VERSION}/mill-dist${ARTIFACT_SUFFIX}-${MILL_VERSION}.jar" - else - MILL_VERSION_TAG=$(echo "$MILL_VERSION" | sed -E 's/([^-]+)(-M[0-9]+)?(-.*)?/\1\2/') - DOWNLOAD_URL="${GITHUB_RELEASE_CDN}${MILL_REPO_URL}/releases/download/${MILL_VERSION_TAG}/${MILL_VERSION}${DOWNLOAD_SUFFIX}" - unset MILL_VERSION_TAG - fi - - # TODO: handle command not found - echo "Downloading mill ${MILL_VERSION} from ${DOWNLOAD_URL} ..." 1>&2 - ${CURL_CMD} -f -L -o "${DOWNLOAD_FILE}" "${DOWNLOAD_URL}" - chmod +x "${DOWNLOAD_FILE}" - mkdir -p "${MILL_DOWNLOAD_PATH}" - mv "${DOWNLOAD_FILE}" "${MILL}" - - unset DOWNLOAD_FILE - unset DOWNLOAD_SUFFIX - fi +if [ "x${XDG_CACHE_HOME}" != "x" ] ; then + MILL_DOWNLOAD_PATH="${XDG_CACHE_HOME}/mill/download" +else + MILL_DOWNLOAD_PATH="${HOME}/.cache/mill/download" fi +MILL_EXEC_PATH="${MILL_DOWNLOAD_PATH}/${MILL_VERSION}" -if [ -z "$MILL_MAIN_CLI" ] ; then - MILL_MAIN_CLI="${0}" -fi +version_remainder="$MILL_VERSION" +MILL_MAJOR_VERSION="${version_remainder%%.*}"; version_remainder="${version_remainder#*.}" +MILL_MINOR_VERSION="${version_remainder%%.*}"; version_remainder="${version_remainder#*.}" -MILL_FIRST_ARG="" -if [ "$1" = "--bsp" ] || [ "$1" = "-i" ] || [ "$1" = "--interactive" ] || [ "$1" = "--no-server" ] || [ "$1" = "--repl" ] || [ "$1" = "--help" ] ; then - # Need to preserve the first position of those listed options - MILL_FIRST_ARG=$1 - shift +if [ ! -s "$MILL_EXEC_PATH" ] ; then + mkdir -p "$MILL_DOWNLOAD_PATH" + if [ "$MILL_MAJOR_VERSION" -gt 0 ] || [ "$MILL_MINOR_VERSION" -ge 5 ] ; then + ASSEMBLY="-assembly" + fi + DOWNLOAD_FILE=$MILL_EXEC_PATH-tmp-download + MILL_VERSION_TAG=$(echo $MILL_VERSION | sed -E 's/([^-]+)(-M[0-9]+)?(-.*)?/\1\2/') + MILL_DOWNLOAD_URL="https://github.com/lihaoyi/mill/releases/download/${MILL_VERSION_TAG}/$MILL_VERSION${ASSEMBLY}" + curl --fail -L -o "$DOWNLOAD_FILE" "$MILL_DOWNLOAD_URL" + chmod +x "$DOWNLOAD_FILE" + mv "$DOWNLOAD_FILE" "$MILL_EXEC_PATH" + unset DOWNLOAD_FILE + unset MILL_DOWNLOAD_URL fi unset MILL_DOWNLOAD_PATH -unset MILL_OLD_DOWNLOAD_PATH -unset OLD_MILL unset MILL_VERSION -unset MILL_REPO_URL -# We don't quote MILL_FIRST_ARG on purpose, so we can expand the empty value without quotes -# shellcheck disable=SC2086 -exec "${MILL}" $MILL_FIRST_ARG -D "mill.main.cli=${MILL_MAIN_CLI}" "$@" \ No newline at end of file +exec $MILL_EXEC_PATH "$@" diff --git a/snunit-mill-plugin-itest/src/simple/build.sc b/snunit-mill-plugin-itest/src/simple/build.sc deleted file mode 100644 index 1a656a55..00000000 --- a/snunit-mill-plugin-itest/src/simple/build.sc +++ /dev/null @@ -1,20 +0,0 @@ -import $exec.plugins -import mill._ -import mill.scalalib._ -import mill.scalanativelib._ -import snunit.plugin._ - -val port = 8087 - -object module extends ScalaNativeModule with SNUnit { - def scalaVersion = "3.3.0" - def scalaNativeVersion = "0.4.14" - def snunitPort = port - def ivyDeps = super.ivyDeps() ++ Agg( - ivy"com.github.lolgab::snunit::$snunitVersion" - ) -} -def verify() = T.command { - module.deployToNGINXUnit()() - assert(requests.get(s"http://127.0.0.1:$port").text == "Hello world") -} diff --git a/snunit-mill-plugin/src/DockerClang.scala b/snunit-mill-plugin/src/DockerClang.scala deleted file mode 100644 index 14fb916c..00000000 --- a/snunit-mill-plugin/src/DockerClang.scala +++ /dev/null @@ -1,32 +0,0 @@ -package snunit.plugin - -import mill._ -import mill.scalanativelib._ -import snunit.plugin.ClangScripts -import upickle.default._ - -private[plugin] object UpickleImplicits { - implicit val scriptRW: ReadWriter[ClangScripts.Script] = macroRW[ClangScripts.Script] - implicit val scriptsRW: ReadWriter[ClangScripts.Scripts] = macroRW[ClangScripts.Scripts] -} - -/** Uses Clang from a Docker image to allow building SNUnit binaries for Debian Linux on other operative systems - */ -trait DockerClang extends ScalaNativeModule { - import UpickleImplicits._ - private def createAndWriteClangScripts = T { - ClangScripts.createAndWriteClangScripts( - dest = T.dest.toString, - pwd = T.workspace.toString - ) - } - - override def nativeClang = T { - val scripts = createAndWriteClangScripts() - os.Path(scripts.clang.path) - } - override def nativeClangPP = T { - val scripts = createAndWriteClangScripts() - os.Path(scripts.clangpp.path) - } -} diff --git a/snunit-mill-plugin/src/SNUnit.scala b/snunit-mill-plugin/src/SNUnit.scala deleted file mode 100644 index 99109ec2..00000000 --- a/snunit-mill-plugin/src/SNUnit.scala +++ /dev/null @@ -1,33 +0,0 @@ -package snunit.plugin - -import mill._ -import mill.scalanativelib._ - -trait SNUnit extends ScalaNativeModule { - def snunitVersion: String = snunit.plugin.internal.BuildInfo.snunitVersion - - def snunitCurlCommand: Target[Seq[String]] = T { Seq("curl") } - - private def shared = T.worker { - val log = T.log - val logger = new snunit.plugin.Logger { - def info(s: String): Unit = log.info(s) - def warn(s: String): Unit = log.error(s) - def error(s: String): Unit = log.error(s) - } - new SNUnitPluginShared(BuildTool.Mill, logger, snunitCurlCommand()) - } - - /** Port where SNUnit app runs - */ - def snunitPort: Target[Int] = T { 8080 } - - /** Deploy app to NGINX Unit - */ - def deployToNGINXUnit(): Command[Unit] = T.command { - val executable = nativeLink() - val port = snunitPort() - shared().deployToNGINXUnit(executable.toString, port) - } - -} diff --git a/snunit-mill-plugin/src/snunit/plugin/SNUnit.scala b/snunit-mill-plugin/src/snunit/plugin/SNUnit.scala new file mode 100644 index 00000000..e6837e7c --- /dev/null +++ b/snunit-mill-plugin/src/snunit/plugin/SNUnit.scala @@ -0,0 +1,134 @@ +package snunit.plugin + +import mill._ +import mill.scalanativelib._ +import upickle.default._ + +trait SNUnit extends ScalaNativeModule { + def snunitVersion: String = snunit.plugin.internal.BuildInfo.snunitVersion + def snunitNGINXUnitVersion: Target[String] = Task { "1.34.1" } + def snunitNGINXUnitSources = Task { + val dir = s"unit-${snunitNGINXUnitVersion()}" + val file = s"$dir.tar.gz" + os.write(Task.dest / file, requests.get.stream(s"https://sources.nginx.org/unit/$file")) + val unitDir = Task.dest / dir + os.proc("tar", "xzf", Task.dest / file).call(cwd = Task.dest) + PathRef(Task.dest / dir) + } + + def snunitNGINXUnitBinary = Task { + val platform = System.getProperty("os.name", "unknown") + + val openSslParams = + if (platform.contains("mac")) + List("--cc-opt=-I/opt/homebrew/opt/openssl@3/include", "--ld-opt=-L/opt/homebrew/opt/openssl@3/lib") + else Nil + + // val unitDir = snunitNGINXUnitSources().path + val unitDir = os.Path("/Users/lorenzo/unit") + + os.proc( + "./configure", + "--logdir=./logdir", + "--log=/dev/stdout", + "--runstatedir=./runstatedir", + "--pid=unit.pid", + "--control=unix:control.sock", + "--modulesdir=./modulesdir", + "--statedir=./statedir", + "--tmpdir=/tmp", + // "--otel", TODO: Support otel + "--openssl", + openSslParams + ).call(cwd = unitDir) + os.proc("make", "build/sbin/unitd", "build/lib/libunit.a").call(cwd = unitDir) + val unitd = Task.dest / "unitd" + val libunit = Task.dest / "libunit.a" + os.copy(unitDir / "build/sbin/unitd", unitd) + os.copy(unitDir / "build/lib/libunit.a", libunit) + SNUnit.NGINXUnitInstallation(unitd = PathRef(unitd), libunit = PathRef(libunit)) + } + + /** Port where SNUnit app runs + */ + def snunitPort: Target[Int] = Task { 8080 } + + def snunitNGINXUnitConfig: Target[String] = + s"""{ + | "listeners": { + | "*:${snunitPort()}": { + | "pass": "applications/app" + | } + | }, + | "applications": { + | "app": { + | "type": "external", + | "executable": "${nativeLink()}" + | } + | } + |}""".stripMargin + + def snunitNGINXUnitWorkdir = Task { + Task.dest + } + + /** Run app on NGINX Unit + */ + def snunitRunNGINXUnit(): Command[Unit] = Task.Command { + val wd = snunitNGINXUnitWorkdir() + snunitKillNGINXUnit().apply() + val statedir = wd / "statedir" + os.makeDir.all(statedir) + val nginxUnit = snunitNGINXUnitBinary().unitd.path + val nginxUnitConfig = snunitNGINXUnitConfig() + os.write(statedir / "conf.json", nginxUnitConfig) + os.proc(nginxUnit, "--no-daemon").call(wd) + + () + } + + def snunitKillNGINXUnit(): Command[Unit] = Task.Command { + val pidFile = snunitNGINXUnitWorkdir() / "unit.pid" + + if (os.exists(pidFile)) { + os.proc("kill", os.read(snunitNGINXUnitWorkdir() / "unit.pid").trim).call() + } + + () + } + + def buildDocker(): Command[Unit] = T.command { + // TODO + } + + override def nativeLinkingOptions: Target[Seq[String]] = Task { + val unitBinary = snunitNGINXUnitBinary() + super.nativeLinkingOptions() ++ Seq( + unitBinary.libunit.path.toString + ) + } +} + +object SNUnit { + case class NGINXUnitInstallation(unitd: mill.api.PathRef, libunit: mill.api.PathRef) + object NGINXUnitInstallation { + implicit val rw: ReadWriter[NGINXUnitInstallation] = macroRW + } + + // TODO: Support programmatic config + case class NGINXUnitConfig( + listeners: NGINXUnitConfig.Listeners, + applications: NGINXUnitConfig.Applications + ) + object NGINXUnitConfig { + case class Listeners() + object Listeners { + implicit val rw: ReadWriter[Listeners] = macroRW + } + case class Applications() + object Applications { + implicit val rw: ReadWriter[Applications] = macroRW + } + implicit val rw: ReadWriter[NGINXUnitConfig] = macroRW + } +} diff --git a/snunit-mill-plugin-itest/src/simple/module/src/Main.scala b/snunit-mill-plugin/test/resources/simple/src/Main.scala similarity index 60% rename from snunit-mill-plugin-itest/src/simple/module/src/Main.scala rename to snunit-mill-plugin/test/resources/simple/src/Main.scala index b22e787a..ee2c45e0 100644 --- a/snunit-mill-plugin-itest/src/simple/module/src/Main.scala +++ b/snunit-mill-plugin/test/resources/simple/src/Main.scala @@ -3,7 +3,7 @@ import snunit.* object HelloWorld { def main(args: Array[String]): Unit = { SyncServerBuilder - .setRequestHandler(_.send(StatusCode.OK, "Hello world", Headers.empty)) + .setRequestHandler(_.send(StatusCode.OK, "TEST SNUnit Mill Plugin", Headers.empty)) .build() .listen() } diff --git a/snunit-mill-plugin/test/src/snunit/plugin/SNUnitTests.scala b/snunit-mill-plugin/test/src/snunit/plugin/SNUnitTests.scala new file mode 100644 index 00000000..72b2b623 --- /dev/null +++ b/snunit-mill-plugin/test/src/snunit/plugin/SNUnitTests.scala @@ -0,0 +1,36 @@ +package snunit.plugin + +import mill._ +import mill.scalalib._ +import mill.testkit.{TestBaseModule, UnitTester} +import utest._ + +object SNUnitTests extends TestSuite { + def tests: Tests = Tests { + test("simple") { + object build extends TestBaseModule with SNUnit { + def scalaVersion = BuildInfo.scalaVersion + def scalaNativeVersion = BuildInfo.scalaNativeVersion + override def ivyDeps = Task { super.ivyDeps() ++ Agg(ivy"com.github.lolgab::snunit::$snunitVersion") } + } + + val resourceFolder = os.Path(sys.env("MILL_TEST_RESOURCE_DIR").split(";").head) + + UnitTester(build, resourceFolder / "simple").scoped { eval => + scala.concurrent.ExecutionContext.global.execute(() => eval(build.snunitRunNGINXUnit)) + var started = false + while(!started) { + try { + val response = requests.get("http://127.0.0.1:8080", check = false).text + started = true + response ==> "TEST SNUnit Mill Plugin" + } catch { + case (_: java.net.ConnectException) | _: requests.UnknownHostException => + println("waiting for server to start...") + Thread.sleep(5000) + } + } + } + } + } +} diff --git a/snunit-plugins-shared/src/BuildTool.scala b/snunit-plugins-shared/src/BuildTool.scala deleted file mode 100644 index 317cccf4..00000000 --- a/snunit-plugins-shared/src/BuildTool.scala +++ /dev/null @@ -1,7 +0,0 @@ -package snunit.plugin - -sealed trait BuildTool -object BuildTool { - case object Sbt extends BuildTool - case object Mill extends BuildTool -} diff --git a/snunit-plugins-shared/src/ClangScripts.scala b/snunit-plugins-shared/src/ClangScripts.scala deleted file mode 100644 index 543f48cb..00000000 --- a/snunit-plugins-shared/src/ClangScripts.scala +++ /dev/null @@ -1,38 +0,0 @@ -package snunit.plugin - -import java.nio.file.{Files, Paths} -import java.nio.file.attribute._ - -object ClangScripts { - case class Script(path: String, content: String) - case class Scripts(clang: Script, clangpp: Script) - - def createClangScripts(dest: String, pwd: String): Scripts = { - def absolute(basePath: String, others: String*) = - Paths.get(basePath, others: _*).toAbsolutePath().normalize().toString() - val pwdAbsolute = absolute(pwd) - def script(executable: String) = - s"""#!/bin/bash - |docker run -v "$pwdAbsolute:$pwdAbsolute" --entrypoint $executable lolgab/snunit-clang:0.0.1 "$$@" - |""".stripMargin - - Scripts( - clang = Script(absolute(dest, "clang-scripts", "clang.sh"), script("clang")), - clangpp = Script(absolute(dest, "clang-scripts", "clang++.sh"), script("clang++")) - ) - } - - def writeScript(script: Script): Unit = { - val p = Paths.get(script.path) - Files.createDirectories(p.getParent()) - Files.write(p, script.content.getBytes()) - p.toFile().setExecutable(true) - } - - def createAndWriteClangScripts(dest: String, pwd: String): Scripts = { - val scripts = createClangScripts(dest = dest, pwd = pwd) - writeScript(scripts.clang) - writeScript(scripts.clangpp) - scripts - } -} diff --git a/snunit-plugins-shared/src/Logger.scala b/snunit-plugins-shared/src/Logger.scala deleted file mode 100644 index 69c9e846..00000000 --- a/snunit-plugins-shared/src/Logger.scala +++ /dev/null @@ -1,14 +0,0 @@ -package snunit.plugin - -trait Logger { - def info(s: String): Unit - def warn(s: String): Unit - def error(s: String): Unit -} -object Logger { - def stderr = new Logger { - def info(s: String): Unit = System.err.println(s"[info] $s") - def warn(s: String): Unit = System.err.println(s"[warn] $s") - def error(s: String): Unit = System.err.println(s"[error] $s") - } -} diff --git a/snunit-plugins-shared/src/SNUnitPluginShared.scala b/snunit-plugins-shared/src/SNUnitPluginShared.scala deleted file mode 100644 index 2f09b07b..00000000 --- a/snunit-plugins-shared/src/SNUnitPluginShared.scala +++ /dev/null @@ -1,114 +0,0 @@ -package snunit.plugin - -import scala.sys.process._ -import java.io._ -import scala.util.control.NonFatal - -private[plugin] class SNUnitPluginShared(buildTool: BuildTool, logger: Logger, snunitCurlCommand: Seq[String]) { - private def unitControlSocket(): File = { - val stringPath = Seq("unitd", "--help").!!.linesIterator - .find(_.contains("unix:")) - .get - .replaceAll(".+unix:", "") - .stripSuffix("\"") - new File(stringPath) - } - def isUnitInstalled(): Boolean = { - try { - // collect stderr from unitd - val err = new StringBuilder - val processLogger = ProcessLogger(_ => (), err ++= _) - - Seq("unitd", "--version").!!(processLogger) - err.containsSlice("unit version:") - } catch { - case NonFatal(e) => - logger.error("""Can't find unitd in the path. - |Please install NGINX Unit first. - |You can find instructions here: https://unit.nginx.org/installation""".stripMargin) - false - } - } - private final val notRunningErrorMessage = """NGINX Unit is not running. - |You can run it in a separate terminal with: - |unitd --log /dev/stdout --no-daemon - | - |You can also run it as a daemon: - | - |If you use brew: - |brew services start unit - | - |If you use systemd: - |sudo systemctl start unit.service - | - |You can find more instructions here: https://unit.nginx.org/installation""".stripMargin - - private def controlSocketExists(control: File): Boolean = { - val exists = control.exists() - if (!exists) { - logger.error(s"""Control socket doesn't exist in the default path (${control.toString}). - |This means that $notRunningErrorMessage""".stripMargin) - } - exists - } - def isUnitRunning(control: File): Boolean = { - try { - doCurl(control, "http://localhost/config") - true - } catch { - case NonFatal(e) => - if (control.canRead() && control.canWrite()) { - val setting = buildTool match { - case BuildTool.Sbt => """snunitCurlCommand := Seq("sudo", "curl")""" - case BuildTool.Mill => """override def snunitCurlCommand = Seq("sudo", "curl")""" - } - logger.error(s"""You don't seem to have permissions to read/write the control socket. - |For security reasons NGINX Unit doesn't allow non-root users to change Unit's configuration. - |You can perform Unit commands by changing the curl command used by snunit to connect to Unit: - | $setting - |""".stripMargin) - } else { - logger.error(notRunningErrorMessage) - } - false - } - } - - private def doCurl(control: File, command: String*) = - (snunitCurlCommand ++ Seq("-sL", "--unix-socket", control.toString) ++ command).!! - def deployToNGINXUnit(executable: String, port: Int): Unit = { - require(isUnitInstalled(), "unitd is not installed") - val control = unitControlSocket() - require(controlSocketExists(control), "control socket doesn't exist") - require(isUnitRunning(control), "unitd is not available") - val config = s"""{ - "applications": { - "app": { - "type": "external", - "executable": "$executable" - } - }, - "listeners": { - "*:$port": { - "pass": "applications/app" - } - } - }""" - val result = doCurl( - control, - "-X", - "PUT", - "-d", - config, - "http://localhost/config" - ) - logger.info(result) - restartApp(control) - } - - private def restartApp(control: File) = { - val result = doCurl(control, "http://localhost/control/applications/app/restart") - - logger.info(result) - } -} diff --git a/snunit-plugins-shared/test/src/ClangScriptsTests.scala b/snunit-plugins-shared/test/src/ClangScriptsTests.scala deleted file mode 100644 index 65304ded..00000000 --- a/snunit-plugins-shared/test/src/ClangScriptsTests.scala +++ /dev/null @@ -1,36 +0,0 @@ -package snunit.plugin - -import utest._ - -object ClangScriptsTests extends TestSuite { - val tests = Tests { - test("createClangScripts") { - val pwd = os.pwd.toString - val result = ClangScripts.createClangScripts(".", ".") - - val expected = ClangScripts.Scripts( - clang = ClangScripts.Script( - path = s"$pwd/clang-scripts/clang.sh", - content = s"""#!/bin/bash - |docker run -v "$pwd:$pwd" --entrypoint clang lolgab/snunit-clang:0.0.1 "$$@" - |""".stripMargin - ), - clangpp = ClangScripts.Script( - path = s"$pwd/clang-scripts/clang++.sh", - content = s"""#!/bin/bash - |docker run -v "$pwd:$pwd" --entrypoint clang++ lolgab/snunit-clang:0.0.1 "$$@" - |""".stripMargin - ) - ) - assert(result == expected) - } - test("run scripts") { - val dir = os.temp.dir() - try { - val scripts = ClangScripts.createAndWriteClangScripts(dir.toString(), dir.toString()) - os.proc(scripts.clang.path, "--version").call() - os.proc(scripts.clangpp.path, "--version").call() - } finally os.remove.all(dir) - } - } -} diff --git a/snunit-plugins-shared/test/src/SNUnitPluginSharedTests.scala b/snunit-plugins-shared/test/src/SNUnitPluginSharedTests.scala deleted file mode 100644 index 1677b4fc..00000000 --- a/snunit-plugins-shared/test/src/SNUnitPluginSharedTests.scala +++ /dev/null @@ -1,15 +0,0 @@ -package snunit.plugin - -import utest._ - -object SNUnitPluginSharedTests extends TestSuite { - val snunitPluginShared = new SNUnitPluginShared(BuildTool.Sbt, Logger.stderr, Seq("curl")) - import snunitPluginShared._ - val tests = Tests { - test("isUnitInstalled should return true if unitd is available") { - // throws if not available - os.proc("which", "unitd").call() - assert(isUnitInstalled()) - } - } -} diff --git a/versions.mill.scala b/versions.mill.scala index 078fec95..1f5928f7 100644 --- a/versions.mill.scala +++ b/versions.mill.scala @@ -7,7 +7,7 @@ object Versions { val upickle = "4.0.2" val undertow = "2.3.10.Final" val scala212 = "2.12.19" - val scala3 = "3.4.2" + val scala3 = "3.5.2" val tapir = "1.11.1" val cask = "0.10.2" val catsEffect = "3.6-623178c"