Skip to content

Commit 037e421

Browse files
committed
WIP add Inputs trait and implementing classes
1 parent 466ee4a commit 037e421

File tree

6 files changed

+128
-43
lines changed

6 files changed

+128
-43
lines changed
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package scala.build.input.compose
2+
3+
import scala.build.input.WorkspaceOrigin
4+
import scala.build.input.ModuleInputs
5+
import scala.build.options.BuildOptions
6+
7+
sealed trait Inputs {
8+
9+
def modules: Seq[ModuleInputs]
10+
11+
/** Module targeted by the user. If a command requires a target to be executed (e.g. run or compile), it should be executed on this module. */
12+
def targetModule: ModuleInputs
13+
/** Build order for modules to execute the command on the [[targetModule]] */
14+
def modulesBuildOrder: Seq[ModuleInputs]
15+
def workspaceOrigin: WorkspaceOrigin
16+
def workspace: os.Path
17+
18+
def preprocessInputs(preprocess: ModuleInputs => (ModuleInputs, BuildOptions)): (Inputs, Seq[BuildOptions])
19+
}
20+
21+
/** Result of using [[InputsComposer]] with module config file present */
22+
case class ComposedInputs(
23+
modules: Seq[ModuleInputs],
24+
targetModule: ModuleInputs,
25+
workspace: os.Path,
26+
) extends Inputs {
27+
28+
// Forced to be the directory where module config file (modules.yaml) resides
29+
override val workspaceOrigin: WorkspaceOrigin = WorkspaceOrigin.Forced
30+
31+
lazy val modulesBuildOrder: Seq[ModuleInputs] = {
32+
val nameMap = modules.map(m => m.projectName -> m)
33+
val dependencyGraph = modules.map(m => m.projectName -> m.moduleDependencies)
34+
35+
val visited = mutable.Set.empty[Name] // Track visited nodes
36+
val result = mutable.Stack.empty[Name] // Use a stack to build the result in reverse order
37+
38+
def visit(node: ProjectName): Unit = {
39+
if (!visited.contains(node)) {
40+
visited += node
41+
dependencyGraph.getOrElse(node, Nil).foreach(visit) // Visit all the linked nodes first
42+
result.push(node) // Add the current node after visiting linked nodes
43+
}
44+
}
45+
46+
dependencyGraph.keys.foreach(visit)
47+
48+
result.toSeq.reverse
49+
}
50+
51+
def preprocessInputs(preprocess: ModuleInputs => (ModuleInputs, BuildOptions)): (ComposedInputs, Seq[BuildOptions]) = {
52+
val (preprocessedModules, buildOptions) =>
53+
modules.filter(_.projectName == targetModule.projectName)
54+
.map(preprocess)
55+
.unzip
56+
57+
val preprocessedTargetModule = preprocess(targetModule)
58+
59+
copy(modules = preprocessedModules ++ preprocessedTargetModule, targetModule = preprocessedTargetModule) -> buildOptions
60+
}
61+
}
62+
63+
/** Essentially a wrapper over a single module, no config file for modules involved */
64+
case class SimpleInputs(
65+
singleModule: ModuleInputs,
66+
) extends Inputs {
67+
override val modules: Seq[ModuleInputs] = Seq(singleModule)
68+
69+
override val targetModule: ModuleInputs = singleModule
70+
71+
override val modulesBuildOrder = modules
72+
73+
override val workspace: os.Path = singleModule.workspace
74+
75+
override val workspaceOrigin: WorkspaceOrigin = singleModule.workspaceOrigin
76+
77+
def preprocessInputs(preprocess: ModuleInputs => (ModuleInputs, BuildOptions)): (ComposedInputs, Seq[BuildOptions]) =
78+
copy(singleModule = preprocess(singleModule)) -> buildOptions
79+
}

modules/build/src/main/scala/scala/build/input/InputsComposer.scala renamed to modules/build/src/main/scala/scala/build/input/compose/InputsComposer.scala

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package scala.build.input
1+
package scala.build.input.compose
22

