All Downloads are FREE. Search and download functionalities are using the official Maven repository.

scala.build.Project.scala Maven / Gradle / Ivy

package scala.build

import _root_.bloop.config.{Config => BloopConfig, ConfigCodecs => BloopCodecs}
import _root_.coursier.{Dependency => CsDependency, core => csCore, util => csUtil}
import com.github.plokhotnyuk.jsoniter_scala.core.{writeToArray => writeAsJsonToArray}
import coursier.core.Classifier

import java.io.ByteArrayOutputStream
import java.nio.charset.StandardCharsets
import java.nio.file.Path
import java.util.Arrays

import scala.build.options.{ScalacOpt, Scope, ShadowingSeq}

final case class Project(
  workspace: os.Path,
  directory: os.Path,
  argsFilePath: os.Path,
  classesDir: os.Path,
  scaladocDir: os.Path,
  scalaCompiler: Option[ScalaCompilerParams],
  scalaJsOptions: Option[BloopConfig.JsConfig],
  scalaNativeOptions: Option[BloopConfig.NativeConfig],
  projectName: String,
  classPath: Seq[os.Path],
  sources: Seq[os.Path],
  resolution: Option[BloopConfig.Resolution],
  resourceDirs: Seq[os.Path],
  javaHomeOpt: Option[os.Path],
  scope: Scope,
  javacOptions: List[String]
) {

  import Project._

  def bloopProject: BloopConfig.Project = {
    val platform = (scalaJsOptions, scalaNativeOptions) match {
      case (None, None) =>
        val baseJvmConf = bloopJvmPlatform
        baseJvmConf.copy(
          // We don't pass jvm home here, because it applies only to java files compilation
          config = baseJvmConf.config.copy(home = None)
        )
      case (Some(jsConfig), _) => BloopConfig.Platform.Js(config = jsConfig, mainClass = None)
      case (_, Some(nativeConfig)) =>
        BloopConfig.Platform.Native(config = nativeConfig, mainClass = None)
    }
    val scalaConfigOpt = scalaCompiler.map { scalaCompiler0 =>
      bloopScalaConfig("org.scala-lang", "scala-compiler", scalaCompiler0.scalaVersion).copy(
        options = updateScalacOptions(scalaCompiler0.scalacOptions).map(_.value),
        jars = scalaCompiler0.compilerClassPath.map(_.toNIO).toList
      )
    }
    baseBloopProject(
      projectName,
      directory.toNIO,
      (directory / ".bloop" / projectName).toNIO,
      classesDir.toNIO,
      scope
    )
      .copy(
        workspaceDir = Some(workspace.toNIO),
        classpath = classPath.map(_.toNIO).toList,
        sources = sources.iterator.map(_.toNIO).toList,
        resources = Some(resourceDirs).filter(_.nonEmpty).map(_.iterator.map(_.toNIO).toList),
        platform = Some(platform),
        `scala` = scalaConfigOpt,
        java = Some(BloopConfig.Java(javacOptions)),
        resolution = resolution
      )
  }

  def bloopFile: BloopConfig.File =
    BloopConfig.File(BloopConfig.File.LatestVersion, bloopProject)

  private def updateScalacOptions(scalacOptions: Seq[String]): List[ScalacOpt] =
    ShadowingSeq.from(scalacOptions.map(ScalacOpt(_))).values.map { l =>
      // only look at the head, the tail is only values passed to it
      l.headOption match {
        case Some(opt) if opt.value.startsWith("-coverage-out:") =>
          // actual -coverage-out: option
          val maybeRelativePath = opt.value.stripPrefix("-coverage-out:")
          val absolutePath      = os.Path(maybeRelativePath, Os.pwd)
          ScalacOpt(s"-coverage-out:$absolutePath") +: l.tail
        case _ =>
          // not a -coverage-out: option
          l
      }
    }.flatten.toList

  private def maybeUpdateInputs(logger: Logger): Boolean = {
    val dest = directory / ".bloop" / s"$projectName.inputs.txt"
    val onDiskOpt =
      if (os.exists(dest)) Some(os.read.bytes(dest))
      else None
    val newContent = {
      val linesIt =
        if (sources.forall(_.startsWith(workspace)))
          sources.iterator.map(_.relativeTo(workspace).toString)
        else
          sources.iterator.map(_.toString)
      val it = linesIt.map(_ + System.lineSeparator()).map(_.getBytes(StandardCharsets.UTF_8))
      val b  = new ByteArrayOutputStream
      for (elem <- it)
        b.write(elem)
      b.toByteArray()
    }
    val doWrite = onDiskOpt.forall(onDisk => !Arrays.equals(onDisk, newContent))
    if (doWrite) {
      logger.debug(s"Writing source file list in $dest")
      os.write.over(dest, newContent, createFolders = true)
    }
    else
      logger.debug(s"Source file list in $dest doesn't need updating")
    doWrite
  }

  def writeBloopFile(strictCheck: Boolean, logger: Logger): Boolean = {
    lazy val bloopFileContent =
      writeAsJsonToArray(bloopFile)(BloopCodecs.codecFile)
    val dest = directory / ".bloop" / s"$projectName.json"
    val doWrite =
      if (strictCheck)
        !os.isFile(dest) || {
          logger.debug(s"Checking Bloop project in $dest")
          val currentContent = os.read.bytes(dest)
          !Arrays.equals(currentContent, bloopFileContent)
        }
      else
        maybeUpdateInputs(logger) || !os.isFile(dest)
    if (doWrite) {
      logger.debug(s"Writing bloop project in $dest")
      os.write.over(dest, bloopFileContent, createFolders = true)
    }
    else
      logger.debug(s"Bloop project in $dest doesn't need updating")
    doWrite
  }
}

