Skip to content

Commit 085006e

Browse files
authored
Set globally Java properties for ScalaCLI launcher (#2317)
* Add java.properties key into config * sclicheck: remove config file for clear command * Ask user to confirm when change value for key * Load java properties into ScalaCli from config * Add docs
1 parent dfd5339 commit 085006e

File tree

14 files changed

+156
-13
lines changed

14 files changed

+156
-13
lines changed

modules/cli/src/main/scala/scala/cli/ScalaCli.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,17 @@ object ScalaCli {
186186
System.err.println(
187187
s"Warning: Only java properties are supported in .scala-jvmopts file. Other options are ignored: ${otherOpts.mkString(", ")} "
188188
)
189+
// load java properties from config
190+
for {
191+
configDb <- ConfigDbUtils.configDb.toOption
192+
properties <- configDb.get(Keys.javaProperties).getOrElse(Nil)
193+
}
194+
properties.foreach { opt =>
195+
opt.stripPrefix("-D").split("=", 2).match {
196+
case Array(key, value) => System.setProperty(key, value)
197+
case _ => System.err.println(s"Warning: Invalid java property in config: $opt")
198+
}
199+
}
189200
}
190201

191202
private def main0(args: Array[String]): Unit = {

modules/cli/src/main/scala/scala/cli/commands/config/Config.scala

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,7 @@ import scala.cli.commands.publish.ConfigUtil.*
1616
import scala.cli.commands.shared.HelpGroup
1717
import scala.cli.commands.util.JvmUtils
1818
import scala.cli.commands.{ScalaCommand, SpecificationLevel}
19-
import scala.cli.config.{
20-
ConfigDb,
21-
Keys,
22-
PasswordOption,
23-
PublishCredentials,
24-
RepositoryCredentials,
25-
Secret
26-
}
19+
import scala.cli.config._
2720
import scala.cli.util.ArgHelpers.*
2821
import scala.cli.util.ConfigDbUtils
2922
object Config extends ScalaCommand[ConfigOptions] {
@@ -285,6 +278,8 @@ object Config extends ScalaCommand[ConfigOptions] {
285278
else
286279
values
287280

281+
checkIfAskForUpdate(entry, finalValues, db, options)
282+
288283
db.setFromString(entry, finalValues)
289284
.wrapConfigException
290285
.orExit(logger)
@@ -298,4 +293,49 @@ object Config extends ScalaCommand[ConfigOptions] {
298293
}
299294
}
300295
}
296+
297+
/** Check whether to ask for an update depending on the provided key.
298+
*/
299+
private def checkIfAskForUpdate(
300+
entry: Key[_],
301+
newValues: Seq[String],
302+
db: ConfigDb,
303+
options: ConfigOptions
304+
): Unit = entry match {
305+
case listEntry: Key.StringListEntry =>
306+
val previousValue = db.get(listEntry).wrapConfigException.orExit(logger).getOrElse(Nil)
307+
308+
confirmUpdateValue(
309+
listEntry.fullName,
310+
previousValue,
311+
newValues,
312+
options
313+
).wrapConfigException.orExit(logger)
314+
case _ => ()
315+
}
316+
317+
/** If the new value is different from the previous value, ask user for confirmation or suggest to
318+
* use --force option. If the new value is the same as the previous value, confirm the operation.
319+
* If force option is provided, skip the confirmation.
320+
*/
321+
private def confirmUpdateValue(
322+
keyFullName: String,
323+
previousValues: Seq[String],
324+
newValues: Seq[String],
325+
options: ConfigOptions
326+
): Either[Exception, Unit] =
327+
val (newValuesStr, previousValueStr) = (newValues.mkString(", "), previousValues.mkString(", "))
328+
val shouldUpdate = !options.force && newValuesStr != previousValueStr && previousValues.nonEmpty
329+
330+
if shouldUpdate then
331+
val interactive = options.global.logging.verbosityOptions.interactiveInstance()
332+
val msg =
333+
s"Do you want to change the key '$keyFullName' from '$previousValueStr' to '$newValuesStr'?"
334+
interactive.confirmOperation(msg) match {
335+
case Some(true) => Right(())
336+
case _ => Left(new Exception(
337+
s"Unable to change the value for the key: '$keyFullName' from '$previousValueStr' to '$newValuesStr' without the force flag. Please pass -f or --force to override."
338+
))
339+
}
340+
else Right(())
301341
}

modules/cli/src/main/scala/scala/cli/commands/config/ConfigOptions.scala

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,13 @@ final case class ConfigOptions(
7575
@HelpMessage("For repository.credentials, whether to use these credentials should be passed upon redirection")
7676
@Tag(tags.restricted)
7777
@Tag(tags.inShortHelp)
78-
passOnRedirect: Option[Boolean] = None
78+
passOnRedirect: Option[Boolean] = None,
79+
@Group(HelpGroup.Config.toString)
80+
@HelpMessage("Force overwriting values for key")
81+
@ExtraName("f")
82+
@Tag(tags.inShortHelp)
83+
@Tag(tags.should)
84+
force: Boolean = false
7985
) extends HasGlobalOptions
8086
// format: on
8187

modules/config/src/main/scala/scala/cli/config/Keys.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,13 @@ object Keys {
126126
"Default repository, syntax: https://first-repo.company.com https://second-repo.company.com",
127127
specificationLevel = SpecificationLevel.RESTRICTED
128128
)
129+
val javaProperties = new Key.StringListEntry(
130+
prefix = Nil,
131+
name = "java.properties",
132+
description =
133+
"Java properties",
134+
specificationLevel = SpecificationLevel.SHOULD
135+
)
129136

130137
// Kept for binary compatibility
131138
val repositoriesMirrors = repositoryMirrors
@@ -161,6 +168,7 @@ object Keys {
161168
ghToken,
162169
globalInteractiveWasSuggested,
163170
interactive,
171+
javaProperties,
164172
suppressDirectivesInMultipleFilesWarning,
165173
suppressOutdatedDependenciessWarning,
166174
suppressExperimentalFeatureWarning,

modules/docs-tests/src/main/scala/sclicheck/sclicheck.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,7 @@ def checkFile(file: os.Path, options: Options): Unit =
284284
}
285285
case Commands.Clear(_) =>
286286
os.list(out).filterNot(_ == binDir).foreach(os.remove.all)
287+
os.remove(os.Path(sys.env("SCALA_CLI_CONFIG")))
287288

288289
try
289290
println(Blue(s"\n[${file.relativeTo(os.pwd)}] Running checks in $out"))

modules/integration/src/test/scala/scala/cli/integration/ConfigTests.scala

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,4 +522,42 @@ class ConfigTests extends ScalaCliSuite {
522522
}
523523
}
524524

