Skip to content

Commit

Permalink
Add new types to enforce wrapping scripts before building project
Browse files Browse the repository at this point in the history
  • Loading branch information
MaciejG604 committed May 3, 2023
1 parent 7ecc0ef commit d9caa73
Show file tree
Hide file tree
Showing 8 changed files with 186 additions and 61 deletions.
141 changes: 104 additions & 37 deletions modules/build/src/main/scala/scala/build/CrossSources.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,94 @@ import scala.build.options.{
}
import scala.build.preprocessing.*

final case class CrossSources(
/** CrossSources with unwrapped scripts, use [[withWrappedScripts]] to wrap them and obtain an
* instance of CrossSources
*
* See [[CrossSources]] for more information
*
* @param paths
* paths and realtive paths to sources on disk, wrapped in their build requirements
* @param inMemory
* in memory sources (e.g. snippets) wrapped in their build requirements
* @param defaultMainClass
* @param resourceDirs
* @param buildOptions
* build options from sources
* @param unwrappedScripts
* in memory script sources, their code must be wrapped before compiling
*/
sealed class UnwrappedCrossSources(
paths: Seq[WithBuildRequirements[(os.Path, os.RelPath)]],
inMemory: Seq[WithBuildRequirements[Sources.InMemory]],
defaultMainClass: Option[String],
resourceDirs: Seq[WithBuildRequirements[os.Path]],
buildOptions: Seq[WithBuildRequirements[BuildOptions]]
buildOptions: Seq[WithBuildRequirements[BuildOptions]],
unwrappedScripts: Seq[WithBuildRequirements[Sources.UnwrappedScript]]
) {

/** For all unwrapped script sources contained in this object wrap them according to provided
* BuildOptions
*
* @param buildOptions
* options used to choose the script wrapper
* @return
* CrossSources with all the scripts wrapped
*/
def withWrappedScripts(buildOptions: BuildOptions): CrossSources = {
val codeWrapper = ScriptPreprocessor.getScriptWrapper(buildOptions)

val wrappedScripts = unwrappedScripts.map { unwrapppedWithRequirements =>
unwrapppedWithRequirements.map(_.wrap(codeWrapper))
}

CrossSources(
paths,
inMemory ++ wrappedScripts,
defaultMainClass,
resourceDirs,
this.buildOptions
)
}

def sharedOptions(baseOptions: BuildOptions): BuildOptions =
buildOptions
.filter(_.requirements.isEmpty)
.map(_.value)
.foldLeft(baseOptions)(_ orElse _)

private def needsScalaVersion =
protected def needsScalaVersion =
paths.exists(_.needsScalaVersion) ||
inMemory.exists(_.needsScalaVersion) ||
resourceDirs.exists(_.needsScalaVersion) ||
buildOptions.exists(_.needsScalaVersion)
}

/** Information gathered from preprocessing command inputs - sources and build options from using
* directives
*
* @param paths
* paths and realtive paths to sources on disk, wrapped in their build requirements
* @param inMemory
* in memory sources (e.g. snippets and wrapped scripts) wrapped in their build requirements
* @param defaultMainClass
* @param resourceDirs
* @param buildOptions
* build options from sources
*/
final case class CrossSources(
paths: Seq[WithBuildRequirements[(os.Path, os.RelPath)]],
inMemory: Seq[WithBuildRequirements[Sources.InMemory]],
defaultMainClass: Option[String],
resourceDirs: Seq[WithBuildRequirements[os.Path]],
buildOptions: Seq[WithBuildRequirements[BuildOptions]]
) extends UnwrappedCrossSources(
paths,
inMemory,
defaultMainClass,
resourceDirs,
buildOptions,
Nil
) {
def scopedSources(baseOptions: BuildOptions): Either[BuildException, ScopedSources] = either {

val sharedOptions0 = sharedOptions(baseOptions)
Expand Down Expand Up @@ -103,34 +171,6 @@ final case class CrossSources(
crossSources0.buildOptions.map(_.scopedValue(defaultScope))
)
}

/** For all unwrapped script sources contained in this object wrap them according to provided
* BuildOptions
*
* @param buildOptions
* options used to choose the script wrapper
* @return
* this with all the script code wrapped
*/
def withWrappedScripts(buildOptions: BuildOptions): CrossSources =
copy(
inMemory = inMemory.map {
case WithBuildRequirements(requirements, source) if source.wrapScriptFunOpt.isDefined =>
val wrapScriptFun = source.wrapScriptFunOpt.get
val codeWrapper = ScriptPreprocessor.getScriptWrapper(buildOptions)
val (wrappedCode, topWrapperLen) = wrapScriptFun(codeWrapper)

WithBuildRequirements(
requirements,
source.copy(
generatedContent = wrappedCode,
topWrapperLen = topWrapperLen,
wrapScriptFunOpt = None
)
)
case p => p
}
)
}

object CrossSources {
Expand All @@ -156,7 +196,7 @@ object CrossSources {
logger: Logger,
suppressWarningOptions: SuppressWarningOptions,
maybeRecoverOnError: BuildException => Option[BuildException] = e => Some(e)
): Either[BuildException, (CrossSources, Inputs)] = either {
): Either[BuildException, (UnwrappedCrossSources, Inputs)] = either {

def preprocessSources(elems: Seq[SingleElement])
: Either[BuildException, Seq[PreprocessedSource]] =
Expand Down Expand Up @@ -255,7 +295,17 @@ object CrossSources {
val baseReqs0 = baseReqs(m.scopePath)
WithBuildRequirements(
m.requirements.fold(baseReqs0)(_ orElse baseReqs0),
Sources.InMemory(m.originalPath, m.relPath, m.code, m.ignoreLen, m.wrapScriptFunOpt)
Sources.InMemory(m.originalPath, m.relPath, m.code, m.ignoreLen)
) -> m.directivesPositions
}
val unwrappedScriptsWithDirectivePositions
: Seq[(WithBuildRequirements[Sources.UnwrappedScript], Option[DirectivesPositions])] =
preprocessedSources.collect {
case m: PreprocessedSource.UnwrappedScript =>
val baseReqs0 = baseReqs(m.scopePath)
WithBuildRequirements(
m.requirements.fold(baseReqs0)(_ orElse baseReqs0),
Sources.UnwrappedScript(m.originalPath, m.relPath, m.wrapScriptFun)
) -> m.directivesPositions
}

Expand All @@ -267,14 +317,20 @@ object CrossSources {
)

lazy val allPathsWithDirectivesByScope: Map[Scope, Seq[(os.Path, DirectivesPositions)]] =
(pathsWithDirectivePositions ++ inMemoryWithDirectivePositions)
(pathsWithDirectivePositions ++
inMemoryWithDirectivePositions ++
unwrappedScriptsWithDirectivePositions)
.flatMap { (withBuildRequirements, directivesPositions) =>
val scope = withBuildRequirements.scopedValue(Scope.Main).scope
val path: os.Path = withBuildRequirements.value match
case im: Sources.InMemory =>
im.originalPath match
case Right((_, p: os.Path)) => p
case _ => inputs.workspace / im.generatedRelPath
case us: Sources.UnwrappedScript =>
us.originalPath match
case Right((_, p: os.Path)) => p
case _ => inputs.workspace / us.generatedRelPath
case (p: os.Path, _) => p
directivesPositions.map((path, scope, _))
}
Expand Down Expand Up @@ -302,9 +358,20 @@ object CrossSources {
}
}

val paths = pathsWithDirectivePositions.map(_._1)
val inMemory = inMemoryWithDirectivePositions.map(_._1)
(CrossSources(paths, inMemory, defaultMainClassOpt, resourceDirs, buildOptions), allInputs)
val paths = pathsWithDirectivePositions.map(_._1)
val inMemory = inMemoryWithDirectivePositions.map(_._1)
val unwrappedScripts = unwrappedScriptsWithDirectivePositions.map(_._1)
(
UnwrappedCrossSources(
paths,
inMemory,
defaultMainClassOpt,
resourceDirs,
buildOptions,
unwrappedScripts
),
allInputs
)
}

private def resolveInputsFromSources(sources: Seq[Positioned[os.Path]], enableMarkdown: Boolean) =
Expand Down
14 changes: 12 additions & 2 deletions modules/build/src/main/scala/scala/build/Sources.scala
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,20 @@ object Sources {
originalPath: Either[String, (os.SubPath, os.Path)],
generatedRelPath: os.RelPath,
generatedContent: String,
topWrapperLen: Int,
wrapScriptFunOpt: Option[CodeWrapper => (String, Int)] = None
topWrapperLen: Int
)

final case class UnwrappedScript(
originalPath: Either[String, (os.SubPath, os.Path)],
generatedRelPath: os.RelPath,
wrapScriptFun: CodeWrapper => (String, Int)
) {
def wrap(wrapper: CodeWrapper): InMemory = {
val (content, topWrapperLen) = wrapScriptFun(wrapper)
InMemory(originalPath, generatedRelPath, content, topWrapperLen)
}
}

/** The default preprocessor list.
*
* @param codeWrapper
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,25 @@ object PreprocessedSource {
scopedRequirements: Seq[Scoped[BuildRequirements]],
mainClassOpt: Option[String],
scopePath: ScopePath,
directivesPositions: Option[DirectivesPositions],
wrapScriptFunOpt: Option[CodeWrapper => (String, Int)] = None
directivesPositions: Option[DirectivesPositions]
) extends PreprocessedSource {
def reportingPath: Either[String, os.Path] =
originalPath.map(_._2)
}

final case class UnwrappedScript(
originalPath: Either[String, (os.SubPath, os.Path)],
relPath: os.RelPath,
options: Option[BuildOptions],
optionsWithTargetRequirements: List[WithBuildRequirements[BuildOptions]],
requirements: Option[BuildRequirements],
scopedRequirements: Seq[Scoped[BuildRequirements]],
mainClassOpt: Option[String],
scopePath: ScopePath,
directivesPositions: Option[DirectivesPositions],
wrapScriptFun: CodeWrapper => (String, Int)
) extends PreprocessedSource

final case class NoSourceCode(
options: Option[BuildOptions],
optionsWithTargetRequirements: List[WithBuildRequirements[BuildOptions]],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ case object ScriptPreprocessor extends Preprocessor {
maybeRecoverOnError: BuildException => Option[BuildException],
allowRestrictedFeatures: Boolean,
suppressWarningOptions: SuppressWarningOptions
): Either[BuildException, List[PreprocessedSource.InMemory]] = either {
): Either[BuildException, List[PreprocessedSource.UnwrappedScript]] = either {

val (contentIgnoredSheBangLines, _) = SheBang.ignoreSheBangLines(content)

Expand Down Expand Up @@ -119,19 +119,17 @@ case object ScriptPreprocessor extends Preprocessor {
val className = (pkg :+ wrapper).map(_.raw).mkString(".")
val relPath = os.rel / (subPath / os.up) / s"${subPath.last.stripSuffix(".sc")}.scala"

val file = PreprocessedSource.InMemory(
val file = PreprocessedSource.UnwrappedScript(
originalPath = reportingPath.map((subPath, _)),
relPath = relPath,
code = "", // code is captured in wrapScriptFun's closure
ignoreLen = 0,
options = Some(processingOutput.opts),
optionsWithTargetRequirements = processingOutput.optsWithReqs,
requirements = Some(processingOutput.globalReqs),
scopedRequirements = processingOutput.scopedReqs,
mainClassOpt = Some(CodeWrapper.mainClassObject(Name(className)).backticked),
scopePath = scopePath,
directivesPositions = processingOutput.directivesPositions,
wrapScriptFunOpt = Some(wrapScriptFun)
wrapScriptFun = wrapScriptFun
)
List(file)
}
Expand Down

0 comments on commit d9caa73

Please sign in to comment.