Skip to content

Introduce os.SubProcess.env DynamicVariable to override default env #295

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Aug 30, 2024
13 changes: 13 additions & 0 deletions Readme.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1667,6 +1667,19 @@ val sha = os.spawn(cmd = ("shasum", "-a", "256"), stdin = gzip.stdout)
sha.stdout.trim ==> "acc142175fa520a1cb2be5b97cbbe9bea092e8bba3fe2e95afa645615908229e -"
----

==== Customizing the default environment

Client-server CLI applications sometimes want to run subprocesses on the server based on the environment of the client.
It is possible to customize the default environment passed to subprocesses by setting the `os.SubProcess.env` threadlocal:

[source,scala]
----
val clientEnvironment: Map[String, String] = ???
os.SubProcess.env.withValue(clientEnvironment) {
os.call(command) // clientEnvironment is passed by default instead of the system environment
}
----

== Spawning Pipelines of Subprocesses

After constructing a subprocess with `os.proc`, you can use the `pipeTo` method
Expand Down
3 changes: 2 additions & 1 deletion build.sc
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ trait SafeDeps extends ScalaModule {
}

trait MiMaChecks extends Mima {
def mimaPreviousVersions = Seq("0.9.0", "0.9.1", "0.9.2", "0.9.3", "0.10.0")
def mimaPreviousVersions =
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")
override def mimaBinaryIssueFilters: T[Seq[ProblemFilter]] = Seq(
ProblemFilter.exclude[ReversedMissingMethodProblem]("os.PathConvertible.isCustomFs"),
// this is fine, because ProcessLike is sealed (and its subclasses should be final)
Expand Down
27 changes: 19 additions & 8 deletions os/src/ProcessOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -486,17 +486,28 @@ private[os] object ProcessOps {

val environment = builder.environment()

if (!propagateEnv) {
environment.clear()
}

if (env != null) {
for ((k, v) <- env) {
if (v != null) builder.environment().put(k, v)
else builder.environment().remove(k)
def addToProcessEnv(env: Map[String, String]) =
if (env != null) {
for ((k, v) <- env) {
if (v != null) environment.put(k, v)
else environment.remove(k)
}
}

os.SubProcess.env.value match {
case null =>
if (!propagateEnv) {
environment.clear()
}
case subProcessEnvValue =>
environment.clear()
if (propagateEnv) {
addToProcessEnv(subProcessEnvValue)
}
}

addToProcessEnv(env)

builder.directory(Option(cwd).getOrElse(os.pwd).toIO)

builder
Expand Down
6 changes: 6 additions & 0 deletions os/src/SubProcess.scala
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,12 @@ class SubProcess(

object SubProcess {

/**
* The env passed by default to child processes.
* When `null`, the system environment is used.
*/
val env = new scala.util.DynamicVariable[Map[String, String]](null)

/**
* A [[BufferedWriter]] with the underlying [[java.io.OutputStream]] exposed
*
Expand Down
22 changes: 22 additions & 0 deletions os/test/src/SubprocessTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,28 @@ object SubprocessTests extends TestSuite {
}
}
}
test("envWithValue") {
if (Unix()) {
val variableName = "TEST_ENV_FOO"
val variableValue = "bar"
def envValue() = os.proc(
"bash",
"-c",
s"""if [ -z $${$variableName+x} ]; then echo "unset"; else echo "$$$variableName"; fi"""
).call().out.lines().head

val before = envValue()
assert(before == "unset")

os.SubProcess.env.withValue(Map(variableName -> variableValue)) {
val res = envValue()
assert(res == variableValue)
}

val after = envValue()
assert(after == "unset")
}
}
test("multiChunk") {
// Make sure that in the case where multiple chunks are being read from
// the subprocess in quick succession, we ensure that the output handler
Expand Down