I've set up a devcontainer in a Visual Studio Code Ruby project, and the launch configurations don't seem to be able to override environment variables from the devcontainer. I set up the devcontainer to launch from Docker Compose, with one environment variable set in the .env
file.
EXAMPLE_VALUE=from-env-file
Another environment variable is set in compose.yml
.
services:
active_sinatra:
restart: unless-stopped
image: donkirkby/active-sinatra-devcontainer:v0.2.2
environment:
- EXAMPLE_VALUE
- EXAMPLE_VALUE2=from-compose-file
volumes:
- .:/opt/active_sinatra
I wrote example_script.rb
to display the environment variables, plus a third that isn't set yet.
value = ENV.fetch('EXAMPLE_VALUE', 'not set')
puts "example value is #{value.inspect}."
value2 = ENV.fetch('EXAMPLE_VALUE2', 'not set')
puts "example value 2 is #{value2.inspect}."
value3 = ENV.fetch('EXAMPLE_VALUE3', 'not set')
puts "example value 3 is #{value3.inspect}."
I set up a launch configuration that overrides all three environment variables.
{
"type": "ruby_lsp",
"name": "Debug example",
"request": "launch",
"program": "ruby /opt/active_sinatra/example_script.rb",
"env": {
"EXAMPLE_VALUE": "1 from launch",
"EXAMPLE_VALUE2": "2 from launch",
"EXAMPLE_VALUE3": "3 from launch"
}
},
Unfortunately, my Ruby script only sees the third environment variable when I debug it with the launch configuration.
Ruby REPL: You can run any Ruby expression here.
Note that output to the STDOUT/ERR printed on the TERMINAL.
[experimental]
`,COMMAND` runs `COMMAND` debug command (ex: `,info`).
`,help` to list all debug commands.
DEBUGGER: Disconnected.
example value is "from-env-file".
example value 2 is "from-compose-file".
example value 3 is "3 from launch".
Is there a way to change one of the existing environment variables using the launch configuration, or do I have to change the Docker Compose configuration, relaunch the container, and then launch my debugger?
If you want to play around with my example code, you can see it on GitHub, then open a codespace on the docker-compose
branch.
I'll wager you can blame the current implementation of Debugger.resolveDebugConfiguration
in debugger.ts
.
It beings with the doc comment:
Resolve the user's debugger configuration. Here we receive what is configured in launch.json and can modify and insert defaults for the user. The most important thing is making sure the Ruby environment is a part of it so that we launch using the right bundle and Ruby version.
I highly suspect these lines:
if (debugConfiguration.env) { // If the user has their own debug launch configurations, we still need to inject the Ruby environment debugConfiguration.env = Object.assign( debugConfiguration.env, workspace.ruby.env, );
Quoting MDN, which is a little more obvious than the ECMAScript spec here,
Properties in the target object are overwritten by properties in the sources if they have the same key. Later sources' properties overwrite earlier ones.
We can see workspace
defined as workspace = this.workspaceResolver(folder?.uri);
. Debugger#workspaceResolver
is initialized in the constructor, and the constructor is called in rubyLsp.ts
, passing RubyLsp#workspaceResolver
. In any case, Ruby#env
wraps _env
, which is modified in Ruby#mergeComposedEnvironment
and Ruby#runActivation
, which gets the new value from a call to VersionManager#activate
. There are a lot of version managers, but from sampling them all (Asdf
, Mise
, Custom
, None
, Rbenv
and Rvm
, and Shadowenv
), they all seem to include process.env
in what they provide as the new value. I'll guess that that's the issue, assuming that the extension host starts with an environment influenced by the environment of your devcontainer.
Documentation of the Ruby LSP activation is here.
There's potential you could work around the issue by using custom activation and setting your stuff in there instead of in your launch config, but that just seems like a gross thing to do. I'd suggest you raise this issue to the maintainers of the extension. The solution might be as simple as changing this:
debugConfiguration.env = Object.assign(
debugConfiguration.env,
workspace.ruby.env,
);
to this:
debugConfiguration.env = {
...workspace.ruby.env,
...debugConfiguration.env,
};
but I can't speak for that because I'm not a maintainer and I don't know the design context, and I don't even do Ruby development or use this extension. I could be totally wrong.
An issue ticket has been raised at Environment variables can't be overridden in debug launch #3265. I wrote a pull request at https://github.com/Shopify/ruby-lsp/pull/3266.
*Unrelated... why do they assign the result of Object.assign
to what they passed as its first argument?