diff --git a/modules/build/src/main/scala/scala/build/CrossSources.scala b/modules/build/src/main/scala/scala/build/CrossSources.scala index fa6f10b99a..99b460eb08 100644 --- a/modules/build/src/main/scala/scala/build/CrossSources.scala +++ b/modules/build/src/main/scala/scala/build/CrossSources.scala @@ -11,14 +11,14 @@ final case class CrossSources( inMemory: Seq[HasBuildRequirements[(Either[String, os.Path], os.RelPath, String, Int)]], mainClass: Option[String], resourceDirs: Seq[os.Path], - buildOptions: Seq[HasBuildRequirements[(Either[String, os.Path], BuildOptions)]] + buildOptions: Seq[HasBuildRequirements[BuildOptions]] ) { def sources(baseOptions: BuildOptions): Sources = { val sharedOptions = buildOptions .filter(_.requirements.isEmpty) - .map(_.value._2) + .map(_.value) .foldLeft(baseOptions)(_ orElse _) val retainedScalaVersion = sharedOptions.scalaParams.scalaVersion @@ -26,7 +26,7 @@ final case class CrossSources( val buildOptionsWithScalaVersion = buildOptions .flatMap(_.withScalaVersion(retainedScalaVersion).toSeq) .filter(_.requirements.isEmpty) - .map(_.value._2) + .map(_.value) .foldLeft(sharedOptions)(_ orElse _) val platform = @@ -56,7 +56,7 @@ final case class CrossSources( buildOptions .flatMap(_.withScalaVersion(retainedScalaVersion).toSeq) .flatMap(_.withPlatform(platform).toSeq) - .map(_.value._2) + .map(_.value) .foldLeft(BuildOptions() /* not baseOptions */ )(_ orElse _) ) } @@ -81,29 +81,24 @@ object CrossSources { .map(_.flatten) } - val buildOptions = preprocessedSources.flatMap { - case d: PreprocessedSource.OnDisk => - d.options.toSeq.map { opt => - HasBuildRequirements( - d.requirements.getOrElse(BuildRequirements()), - (Right(d.path), opt) - ) - } - case m: PreprocessedSource.InMemory => - m.options.toSeq.map { opt => - HasBuildRequirements( - m.requirements.getOrElse(BuildRequirements()), - (m.reportingPath, opt) - ) - } - case n: PreprocessedSource.NoSourceCode => - val elem = HasBuildRequirements( - n.requirements.getOrElse(BuildRequirements()), - (Right(n.path), n.options.getOrElse(BuildOptions())) - ) - Seq(elem) - case _ => - Nil + val scopedRequirements = preprocessedSources.flatMap(_.scopedRequirements) + val scopedRequirementsByRoot = scopedRequirements.groupBy(_.path.root) + def baseReqs(path: PreprocessedSource.ScopePath): BuildRequirements = + scopedRequirementsByRoot + .getOrElse(path.root, Nil) + .flatMap(_.valueFor(path).toSeq) + .foldLeft(BuildRequirements())(_ orElse _) + + val buildOptions = for { + s <- preprocessedSources + opt <- s.options.toSeq + if opt != BuildOptions() + } yield { + val baseReqs0 = baseReqs(s.scopePath) + HasBuildRequirements( + s.requirements.fold(baseReqs0)(_ orElse baseReqs0), + opt + ) } val mainClassOpt = value { @@ -122,15 +117,17 @@ object CrossSources { val paths = preprocessedSources.collect { case d: PreprocessedSource.OnDisk => + val baseReqs0 = baseReqs(d.scopePath) HasBuildRequirements( - d.requirements.getOrElse(BuildRequirements()), + d.requirements.fold(baseReqs0)(_ orElse baseReqs0), (d.path, d.path.relativeTo(inputs.workspace)) ) } val inMemory = preprocessedSources.collect { case m: PreprocessedSource.InMemory => + val baseReqs0 = baseReqs(m.scopePath) HasBuildRequirements( - m.requirements.getOrElse(BuildRequirements()), + m.requirements.fold(baseReqs0)(_ orElse baseReqs0), (m.reportingPath, m.relPath, m.code, m.ignoreLen) ) } diff --git a/modules/build/src/main/scala/scala/build/Inputs.scala b/modules/build/src/main/scala/scala/build/Inputs.scala index 311d22e551..d5384a2edb 100644 --- a/modules/build/src/main/scala/scala/build/Inputs.scala +++ b/modules/build/src/main/scala/scala/build/Inputs.scala @@ -7,6 +7,7 @@ import java.security.MessageDigest import java.util.zip.{ZipEntry, ZipInputStream} import scala.annotation.tailrec +import scala.build.preprocessing.PreprocessedSource import scala.util.matching.Regex final case class Inputs( @@ -137,6 +138,9 @@ object Inputs { val idx = source.lastIndexOf('/') os.sub / source.drop(idx + 1) } + + def scopePath: PreprocessedSource.ScopePath = + PreprocessedSource.ScopePath(source, subPath) } sealed trait SingleFile extends OnDisk with SingleElement @@ -345,7 +349,7 @@ object Inputs { else forNonEmptyArgs(args, cwd, directories, baseProjectName, download, stdinOpt, acceptFds) - def default(cwd: os.Path = Os.pwd): Option[Inputs] = + def default(): Option[Inputs] = None def empty(workspace: os.Path): Inputs = diff --git a/modules/build/src/main/scala/scala/build/internal/AmmUtil.scala b/modules/build/src/main/scala/scala/build/internal/AmmUtil.scala index 357f0049bb..4a6b8b27a2 100644 --- a/modules/build/src/main/scala/scala/build/internal/AmmUtil.scala +++ b/modules/build/src/main/scala/scala/build/internal/AmmUtil.scala @@ -4,26 +4,10 @@ package scala.build.internal object AmmUtil { val upPathSegment = "^" - def pathToPackageWrapper( - flexiblePkgName0: Seq[Name], - relPath0: os.RelPath - ): (Seq[Name], Name) = { - var flexiblePkgName = flexiblePkgName0 - var relPath = relPath0 / os.up - val fileName = relPath0.last - while ( - flexiblePkgName.length > 1 && - flexiblePkgName.last.encoded != upPathSegment && - relPath.ups > 0 - ) { - flexiblePkgName = flexiblePkgName.dropRight(1) - relPath = os.RelPath(relPath.segments, relPath.ups - 1) - } - val pkg = { - val ups = Seq.fill(relPath.ups)(upPathSegment) - val rest = relPath.segments - flexiblePkgName ++ (ups ++ rest).map(Name(_)) - } + def pathToPackageWrapper(relPath0: os.SubPath): (Seq[Name], Name) = { + val relPath = relPath0 / os.up + val fileName = relPath0.last + val pkg = relPath.segments.map(Name(_)) val wrapper = fileName.lastIndexOf('.') match { case -1 => fileName case i => fileName.take(i) diff --git a/modules/build/src/main/scala/scala/build/options/BuildRequirements.scala b/modules/build/src/main/scala/scala/build/options/BuildRequirements.scala index 9d5feb6047..01ce400547 100644 --- a/modules/build/src/main/scala/scala/build/options/BuildRequirements.scala +++ b/modules/build/src/main/scala/scala/build/options/BuildRequirements.scala @@ -2,7 +2,7 @@ package scala.build.options final case class BuildRequirements( scalaVersion: Seq[BuildRequirements.VersionRequirement] = Nil, - platform: Option[BuildRequirements.PlatformRequirement] = None + platform: Seq[BuildRequirements.PlatformRequirement] = Nil ) { def withScalaVersion(sv: String): Either[String, BuildRequirements] = { val dontPass = scalaVersion.filter(!_.valid(sv)) @@ -12,10 +12,10 @@ final case class BuildRequirements( Left(dontPass.map(_.failedMessage).mkString(", ")) } def withPlatform(pf: Platform): Either[String, BuildRequirements] = - platform match { + BuildRequirements.PlatformRequirement.merge(platform) match { case None => Right(this) case Some(platform0) => - if (platform0.valid(pf)) Right(copy(platform = None)) + if (platform0.valid(pf)) Right(copy(platform = Nil)) else Left(platform0.failedMessage) } def isEmpty: Boolean = @@ -73,6 +73,18 @@ object BuildRequirements { "Expected platform: " + platforms.toVector.map(_.repr).sorted.mkString(" or ") } + object PlatformRequirement { + def merge(requirements: Seq[PlatformRequirement]): Option[PlatformRequirement] = + if (requirements.isEmpty) None + else if (requirements.lengthCompare(1) == 0) Some(requirements.head) + else { + val platforms = requirements.tail.foldLeft(requirements.head.platforms) { (acc, req) => + acc.intersect(req.platforms) + } + Some(PlatformRequirement(platforms)) + } + } + implicit val monoid: ConfigMonoid[BuildRequirements] = ConfigMonoid.derive } diff --git a/modules/build/src/main/scala/scala/build/preprocessing/JavaPreprocessor.scala b/modules/build/src/main/scala/scala/build/preprocessing/JavaPreprocessor.scala index 80289924ab..b1dbc48101 100644 --- a/modules/build/src/main/scala/scala/build/preprocessing/JavaPreprocessor.scala +++ b/modules/build/src/main/scala/scala/build/preprocessing/JavaPreprocessor.scala @@ -10,7 +10,7 @@ case object JavaPreprocessor extends Preprocessor { : Option[Either[BuildException, Seq[PreprocessedSource]]] = input match { case j: Inputs.JavaFile => - Some(Right(Seq(PreprocessedSource.OnDisk(j.path, None, None, None)))) + Some(Right(Seq(PreprocessedSource.OnDisk(j.path, None, None, Nil, None)))) case v: Inputs.VirtualJavaFile => val content = new String(v.content, StandardCharsets.UTF_8) @@ -21,7 +21,9 @@ case object JavaPreprocessor extends Preprocessor { 0, None, None, - None + Nil, + None, + v.scopePath ) Some(Right(Seq(s))) diff --git a/modules/build/src/main/scala/scala/build/preprocessing/PreprocessedSource.scala b/modules/build/src/main/scala/scala/build/preprocessing/PreprocessedSource.scala index e4d025963f..37355c593b 100644 --- a/modules/build/src/main/scala/scala/build/preprocessing/PreprocessedSource.scala +++ b/modules/build/src/main/scala/scala/build/preprocessing/PreprocessedSource.scala @@ -6,6 +6,9 @@ sealed abstract class PreprocessedSource extends Product with Serializable { def options: Option[BuildOptions] def requirements: Option[BuildRequirements] def mainClassOpt: Option[String] + + def scopedRequirements: Seq[PreprocessedSource.Scoped[BuildRequirements]] + def scopePath: PreprocessedSource.ScopePath } object PreprocessedSource { @@ -14,8 +17,12 @@ object PreprocessedSource { path: os.Path, options: Option[BuildOptions], requirements: Option[BuildRequirements], + scopedRequirements: Seq[Scoped[BuildRequirements]], mainClassOpt: Option[String] - ) extends PreprocessedSource + ) extends PreprocessedSource { + def scopePath: ScopePath = + ScopePath.fromPath(path) + } final case class InMemory( reportingPath: Either[String, os.Path], relPath: os.RelPath, @@ -23,14 +30,19 @@ object PreprocessedSource { ignoreLen: Int, options: Option[BuildOptions], requirements: Option[BuildRequirements], - mainClassOpt: Option[String] + scopedRequirements: Seq[Scoped[BuildRequirements]], + mainClassOpt: Option[String], + scopePath: ScopePath ) extends PreprocessedSource final case class NoSourceCode( options: Option[BuildOptions], requirements: Option[BuildRequirements], + scopedRequirements: Seq[Scoped[BuildRequirements]], path: os.Path ) extends PreprocessedSource { def mainClassOpt: None.type = None + def scopePath: ScopePath = + ScopePath.fromPath(path) } private def index(s: PreprocessedSource): Int = @@ -64,4 +76,29 @@ object PreprocessedSource { } } + final case class ScopePath( + root: String, + path: os.SubPath + ) { + def /(subPath: os.PathChunk): ScopePath = + copy(path = path / subPath) + } + + object ScopePath { + def fromPath(path: os.Path): ScopePath = { + def root(p: os.Path): os.Path = + if (p.segmentCount > 0) root(p / os.up) else p + val root0 = root(path) + ScopePath(root0.toString, path.subRelativeTo(root0)) + } + } + + final case class Scoped[T](path: ScopePath, value: T) { + def appliesTo(candidate: ScopePath): Boolean = + path.root == candidate.root && + candidate.path.startsWith(path.path) + def valueFor(candidate: ScopePath): Option[T] = + if (appliesTo(candidate)) Some(value) else None + } + } diff --git a/modules/build/src/main/scala/scala/build/preprocessing/ScalaPreprocessor.scala b/modules/build/src/main/scala/scala/build/preprocessing/ScalaPreprocessor.scala index b25396ffbf..2369cf7e4f 100644 --- a/modules/build/src/main/scala/scala/build/preprocessing/ScalaPreprocessor.scala +++ b/modules/build/src/main/scala/scala/build/preprocessing/ScalaPreprocessor.scala @@ -43,14 +43,19 @@ case object ScalaPreprocessor extends Preprocessor { input match { case f: Inputs.ScalaFile => val inferredClsName = { - val (pkg, wrapper) = AmmUtil.pathToPackageWrapper(Nil, f.subPath) + val (pkg, wrapper) = AmmUtil.pathToPackageWrapper(f.subPath) (pkg :+ wrapper).map(_.raw).mkString(".") } val res = either { - val source = value(process(f.path)) match { + val printablePath = + if (f.path.startsWith(Os.pwd)) f.path.relativeTo(Os.pwd).toString + else f.path.toString + val content = os.read(f.path) + val scopePath = PreprocessedSource.ScopePath.fromPath(f.path) + val source = value(process(content, printablePath, scopePath / os.up)) match { case None => - PreprocessedSource.OnDisk(f.path, None, None, Some(inferredClsName)) - case Some((requirements, options, updatedCode)) => + PreprocessedSource.OnDisk(f.path, None, None, Nil, Some(inferredClsName)) + case Some((requirements, scopedRequirements, options, Some(updatedCode))) => PreprocessedSource.InMemory( Right(f.path), f.subPath, @@ -58,6 +63,16 @@ case object ScalaPreprocessor extends Preprocessor { 0, Some(options), Some(requirements), + scopedRequirements, + Some(inferredClsName), + scopePath + ) + case Some((requirements, scopedRequirements, options, None)) => + PreprocessedSource.OnDisk( + f.path, + Some(options), + Some(requirements), + scopedRequirements, Some(inferredClsName) ) } @@ -68,16 +83,19 @@ case object ScalaPreprocessor extends Preprocessor { case v: Inputs.VirtualScalaFile => val res = either { val content = new String(v.content, StandardCharsets.UTF_8) - val (requirements, options, updatedContent) = value(process(content, v.source)) - .getOrElse((BuildRequirements(), BuildOptions(), content)) + val (requirements, scopedRequirements, options, updatedContentOpt) = + value(process(content, v.source, v.scopePath / os.up)) + .getOrElse((BuildRequirements(), Nil, BuildOptions(), None)) val s = PreprocessedSource.InMemory( Left(v.source), v.subPath, - updatedContent, + updatedContentOpt.getOrElse(content), 0, Some(options), Some(requirements), - None + scopedRequirements, + None, + v.scopePath ) Seq(s) } @@ -87,37 +105,35 @@ case object ScalaPreprocessor extends Preprocessor { None } - def process(path: os.Path) - : Either[BuildException, Option[(BuildRequirements, BuildOptions, String)]] = { - val printablePath = - if (path.startsWith(Os.pwd)) path.relativeTo(Os.pwd).toString - else path.toString - val content = os.read(path) - process(content, printablePath) - } def process( content: String, - printablePath: String - ): Either[BuildException, Option[(BuildRequirements, BuildOptions, String)]] = either { + printablePath: String, + scopeRoot: PreprocessedSource.ScopePath + ): Either[BuildException, Option[( + BuildRequirements, + Seq[PreprocessedSource.Scoped[BuildRequirements]], + BuildOptions, + Option[String] + )]] = either { val afterUsing = value { - processUsing(content) + processUsing(content, scopeRoot) .sequence } val afterProcessImports = - processSpecialImports(afterUsing.map(_._3).getOrElse(content), printablePath) + processSpecialImports(afterUsing.flatMap(_._4).getOrElse(content), printablePath) if (afterUsing.isEmpty && afterProcessImports.isEmpty) None else { val allRequirements = afterUsing.map(_._1).toSeq ++ afterProcessImports.map(_._1).toSeq val summedRequirements = allRequirements.foldLeft(BuildRequirements())(_ orElse _) - val allOptions = afterUsing.map(_._2).toSeq ++ afterProcessImports.map(_._2).toSeq + val allOptions = afterUsing.map(_._3).toSeq ++ afterProcessImports.map(_._2).toSeq val summedOptions = allOptions.foldLeft(BuildOptions())(_ orElse _) - val lastContent = afterProcessImports + val lastContentOpt = afterProcessImports .map(_._3) - .orElse(afterUsing.map(_._3)) - .getOrElse(content) - Some((summedRequirements, summedOptions, lastContent)) + .orElse(afterUsing.flatMap(_._4)) + val scopedRequirements = afterUsing.map(_._2).getOrElse(Nil) + Some((summedRequirements, scopedRequirements, summedOptions, lastContentOpt)) } } @@ -150,8 +166,13 @@ case object ScalaPreprocessor extends Preprocessor { } } - private def directivesBuildRequirements(directives: Seq[Directive]) - : Either[BuildException, BuildRequirements] = { + private def directivesBuildRequirements( + directives: Seq[Directive], + scopeRoot: PreprocessedSource.ScopePath + ): Either[ + BuildException, + (BuildRequirements, Seq[PreprocessedSource.Scoped[BuildRequirements]]) + ] = { val results = directives .filter(_.tpe == Directive.Require) .map { dir => @@ -165,7 +186,13 @@ case object ScalaPreprocessor extends Preprocessor { case None => Left(new UnusedDirectiveError(dir)) case Some(Right(reqs)) => - Right(reqs) + val value = dir.scope match { + case None => (reqs, Nil) + case Some(sc) => + val scopePath = scopeRoot / os.SubPath(sc.split("/").toVector) + (BuildRequirements(), Seq(PreprocessedSource.Scoped(scopePath, reqs))) + } + Right(value) case Some(Left(err)) => Left(new InvalidDirectiveError(dir, err)) } @@ -174,30 +201,41 @@ case object ScalaPreprocessor extends Preprocessor { results .sequence .left.map(CompositeBuildException(_)) - .map { allReqs => - allReqs.foldLeft(BuildRequirements())(_ orElse _) + .map { allValues => + val allReqs = allValues.map(_._1) + val allScopedReqs = allValues.flatMap(_._2) + (allReqs.foldLeft(BuildRequirements())(_ orElse _), allScopedReqs) } } private def processUsing( - content: String - ): Option[Either[BuildException, (BuildRequirements, BuildOptions, String)]] = - TemporaryDirectivesParser.parseDirectives(content).flatMap { - case (_, _) => - // TODO Warn about unrecognized directives - // TODO Report via some diagnostics malformed directives - - TemporaryDirectivesParser.parseDirectives(content).map { - case (directives, updatedContent) => - val tuple = ( - directivesBuildRequirements(directives), - directivesBuildOptions(directives), - Right(updatedContent) - ) - tuple - .traverseN - .left.map(CompositeBuildException(_)) - } + content: String, + scopeRoot: PreprocessedSource.ScopePath + ): Option[Either[ + BuildException, + ( + BuildRequirements, + Seq[PreprocessedSource.Scoped[BuildRequirements]], + BuildOptions, + Option[String] + ) + ]] = + // TODO Warn about unrecognized directives + // TODO Report via some diagnostics malformed directives + TemporaryDirectivesParser.parseDirectives(content).map { + case (directives, updatedContentOpt) => + val tuple = ( + directivesBuildRequirements(directives, scopeRoot), + directivesBuildOptions(directives), + Right(updatedContentOpt) + ) + tuple + .traverseN + .left.map(CompositeBuildException(_)) + .map { + case ((reqs, scopedReqs), options, contentOpt) => + (reqs, scopedReqs, options, contentOpt) + } } private def processSpecialImports( diff --git a/modules/build/src/main/scala/scala/build/preprocessing/ScriptPreprocessor.scala b/modules/build/src/main/scala/scala/build/preprocessing/ScriptPreprocessor.scala index e4e50f8f1d..3d9c84ce00 100644 --- a/modules/build/src/main/scala/scala/build/preprocessing/ScriptPreprocessor.scala +++ b/modules/build/src/main/scala/scala/build/preprocessing/ScriptPreprocessor.scala @@ -26,7 +26,8 @@ final case class ScriptPreprocessor(codeWrapper: CodeWrapper) extends Preprocess content, printablePath, codeWrapper, - script.subPath + script.subPath, + PreprocessedSource.ScopePath.fromPath(script.path) ) } Seq(preprocessed) @@ -43,7 +44,8 @@ final case class ScriptPreprocessor(codeWrapper: CodeWrapper) extends Preprocess content, script.source, codeWrapper, - script.wrapperPath + script.wrapperPath, + script.scopePath ) } Seq(preprocessed) @@ -81,27 +83,27 @@ object ScriptPreprocessor { content: String, printablePath: String, codeWrapper: CodeWrapper, - subPath: os.SubPath + subPath: os.SubPath, + scopePath: PreprocessedSource.ScopePath ): Either[BuildException, PreprocessedSource.InMemory] = either { val contentIgnoredSheBangLines = ignoreSheBangLines(content) - val (pkg, wrapper) = AmmUtil.pathToPackageWrapper(Nil, subPath) + val (pkg, wrapper) = AmmUtil.pathToPackageWrapper(subPath) - val (requirements, options, updatedCode) = - value(ScalaPreprocessor.process(contentIgnoredSheBangLines, printablePath)) - .getOrElse((BuildRequirements(), BuildOptions(), contentIgnoredSheBangLines)) + val (requirements, scopedRequirements, options, updatedCodeOpt) = + value(ScalaPreprocessor.process(contentIgnoredSheBangLines, printablePath, scopePath / os.up)) + .getOrElse((BuildRequirements(), Nil, BuildOptions(), None)) val (code, topWrapperLen, _) = codeWrapper.wrapCode( pkg, wrapper, - updatedCode + updatedCodeOpt.getOrElse(contentIgnoredSheBangLines) ) val className = (pkg :+ wrapper).map(_.raw).mkString(".") - val components = className.split('.') - val relPath = os.rel / components.init.toSeq / s"${components.last}.scala" + val relPath = os.rel / (subPath / os.up) / s"${subPath.last.stripSuffix(".sc")}.scala" PreprocessedSource.InMemory( reportingPath, relPath, @@ -109,7 +111,9 @@ object ScriptPreprocessor { topWrapperLen, Some(options), Some(requirements), - Some(className) + scopedRequirements, + Some(className), + scopePath ) } diff --git a/modules/build/src/main/scala/scala/build/preprocessing/TemporaryDirectivesParser.scala b/modules/build/src/main/scala/scala/build/preprocessing/TemporaryDirectivesParser.scala index 5285579e33..c8566be955 100644 --- a/modules/build/src/main/scala/scala/build/preprocessing/TemporaryDirectivesParser.scala +++ b/modules/build/src/main/scala/scala/build/preprocessing/TemporaryDirectivesParser.scala @@ -12,10 +12,16 @@ object TemporaryDirectivesParser { def sc = P(";") def nl = P(("\r".? ~ "\n").rep(1)) def tpe = { - def usingTpe = P(ws.? ~ ("using" | "@using" | "// using")) - .map(_ => (Directive.Using: Directive.Type)) - def requireTpe = P(ws.? ~ ("require" | "@require" | "// require")) - .map(_ => (Directive.Require: Directive.Type)) + def commentedUsingTpe = P("// using") + .map(_ => (Directive.Using: Directive.Type, true)) + def usingKeywordTpe = P("using" | "@using") + .map(_ => (Directive.Using: Directive.Type, false)) + def usingTpe = P(ws.? ~ (commentedUsingTpe | usingKeywordTpe)) + def commentedRequireTpe = P("// require") + .map(_ => (Directive.Require: Directive.Type, true)) + def requireKeywordTpe = P("require" | "@require") + .map(_ => (Directive.Require: Directive.Type, false)) + def requireTpe = P(ws.? ~ (commentedRequireTpe | requireKeywordTpe)) P(usingTpe | requireTpe) } @@ -32,18 +38,21 @@ object TemporaryDirectivesParser { P("\\\"").map(_ => "\"") ) def quotedElem = P("\"" ~ charInQuote.rep ~ "\"").map(_.mkString) - def elem = P(simpleElem | quotedElem) + def elem = P(!("in" ~ (ws | "," | nl)) ~ (simpleElem | quotedElem)) def parser = P( tpe ~ ws ~ - elem.rep(1, sep = P(ws)).rep(1, sep = P(ws.? ~ "," ~ ws.?)) ~ + (elem.rep(1, sep = P(ws)) ~ (ws ~ "in" ~ ws ~ elem).?).rep(1, sep = P(ws.? ~ "," ~ ws.?)) ~ nl.? ~ sc.? ~ nl.? ) parser.map { - case (tpe0, allElems) => - allElems.map(elems => Directive(tpe0, elems)) + case (tpe0, isComment, allElems) => + allElems.map { + case (elems, scopeOpt) => + Directive(tpe0, elems, scopeOpt, isComment) + } } } @@ -58,7 +67,7 @@ object TemporaryDirectivesParser { res.fold((err, _, _) => sys.error(err), (dirOpt, idx) => dirOpt.map((_, idx + fromIndex))) } - def parseDirectives(content: String): Option[(List[Directive], String)] = { + def parseDirectives(content: String): Option[(List[Directive], Option[String])] = { def helper(fromIndex: Int, acc: List[Directive]): (List[Directive], Int) = parseDirective(content, fromIndex) match { @@ -72,12 +81,17 @@ object TemporaryDirectivesParser { assert(directives.isEmpty) None } - else - Some(( - directives, - content.take(codeStartsAt).map(c => if (c.isControl) c else ' ') ++ - content.drop(codeStartsAt) - )) + else { + val onlyCommentedDirectives = directives.forall(_.isComment) + val updatedContentOpt = + if (onlyCommentedDirectives) None + else + Some { + content.take(codeStartsAt).map(c => if (c.isControl) c else ' ') ++ + content.drop(codeStartsAt) + } + Some((directives, updatedContentOpt)) + } } } diff --git a/modules/build/src/main/scala/scala/build/preprocessing/directives/Directive.scala b/modules/build/src/main/scala/scala/build/preprocessing/directives/Directive.scala index 44ee406c37..f24caf657c 100644 --- a/modules/build/src/main/scala/scala/build/preprocessing/directives/Directive.scala +++ b/modules/build/src/main/scala/scala/build/preprocessing/directives/Directive.scala @@ -2,7 +2,9 @@ package scala.build.preprocessing.directives final case class Directive( tpe: Directive.Type, - values: Seq[String] + values: Seq[String], + scope: Option[String], + isComment: Boolean ) object Directive { diff --git a/modules/build/src/main/scala/scala/build/preprocessing/directives/RequirePlatformsDirectiveHandler.scala b/modules/build/src/main/scala/scala/build/preprocessing/directives/RequirePlatformsDirectiveHandler.scala index 39dd395386..0ef5aa4d9a 100644 --- a/modules/build/src/main/scala/scala/build/preprocessing/directives/RequirePlatformsDirectiveHandler.scala +++ b/modules/build/src/main/scala/scala/build/preprocessing/directives/RequirePlatformsDirectiveHandler.scala @@ -17,7 +17,7 @@ case object RequirePlatformsDirectiveHandler extends RequireDirectiveHandler { Platform.parseSpec(directive.values.map(Platform.normalize)) match { case Some(platforms) => val reqs = BuildRequirements( - platform = Some(BuildRequirements.PlatformRequirement(platforms)) + platform = Seq(BuildRequirements.PlatformRequirement(platforms)) ) Some(Right(reqs)) case None => diff --git a/modules/build/src/test/scala/scala/build/tests/BuildTests.scala b/modules/build/src/test/scala/scala/build/tests/BuildTests.scala index e07e9a22bc..fed898a404 100644 --- a/modules/build/src/test/scala/scala/build/tests/BuildTests.scala +++ b/modules/build/src/test/scala/scala/build/tests/BuildTests.scala @@ -406,4 +406,66 @@ class BuildTests extends munit.FunSuite { ) } } + + test("ignore files if wrong Scala version requirement via in clause") { + val testInputs = TestInputs( + os.rel / "Simple.scala" -> + """// require scala == 2.12 in my-scala-2.12/ + |object Simple { + | def main(args: Array[String]): Unit = + | println("Hello") + |} + |""".stripMargin, + os.rel / "my-scala-2.12" / "Ignored.scala" -> + """object Ignored { + | def foo = 2 + |} + |""".stripMargin + ) + testInputs.withBuild(defaultOptions, buildThreads, bloopConfig) { (root, inputs, maybeBuild) => + maybeBuild.orThrow.assertGeneratedEquals( + "Simple.class", + "Simple$.class" + ) + } + } + test("ignore files if wrong Scala target requirement via in clause") { + val testInputs = TestInputs( + os.rel / "Simple.scala" -> + """require scala.js in js-sources/ + |object Simple { + | def main(args: Array[String]): Unit = + | println("Hello") + |} + |""".stripMargin, + os.rel / "js-sources" / "Ignored.scala" -> + """object Ignored { + | def foo = 2 + |} + |""".stripMargin + ) + testInputs.withBuild(defaultOptions, buildThreads, bloopConfig) { (root, inputs, maybeBuild) => + maybeBuild.orThrow.assertGeneratedEquals( + "Simple.class", + "Simple$.class" + ) + } + } + + test("Pass files with only commented directives as is to scalac") { + val testInputs = TestInputs( + os.rel / "Simple.scala" -> + """// using com.lihaoyi::pprint:0.6.6 + |object Simple { + | def main(args: Array[String]): Unit = + | pprint.log("Hello " + "from tests") + |} + |""".stripMargin + ) + testInputs.withBuild(defaultOptions, buildThreads, bloopConfig) { (root, inputs, maybeBuild) => + val sources = maybeBuild.toOption.get.sources + expect(sources.inMemory.isEmpty) + expect(sources.paths.lengthCompare(1) == 0) + } + } } diff --git a/modules/cli/src/main/scala/scala/cli/commands/Clean.scala b/modules/cli/src/main/scala/scala/cli/commands/Clean.scala index efda82a552..e73cd86d67 100644 --- a/modules/cli/src/main/scala/scala/cli/commands/Clean.scala +++ b/modules/cli/src/main/scala/scala/cli/commands/Clean.scala @@ -12,7 +12,7 @@ object Clean extends ScalaCommand[CleanOptions] { args.all, Os.pwd, options.directories.directories, - defaultInputs = () => Inputs.default(Os.pwd) + defaultInputs = () => Inputs.default() ) match { case Left(message) => System.err.println(message) diff --git a/project/deps.sc b/project/deps.sc index 5d2742c40c..44bbdbdfb0 100644 --- a/project/deps.sc +++ b/project/deps.sc @@ -1,16 +1,22 @@ import mill._, scalalib._ object Scala { - def scala212 = "2.12.14" + def scala212 = "2.12.15" def scala213 = "2.13.6" def scala3 = "3.0.2" val allScala2 = Seq(scala213, scala212) val all = allScala2 ++ Seq(scala3) - def listAll: Seq[String] = - (6 to 13).map(i => s"2.12.$i") ++ Seq(scala212) ++ - (0 to 5).map(i => s"2.13.$i") ++ Seq(scala213) ++ - (0 to 1).map(i => s"3.0.$i") ++ Seq(scala3) + def listAll: Seq[String] = { + def patchVer(sv: String): Int = + sv.split('.').drop(2).head.takeWhile(_.isDigit).toInt + val max212 = patchVer(scala212) + val max213 = patchVer(scala213) + val max3 = patchVer(scala3) + (6 until max212).map(i => s"2.12.$i") ++ Seq(scala212) ++ + (0 until max213).map(i => s"2.13.$i") ++ Seq(scala213) ++ + (0 until max3).map(i => s"3.0.$i") ++ Seq(scala3) + } // The Scala version used to build the CLI itself. // We should be able to switch to 2.13.x when bumping the scala-native version. @@ -32,10 +38,11 @@ object Deps { object Versions { def coursier = "2.0.16+73-gddc6d9cc9" def scalaJs = "1.5.1" + def scalaMeta = "4.4.28" def scalaNative = "0.4.0" def scalaPackager = "0.1.22" } - def ammonite = ivy"com.lihaoyi:::ammonite:2.4.0-20-f3d8171f" + def ammonite = ivy"com.lihaoyi:::ammonite:2.4.0-23-76673f7f" def asm = ivy"org.ow2.asm:asm:9.1" def bloopConfig = ivy"ch.epfl.scala::bloop-config:1.4.8-124-49a6348a" def bsp4j = ivy"ch.epfl.scala:bsp4j:2.0.0-M13" @@ -53,6 +60,7 @@ object Deps { def munit = ivy"org.scalameta::munit:0.7.25" def nativeTestRunner = ivy"org.scala-native::test-runner:${Versions.scalaNative}" def nativeTools = ivy"org.scala-native::tools:${Versions.scalaNative}" + def organizeImports = ivy"com.github.liancheng::organize-imports:0.5.0" def osLib = ivy"com.lihaoyi::os-lib:0.7.5" def pprint = ivy"com.lihaoyi::pprint:0.6.6" def prettyStacktraces = ivy"org.virtuslab::pretty-stacktraces:0.0.0+27-b9d69198-SNAPSHOT" @@ -64,11 +72,12 @@ object Deps { def scalaJsLinker = ivy"org.scala-js::scalajs-linker:${Versions.scalaJs}" def scalaJsLinkerInterface = ivy"org.scala-js::scalajs-linker-interface:${Versions.scalaJs}" def scalaJsTestAdapter = ivy"org.scala-js::scalajs-sbt-test-adapter:${Versions.scalaJs}" - def scalametaTrees = ivy"org.scalameta::trees:4.4.21" + def scalametaTrees = ivy"org.scalameta::trees:${Versions.scalaMeta}" def scalaPackager = ivy"org.virtuslab::scala-packager:${Versions.scalaPackager}" def scalaPackagerCli = ivy"org.virtuslab::scala-packager-cli:${Versions.scalaPackager}" def scalaparse = ivy"com.lihaoyi::scalaparse:2.3.2" def scalaReflect(sv: String) = ivy"org.scala-lang:scala-reflect:$sv" + def semanticDbScalac = ivy"org.scalameta:::semanticdb-scalac:${Versions.scalaMeta}" def shapeless = ivy"com.chuusai::shapeless:2.3.7" def slf4jNop = ivy"org.slf4j:slf4j-nop:1.8.0-beta4" def snailgun = ivy"me.vican.jorge::snailgun-core:0.4.0" diff --git a/project/settings.sc b/project/settings.sc index 2f56bc7760..4454e8b26e 100644 --- a/project/settings.sc +++ b/project/settings.sc @@ -595,6 +595,10 @@ trait ScalaCliScalafixModule extends ScalafixModule { else Some(os.pwd / ".scalafix3.conf") } def scalafixIvyDeps = super.scalafixIvyDeps() ++ Seq( - ivy"com.github.liancheng::organize-imports:0.5.0" + Deps.organizeImports ) + def scalacPluginIvyDeps = super.scalacPluginIvyDeps() ++ { + if (scalaVersion().startsWith("2.")) Seq(Deps.semanticDbScalac) + else Nil + } }