diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 015e2f0c50..8fb0c2de55 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -32,7 +32,9 @@ jobs: - name: Build bundle env: PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} - run: ./sbt "; + projectJVM/publishSigned; sbtAirframe/publishSigned;" + run: | + ./sbt "; + projectJVM/publishSigned; sbtAirframe/publishSigned;" + DOTTY=true ./sbt projectDotty/publishSigned - name: Release to Sonatype env: SONATYPE_USERNAME: '${{ secrets.SONATYPE_USER }}' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6df9756cd2..57ea7e03ff 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -55,6 +55,21 @@ jobs: restore-keys: ${{ runner.os }}-scala-2.13- - name: Scala 2.13 test run: ./sbt ++2.13.4 projectJVM/test + test_3: + name: Scala 3.x (Dotty) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: olafurpg/setup-scala@v10 + with: + java-version: adopt@1.11 + - uses: actions/cache@v1 + with: + path: ~/.cache + key: ${{ runner.os }}-scala-3-${{ hashFiles('**/*.sbt') }} + restore-keys: ${{ runner.os }}-scala-3- + - name: Scala 3.x test + run: DOTTY=true ./sbt dottyTest/run test_js: name: Scala.js / Scala 2.12 runs-on: ubuntu-latest diff --git a/README.md b/README.md index c80eddfdf8..80679c0788 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,18 @@ Airframe https://wvlet.org/airframe is a collection of [lightweight building blo

logo