33
import toml.Value
44
import toml.Value.*
@@ -7,6 +7,7 @@ import scala.build.EitherCps.*
77
import scala.build.EitherSequence
88
import scala.build.bsp.buildtargets.ProjectName
99
import scala.build.errors.{BuildException, CompositeBuildException, ModuleConfigurationError}
10+
import scala.build.input.{InputsComposer, ModuleInputs}
1011
import scala.build.internal.Constants
1112
import scala.build.options.BuildOptions
1213
import scala.collection.mutable
@@ -113,7 +114,7 @@ object InputsComposer {
113114
/** Creates [[ModuleInputs]] given the initial arguments passed to the command, Looks for module
114115
* config .toml file and if found composes module inputs according to the defined config, otherwise
115116
* if module config is not found or if [[allowForbiddenFeatures]] is not set, returns only one
116-
* basic module created from initial args (see [[basicInputs]])
117+
* basic module created from initial args (see [[simpleInputs]])
117118
*
118119
* @param args
119120
* initial args passed to command
@@ -133,9 +134,9 @@ final case class InputsComposer(
133134
import InputsComposer.*
134135

135136
/** Inputs with no dependencies coming only from args */
136-
def basicInputs = for (inputs <- inputsFromArgs(args, None)) yield Seq(inputs)
137+
private def simpleInputs = for (inputs <- inputsFromArgs(args, None)) yield SimpleInputs(inputs)
137138

138-
def getModuleInputs: Either[BuildException, Seq[ModuleInputs]] =
139+
def getInputs: Either[BuildException, Inputs] =
139140
if allowForbiddenFeatures then
140141
findModuleConfig(args, cwd) match {
141142
case Right(Some(moduleConfigPath)) =>
@@ -149,10 +150,10 @@ final case class InputsComposer(
149150
_ <- checkForCycles(modules)
150151
moduleInputs <- fromModuleDefinitions(modules, moduleConfigPath)
151152
} yield moduleInputs
152-
case Right(None) => basicInputs
153+
case Right(None) => simpleInputs
153154
case Left(err) => Left(err)
154155
}
155-
else basicInputs
156+
else simpleInputs
156157

157158
// private def readScalaVersion(value: Value): Either[String, String] = value match {
158159
// case Str(version) => Right(version)
@@ -197,26 +198,29 @@ final case class InputsComposer(
197198
private def fromModuleDefinitions(
198199
modules: Seq[ModuleDefinition],
199200
moduleConfigPath: os.Path
200-
): Either[BuildException, Seq[ModuleInputs]] = either {
201+
): Either[BuildException, ComposedInputs] = either {
201202
val workspacePath = moduleConfigPath / os.up
202-
val moduleInputsInfo = modules.map { m =>
203+
val moduleInputsInfo: Map[ModuleDefinition, ModuleInputs] = modules.map { m =>
203204
val moduleName = ProjectName(m.name)
204205
val argsWithWorkspace = m.roots.map(r => os.Path(r, workspacePath).toString)
205206
val moduleInputs = inputsFromArgs(argsWithWorkspace, Some(moduleName))
206207
m -> value(moduleInputs).copy(mayAppendHash = false)
207208
}
208209

209210
val projectNameMap: Map[String, ProjectName] =
210-
moduleInputsInfo.map((moduleDef, inputs) => moduleDef.name -> inputs.projectName).toMap
211+
moduleInputsInfo.map((moduleDef, module) => moduleDef.name -> module.projectName).toMap
211212

212-
val moduleInputs = moduleInputsInfo.map { (moduleDef, inputs) =>
213+
val moduleInputs = moduleInputsInfo.map { (moduleDef, module) =>
213214
val moduleDeps: Seq[ProjectName] = moduleDef.dependsOn.map(projectNameMap)
214215

215-
inputs.dependsOn(moduleDeps)
216+
module.dependsOn(moduleDeps)
216217
.withForcedWorkspace(workspacePath)
217218
.copy(mayAppendHash = false)
218219
}
219220

220-
moduleInputs
221+
val targetModule = modules.find(_.roots.toSet equals args.toSet)
222+
.getOrElse(moduleInputs.head)
223+
224+
ComposedInputs(modules = moduleInputs, targetModule = targetModule, workspace = workspacePath)
221225
}
222226
}

modules/build/src/test/scala/scala/build/input/InputsComposerTest.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package scala.build.input
33
import scala.build.Build
44
import scala.build.bsp.buildtargets.ProjectName
55
import scala.build.errors.BuildException
6+
import scala.build.input.compose.InputsComposer
67
import scala.build.internal.Constants
78
import scala.build.options.BuildOptions
89
import scala.build.tests.{TestInputs, TestUtil}
@@ -57,7 +58,7 @@ class InputsComposerTest extends TestUtil.ScalaCliBuildSuite {
5758
Right(Build.updateInputs(emptyInputs, BuildOptions()))
5859
}
5960
val modules = InputsComposer(Seq(root.toString), root, argsToInputs, true)
60-
.getModuleInputs
61+
.getInputs
6162
.toSeq
6263
.flatten
6364

modules/cli/src/main/scala/scala/cli/commands/bsp/Bsp.scala

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import com.github.plokhotnyuk.jsoniter_scala.macros.JsonCodecMaker
66

77
import scala.build.EitherCps.{either, value}
88
import scala.build.*
9+
import scala.build.input.compose
910
import scala.build.bsp.{BspReloadableOptions, BspThreads}
1011
import scala.build.errors.BuildException
1112
import scala.build.input.ModuleInputs
@@ -83,7 +84,7 @@ object Bsp extends ScalaCommand[BspOptions] {
8384

8485
refreshPowerMode(getLauncherOptions(), getSharedOptions(), getEnvsFromFile())
8586

86-
val preprocessInputs: Seq[String] => Either[BuildException, Seq[(ModuleInputs, BuildOptions)]] =
87+
val preprocessInputs: Seq[String] => Either[BuildException, (compose.Inputs, Seq[BuildOptions])] =
8788
argsSeq =>
8889
either {
8990
val sharedOptions = getSharedOptions()
@@ -96,12 +97,12 @@ object Bsp extends ScalaCommand[BspOptions] {
9697
val latestLogger = sharedOptions.logging.logger
9798
val persistentLogger = new PersistentDiagnosticLogger(latestLogger)
9899

99-
val initialInputs: Seq[ModuleInputs] = value(sharedOptions.composeInputs(argsSeq))
100+
val initialInputs: compose.Inputs = value(sharedOptions.composeInputs(argsSeq))
100101

101102
if (sharedOptions.logging.verbosity >= 3)
102103
pprint.err.log(initialInputs)
103104

104-
for (moduleInputs <- initialInputs) yield {
105+
initialInputs.preprocessInputs { moduleInputs =>
105106
val crossResult = CrossSources.forModuleInputs(
106107
moduleInputs,
107108
Sources.defaultPreprocessors(
@@ -137,8 +138,8 @@ object Bsp extends ScalaCommand[BspOptions] {
137138

138139
// TODO reported override option values
139140
// FIXME Only some options need to be unified for the whole project, like scala version, JVM
140-
val combinedBuildOptions = inputsAndBuildOptions.map(_._2).reduceLeft(_ orElse _)
141-
val inputs = inputsAndBuildOptions.map(_._1)
141+
val combinedBuildOptions = inputsAndBuildOptions._2.reduceLeft(_ orElse _)
142+
val inputs = inputsAndBuildOptions._1
142143

143144
if (options.shared.logging.verbosity >= 3)
144145
pprint.err.log(combinedBuildOptions)
@@ -179,14 +180,12 @@ object Bsp extends ScalaCommand[BspOptions] {
179180
)
180181
}
181182

182-
CurrentParams.workspaceOpt =
183-
Some(inputs.head.workspace) // FIXME .head, introduce better types for Seq[ModuleInputs] that will have a common workspace
184-
val actionableDiagnostics =
185-
options.shared.logging.verbosityOptions.actions
183+
CurrentParams.workspaceOpt = Some(inputs.workspace)
184+
val actionableDiagnostics = options.shared.logging.verbosityOptions.actions
186185

187186
BspThreads.withThreads { threads =>
188187
val bsp = scala.build.bsp.Bsp.create(
189-
preprocessInputs.andThen(_.map(_.map(_._1))),
188+
preprocessInputs.andThen(_.map(_._1)),
190189
bspReloadableOptionsReference,
191190
threads,
192191
System.in,

modules/cli/src/main/scala/scala/cli/commands/setupide/SetupIde.scala

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@ import com.github.plokhotnyuk.jsoniter_scala.macros.JsonCodecMaker
77
import com.google.gson.GsonBuilder
88

99
import java.nio.charset.{Charset, StandardCharsets}
10-
1110
import scala.build.EitherCps.{either, value}
1211
import scala.build.*
1312
import scala.build.bsp.IdeInputs
1413
import scala.build.errors.{BuildException, WorkspaceError}
15-
import scala.build.input.{InputsComposer, ModuleInputs, OnDisk, Virtual, WorkspaceOrigin}
14+
import scala.build.input.compose
15+
import scala.build.input.compose.{ComposedInputs, InputsComposer, SimpleInputs}
16+
import scala.build.input.{ModuleInputs, OnDisk, Virtual, WorkspaceOrigin}
1617
import scala.build.internal.Constants
1718
import scala.build.internals.EnvVar
1819
import scala.build.options.{BuildOptions, Scope}
@@ -68,12 +69,12 @@ object SetupIde extends ScalaCommand[SetupIdeOptions] {
6869

6970
override def runCommand(options: SetupIdeOptions, args: RemainingArgs, logger: Logger): Unit = {
7071
val buildOptions = buildOptionsOrExit(options)
71-
val moduleInputs = options.shared.composeInputs(args.all).orExit(logger)
72-
CurrentParams.workspaceOpt = Some(moduleInputs.head.workspace)
72+
val inputs = options.shared.composeInputs(args.all).orExit(logger)
73+
CurrentParams.workspaceOpt = Some(inputs.workspace)
7374

7475
val bspPath = writeBspConfiguration(
7576
options,
76-
moduleInputs,
77+
inputs,
7778
buildOptions,
7879
previousCommandName = None,
7980
args = args.all
@@ -92,7 +93,7 @@ object SetupIde extends ScalaCommand[SetupIdeOptions] {
9293
): Unit =
9394
writeBspConfiguration(
9495
SetupIdeOptions(shared = options),
95-
Seq(inputs),
96+
SimpleInputs(inputs),
9697
buildOptions,
9798
previousCommandName,
9899
args
@@ -106,13 +107,13 @@ object SetupIde extends ScalaCommand[SetupIdeOptions] {
106107

107108
private def writeBspConfiguration(
108109
options: SetupIdeOptions,
109-
moduleInputs: Seq[ModuleInputs],
110+
inputs: compose.Inputs,
110111
buildOptions: BuildOptions,
111112
previousCommandName: Option[String],
112113
args: Seq[String]
113114
): Either[BuildException, Option[os.Path]] = either {
114115

115-
val virtualInputs = moduleInputs.flatMap(_.elements).collect {
116+
val virtualInputs = inputs.modules.flatMap(_.elements).collect {
116117
case v: Virtual => v
117118
}
118119
if (virtualInputs.nonEmpty)
@@ -125,9 +126,9 @@ object SetupIde extends ScalaCommand[SetupIdeOptions] {
125126
val logger = options.shared.logger
126127

127128
if (buildOptions.classPathOptions.allExtraDependencies.toSeq.nonEmpty)
128-
for (module <- moduleInputs) do value(downloadDeps(module, buildOptions, logger))
129+
for (module <- inputs.modules) do value(downloadDeps(module, buildOptions, logger))
129130

130-
val workspace = moduleInputs.head.workspace
131+
val workspace = inputs.workspace
131132

132133
val (bspName, bspJsonDestination) = bspDetails(workspace, options.bspFile)
133134
val scalaCliBspJsonDestination = workspace / Constants.workspaceDirName / "ide-options-v2.json"
@@ -138,12 +139,12 @@ object SetupIde extends ScalaCommand[SetupIdeOptions] {
138139
val scalaCliBspEnvsJsonDestination = workspace / Constants.workspaceDirName / "ide-envs.json"
139140

140141
// FIXME single modules can also be defined with module config toml file
141-
val inputArgs = if moduleInputs.size > 1 then
142-
InputsComposer.findModuleConfig(args, Os.pwd)
143-
.orExit(logger)
144-
.fold(args)(p => Seq(p.toString))
145-
else
146-
moduleInputs.head.elements
142+
val inputArgs = inputs match
143+
case ComposedInputs(modules, targetModule, workspace) =>
144+
InputsComposer.findModuleConfig(args, Os.pwd)
145+
.orExit(logger)
146+
.fold(args)(p => Seq(p.toString))
147+
case SimpleInputs(singleModule) => singleModule.elements
147148
.collect { case d: OnDisk => d.path.toString }
148149

149150
val ideInputs = IdeInputs(
@@ -192,7 +193,7 @@ object SetupIde extends ScalaCommand[SetupIdeOptions] {
192193
val envsForBsp = sys.env.filter((key, _) => EnvVar.allBsp.map(_.name).contains(key))
193194
val scalaCliBspEnvsJson = writeToArray(envsForBsp)
194195

195-
if (moduleInputs.head.workspaceOrigin.contains(WorkspaceOrigin.HomeDir))
196+
if (inputs.workspaceOrigin.contains(WorkspaceOrigin.HomeDir))
196197
value(Left(new WorkspaceError(
197198
s"""$baseRunnerName can not determine where to write its BSP configuration.
198199
|Set an explicit BSP directory path via `--bsp-directory`.

modules/cli/src/main/scala/scala/cli/commands/shared/SharedOptions.scala

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,10 @@ import scala.build.bsp.buildtargets.ProjectName
2424
import scala.build.compiler.{BloopCompilerMaker, ScalaCompilerMaker, SimpleScalaCompilerMaker}
2525
import scala.build.directives.DirectiveDescription
2626
import scala.build.errors.{AmbiguousPlatformError, BuildException, ConfigDbException, Severity}
27+
import scala.build.input.compose
28+
import scala.build.input.compose.InputsComposer
2729
import scala.build.input.{
2830
Element,
29-
InputsComposer,
3031
ModuleInputs,
3132
ResourceDirectory,
3233
ScalaCliInvokeData
@@ -656,7 +657,7 @@ final case class SharedOptions(
656657
def composeInputs(
657658
args: Seq[String],
658659
defaultInputs: () => Option[ModuleInputs] = () => ModuleInputs.default()
659-
)(using ScalaCliInvokeData): Either[BuildException, Seq[ModuleInputs]] = {
660+
)(using ScalaCliInvokeData): Either[BuildException, compose.Inputs] = {
660661
val updatedModuleInputsFromArgs
661662
: (Seq[String], Option[ProjectName]) => Either[BuildException, ModuleInputs] =
662663
(args, projectNameOpt) =>
@@ -670,7 +671,7 @@ final case class SharedOptions(
670671
Os.pwd,
671672
updatedModuleInputsFromArgs,
672673
ScalaCli.allowRestrictedFeatures
673-
).getModuleInputs
674+
).getInputs
674675
}
675676

676677
def inputs(

0 commit comments

Comments
 (0)