525+
test("change value for key") {
526+
val configFile = os.rel / "config" / "config.json"
527+
val configEnv = Map("SCALA_CLI_CONFIG" -> configFile.toString)
528+
val (props, props2, props3) = ("props=test", "props2=test2", "props3=test3")
529+
val key = "java.properties"
530+
TestInputs.empty.fromRoot { root =>
531+
// set some values first time
532+
os.proc(TestUtil.cli, "--power", "config", key, props, props2).call(
533+
cwd = root,
534+
env = configEnv
535+
)
536+
537+
// override some values should throw error without force flag
538+
val res = os.proc(TestUtil.cli, "--power", "config", key, props, props2, props3).call(
539+
cwd = root,
540+
env = configEnv,
541+
check = false,
542+
mergeErrIntoOut = true
543+
)
544+
545+
expect(res.exitCode == 1)
546+
expect(res.out.trim().contains("pass -f or --force"))
547+
548+
os.proc(TestUtil.cli, "--power", "config", key, props, props2, props3, "-f").call(
549+
cwd = root,
550+
env = configEnv,
551+
check = false
552+
)
553+
val propertiesFromConfig = os.proc(TestUtil.cli, "--power", "config", key)
554+
.call(cwd = root, env = configEnv)
555+
.out.trim()
556+
557+
expect(propertiesFromConfig.contains(props))
558+
expect(propertiesFromConfig.contains(props2))
559+
expect(propertiesFromConfig.contains(props3))
560+
}
561+
}
562+
525563
}

modules/integration/src/test/scala/scala/cli/integration/SipScalaTests.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class SipScalaTests extends ScalaCliSuite {
3232
os.proc(TestUtil.cli, "config", configKey, disableSetting)
3333
.call(cwd = root, env = homeEnv)
3434
testWhenDisabled(root, homeEnv)
35-
os.proc(TestUtil.cli, "config", configKey, "true")
35+
os.proc(TestUtil.cli, "config", configKey, "true", "-f")
3636
.call(cwd = root, env = homeEnv)
3737
testWhenEnabled(root, homeEnv)
3838
}

website/docs/commands/publishing/publish-setup.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ scala-cli --power config publish.user.email "[email protected]"
6666
scala-cli --power config publish.user.url "https://alex.me"
6767
```
6868

69+
<!-- clear -->
70+
6971
The email can be left empty if you'd rather not put your email in POM files:
7072
```bash
7173
scala-cli --power config publish.user.email ""

website/docs/reference/cli-options.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,12 @@ For repository.credentials, whether to use these credentials are optional
207207

208208
For repository.credentials, whether to use these credentials should be passed upon redirection
209209

210+
### `--force`
211+
212+
Aliases: `-f`
213+
214+
Force overwriting values for key
215+
210216
## Cross options
211217

212218
Available in commands:

website/docs/reference/commands.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ Available keys:
5555
- httpProxy.user HTTP proxy user (used for authentication).
5656
- interactive Globally enables interactive mode (the '--interactive' flag).
5757
- interactive-was-suggested Setting indicating if the global interactive mode was already suggested.
58+
- java.properties Java properties
5859
- pgp.public-key The PGP public key, used for signing.
5960
- pgp.secret-key The PGP secret key, used for signing.
6061
- pgp.secret-key-password The PGP secret key password, used for signing.

0 commit comments

Comments
 (0)