object Project {

  def resolution(
    detailedArtifacts: Seq[(CsDependency, csCore.Publication, csUtil.Artifact, os.Path)]
  ): BloopConfig.Resolution = {
    val indices = detailedArtifacts.map(_._1.moduleVersion).zipWithIndex.toMap
    val modules = detailedArtifacts
      .groupBy(_._1.moduleVersion)
      .toVector
      .sortBy { case (modVer, _) => indices.getOrElse(modVer, Int.MaxValue) }
      .iterator
      .map {
        case ((mod, ver), values) =>
          val artifacts = values.toList.map {
            case (_, pub, _, f) =>
              val classifier =
                if (pub.classifier == Classifier.empty) None
                else Some(pub.classifier.value)
              BloopConfig.Artifact(pub.name, classifier, None, f.toNIO)
          }
          BloopConfig.Module(mod.organization.value, mod.name.value, ver, None, artifacts)
      }
      .toList
    BloopConfig.Resolution(modules)
  }

  private def setProjectTestConfig(p: BloopConfig.Project): BloopConfig.Project =
    p.copy(
      dependencies = List(p.name.stripSuffix("-test")),
      test = Some(
        BloopConfig.Test(
          frameworks = BloopConfig.TestFramework.DefaultFrameworks,
          options = BloopConfig.TestOptions.empty
        )
      ),
      tags = Some(List("test"))
    )

  private def baseBloopProject(
    name: String,
    directory: Path,
    out: Path,
    classesDir: Path,
    scope: Scope
  ): BloopConfig.Project = {
    val project = BloopConfig.Project(
      name = name,
      directory = directory,
      workspaceDir = None,
      sources = Nil,
      sourcesGlobs = None,
      sourceRoots = None,
      dependencies = Nil,
      classpath = Nil,
      out = out,
      classesDir = classesDir,
      resources = None,
      `scala` = None,
      java = None,
      sbt = None,
      test = None,
      platform = None,
      resolution = None,
      tags = Some(List("library")),
      sourceGenerators = None
    )
    if (scope == Scope.Test)
      setProjectTestConfig(project)
    else project
  }

  private def bloopJvmPlatform: BloopConfig.Platform.Jvm =
    BloopConfig.Platform.Jvm(
      config = BloopConfig.JvmConfig(None, Nil),
      mainClass = None,
      runtimeConfig = None,
      classpath = None,
      resources = None
    )
  private def bloopScalaConfig(
    organization: String,
    name: String,
    version: String
  ): BloopConfig.Scala =
    BloopConfig.Scala(
      organization = organization,
      name = name,
      version = version,
      options = Nil,
      jars = Nil,
      analysis = None,
      setup = None
    )
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy