Skip to content

Commit 9635628

Browse files
committed
WIP add Inputs trait and implementing classes
1 parent 5f79f49 commit 9635628

File tree

6 files changed

+129
-48
lines changed

6 files changed

+129
-48
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: 10 additions & 15 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
@@ -82,7 +83,7 @@ object Bsp extends ScalaCommand[BspOptions] {
8283

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

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

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

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

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

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

142143
if (options.shared.logging.verbosity >= 3)
143144
pprint.err.log(combinedBuildOptions)
@@ -151,13 +152,9 @@ object Bsp extends ScalaCommand[BspOptions] {
151152
val launcherOptions = getLauncherOptions()
152153
val envs = getEnvsFromFile()
153154
val bspBuildOptions = buildOptions(sharedOptions, launcherOptions, envs)
154-
<<<<<<< HEAD
155-
.orElse(finalBuildOptions)
156-
refreshPowerMode(launcherOptions, sharedOptions, envs)
157-
=======
158155
.orElse(combinedBuildOptions)
156+
refreshPowerMode(launcherOptions, sharedOptions, envs)
159157

160-
>>>>>>> cf44243b7 (Add mechanism for picking the highest java version for bloop, add docs, add fixme)
161158
BspReloadableOptions(
162159
buildOptions = bspBuildOptions,
163160
bloopRifleConfig = sharedOptions.bloopRifleConfig(Some(bspBuildOptions))
@@ -181,14 +178,12 @@ object Bsp extends ScalaCommand[BspOptions] {
181178
)
182179
}
183180

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

189184
BspThreads.withThreads { threads =>
190185
val bsp = scala.build.bsp.Bsp.create(
191-
preprocessInputs.andThen(_.map(_.map(_._1))),
186+
preprocessInputs.andThen(_.map(_._1)),
192187
bspReloadableOptionsReference,
193188
threads,
194189
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.options.{BuildOptions, Scope}
1819
import scala.cli.CurrentParams
@@ -67,12 +68,12 @@ object SetupIde extends ScalaCommand[SetupIdeOptions] {
6768

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

7374
val bspPath = writeBspConfiguration(
7475
options,
75-
moduleInputs,
76+
inputs,
7677
buildOptions,
7778
previousCommandName = None,
7879
args = args.all
@@ -91,7 +92,7 @@ object SetupIde extends ScalaCommand[SetupIdeOptions] {
9192
): Unit =
9293
writeBspConfiguration(
9394
SetupIdeOptions(shared = options),
94-
Seq(inputs),
95+
SimpleInputs(inputs),
9596
buildOptions,
9697
previousCommandName,
9798
args
@@ -105,13 +106,13 @@ object SetupIde extends ScalaCommand[SetupIdeOptions] {
105106

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

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

126127
if (buildOptions.classPathOptions.allExtraDependencies.toSeq.nonEmpty)
127-
for (module <- moduleInputs) do value(downloadDeps(module, buildOptions, logger))
128+
for (module <- inputs.modules) do value(downloadDeps(module, buildOptions, logger))
128129

129-
val workspace = moduleInputs.head.workspace
130+
val workspace = inputs.workspace
130131

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

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

148149
val ideInputs = IdeInputs(
@@ -190,7 +191,7 @@ object SetupIde extends ScalaCommand[SetupIdeOptions] {
190191
val scalaCliBspInputsJson = writeToArray(ideInputs)
191192
val scalaCliBspEnvsJson = writeToArray(sys.env)
192193

193-
if (moduleInputs.head.workspaceOrigin.contains(WorkspaceOrigin.HomeDir))
194+
if (inputs.workspaceOrigin.contains(WorkspaceOrigin.HomeDir))
194195
value(Left(new WorkspaceError(
195196
s"""$baseRunnerName can not determine where to write its BSP configuration.
196197
|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
@@ -633,7 +634,7 @@ final case class SharedOptions(
633634
def composeInputs(
634635
args: Seq[String],
635636
defaultInputs: () => Option[ModuleInputs] = () => ModuleInputs.default()
636-
)(using ScalaCliInvokeData): Either[BuildException, Seq[ModuleInputs]] = {
637+
)(using ScalaCliInvokeData): Either[BuildException, compose.Inputs] = {
637638
val updatedModuleInputsFromArgs
638639
: (Seq[String], Option[ProjectName]) => Either[BuildException, ModuleInputs] =
639640
(args, projectNameOpt) =>
@@ -647,7 +648,7 @@ final case class SharedOptions(
647648
Os.pwd,
648649
updatedModuleInputsFromArgs,
649650
ScalaCli.allowRestrictedFeatures
650-
).getModuleInputs
651+
).getInputs
651652
}
652653

653654
def inputs(

0 commit comments

Comments
 (0)