+## Build + +### Dotty (Scala 3.0) + +For developing with Dotty, use DOTTY=true environment variable: +``` +$ DOTTY=true ./sbt +> logJVM/test +``` + +Here is the list of milestones for Dotty support: [#1077](https://github.com/wvlet/airframe/issues/1077) + ## LICENSE [Apache v2](https://github.com/wvlet/airframe/blob/master/LICENSE) diff --git a/airframe-dotty-test/src/main/scala/dotty/test/LogTest.scala b/airframe-dotty-test/src/main/scala/dotty/test/LogTest.scala new file mode 100644 index 0000000000..96876ca760 --- /dev/null +++ b/airframe-dotty-test/src/main/scala/dotty/test/LogTest.scala @@ -0,0 +1,39 @@ +package dotty.test + +import wvlet.log.{LogFormatter, LogLevel, LogSupport, Logger} + +object LogTest extends LogSupport { + + def main(args: Array[String]): Unit = { + //Logger.setDefaultFormatter(LogFormatter.SourceCodeLogFormatter) + info("Hello airframe-log") + debug("Hello airframe-log") + error("Hello airframe-log") + warn("Hello airframe-log") + trace("Hello airframe-log") + + logger.info("direct log") + logger.debug("direct log") + logger.trace("direct log") + logger.warn("direct log") + logger.error("direct log") + + logger.setLogLevel(LogLevel.TRACE) + logger.info("direct log") + logger.debug("direct log") + logger.trace("direct log") + logger.warn("direct log") + logger.error("direct log") + + logger.setLogLevel(LogLevel.WARN) + info("Hello airframe-log") + debug("Hello airframe-log") + error("Hello airframe-log") + warn("Hello airframe-log") + trace("Hello airframe-log") + + warn("exception log test", new IllegalArgumentException("invalid arg")) + + Logger.setDefaultLogLevel(LogLevel.INFO) + } +} diff --git a/airframe-log/jvm/src/main/scala/wvlet/log/AirframeLogManager.scala b/airframe-log/jvm/src/main/scala/wvlet/log/AirframeLogManager.scala index 315d272619..6d969f5429 100644 --- a/airframe-log/jvm/src/main/scala/wvlet/log/AirframeLogManager.scala +++ b/airframe-log/jvm/src/main/scala/wvlet/log/AirframeLogManager.scala @@ -18,7 +18,7 @@ object AirframeLogManager { private[log] var instance: Option[AirframeLogManager] = None private[wvlet] def resetFinally: Unit = { - instance.map(_.reset0) + instance.map(_.reset0()) instance = None } } diff --git a/airframe-log/jvm/src/main/scala/wvlet/log/AsyncHandler.scala b/airframe-log/jvm/src/main/scala/wvlet/log/AsyncHandler.scala index 4bc72529d3..9585c91494 100644 --- a/airframe-log/jvm/src/main/scala/wvlet/log/AsyncHandler.scala +++ b/airframe-log/jvm/src/main/scala/wvlet/log/AsyncHandler.scala @@ -51,7 +51,7 @@ class AsyncHandler(parent: jl.Handler) extends jl.Handler with Guard with AutoCl } } - records.result.map(parent.publish _) + records.result().map(parent.publish _) parent.flush() } diff --git a/airframe-log/jvm/src/main/scala/wvlet/log/io/Resource.scala b/airframe-log/jvm/src/main/scala/wvlet/log/io/Resource.scala index 44635895b3..d9be0d13e1 100644 --- a/airframe-log/jvm/src/main/scala/wvlet/log/io/Resource.scala +++ b/airframe-log/jvm/src/main/scala/wvlet/log/io/Resource.scala @@ -112,7 +112,12 @@ object Resource { * @return */ def find(absoluteResourcePath: String): Option[URL] = - find("", if (absoluteResourcePath.startsWith("/")) absoluteResourcePath.substring(1) else absoluteResourcePath) + find( + "", + if (absoluteResourcePath.startsWith("/")) + absoluteResourcePath.substring(1) + else absoluteResourcePath + ) /** * Finds the java.net.URL of the resource @@ -133,7 +138,7 @@ object Resource { urlClassLoader.getResource(resourcePath) match { case path: URL => Some(path) - case _ => + case null => loop(urlClassLoader.getParent) } case None => None @@ -291,7 +296,7 @@ object Resource { throw new UnsupportedOperationException("resources other than file or jar are not supported: " + resourceURL) } - fileList.result + fileList.result() } /** @@ -301,7 +306,12 @@ object Resource { * @return */ def listResources(packageName: String): Seq[VirtualFile] = - listResources(packageName, { f: String => true }) + listResources( + packageName, + { (f: String) => + true + } + ) /** * Collect resources under the given package @@ -320,7 +330,7 @@ object Resource { for (u <- findResourceURLs(classLoader, packageName)) { b ++= listResources(u, packageName, resourceFilter) } - b.result + b.result() } /** @@ -350,7 +360,7 @@ object Resource { } loop(currentClassLoader) - b.result + b.result() } def findClasses[A]( @@ -358,7 +368,13 @@ object Resource { toSearch: Class[A], classLoader: ClassLoader = Thread.currentThread.getContextClassLoader ): Seq[Class[A]] = { - val classFileList = listResources(packageName, { f: String => f.endsWith(".class") }, classLoader) + val classFileList = listResources( + packageName, + { (f: String) => + f.endsWith(".class") + }, + classLoader + ) def componentName(path: String): Option[String] = { val dot: Int = path.lastIndexOf(".") @@ -384,7 +400,7 @@ object Resource { } } } - b.result + b.result() } def findClasses[A](searchPath: Package, toSearch: Class[A], classLoader: ClassLoader): Seq[Class[A]] = { diff --git a/airframe-log/shared/src/main/scala/wvlet/log/LogMacros.scala b/airframe-log/shared/src/main/scala-2/wvlet/log/LogMacros.scala similarity index 100% rename from airframe-log/shared/src/main/scala/wvlet/log/LogMacros.scala rename to airframe-log/shared/src/main/scala-2/wvlet/log/LogMacros.scala diff --git a/airframe-log/shared/src/main/scala-2/wvlet/log/LoggerBase.scala b/airframe-log/shared/src/main/scala-2/wvlet/log/LoggerBase.scala new file mode 100644 index 0000000000..ffd1519531 --- /dev/null +++ b/airframe-log/shared/src/main/scala-2/wvlet/log/LoggerBase.scala @@ -0,0 +1,67 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package wvlet.log +import scala.language.experimental.macros + +/** + */ +trait LoggerBase { + import LogMacros._ + + def error(message: Any): Unit = macro errorLogMethod + def error(message: Any, cause: Throwable): Unit = + macro errorLogMethodWithCause + + def warn(message: Any): Unit = macro warnLogMethod + def warn(message: Any, cause: Throwable): Unit = macro warnLogMethodWithCause + + def info(message: Any): Unit = macro infoLogMethod + def info(message: Any, cause: Throwable): Unit = macro infoLogMethodWithCause + + def debug(message: Any): Unit = macro debugLogMethod + def debug(message: Any, cause: Throwable): Unit = + macro debugLogMethodWithCause + + def trace(message: Any): Unit = macro traceLogMethod + def trace(message: Any, cause: Throwable): Unit = + macro traceLogMethodWithCause +} + +/** + */ +trait LoggingMethods extends Serializable { + import wvlet.log.LogMacros._ + + protected def error(message: Any): Unit = macro errorLog + protected def error(message: Any, cause: Throwable): Unit = + macro errorLogWithCause + + protected def warn(message: Any): Unit = macro warnLog + protected def warn(message: Any, cause: Throwable): Unit = + macro warnLogWithCause + + protected def info(message: Any): Unit = macro infoLog + protected def info(message: Any, cause: Throwable): Unit = + macro infoLogWithCause + + protected def debug(message: Any): Unit = macro debugLog + protected def debug(message: Any, cause: Throwable): Unit = + macro debugLogWithCause + + protected def trace(message: Any): Unit = macro traceLog + protected def trace(message: Any, cause: Throwable): Unit = + macro traceLogWithCause + + protected def logAt(logLevel: LogLevel, message: Any): Unit = macro logAtImpl +} diff --git a/airframe-log/shared/src/main/scala-3/wvlet/log/LoggerBase.scala b/airframe-log/shared/src/main/scala-3/wvlet/log/LoggerBase.scala new file mode 100644 index 0000000000..0738c6df17 --- /dev/null +++ b/airframe-log/shared/src/main/scala-3/wvlet/log/LoggerBase.scala @@ -0,0 +1,169 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package wvlet.log + +/** + */ +trait LoggerBase { self: Logger => + + inline def error(inline message: Any): Unit = ${ LoggerMacros.errorImpl('this, 'message) } + inline def warn(inline message: Any): Unit = ${ LoggerMacros.warnImpl('this, 'message) } + inline def info(inline message: Any): Unit = ${ LoggerMacros.infoImpl('this, 'message) } + inline def debug(inline message: Any): Unit = ${ LoggerMacros.debugImpl('this, 'message) } + inline def trace(inline message: Any): Unit = ${ LoggerMacros.traceImpl('this, 'message) } + + inline def error(inline message: Any, inline cause: Throwable): Unit = ${ + LoggerMacros.errorWithCauseImpl('this, 'message, 'cause) + } + inline def warn(inline message: Any, inline cause: Throwable): Unit = ${ + LoggerMacros.warnWithCauseImpl('this, 'message, 'cause) + } + + inline def info(inline message: Any, inline cause: Throwable): Unit = ${ + LoggerMacros.infoWithCauseImpl('this, 'message, 'cause) + } + + inline def debug(inline message: Any, inline cause: Throwable): Unit = ${ + LoggerMacros.debugWithCauseImpl('this, 'message, 'cause) + } + + inline def trace(inline message: Any, inline cause: Throwable): Unit = ${ + LoggerMacros.traceWithCauseImpl('this, 'message, 'cause) + } +} + +/** + */ +trait LoggingMethods extends Serializable { + protected def logger: Logger + + inline protected def error(inline message: Any): Unit = ${ LoggerMacros.errorImpl('logger, 'message) } + inline protected def warn(inline message: Any): Unit = ${ LoggerMacros.warnImpl('logger, 'message) } + inline protected def info(inline message: Any): Unit = ${ LoggerMacros.infoImpl('logger, 'message) } + inline protected def debug(inline message: Any): Unit = ${ LoggerMacros.debugImpl('logger, 'message) } + inline protected def trace(inline message: Any): Unit = ${ LoggerMacros.traceImpl('logger, 'message) } + inline protected def logAt(inline logLevel: LogLevel, inline message: Any): Unit = ${ LoggerMacros.logImpl('logger, 'logLevel, 'message) } + + inline protected def error(inline message: Any, inline cause: Throwable): Unit = ${ LoggerMacros.errorWithCauseImpl('logger, 'message, 'cause) } + inline protected def warn(inline message: Any, inline cause: Throwable): Unit = ${ LoggerMacros.warnWithCauseImpl('logger, 'message, 'cause) } + inline protected def info(inline message: Any, inline cause: Throwable): Unit = ${ LoggerMacros.infoWithCauseImpl('logger, 'message, 'cause) } + inline protected def debug(inline message: Any, inline cause: Throwable): Unit = ${ LoggerMacros.debugWithCauseImpl('logger, 'message, 'cause) } + inline protected def trace(inline message: Any, inline cause: Throwable): Unit = ${ LoggerMacros.traceWithCauseImpl('logger, 'message, 'cause) } +} + +private[log] object LoggerMacros { + import scala.quoted._ + + private def sourcePos(using q: Quotes): Expr[wvlet.log.LogSource] = { + import q.reflect._ + val pos = Position.ofMacroExpansion + val line = Expr(pos.startLine) + val column = Expr(pos.endColumn) + val src = pos.sourceFile + val srcPath: java.nio.file.Path = src.jpath + val path = Expr(srcPath.toFile.getPath) + val fileName = Expr(srcPath.getFileName().toString) + '{ wvlet.log.LogSource(${path}, ${fileName}, ${line} + 1, ${column}) } + } + + def logImpl(logger: Expr[Logger], logLevel: Expr[LogLevel], message:Expr[Any])(using q: Quotes): Expr[Unit] = { + '{ + if(${logger}.isEnabled(${logLevel})) { + ${logger}.log(${logLevel}, ${sourcePos}, ${message}) + } + } + } + + def errorImpl(logger: Expr[Logger], message:Expr[Any])(using q: Quotes): Expr[Unit] = { + '{ + if(${logger}.isEnabled(wvlet.log.LogLevel.ERROR)) { + ${logger}.log(wvlet.log.LogLevel.ERROR, ${sourcePos}, ${message}) + } + } + } + + def warnImpl(logger: Expr[Logger], message:Expr[Any])(using q: Quotes): Expr[Unit] = { + '{ + if(${logger}.isEnabled(wvlet.log.LogLevel.WARN)) { + ${logger}.log(wvlet.log.LogLevel.WARN, ${sourcePos}, ${message}) + } + } + } + + def infoImpl(logger: Expr[Logger], message:Expr[Any])(using q: Quotes): Expr[Unit] = { + '{ + if(${logger}.isEnabled(wvlet.log.LogLevel.INFO)) { + ${logger}.log(wvlet.log.LogLevel.INFO, ${sourcePos}, ${message}) + } + } + } + + def debugImpl(logger: Expr[Logger], message:Expr[Any])(using q: Quotes): Expr[Unit] = { + '{ + if(${logger}.isEnabled(wvlet.log.LogLevel.DEBUG)) { + ${logger}.log(wvlet.log.LogLevel.DEBUG, ${sourcePos}, ${message}) + } + } + } + + def traceImpl(logger: Expr[Logger], message:Expr[Any])(using q: Quotes): Expr[Unit] = { + '{ + if(${logger}.isEnabled(wvlet.log.LogLevel.TRACE)) { + ${logger}.log(wvlet.log.LogLevel.TRACE, ${sourcePos}, ${message}) + } + } + } + + def errorWithCauseImpl(logger: Expr[Logger], message:Expr[Any], cause: Expr[Throwable])(using q: Quotes): Expr[Unit] = { + '{ + if(${logger}.isEnabled(wvlet.log.LogLevel.ERROR)) { + ${logger}.logWithCause(wvlet.log.LogLevel.ERROR, ${sourcePos}, ${message}, ${cause}) + } + } + } + + def warnWithCauseImpl(logger: Expr[Logger], message:Expr[Any], cause: Expr[Throwable])(using q: Quotes): Expr[Unit] = { + '{ + if(${logger}.isEnabled(wvlet.log.LogLevel.WARN)) { + ${logger}.logWithCause(wvlet.log.LogLevel.WARN, ${sourcePos}, ${message}, ${cause}) + } + } + } + + def infoWithCauseImpl(logger: Expr[Logger], message:Expr[Any], cause: Expr[Throwable])(using q: Quotes): Expr[Unit] = { + '{ + if(${logger}.isEnabled(wvlet.log.LogLevel.INFO)) { + ${logger}.logWithCause(wvlet.log.LogLevel.INFO, ${sourcePos}, ${message}, ${cause}) + } + } + } + + def debugWithCauseImpl(logger: Expr[Logger], message:Expr[Any], cause: Expr[Throwable])(using q: Quotes): Expr[Unit] = { + '{ + if(${logger}.isEnabled(wvlet.log.LogLevel.DEBUG)) { + ${logger}.logWithCause(wvlet.log.LogLevel.DEBUG, ${sourcePos}, ${message}, ${cause}) + } + } + } + + def traceWithCauseImpl(logger: Expr[Logger], message:Expr[Any], cause: Expr[Throwable])(using q: Quotes): Expr[Unit] = { + '{ + if(${logger}.isEnabled(wvlet.log.LogLevel.TRACE)) { + ${logger}.logWithCause(wvlet.log.LogLevel.TRACE, ${sourcePos}, ${message}, ${cause}) + } + } + } +} + + diff --git a/airframe-log/shared/src/main/scala/wvlet/log/LogFormat.scala b/airframe-log/shared/src/main/scala/wvlet/log/LogFormat.scala index 8ef096694b..56d1b5b876 100644 --- a/airframe-log/shared/src/main/scala/wvlet/log/LogFormat.scala +++ b/airframe-log/shared/src/main/scala/wvlet/log/LogFormat.scala @@ -41,7 +41,9 @@ object LogFormatter { private val testFrameworkFilter = Pattern.compile("""\s+at (sbt\.|org\.scalatest\.|wvlet\.airspec\.).*""") - val DEFAULT_STACKTRACE_FILTER: String => Boolean = { line: String => !testFrameworkFilter.matcher(line).matches() } + val DEFAULT_STACKTRACE_FILTER: String => Boolean = { (line: String) => + !testFrameworkFilter.matcher(line).matches() + } private var stackTraceFilter: String => Boolean = DEFAULT_STACKTRACE_FILTER /** @@ -123,7 +125,8 @@ object LogFormatter { */ object SimpleLogFormatter extends LogFormatter { override def formatLog(r: LogRecord): String = { - val log = s"[${highlightLog(r.level, r.leafLoggerName)}] ${highlightLog(r.level, r.getMessage)}" + val log = + s"[${highlightLog(r.level, r.leafLoggerName)}] ${highlightLog(r.level, r.getMessage)}" appendStackTrace(log, r) } } @@ -186,7 +189,8 @@ object LogFormatter { .map(source => s" ${withColor(Console.BLUE, s"- ${r.getLoggerName}(${source.fileLoc})")}") .getOrElse("") - val log = s"[${highlightLog(r.level, r.level.name)}] ${highlightLog(r.level, r.getMessage)}$loc" + val log = + s"[${highlightLog(r.level, r.level.name)}] ${highlightLog(r.level, r.getMessage)}$loc" appendStackTrace(log, r) } } diff --git a/airframe-log/shared/src/main/scala/wvlet/log/LogSupport.scala b/airframe-log/shared/src/main/scala/wvlet/log/LogSupport.scala index 025ed60bf9..9388ee65f4 100644 --- a/airframe-log/shared/src/main/scala/wvlet/log/LogSupport.scala +++ b/airframe-log/shared/src/main/scala/wvlet/log/LogSupport.scala @@ -39,28 +39,6 @@ trait LocalLogger { protected[this] val logger: Logger = Logger(LogEnv.getLoggerName(this.getClass)) } -trait LoggingMethods extends Serializable { - import LogMacros._ - - protected def error(message: Any): Unit = macro errorLog - protected def error(message: Any, cause: Throwable): Unit = macro errorLogWithCause - - protected def warn(message: Any): Unit = macro warnLog - protected def warn(message: Any, cause: Throwable): Unit = macro warnLogWithCause - - protected def info(message: Any): Unit = macro infoLog - protected def info(message: Any, cause: Throwable): Unit = macro infoLogWithCause - - protected def debug(message: Any): Unit = macro debugLog - protected def debug(message: Any, cause: Throwable): Unit = macro debugLogWithCause - - protected def trace(message: Any): Unit = macro traceLog - protected def trace(message: Any, cause: Throwable): Unit = macro traceLogWithCause - - protected def logAt(logLevel: LogLevel, message: Any): Unit = macro logAtImpl -} - trait PublicLoggingMethods extends Serializable { p => - import LogMacros._ protected[this] def logger: Logger } diff --git a/airframe-log/shared/src/main/scala/wvlet/log/Logger.scala b/airframe-log/shared/src/main/scala/wvlet/log/Logger.scala index e6ec9f2c0b..8e0417fd5d 100644 --- a/airframe-log/shared/src/main/scala/wvlet/log/Logger.scala +++ b/airframe-log/shared/src/main/scala/wvlet/log/Logger.scala @@ -14,7 +14,6 @@ package wvlet.log import java.util.concurrent.ConcurrentHashMap -import java.util.logging._ import java.util.{Properties, logging => jl} import scala.annotation.tailrec @@ -33,8 +32,8 @@ class Logger( * If wrapped is null, _log method will find or create the logger instance. */ @transient private[log] var wrapped: jl.Logger -) extends Serializable { - import LogMacros._ +) extends LoggerBase + with Serializable { private def _log = { if (wrapped == null) { @@ -43,24 +42,6 @@ class Logger( wrapped } - def error(message: Any): Unit = macro errorLogMethod - def error(message: Any, cause: Throwable): Unit = - macro errorLogMethodWithCause - - def warn(message: Any): Unit = macro warnLogMethod - def warn(message: Any, cause: Throwable): Unit = macro warnLogMethodWithCause - - def info(message: Any): Unit = macro infoLogMethod - def info(message: Any, cause: Throwable): Unit = macro infoLogMethodWithCause - - def debug(message: Any): Unit = macro debugLogMethod - def debug(message: Any, cause: Throwable): Unit = - macro debugLogMethodWithCause - - def trace(message: Any): Unit = macro traceLogMethod - def trace(message: Any, cause: Throwable): Unit = - macro traceLogMethodWithCause - def getName = name def getLogLevel: LogLevel = { @@ -88,7 +69,7 @@ class Logger( resetHandler(new ConsoleLogHandler(formatter)) } - def resetHandler(h: Handler): Unit = { + def resetHandler(h: jl.Handler): Unit = { clearHandlers _log.addHandler(h) setUseParentHandlers(false) @@ -98,7 +79,7 @@ class Logger( Option(wrapped.getParent).map(x => Logger(x.getName)) } - def addHandler(h: Handler): Unit = { + def addHandler(h: jl.Handler): Unit = { _log.addHandler(h) } @@ -134,7 +115,7 @@ class Logger( } } - def getHandlers: Seq[Handler] = { + def getHandlers: Seq[jl.Handler] = { wrapped.getHandlers.toSeq } @@ -146,7 +127,7 @@ class Logger( _log.isLoggable(level.jlLevel) } - def log(record: LogRecord): Unit = { + def log(record: wvlet.log.LogRecord): Unit = { record.setLoggerName(name) _log.log(record) } @@ -199,7 +180,7 @@ object Logger { private lazy val loggerCache = new ConcurrentHashMap[String, Logger].asScala - lazy val rootLogger = { + val rootLogger = { val l = initLogger(name = "", handlers = Seq(LogEnv.defaultHandler)) if (LogEnv.isScalaJS) { l.setLogLevel(LogLevel.INFO) @@ -219,7 +200,7 @@ object Logger { private[log] def initLogger( name: String, level: Option[LogLevel] = None, - handlers: Seq[Handler] = Seq.empty, + handlers: Seq[jl.Handler] = Seq.empty, useParents: Boolean = true ): Logger = { val logger = Logger.apply(name) diff --git a/airframe-log/shared/src/main/scala/wvlet/log/io/StopWatch.scala b/airframe-log/shared/src/main/scala/wvlet/log/io/StopWatch.scala index fd21521e6c..38f2498e45 100644 --- a/airframe-log/shared/src/main/scala/wvlet/log/io/StopWatch.scala +++ b/airframe-log/shared/src/main/scala/wvlet/log/io/StopWatch.scala @@ -199,7 +199,7 @@ trait TimeReport extends Ordered[TimeReport] { for (i <- 0 until repeat) { s.resume try { - body + body() } finally { val intervalTime = s.stop timeReport += intervalTime @@ -227,7 +227,7 @@ trait TimeReport extends Ordered[TimeReport] { } def median: Double = { - val r = timeReport.result + val r = timeReport.result() r.length r.sorted.apply(r.length / 2) } @@ -279,7 +279,7 @@ trait TimeReport extends Ordered[TimeReport] { lines += indent(1, v.genReportLine) } - lines.result.mkString("\n") + lines.result().mkString("\n") } override def toString: String = report @@ -290,7 +290,7 @@ class StopWatch { val RUNNING, STOPPED = Value } - private var lastSystemTime: Double = System.nanoTime + private var lastSystemTime: Double = System.nanoTime().toDouble private var elapsedTimeAccumulated: Double = 0L private var state = State.RUNNING @@ -303,7 +303,7 @@ class StopWatch { */ def getElapsedTime: Double = { if (state == State.RUNNING) { - val now = System.nanoTime() + val now = System.nanoTime().toDouble val diff = now - lastSystemTime (elapsedTimeAccumulated + diff) / NANO_UNIT } else { @@ -317,7 +317,7 @@ class StopWatch { * beginning from this method call. */ def reset: Unit = { - lastSystemTime = System.nanoTime() + lastSystemTime = System.nanoTime().toDouble elapsedTimeAccumulated = 0L } @@ -332,7 +332,7 @@ class StopWatch { } // elapsed time - val now = System.nanoTime() + val now = System.nanoTime().toDouble val diff = now - lastSystemTime elapsedTimeAccumulated += diff lastSystemTime = now @@ -349,7 +349,7 @@ class StopWatch { return } - lastSystemTime = System.nanoTime() + lastSystemTime = System.nanoTime().toDouble state = State.RUNNING } diff --git a/build.sbt b/build.sbt index 5350214e06..7161fe182a 100644 --- a/build.sbt +++ b/build.sbt @@ -52,9 +52,10 @@ Global / onChangedBuildSource := ReloadOnSourceChanges // Disable the pipelining available since sbt-1.4.0. It caused compilation failure ThisBuild / usePipelining := false +// A build configuration switch for working on Dotty migration. This needs to be removed eventually +val DOTTY = sys.env.isDefinedAt("DOTTY") // For using Scala 2.12 in sbt -val IS_DOTTY = sys.env.isDefinedAt("DOTTY") -scalaVersion in ThisBuild := { if (IS_DOTTY) SCALA_3_0 else SCALA_2_12 } +scalaVersion in ThisBuild := { if (DOTTY) SCALA_3_0 else SCALA_2_12 } organization in ThisBuild := "org.wvlet.airframe" // Use dynamic snapshot version strings for non tagged versions @@ -83,16 +84,23 @@ val buildSettings = Seq[Setting[_]]( javacOptions ++= Seq("-source", "1.8", "-target", "1.8"), scalacOptions ++= Seq( "-feature", - "-deprecation", - // Necessary for tracking source code range in airframe-rx demo - "-Yrangepos" - ) ++ (if (IS_DOTTY) Seq("-Ytasty-reader") else Seq.empty), + "-deprecation" + ) ++ { + if (DOTTY) { + Seq.empty + } else { + Seq( + // Necessary for tracking source code range in airframe-rx demo + "-Yrangepos" + ) + } + }, testFrameworks += new TestFramework("wvlet.airspec.Framework"), libraryDependencies ++= Seq( - "org.wvlet.airframe" %%% "airspec" % AIRSPEC_VERSION % Test, - "org.scalacheck" %%% "scalacheck" % SCALACHECK_VERSION % Test + ("org.wvlet.airframe" %%% "airspec" % AIRSPEC_VERSION % Test).withDottyCompat(scalaVersion.value), + ("org.scalacheck" %%% "scalacheck" % SCALACHECK_VERSION % Test).withDottyCompat(scalaVersion.value) ) ++ { - if (IS_DOTTY) + if (DOTTY) Seq.empty else Seq("org.scala-lang.modules" %%% "scala-collection-compat" % "2.3.1") @@ -218,6 +226,15 @@ lazy val projectJS = ) .aggregate(jsProjects: _*) +// For Dotty (Scala 3) +lazy val projectDotty = + project + .settings( + noPublish, + crossScalaVersions := Seq(SCALA_3_0) + ) + .aggregate(logJVM) + lazy val docs = project .in(file("airframe-docs")) @@ -415,7 +432,7 @@ lazy val launcher = val logDependencies = { scalaVersion: String => scalaVersion match { - case s if IS_DOTTY => + case s if DOTTY => Seq.empty case _ => Seq("org.scala-lang" % "scala-reflect" % scalaVersion % Provided) @@ -434,7 +451,23 @@ lazy val log: sbtcrossproject.CrossProject = .settings( name := "airframe-log", description := "Fancy logger for Scala", - libraryDependencies ++= logDependencies(scalaVersion.value) + scalacOptions ++= { if (isDotty.value) Seq("-source:3.0-migration") else Nil }, + libraryDependencies ++= logDependencies(scalaVersion.value), + crossScalaVersions := { if (DOTTY) withDotty else targetScalaVersions }, + unmanagedSourceDirectories in Compile ++= { + scalaBinaryVersion.value match { + case v if v.startsWith("2.") => + Seq( + baseDirectory.value.getParentFile / "shared" / "src" / "main" / "scala-2" + ) + case v if v.startsWith("3.") => + Seq( + baseDirectory.value.getParentFile / "shared" / "src" / "main" / "scala-3" + ) + case _ => + Seq.empty + } + } ) .jvmSettings( libraryDependencies ++= logJVMDependencies, @@ -849,6 +882,19 @@ lazy val sbtAirframe = ) .dependsOn(controlJVM, codecJVM, logJVM, httpJVM % Test) +// Dotty test project +lazy val dottyTest = + project + .in(file("airframe-dotty-test")) + .settings(buildSettings) + .settings(noPublish) + .settings( + name := "airframe-dotty-test", + description := "test for dotty", + crossScalaVersions := { if (DOTTY) withDotty else targetScalaVersions } + ) + .dependsOn(logJVM) + /** * AirSpec build definitions. * @@ -877,7 +923,8 @@ val airspecBuildSettings = Seq[Setting[_]]( val sourceDirs = for (m <- airspecDependsOn.value) yield { Seq( file(s"${baseDir}/${m}/src/main/scala"), - file(s"${baseDir}/${m}/shared/src/main/scala") + file(s"${baseDir}/${m}/shared/src/main/scala"), + file(s"${baseDir}/${m}/shared/src/main/scala-2") ) } sourceDirs.flatten