Skip to content

Commit 2cf64ad

Browse files
authored
Merge pull request #1259 from romanowski/sip-directives
Restrict directives based on command used
2 parents 42718f4 + 83de641 commit 2cf64ad

36 files changed

+186
-45
lines changed

modules/build/src/main/scala/scala/build/CrossSources.scala

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,14 @@ object CrossSources {
132132
.map { elem =>
133133
preprocessors
134134
.iterator
135-
.flatMap(p => p.preprocess(elem, logger, maybeRecoverOnError).iterator)
135+
.flatMap(p =>
136+
p.preprocess(
137+
elem,
138+
logger,
139+
maybeRecoverOnError,
140+
inputs.withRestrictedFeatures
141+
).iterator
142+
)
136143
.take(1)
137144
.toList
138145
.headOption

modules/build/src/main/scala/scala/build/Inputs.scala

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ final case class Inputs(
2121
workspace: os.Path,
2222
baseProjectName: String,
2323
mayAppendHash: Boolean,
24-
workspaceOrigin: Option[WorkspaceOrigin]
24+
workspaceOrigin: Option[WorkspaceOrigin],
25+
withRestrictedFeatures: Boolean
2526
) {
2627

2728
def isEmpty: Boolean =
@@ -266,7 +267,8 @@ object Inputs {
266267
validElems: Seq[Element],
267268
baseProjectName: String,
268269
directories: Directories,
269-
forcedWorkspace: Option[os.Path]
270+
forcedWorkspace: Option[os.Path],
271+
withRestrictedFeatures: Boolean
270272
): Inputs = {
271273

272274
assert(validElems.nonEmpty)
@@ -310,7 +312,8 @@ object Inputs {
310312
workspace,
311313
baseProjectName,
312314
mayAppendHash = needsHash,
313-
workspaceOrigin = Some(workspaceOrigin0)
315+
workspaceOrigin = Some(workspaceOrigin0),
316+
withRestrictedFeatures
314317
)
315318
}
316319

@@ -432,7 +435,8 @@ object Inputs {
432435
scalaSnippetList: List[String],
433436
javaSnippetList: List[String],
434437
acceptFds: Boolean,
435-
forcedWorkspace: Option[os.Path]
438+
forcedWorkspace: Option[os.Path],
439+
withRestrictedFeatures: Boolean
436440
): Either[BuildException, Inputs] = {
437441
val validatedArgs: Seq[Either[String, Seq[Element]]] =
438442
validateArgs(args, cwd, download, stdinOpt, acceptFds)
@@ -448,7 +452,13 @@ object Inputs {
448452
}.flatten
449453
assert(validElems.nonEmpty)
450454

451-
Right(forValidatedElems(validElems, baseProjectName, directories, forcedWorkspace))
455+
Right(forValidatedElems(
456+
validElems,
457+
baseProjectName,
458+
directories,
459+
forcedWorkspace,
460+
withRestrictedFeatures
461+
))
452462
}
453463
else
454464
Left(new InputsException(invalid.mkString(System.lineSeparator())))
@@ -466,7 +476,8 @@ object Inputs {
466476
scalaSnippetList: List[String] = List.empty,
467477
javaSnippetList: List[String] = List.empty,
468478
acceptFds: Boolean = false,
469-
forcedWorkspace: Option[os.Path] = None
479+
forcedWorkspace: Option[os.Path] = None,
480+
withRestrictedFeatures: Boolean
470481
): Either[BuildException, Inputs] =
471482
if (
472483
args.isEmpty && scriptSnippetList.isEmpty && scalaSnippetList.isEmpty && javaSnippetList.isEmpty
@@ -486,7 +497,8 @@ object Inputs {
486497
scalaSnippetList,
487498
javaSnippetList,
488499
acceptFds,
489-
forcedWorkspace
500+
forcedWorkspace,
501+
withRestrictedFeatures
490502
)
491503

492504
def default(): Option[Inputs] =
@@ -499,6 +511,9 @@ object Inputs {
499511
workspace = workspace,
500512
baseProjectName = "project",
501513
mayAppendHash = true,
502-
workspaceOrigin = None
514+
workspaceOrigin = None,
515+
withRestrictedFeatures = false
503516
)
517+
518+
def empty(projectName: String) = Inputs(Nil, None, os.pwd, projectName, false, None, false)
504519
}

modules/build/src/main/scala/scala/build/preprocessing/DataPreprocessor.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ case object DataPreprocessor extends Preprocessor {
99
def preprocess(
1010
input: Inputs.SingleElement,
1111
logger: Logger,
12-
maybeRecoverOnError: BuildException => Option[BuildException] = e => Some(e)
12+
maybeRecoverOnError: BuildException => Option[BuildException] = e => Some(e),
13+
withRestrictedFeatures: Boolean
1314
): Option[Either[BuildException, Seq[PreprocessedSource]]] =
1415
input match {
1516
case file: Inputs.VirtualData =>

modules/build/src/main/scala/scala/build/preprocessing/DirectivesProcessor.scala

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import scala.build.preprocessing.directives.{
99
ScopedDirective,
1010
StrictDirective
1111
}
12+
import scala.build.errors.DirectiveErrors
13+
import scala.build.preprocessing.directives.DirectiveUtil
1214

1315
object DirectivesProcessor {
1416

@@ -23,18 +25,27 @@ object DirectivesProcessor {
2325
handlers: Seq[DirectiveHandler[T]],
2426
path: Either[String, os.Path],
2527
cwd: ScopePath,
26-
logger: Logger
28+
logger: Logger,
29+
withRestrictedFeatures: Boolean
2730
): Either[BuildException, DirectivesProcessorOutput[T]] = {
2831
val configMonoidInstance = implicitly[ConfigMonoid[T]]
2932

30-
// val values = directives.map {
31-
// case (k, v) =>
32-
// k.getPath.asScala.mkString(".") -> v
33-
// }
33+
def handleValues(handler: DirectiveHandler[T])(
34+
scopedDirective: ScopedDirective,
35+
logger: Logger
36+
) =
37+
if (withRestrictedFeatures && handler.isRestricted)
38+
val msg =
39+
"This directive is not supported with 'scala' command. Please run it with `scala-cli` command or with `--power` flag."
40+
Left(DirectiveErrors(
41+
::(msg, Nil),
42+
DirectiveUtil.positions(scopedDirective.directive.values, path)
43+
))
44+
else handler.handleValues(scopedDirective, logger)
3445

3546
val handlersMap = handlers
3647
.flatMap { handler =>
37-
handler.keys.map(k => k -> handler.handleValues _)
48+
handler.keys.map(k => k -> handleValues(handler))
3849
}
3950
.toMap
4051

modules/build/src/main/scala/scala/build/preprocessing/JavaPreprocessor.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ final case class JavaPreprocessor(
3434
def preprocess(
3535
input: Inputs.SingleElement,
3636
logger: Logger,
37-
maybeRecoverOnError: BuildException => Option[BuildException] = e => Some(e)
37+
maybeRecoverOnError: BuildException => Option[BuildException] = e => Some(e),
38+
withRestrictedFeatures: Boolean
3839
): Option[Either[BuildException, Seq[PreprocessedSource]]] =
3940
input match {
4041
case j: Inputs.JavaFile => Some(either {
@@ -54,7 +55,8 @@ final case class JavaPreprocessor(
5455
usingDirectiveHandlers,
5556
Right(j.path),
5657
scopePath,
57-
logger
58+
logger,
59+
withRestrictedFeatures
5860
))
5961
Seq(PreprocessedSource.OnDisk(
6062
j.path,

modules/build/src/main/scala/scala/build/preprocessing/Preprocessor.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ trait Preprocessor {
77
def preprocess(
88
input: Inputs.SingleElement,
99
logger: Logger,
10-
maybeRecoverOnError: BuildException => Option[BuildException] = e => Some(e)
10+
maybeRecoverOnError: BuildException => Option[BuildException] = e => Some(e),
11+
withRestrictedFeatures: Boolean
1112
): Option[Either[BuildException, Seq[PreprocessedSource]]]
1213
}

modules/build/src/main/scala/scala/build/preprocessing/ScalaPreprocessor.scala

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ case object ScalaPreprocessor extends Preprocessor {
7171
def preprocess(
7272
input: Inputs.SingleElement,
7373
logger: Logger,
74-
maybeRecoverOnError: BuildException => Option[BuildException] = e => Some(e)
74+
maybeRecoverOnError: BuildException => Option[BuildException] = e => Some(e),
75+
withRestrictedFeatures: Boolean
7576
): Option[Either[BuildException, Seq[PreprocessedSource]]] =
7677
input match {
7778
case f: Inputs.ScalaFile =>
@@ -80,7 +81,14 @@ case object ScalaPreprocessor extends Preprocessor {
8081
val scopePath = ScopePath.fromPath(f.path)
8182
val source =
8283
value(
83-
process(content, Right(f.path), scopePath / os.up, logger, maybeRecoverOnError)
84+
process(
85+
content,
86+
Right(f.path),
87+
scopePath / os.up,
88+
logger,
89+
maybeRecoverOnError,
90+
withRestrictedFeatures
91+
)
8492
) match {
8593
case None =>
8694
PreprocessedSource.OnDisk(f.path, None, None, Nil, None)
@@ -123,7 +131,14 @@ case object ScalaPreprocessor extends Preprocessor {
123131
val content = new String(v.content, StandardCharsets.UTF_8)
124132
val (requirements, scopedRequirements, options, updatedContentOpt) =
125133
value(
126-
process(content, Left(v.source), v.scopePath / os.up, logger, maybeRecoverOnError)
134+
process(
135+
content,
136+
Left(v.source),
137+
v.scopePath / os.up,
138+
logger,
139+
maybeRecoverOnError,
140+
withRestrictedFeatures
141+
)
127142
).map {
128143
case ProcessingOutput(reqs, scopedReqs, opts, updatedContent) =>
129144
(reqs, scopedReqs, opts, updatedContent)
@@ -152,11 +167,19 @@ case object ScalaPreprocessor extends Preprocessor {
152167
path: Either[String, os.Path],
153168
scopeRoot: ScopePath,
154169
logger: Logger,
155-
maybeRecoverOnError: BuildException => Option[BuildException]
170+
maybeRecoverOnError: BuildException => Option[BuildException],
171+
withRestrictedFeatures: Boolean
156172
): Either[BuildException, Option[ProcessingOutput]] = either {
157173
val (content0, isSheBang) = SheBang.ignoreSheBangLines(content)
158174
val afterStrictUsing: StrictDirectivesProcessingOutput =
159-
value(processStrictUsing(content0, path, scopeRoot, logger, maybeRecoverOnError))
175+
value(processStrictUsing(
176+
content0,
177+
path,
178+
scopeRoot,
179+
logger,
180+
maybeRecoverOnError,
181+
withRestrictedFeatures
182+
))
160183

161184
val afterProcessImports: Option[SpecialImportsProcessingOutput] = value {
162185
processSpecialImports(
@@ -273,7 +296,8 @@ case object ScalaPreprocessor extends Preprocessor {
273296
path: Either[String, os.Path],
274297
cwd: ScopePath,
275298
logger: Logger,
276-
maybeRecoverOnError: BuildException => Option[BuildException] = e => Some(e)
299+
maybeRecoverOnError: BuildException => Option[BuildException] = e => Some(e),
300+
withRestrictedFeatures: Boolean
277301
): Either[BuildException, StrictDirectivesProcessingOutput] = either {
278302
val contentChars = content.toCharArray
279303
val ExtractedDirectives(codeOffset, directives0) =
@@ -292,7 +316,8 @@ case object ScalaPreprocessor extends Preprocessor {
292316
usingDirectiveHandlers,
293317
path,
294318
cwd,
295-
logger
319+
logger,
320+
withRestrictedFeatures
296321
)
297322
}
298323

@@ -304,7 +329,8 @@ case object ScalaPreprocessor extends Preprocessor {
304329
requireDirectiveHandlers,
305330
path,
306331
cwd,
307-
logger
332+
logger,
333+
withRestrictedFeatures
308334
)
309335
}
310336

modules/build/src/main/scala/scala/build/preprocessing/ScriptPreprocessor.scala

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ final case class ScriptPreprocessor(codeWrapper: CodeWrapper) extends Preprocess
1313
def preprocess(
1414
input: Inputs.SingleElement,
1515
logger: Logger,
16-
maybeRecoverOnError: BuildException => Option[BuildException] = e => Some(e)
16+
maybeRecoverOnError: BuildException => Option[BuildException] = e => Some(e),
17+
withRestrictedFeatures: Boolean
1718
): Option[Either[BuildException, Seq[PreprocessedSource]]] =
1819
input match {
1920
case script: Inputs.Script =>
@@ -27,7 +28,8 @@ final case class ScriptPreprocessor(codeWrapper: CodeWrapper) extends Preprocess
2728
script.subPath,
2829
ScopePath.fromPath(script.path),
2930
logger,
30-
maybeRecoverOnError
31+
maybeRecoverOnError,
32+
withRestrictedFeatures
3133
)
3234
}
3335
preprocessed
@@ -46,7 +48,8 @@ final case class ScriptPreprocessor(codeWrapper: CodeWrapper) extends Preprocess
4648
script.wrapperPath,
4749
script.scopePath,
4850
logger,
49-
maybeRecoverOnError
51+
maybeRecoverOnError,
52+
withRestrictedFeatures
5053
)
5154
}
5255
preprocessed
@@ -67,7 +70,8 @@ object ScriptPreprocessor {
6770
subPath: os.SubPath,
6871
scopePath: ScopePath,
6972
logger: Logger,
70-
maybeRecoverOnError: BuildException => Option[BuildException]
73+
maybeRecoverOnError: BuildException => Option[BuildException],
74+
withRestrictedFeatures: Boolean
7175
): Either[BuildException, List[PreprocessedSource.InMemory]] = either {
7276

7377
val (contentIgnoredSheBangLines, _) = SheBang.ignoreSheBangLines(content)
@@ -80,7 +84,8 @@ object ScriptPreprocessor {
8084
reportingPath,
8185
scopePath / os.up,
8286
logger,
83-
maybeRecoverOnError
87+
maybeRecoverOnError,
88+
withRestrictedFeatures
8489
))
8590
.getOrElse(ProcessingOutput(BuildRequirements(), Nil, BuildOptions(), None))
8691

modules/build/src/test/scala/scala/build/tests/BuildProjectTests.scala

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ class BuildProjectTests extends munit.FunSuite {
8383
)
8484
)
8585

86-
val inputs = Inputs(Nil, None, os.pwd, "project", false, None)
86+
val inputs = Inputs.empty("project")
8787
val sources = Sources(Nil, Nil, None, Nil, options)
8888
val logger = new LoggerMock()
8989
val res = Build.buildProject(
@@ -163,19 +163,18 @@ class BuildProjectTests extends munit.FunSuite {
163163
}
164164

165165
test("workspace for bsp") {
166-
val workspacePath = os.pwd
167166
val options = BuildOptions(
168167
internal = InternalOptions(localRepository =
169168
LocalRepo.localRepo(scala.build.Directories.default().localRepoDir)
170169
)
171170
)
172-
val inputs = Inputs(Nil, None, workspacePath, "project", false, None)
171+
val inputs = Inputs.empty("project")
173172
val sources = Sources(Nil, Nil, None, Nil, options)
174173
val logger = new LoggerMock()
175174

176175
val project =
177176
Build.buildProject(inputs, sources, Nil, options, None, Scope.Main, logger).orThrow
178177

179-
expect(project.workspace == workspacePath)
178+
expect(project.workspace == inputs.workspace)
180179
}
181180
}

modules/build/src/test/scala/scala/build/tests/PreprocessingTests.scala

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class PreprocessingTests extends munit.FunSuite {
1212
val logger = TestLogger()
1313
val scalaFile = Inputs.ScalaFile(os.temp.dir(), os.SubPath("NotExists.scala"))
1414

15-
val res = ScalaPreprocessor.preprocess(scalaFile, logger)
15+
val res = ScalaPreprocessor.preprocess(scalaFile, logger, withRestrictedFeatures = false)
1616
val expectedMessage = s"File not found: ${scalaFile.path}"
1717

1818
assert(res.nonEmpty)
@@ -24,7 +24,11 @@ class PreprocessingTests extends munit.FunSuite {
2424
val logger = TestLogger()
2525
val scalaScript = Inputs.Script(os.temp.dir(), os.SubPath("NotExists.sc"))
2626

27-
val res = ScriptPreprocessor(CustomCodeWrapper).preprocess(scalaScript, logger)
27+
val res = ScriptPreprocessor(CustomCodeWrapper).preprocess(
28+
scalaScript,
29+
logger,
30+
withRestrictedFeatures = false
31+
)
2832
val expectedMessage = s"File not found: ${scalaScript.path}"
2933

3034
assert(res.nonEmpty)

0 commit comments

Comments
 (0)