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

bleep.commands.BuildProjectMergeInto.scala Maven / Gradle / Ivy

package bleep
package commands

import bleep.rewrites.BuildRewrite

import java.nio.file.{Files, Path}
import scala.collection.immutable.SortedMap

class BuildProjectMergeInto(projectName: model.ProjectName, into: model.ProjectName) extends BleepBuildCommand {
  object rewrite extends BuildRewrite {
    override val name: model.BuildRewriteName = model.BuildRewriteName("merge-into")

    def rewriteProject(p: model.Project): model.Project =
      p.copy(
        dependsOn = p.dependsOn.map {
          case `projectName` => into
          case other         => other
        },
        sourcegen = p.sourcegen.map(rewriteScriptDef)
      )

    override protected def newExplodedProjects(oldBuild: model.Build, buildPaths: BuildPaths): Map[model.CrossProjectName, model.Project] =
      oldBuild.explodedProjects.flatMap {
        case (crossName, _) if crossName.name == projectName =>
          None
        case (crossName, p0) if crossName.name == into =>
          val p1 = {
            // copy everything from `projectName` (with non-mergeables from `into` taking precedence)
            val copyFrom = oldBuild.explodedProjects(crossName.copy(name = projectName))
            p0.union(copyFrom)
          }
          // remove `dependsOn` from `projectName` and `into` or transitive dependencies of `into`
          val p2 = {
            val removeDependsOn = oldBuild.transitiveDependenciesFor(crossName).keySet ++ Iterable(projectName, into)
            p1.copy(dependsOn = p1.dependsOn.filterNot(removeDependsOn))
          }
          Some(crossName -> p2)
        case (crossName, p) => Some((crossName, rewriteProject(p)))
      }
  }

  def rewriteScriptDef(s: model.ScriptDef): model.ScriptDef =
    s match {
      case s @ model.ScriptDef.Main(model.CrossProjectName(`projectName`, _), _, _) =>
        s.copy(project = model.CrossProjectName(into, s.project.crossId))
      case s => s
    }

  override def run(started: Started): Either[BleepException, Unit] = {
    val crossProjects: SortedMap[model.CrossProjectName, model.Project] =
      started.build.explodedProjects.iterator.collect { case (cp, p) if cp.name == projectName => (cp, p) }.to(SortedMap)
    val intoCrossProjects: SortedMap[model.CrossProjectName, model.Project] =
      started.build.explodedProjects.iterator.collect { case (cp, p) if cp.name == into => (cp, p) }.to(SortedMap)

    if (crossProjects.isEmpty)
      return Left(new BleepException.Text(s"Project ${projectName.value} does not exist"))
    if (intoCrossProjects.isEmpty)
      return Left(new BleepException.Text(s"Project ${into.value} does not exist"))

    if (crossProjects.size != intoCrossProjects.size)
      return Left(
        new BleepException.Text(
          s"Can only merge projects with same number of crossIds: ${projectName.value}: ${crossProjects.keys} vs ${into.value} ${intoCrossProjects.keys}"
        )
      )

    // can likely make this work, but should write some careful tests first
    (crossProjects.values.flatMap(_.`source-layout`), intoCrossProjects.values.flatMap(_.`source-layout`)) match {
      case (left, right) if left != right =>
        return Left(
          new BleepException.Text(
            s"Can only merge projects with same source layout: ${projectName.value}: ${left.mkString(", ")} vs ${into.value} ${right.mkString(", ")}"
          )
        )
      case _ => ()
    }

    val rewrittenBuild = {
      val build0 = rewrite(started.build.requireFileBacked(ctx = "command project-merge-into"), started.buildPaths)
      val build1 = build0.copy(file = build0.file.copy(scripts = build0.file.scripts.map { case (sn, sd) => (sn, sd.map(rewriteScriptDef)) }))
      BuildReapplyTemplates.normalize(build1, started.buildPaths)
    }

    def pathsFor(crossName: model.CrossProjectName): List[Path] = {
      val projectPaths = started.projectPaths(crossName)
      (projectPaths.sourcesDirs.fromSourceLayout.iterator ++ projectPaths.resourcesDirs.fromSourceLayout.iterator).toList
    }

    val b = Map.newBuilder[Path, Path]
    val errors = List.newBuilder[String]
    crossProjects.zip(intoCrossProjects).foreach { case ((crossName, _), (crossNameInto, _)) =>
      pathsFor(crossName).zip(pathsFor(crossNameInto)).foreach { case (fromSrc, intoSrc) =>
        if (Files.exists(fromSrc)) {
          Files.walk(fromSrc).forEach { fromPath =>
            if (Files.isRegularFile(fromPath)) {
              println(fromPath)
              val intoPath = intoSrc.resolve(fromSrc.relativize(fromPath))
              if (Files.exists(intoPath))
                errors += s"Expected to move project ${projectName.value} from $fromPath to $intoPath, but it already exists"
              b += fromPath -> intoPath
            }
          }
        }
      }
    }

    errors.result() match {
      case Nil =>
        Right(
          commit(
            started.logger,
            started.buildPaths,
            filesToMove = b.result().to(SortedMap),
            rewrittenBuild = rewrittenBuild.file
          )
        )
      case nonEmpty => Left(new bleep.BleepException.Text(nonEmpty.mkString("\n")))
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy