Skip to content

Commit 8d0abda

Browse files
lolgablefou
andauthored
Introduce os.SubProcess.env DynamicVariable to override default env (#295)
In Mill and any client-server application, it is handy to start processes on the server passing the client environment. This requires passing the environment manually to all the `os.proc(...).call()`s. An easier alternative is to override the environment as we do for `os.Inherit` `out`, `in`, and `err` pipes. So users can forget about the environment and have the right one passed automatically. Pull request: #295 --------- Co-authored-by: Tobias Roeser <[email protected]>
1 parent ac17c9c commit 8d0abda

File tree

5 files changed

+62
-9
lines changed

5 files changed

+62
-9
lines changed

Readme.adoc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1667,6 +1667,19 @@ val sha = os.spawn(cmd = ("shasum", "-a", "256"), stdin = gzip.stdout)
16671667
sha.stdout.trim ==> "acc142175fa520a1cb2be5b97cbbe9bea092e8bba3fe2e95afa645615908229e -"
16681668
----
16691669

1670+
==== Customizing the default environment
1671+
1672+
Client-server CLI applications sometimes want to run subprocesses on the server based on the environment of the client.
1673+
It is possible to customize the default environment passed to subprocesses by setting the `os.SubProcess.env` threadlocal:
1674+
1675+
[source,scala]
1676+
----
1677+
val clientEnvironment: Map[String, String] = ???
1678+
os.SubProcess.env.withValue(clientEnvironment) {
1679+
os.call(command) // clientEnvironment is passed by default instead of the system environment
1680+
}
1681+
----
1682+
16701683
== Spawning Pipelines of Subprocesses
16711684

16721685
After constructing a subprocess with `os.proc`, you can use the `pipeTo` method

build.sc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ trait SafeDeps extends ScalaModule {
5151
}
5252

5353
trait MiMaChecks extends Mima {
54-
def mimaPreviousVersions = Seq("0.9.0", "0.9.1", "0.9.2", "0.9.3", "0.10.0")
54+
def mimaPreviousVersions =
55+
Seq("0.9.0", "0.9.1", "0.9.2", "0.9.3", "0.10.0", "0.10.1", "0.10.2", "0.10.3", "0.10.4")
5556
override def mimaBinaryIssueFilters: T[Seq[ProblemFilter]] = Seq(
5657
ProblemFilter.exclude[ReversedMissingMethodProblem]("os.PathConvertible.isCustomFs"),
5758
// this is fine, because ProcessLike is sealed (and its subclasses should be final)

os/src/ProcessOps.scala

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -486,17 +486,28 @@ private[os] object ProcessOps {
486486

487487
val environment = builder.environment()
488488

489-
if (!propagateEnv) {
490-
environment.clear()
491-
}
492-
493-
if (env != null) {
494-
for ((k, v) <- env) {
495-
if (v != null) builder.environment().put(k, v)
496-
else builder.environment().remove(k)
489+
def addToProcessEnv(env: Map[String, String]) =
490+
if (env != null) {
491+
for ((k, v) <- env) {
492+
if (v != null) environment.put(k, v)
493+
else environment.remove(k)
494+
}
497495
}
496+
497+
os.SubProcess.env.value match {
498+
case null =>
499+
if (!propagateEnv) {
500+
environment.clear()
501+
}
502+
case subProcessEnvValue =>
503+
environment.clear()
504+
if (propagateEnv) {
505+
addToProcessEnv(subProcessEnvValue)
506+
}
498507
}
499508

509+
addToProcessEnv(env)
510+
500511
builder.directory(Option(cwd).getOrElse(os.pwd).toIO)
501512

502513
builder

os/src/SubProcess.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,12 @@ class SubProcess(
162162

163163
object SubProcess {
164164

165+
/**
166+
* The env passed by default to child processes.
167+
* When `null`, the system environment is used.
168+
*/
169+
val env = new scala.util.DynamicVariable[Map[String, String]](null)
170+
165171
/**
166172
* A [[BufferedWriter]] with the underlying [[java.io.OutputStream]] exposed
167173
*

os/test/src/SubprocessTests.scala

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,28 @@ object SubprocessTests extends TestSuite {
146146
}
147147
}
148148
}
149+
test("envWithValue") {
150+
if (Unix()) {
151+
val variableName = "TEST_ENV_FOO"
152+
val variableValue = "bar"
153+
def envValue() = os.proc(
154+
"bash",
155+
"-c",
156+
s"""if [ -z $${$variableName+x} ]; then echo "unset"; else echo "$$$variableName"; fi"""
157+
).call().out.lines().head
158+
159+
val before = envValue()
160+
assert(before == "unset")
161+
162+
os.SubProcess.env.withValue(Map(variableName -> variableValue)) {
163+
val res = envValue()
164+
assert(res == variableValue)
165+
}
166+
167+
val after = envValue()
168+
assert(after == "unset")
169+
}
170+
}
149171
test("multiChunk") {
150172
// Make sure that in the case where multiple chunks are being read from
151173
// the subprocess in quick succession, we ensure that the output handler

0 commit comments

Comments
 (0)