bashshellvisual-studio-code

Why the exported Shell functions are inaccessible in VS Code?


I've got a workspace in VS Code and want a pre-configured environment when working on it. For this purpose, I've created a script, shown below, to be sourced before launching VS Code.

#!/bin/bash

if [ -n "${SCRIPT_HELPERS_LOADED}" ]; then
    logDebug "Script helpers already loaded!"

    return 0
fi

# Some functions and variables...

for func in $(declare -F | awk '{print $3}'); do
    if [[ "${func}" != _* ]]; then
        logDebug "Exporting function: '${func?}'"

        export -f "${func?}"
    fi
done

export SCRIPT_HELPERS_LOADED=1

logInfo "Environment set up."

This is how I source this script.

projectFolder$ source scripts/script-helpers.sh
[L70][INFO] Environment set up.
projectFolder$ logInfo "Just a message."
[L2][INFO] Just a message.
projectFolder$ code .

As you can see, I'm able to use one of the exported functions in the same terminal. When VS Code is launched, the exported functions are not accessible through its integrated terminal. Somehow, the exported variables are accessible.

projectFolder$ echo $SCRIPT_HELPERS_LOADED 
1
projectFolder$ logInfo "Another message"
logInfo: command not found 

How can I solve this problem?



Solution

  • Solution Omitting the tasks.json

    I might have found a solution thanks to a colleague. Appending the below to the .vscode/settings.json does exactly what I ask without modifying the existing files.

    {
        "terminal.integrated.profiles.linux": {
            "bash": {
                "path": "bash",
                "icon": "terminal-bash",
                "args": [
                    "-c",
                    "source ${workspaceFolder}/scripts/script-helpers.sh; bash"
                ]
            },
        },
        "terminal.integrated.defaultProfile.linux": "bash",
    }
    

    Wrapper Script Solution

    Somehow, the above approach corrupts shell typed tasks. For instance, the below task doesn't generate any output and it never ends. .vscode/tasks.json

    {
        "version": "2.0.0",
        "tasks":
        [
            {
                "label": "Do Something",
                "type": "shell",
                "group": "build",
                "command": [
                    "echo 'Doing something...';"
                ]
            }
        ]
    }
    

    Using bash -c "source ${workspaceFolder}/scripts/script-helpers.sh; bash" isn't suitable for tasks as VS Code spawns any task as below /usr/bin/bash <arguments from .settings.json> '-c' <task body>. Hence, if we use -c in argument list, there will be multiple -c arguments. Instead of -c, we need to use --init-file which by default equals to ${HOME}/.bashrc.

    Another problem is that providing your custom script as --init-file prevents sourcing the ${HOME}/.bashrc and you will not get your default terminal environment, e.g. custom prompt, custom commands, aliases, etc.

    Therefore, we need to create a wrapper script that sources both the custom environment script and the default environment script. Then, providing it as the --init-file will solve the problem. In my case, the wrapper script placed at .vscode/environment.sh looks as below.

    #!/bin/bash
    
    SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
    TOPDIR="$(realpath ${SCRIPT_DIR}/..)"
    
    source "${HOME}/.bashrc"
    if [ $? -ne 0 ]; then
        >&2 echo "Couldn't source '.bashrc'!";
    
        return 1;
    fi
    
    source "${TOPDIR}/scripts/script-helpers.sh"
    if [ $? -ne 0 ]; then
        >&2 echo "Couldn't source 'script-helpers.sh'!";
    
        return 1;
    fi
    

    And the .vscode/settings.json will include the below.

    {
        "terminal.integrated.profiles.linux": {
            "bash": {
                "path": "bash",
                "icon": "terminal-bash",
                "args": [
                    "--init-file",
                    "${workspaceFolder}/.vscode/environment.sh"
                ]
            },
        },
        "terminal.integrated.defaultProfile.linux": "bash",
    }