Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor using directives processing #2066

Merged
merged 2 commits into from May 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -12,8 +12,7 @@ import scala.build.options.{
SuppressWarningOptions,
WithBuildRequirements
}
import scala.build.preprocessing.DirectivesProcessor.DirectivesProcessorOutput
import scala.build.preprocessing.PreprocessingUtil.optionsAndPositionsFromDirectives
import scala.build.preprocessing.directives.PreprocessedDirectives

case object DataPreprocessor extends Preprocessor {
def preprocess(
Expand All @@ -27,35 +26,29 @@ case object DataPreprocessor extends Preprocessor {
case file: VirtualData =>
val res = either {
val content = new String(file.content, StandardCharsets.UTF_8)
val (
updatedOptions: BuildOptions,
optsWithReqs: List[WithBuildRequirements[BuildOptions]],
directivesPositions: Option[DirectivesPositions]
) = value {
optionsAndPositionsFromDirectives(
val preprocessedDirectives: PreprocessedDirectives = value {
DirectivesPreprocessor.preprocess(
content,
file.scopePath,
Left(file.subPath.toString),
file.scopePath,
logger,
maybeRecoverOnError,
allowRestrictedFeatures,
suppressWarningOptions
)
}

val inMemory = Seq(
PreprocessedSource.InMemory(
originalPath = Left(file.source),
relPath = file.subPath,
code = content,
ignoreLen = 0,
options = Some(updatedOptions),
optionsWithTargetRequirements = optsWithReqs,
options = Some(preprocessedDirectives.globalUsings),
optionsWithTargetRequirements = preprocessedDirectives.usingsWithReqs,
requirements = Some(BuildRequirements()),
scopedRequirements = Nil,
mainClassOpt = None,
scopePath = file.scopePath,
directivesPositions
preprocessedDirectives.directivesPositions
)
)
inMemory
Expand Down
@@ -0,0 +1,197 @@
package scala.build.preprocessing
import scala.build.EitherCps.{either, value}
import scala.build.Logger
import scala.build.Ops.*
import scala.build.directives.{
HasBuildOptions,
HasBuildOptionsWithRequirements,
HasBuildRequirements
}
import scala.build.errors.{
BuildException,
CompositeBuildException,
DirectiveErrors,
UnusedDirectiveError
}
import scala.build.internal.util.WarningMessages.experimentalDirectiveUsed
import scala.build.options.{
BuildOptions,
BuildRequirements,
ConfigMonoid,
SuppressWarningOptions,
WithBuildRequirements
}
import scala.build.preprocessing.directives.DirectivesPreprocessingUtils.*
import scala.build.preprocessing.directives.PartiallyProcessedDirectives.*
import scala.build.preprocessing.directives.*

object DirectivesPreprocessor {
def preprocess(
content: String,
path: Either[String, os.Path],
cwd: ScopePath,
logger: Logger,
allowRestrictedFeatures: Boolean,
suppressWarningOptions: SuppressWarningOptions,
maybeRecoverOnError: BuildException => Option[BuildException] = e => Some(e)
): Either[BuildException, PreprocessedDirectives] = either {
val directives = value {
ExtractedDirectives.from(content.toCharArray, path, logger, maybeRecoverOnError)
}
value {
preprocess(
directives,
path,
cwd,
logger,
allowRestrictedFeatures,
suppressWarningOptions
)
}
}

def preprocess(
extractedDirectives: ExtractedDirectives,
path: Either[String, os.Path],
cwd: ScopePath,
logger: Logger,
allowRestrictedFeatures: Boolean,
suppressWarningOptions: SuppressWarningOptions
): Either[BuildException, PreprocessedDirectives] = either {
val ExtractedDirectives(directives, directivesPositions) = extractedDirectives
def preprocessWithDirectiveHandlers[T: ConfigMonoid](
remainingDirectives: Seq[StrictDirective],
directiveHandlers: Seq[DirectiveHandler[T]]
): Either[BuildException, PartiallyProcessedDirectives[T]] =
applyDirectiveHandlers(
remainingDirectives,
directiveHandlers,
path,
cwd,
logger,
allowRestrictedFeatures,
suppressWarningOptions
)

val (
buildOptionsWithoutRequirements: PartiallyProcessedDirectives[BuildOptions],
buildOptionsWithTargetRequirements: PartiallyProcessedDirectives[
List[WithBuildRequirements[BuildOptions]]
],
scopedBuildRequirements: PartiallyProcessedDirectives[BuildRequirements],
unusedDirectives: Seq[StrictDirective]
) = value {
for {
regularUsingDirectives: PartiallyProcessedDirectives[BuildOptions] <-
preprocessWithDirectiveHandlers(directives, usingDirectiveHandlers)
usingDirectivesWithRequirements: PartiallyProcessedDirectives[
List[WithBuildRequirements[BuildOptions]]
] <-
preprocessWithDirectiveHandlers(
regularUsingDirectives.unused,
usingDirectiveWithReqsHandlers
)
targetDirectives: PartiallyProcessedDirectives[BuildRequirements] <-
preprocessWithDirectiveHandlers(
usingDirectivesWithRequirements.unused,
requireDirectiveHandlers
)
remainingDirectives = targetDirectives.unused
} yield (
regularUsingDirectives,
usingDirectivesWithRequirements,
targetDirectives,
remainingDirectives
)
}

val (optionsWithActualRequirements, optionsWithEmptyRequirements) =
buildOptionsWithTargetRequirements.global.partition(_.requirements.nonEmpty)
val summedOptionsWithNoRequirements =
optionsWithEmptyRequirements
.map(_.value)
.foldLeft(buildOptionsWithoutRequirements.global)((acc, bo) => acc.orElse(bo))

value {
unusedDirectives.toList match {
case Nil =>
Right {
PreprocessedDirectives(
scopedBuildRequirements.global,
summedOptionsWithNoRequirements,
optionsWithActualRequirements,
scopedBuildRequirements.scoped,
strippedContent = None,
directivesPositions
)
}
case unused =>
Left {
CompositeBuildException(
exceptions = unused.map(ScopedDirective(_, path, cwd).unusedDirectiveError)
)
}
}
}
}

private def applyDirectiveHandlers[T: ConfigMonoid](
directives: Seq[StrictDirective],
handlers: Seq[DirectiveHandler[T]],
path: Either[String, os.Path],
cwd: ScopePath,
logger: Logger,
allowRestrictedFeatures: Boolean,
suppressWarningOptions: SuppressWarningOptions
): Either[BuildException, PartiallyProcessedDirectives[T]] = {
val configMonoidInstance = implicitly[ConfigMonoid[T]]
val shouldSuppressExperimentalFeatures =
suppressWarningOptions.suppressExperimentalFeatureWarning.getOrElse(false)

def handleValues(handler: DirectiveHandler[T])(
scopedDirective: ScopedDirective,
logger: Logger
) =
if !allowRestrictedFeatures && (handler.isRestricted || handler.isExperimental) then
val powerDirectiveType = if handler.isExperimental then "experimental" else "restricted"
val msg = // TODO pass the called progName here to print the full config command
s"""The '${scopedDirective.directive.toString}' directive is $powerDirectiveType.
|Please run it with the '--power' flag or turn or turn power mode on globally by running:
| ${Console.BOLD}config power true${Console.RESET}""".stripMargin
Left(DirectiveErrors(
::(msg, Nil),
DirectiveUtil.positions(scopedDirective.directive.values, path)
))
else
if handler.isExperimental && !shouldSuppressExperimentalFeatures then
logger.message(experimentalDirectiveUsed(scopedDirective.directive.toString))
handler.handleValues(scopedDirective, logger)

val handlersMap = handlers
.flatMap { handler =>
handler.keys.map(k => k -> handleValues(handler))
}
.toMap

val unused = directives.filter(d => !handlersMap.contains(d.key))

val res = directives
.iterator
.flatMap {
case d @ StrictDirective(k, _) =>
handlersMap.get(k).iterator.map(_(ScopedDirective(d, path, cwd), logger))
}
.toVector
.sequence
.left.map(CompositeBuildException(_))
.map(_.foldLeft((configMonoidInstance.zero, Seq.empty[Scoped[T]])) {
case ((globalAcc, scopedAcc), ProcessedDirective(global, scoped)) => (
global.fold(globalAcc)(ns => configMonoidInstance.orElse(ns, globalAcc)),
scopedAcc ++ scoped
)
})
res.map {
case (g, s) => PartiallyProcessedDirectives(g, s, unused)
}
}
}

This file was deleted.

Expand Up @@ -46,7 +46,6 @@ object ExtractedDirectives {
contentChars: Array[Char],
path: Either[String, os.Path],
logger: Logger,
cwd: ScopePath,
maybeRecoverOnError: BuildException => Option[BuildException]
): Either[BuildException, ExtractedDirectives] = {
val errors = new mutable.ListBuffer[Diagnostic]
Expand Down