HiveBrain v1.2.0
Get Started
← Back to all entries
snippetMinor

How can I make use of bash functions in a makefile?

Submitted by: @import:stackexchange-devops··
0
Viewed 0 times
canmakefilemakehowfunctionsusebash

Problem

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?

Solution

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.

Code Snippets

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

if [ "${0##*/}" = '_nvm_driver' ]; then
    : NOP
else
    eval "${0##*/}" "$@"
fi
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
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
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
…
${NVM_COMMAND_INFO} arg1 arg2 …

Context

StackExchange DevOps Q#629, answer score: 6

Revisions (0)

No revisions yet.