7

If I need to use something like NVM in my makefile, I run into a problem, because NVM exports some bash functions that are not available in Make, even if my shell is defined as bash in the makefile.

How do I cause make to "inherit" all the parent shell's functions? Is there a way to make NVM accessible to a makefile, without writing export -f for a zillion little NVM bash functions?

  • 2
    Probably related. – Tensibai Mar 23 '17 at 12:42
  • indeed. but does not offer a good solution. I can make it work with prepending source ... to every line of my makefile, for example, but clearly that's sub-optimal. – Assaf Lavie Mar 23 '17 at 12:43
  • Maybe this may worth a word in your question on what you already tried/found and why it doesn't solve your problem, an example Makefile exhibiting the problem could help aslo I think. – Tensibai Mar 23 '17 at 12:45
  • 1
    Let me see if I get right: you're problem is to avoid writing export -f <funcname> for each nvm function of your parent shell. (if this is the case I assume declare -F | sed 's/declare/export/' > func.sh and then a source funcs.sh within the makefile should do.) – Tensibai Mar 23 '17 at 13:04
  • 1
    I'd say this on-topic at SF, SO, or here. It'd be nice if the OP clarified which functions they want from NVM. – chicks Apr 17 '17 at 0:44
6

There are several options that can be used here. Maybe one of the easiest is to install each function as a real script in some directory that is added to the path. Here is how to do this:

First, we choose a path name where to store all these functions, it can be a a directory in our project, where other utility scripts used in our Makefile live. We call it nvm_install_dir

Then we write a _nvm_trampoline script which use the name it is called as to trigger the right function, and create as many file aliases to _nvm_trampoline as there are functions in the script. Note the leading underscore, hinting at the “private” character of the script.

This script can go along the lines of

#!/bin/sh
. "${NVM_DIR}/nvm.sh"

if [ "${0##*/}" = '_nvm_driver' ]; then
    : NOP
else
    eval "${0##*/}" "$@"
fi

Here we use ${0##*/} to remove any path element from the name under which our script is called. We install that script under ${nvm_install_dir} and run once the following utility script:

nvm_install_dir='SET-TO-ACTUAL-PARAMETER-VALUE!'
nvm_file="${NVM_DIR}/nvm.sh"
nvm_trampoline='_nvm_trampoline'

nvm_list_functions()
{
    awk -F'[(][)]' '$1 ~ /^nvm_[^ ]*$/{print($1)}' "${nvm_file}"
}

nvm_install()
{
    nvm_list_functions | {
        while read nvm_function; do
            ln\
                "${nvm_install_dir}/${nvm_driver}"\
                "${nvm_install_dir}/${nvm_function}"
            chmod 755 "${nvm_install_dir}/${nvm_function}"
        done
    }
}

nvm_install

It is a good practice to pack the nvm_install procedure in a real function instead of just inlining its body in the script, as it gives better testing options. (It is easier to comment out the call to nvm_install than the body of the function, if we want to experiment with the script.)

Ater this, the directory pointed to by nvm_install_dir is populated with aliases to _nvm_trampoline that are executable and delegate their work to the corresponding function. We only need to add this directory to our PATH when running make.

A second approach would be to generate pseudo commands for each nvm functions, with the following script:

nvm_file="${NVM_DIR}/nvm.sh"

nvm_list_functions()
{
    awk -F'[(][)]' '$1 ~ /^nvm_[^ ]*$/{print($1)}' "${nvm_file}"
}

nvm_generate()
{
    nvm_list_functions |  {
        while read nvm_function; do
            nvm_FUNCTION=$(printf '%s' "${nvm_function}" | tr '[a-z]' '[A-Z]')
            printf '%s=sh -c \047source "${NVM_DIR}/nvm.sh"; %s "$$@"\047 %s\n'\
                   "${nvm_FUNCTION}"\
                   "${nvm_function}"\
                   "${nvm_function}"
        done
    }
}

nvm_generate

The output of this program consists of make variable assignments

NVM_ECHO=sh -c 'source "${NVM_DIR}/nvm.sh"; nvm_echo "$$@"' nvm_echo
NVM_CD=sh -c 'source "${NVM_DIR}/nvm.sh"; nvm_cd "$$@"' nvm_cd
…
NVM_COMMAND_INFO=sh -c 'source "${NVM_DIR}/nvm.sh"; nvm_command_info "$$@"' nvm_command_info
…

It can be saved to a file Makefile.nvm or nvmtools.mk that can be included our Makefiles. Calling the function nvm_command_info is done with

${NVM_COMMAND_INFO} arg1 arg2 …


Using a customised bash profile is possible, using the assignment SHELL=/bin/bash --rcfile PATH-TO-CUSTOM-PROFILE -i but I would consider this esoteric enough to puzzle the maintenance programmer.

3

One possible issue is that the NVM install script only writes the commands to set itself up to either .bash_profile OR .bashrc. Depending on your systems configuration, it may only be setup for interactive shells, not ones spawned by other programs (more info). Sourcing the nvm setup script can be a good option, as a previous comment noted. If you have a number of commands to run for 1 make target, look into .ONESHELL. Sourcing $NVM_DIR/nvm.sh should take care of it.

Another option that invokes bash directly with the interactive flag might work as well.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.