Is eval $(opam env --switch={switch} --set-switch) equivalent to opam switch set switch?

I mean, we would be running eval $(opam env) either way, I’m not wondering about that part. Just about the way it’s coded.

A process can’t change the environment of an other process. Then opam can’t change the parent process environment (bash or Python).

However, the environment is inherited when a process create a new one. If your script changes its environment (with os.environ) what he call get this environment.

Note also that eval is an internal bash command which doesn’t create a subprocess, then can change the bash environment.

What you would like is a ocaml proxy command which ask opam which file to execute and set its environment. You have it with opam exec --switch my_switch command. But just setting the environment takes 2s.

1 Like

Got it. If this is true and I want build another ocaml project/pkg from within the main python process (regardless of how I build it, with opam install, opam pin, make, make -C etc even if I dispatch those from python) so that any of those build inherit the right opam switch env being set they we always need to update os.environ.

Makes sense, thanks!

curious, can you explain what this does? How does it know what the process of the main python process is and change it’s env properly?

I’ve confirmed this. What I do is run opam switch set coq-8.10 from a python subprocess:

        #     opam_set_switch_via_opam_switch('coq-8.10')
        result ='opam switch set {switch}'.split(), check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

which the docs says returns a completed process.

Then I compare the contents of the env variables of the subprocess by calling via another subprocess the cmd opam env and compare it with the main python process by comparing it with os.environ. I get that the two indeed don’t match:

    # opam_env_dict: dict = get_variables_from_opam_env_output(py_prints_on=py_prints_on)
            result ='opam env'.split(), capture_output=True, text=True)
# ... compare with os.environ
            assert uutils.check_dict1_is_in_dict2(opam_env_dict, os.environ, verbose=True)

assert fails

--> k='OPAM_SWITCH_PREFIX' is in dict2 but with different value 

The only thing that confuses me is that it seems that subprocess has it’s own process that does remember things. I say this because I would have expected the new subprocess that calls opam env to not be affected by the first opam switch set coq-8.10 but it seems it was affected. I expected the 2nd subprocess to spawned from the main python and be independent form the process that called opam switch set coq-8.10.


def check_os_env_has_the_vars_set_by_opam_env_test_():
    py_prints_on: bool = False
    import uutils

    # -- opam switch set for subprocess & get output of opam env
    activate_switch: str = get_active_opam_switch_according_to_bash_opam_switch_cmd(py_prints_on=py_prints_on)

    activate_switch: str = get_active_opam_switch_according_to_bash_opam_switch_cmd(py_prints_on=py_prints_on)

    opam_env_dict: dict = get_variables_from_opam_env_output(py_prints_on=py_prints_on)

    # -- get output of os.env
    # - verify that subprocess indeed calls it's own process indepdent of python main. The fact they don't match means that the subprocess is calling it's own opam env. Mystery is why that subprocess is persistent despite docs saying it returns a completed process:,
    assert not uutils.check_dict1_is_in_dict2(opam_env_dict, os.environ,
                                              verbose=True), f'Err: dict1 is opam env of subprocess so it should point to .opam/coq-8.10 while main python should point to .opam/test (assuming your terminal is indeeed set to opam switch called test)'
    # seems that subprocess persists and still outputs .opam/coq-8.10
    activate_switch: str = get_active_opam_switch_according_to_bash_opam_switch_cmd(py_prints_on=py_prints_on)

If you launch opam switch set {switch}, you tell opam to store in the .opam directory the info about your desire to change the switch. Then this information is virtually shared among all your processes. Afterward, opam env read this information and print the environment change needed by this switch.

The idea is you just type opam switch set {switch} and let the shell execute automatically the eval command according to the PROMPT_COMMAND value. Python has no such an automatic behaviour.

1 Like

opam exec --switch my_switch command won’t change the environment of your Python, but will execute the given command with a modified environment.

If your Python program only execute one command (make), it can be handy. If multiple commands are executed, getting the environment each time will take some time.

Also beware, if your package executes ocaml command through opam exec, your environment will be replaced by the current switch selected by opam switch set.

A complement. You have to way to change the switch:

  • eval $(opam env --switch my_switch --set-switch)
  • opam switch set my_switch and afterward eval $(opam env).

The two options are not strictly equivalent.

The first one change the switch through the environment (OPAMSWITCH variable), then its scope is limited by the current process and its descendence.

The second option change a file in your .opam directory, then you can have a scope in all processes which just evalute opam env. It is handy when the shell has a PROMPT_COMMAND option which evalute opam env after each command. In this case, just an opam switch set my_switch is enough which is handy.

When OPAMSWITCH differ from the (user) global switch, opam list use the OPAMSWITCH and print a warning:

[NOTE] Current switch is set locally through the OPAMSWITCH variable.
       The current global system switch is 4.11.0+flambda.

The right way depends if you want a transcient change or a global and remanent change.

1 Like

The 2nd option opam switch set my_switch and afterward eval $(opam env) I’ve tried running in python and it seems the main python process doesn’t change correctly (by observing the os.environ). If I remember correctly neither do new process created from it using subprocess. I need to build multiple ocaml projects from within python so I assume I need to make sure those subprocess have the right ocaml switch. I was going to do either of these:

  1. I was going to write a big command as a string that made sure the new subprocess had the right switch. Or executed a file that did that already and then ran that file.
  2. Assuming your opam switch set my_switch does change some global thing (that doesn’t affect my main python process but it does affect the new subprocess I run as far as I can see if I remember correctly) – then I can just run opam switch set my_switch in python. The only issue is I can’t run eval $(opam env) in the new subprocess or Idk how to run evals in a new subprocess. Perhaps this step won’t work…

This is rather confusing. Why does opam switch set my_switch change the switch already. Why do we need to run eval $(opam env) too? Seems really confusing to me.

opam switch set will only register a switch by modifying an opam file. Then opam commands will be informed of a change. However, OCaml programs (ocaml, ocamlc, ocamlopt, …) and your shell (or programs like make) aren’t designed to deal with this file, but use the environment (typically PATH to launch the right program, CAML_LD_LIBRARY_PATH to find dynamically some libraries). You also have MANPATH to have man use the right documentation.

Then you need to “convert” the switch choice to a set of environment variables to have a consistent setup.

With some hooks, the eval $(opam env) is implicit in an interactive shell.

1 Like