How to share shell profile between steps? (or: how to use nvm/rvm in steps?)

I have a workflow consisting of two jobs:  frontend (nodejs) & backend (ruby); that I’m attempting to migrate from TravisCI.

The frontend code uses NVM with an .nvmrc file to specify the version of Node.JS to use.

The backend code uses RVM with a .ruby-version file to specify the version of Ruby to use.

TravisCI build environments come with both NVM and RVM pre-installed.

The version of Ruby specified in .ruby-version is automatically detected/installed when the Travis job runs; and a simple nvm install command in the preinstall stage is all that is required to have Travis detect/install the appropriate version of Node.JS.

In Github Actions virtual environments, default versions of both Ruby & Node.JS are pre-installed, however version managers such as NVM/RVM are not.

The ‘official’ way to install a different Ruby/Node version in Github Actions appears to via actions/setup-ruby and actions/setup-node.

Unfortunately, setup-node doesn’t undersand .nvmrc files yet, and setup-ruby doesn’t understand .ruby-version files yet (so the specfied runtime versions would need to be duplicated in the workflow).

To emulate TravisCI, I’m trying to install both NVM and RVM as the first steps in their respective jobs, so that the jobs can use the .nvmrc and .ruby-version files to detect & install the appropriate runtime versions.

The challenge I have is that both NVM and RVM expect to be sourced into the shell after installation, e.g.

echo "source $NVM_DIR/nvm.sh" >> "$HOME/.bash_profile"
...

echo "source $HOME/.rvm/scripts/rvm" >> "$HOME/.bash_profile"
...

As I’ve discovered, each run command in a job is a separate shell process; and the default shell command on the Ubuntu environments is:

bash --noprofile --norc -eo pipefail {0}

(explicitly instructed to not read from .bash_profile and .bashrc files).

I tried adding an explicit shell: bash {0} to each step, but this does not seem to have made any difference.

Below is an example workflow.

For nvm: the --install argument when sourcing into the shell tells nvm to auto-install & switch to the version of node found in the .nvmrc file

For rvm: the rvm_install_on_use_flag environment variable tells rvm to auto-install & switch to the version of ruby found in the .ruby-version file.

frontend:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout repository
      uses: actions/checkout@v1

- name: Check versions (before)
shell: bash {0}
run: |
node --version
npm --version
    - name: Install nvm
      shell: bash {0}
      run: |
        curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.34.0/install.sh | bash
        export NVM_DIR="$HOME/.nvm"
        echo "source $NVM_DIR/nvm.sh --install" >> "$HOME/.bash_profile"

    - name: Check versions (after)
      shell: bash {0}
      run: |
        node --version
        npm --version

  backend:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout repository
      uses: actions/checkout@v1

- name: Install rvm
      shell: bash {0}
      run: |
        gpg --keyserver hkp://pool.sks-keyservers.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
        curl -sSL https://get.rvm.io | bash -s stable
        export rvm_install_on_use_flag=1
        echo "source $HOME/.rvm/scripts/rvm" >> "$HOME/.bash_profile"

    - name: Check versions (after)
      shell: bash {0}
      run: |
        rvm --version
        ruby --version
        gem --version
        bundler --version

For the frontend job, I had expected node --version to be v10.16.3 in the ‘before’ step; and v12.1.0 in the ‘after’ step;  but it is v10.16.3 in both.

It seems that the changes to .bash_profile are not seen by other steps?  (for what it’s worth, I also tried .bashrc instead…to no avail).

Is there a way that I can have the NVM/RVM scripts sourced into all subsequent shell processes in a job?

(also, having to repeat shell: bash {0} in every step is pretty annoying).

2 Likes

You were close: You can actually just add shell: bash -l {0} (the -l was missing from yours) for steps where you need to source the bash_profile. See: https://www.gnu.org/software/bash/manual/html_node/Bash-Startup-Files.html

I am aso assuming that install script primarily exports/updates the PATH to include the nvm install location, so youd only need to add the shell option for steps that need to exec node

The default was to invoke bash non-interactive and non-login since semantically thats closest to what we are doing- just running scripts, and I wanted to avoid adding potential side effects, unexpected behavior, re-running the same statup files repeatedly for each step, etc. The shell option was intended to allow you to manage these types of scenarios as necessary. Its not really the expected default behavior since we have setup actions that download/install tools and modify the PATH, etc, so that the tools installed are available everywhere not just from bash steps that pass down the environment, PATH is tracked by the runner across steps, etc

4 Likes

To have a working sample snippet. Adding a local Composer’s bin to the path. The Drupal step would fail without shell: bash -l {0}.

jobs:  
 build:  
 name: Build  
runs-on: ubuntu-latest  
  
steps:  
 - uses: actions/checkout@v2  
  
- name: Composer  
run: |  
 composer --version  
 composer validate  
 composer install -n  
 echo "export PATH=$PATH:$GITHUB\_WORKSPACE/vendor/bin" \>\> ~/.bash\_profile  
 source ~/.bash\_profile  
 drush --version  
  
- name: Drupal  
shell: bash -l {0}  
run: |  
 drush --version
1 Like