Proposal: add command-chain to apps instead of generating opaque wrappers

Background

Today, depending on the plugin being used, the snapcraft CLI may generate a wrapper per app containing various environment variables, sourcing scripts, etc. before actually calling the command defined. This can look pretty confusing and ugly if you’re not expecting it.

Proposal

Rather than have the snapcraft CLI magically create a wrapper per app, it should install new scripts into the snap and use the command-chain proposal. For example:

# <snip metadata

apps:
  my-app:
    command-chain: [command1, command2]
    command: my-app-command

# <snip parts>

This, in combination with the environment keyword, would completely eliminate wrappers and allow snap run --shell to provide an environment truly representative of the app.

4 Likes

Can you give a real example here? I’m struggling to understand what you’re proposing.

If you could give a real example of what snapcraft does now with a real command and real wrapper and how this command-chain feature will change that.

1 Like

Of course. Run snapcraft prime with this snapcraft.yaml:

name: test-snap
version: '0.1'
summary: summary
description: description

grade: devel
confinement: strict

apps:
  hello:
    command: echo "hello"

parts:
  my-part:
    plugin: nil

Even this ultra-simple example rewrites the command to use a wrapper:

$ cat prime/meta/snap.yaml | grep hello
  hello:
    command: command-hello.wrapper

$ cat prime/command-hello.wrapper 
#!/bin/sh
export PATH="$SNAP/usr/sbin:$SNAP/usr/bin:$SNAP/sbin:$SNAP/bin:$PATH"
export LD_LIBRARY_PATH=$SNAP_LIBRARY_PATH:$LD_LIBRARY_PATH
exec "echo" "hello" "$@"

In many cases, such as this one, the wrapper consists entirely of environment variables, which nowadays should be using the environment property (although hooks don’t support that yet). However, depending on the plugins used, the wrappers can be far more complex, well beyond just exporting variables. Avert your eyes:

#!/bin/sh
export PATH="$SNAP/usr/sbin:$SNAP/usr/bin:$SNAP/sbin:$SNAP/bin:$PATH"
export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$SNAP/lib:$SNAP/usr/lib:$SNAP/lib/x86_64-linux-gnu:$SNAP/usr/lib/x86_64-linux-gnu"
export ROS_MASTER_URI=http://localhost:11311
export ROS_HOME=${SNAP_USER_DATA:-/tmp}/ros
export LC_ALL=C.UTF-8
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$SNAP/lib:$SNAP/usr/lib:$SNAP/lib/x86_64-linux-gnu:$SNAP/usr/lib/x86_64-linux-gnu
export PYTHONPATH=$SNAP/usr/lib/python2.7/dist-packages${PYTHONPATH:+:$PYTHONPATH}
export PATH=$PATH:$SNAP/usr/bin


# Shell quote arbitrary string by replacing every occurrence of '
# with '\'', then put ' at the beginning and end of the string.
# Prepare yourself, fun regex ahead.
quote()
{
    for i; do
        printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/"
    done
    echo " "
}

BACKUP_ARGS=$(quote "$@")
set --

if [ -f $SNAP/opt/ros/kinetic/setup.sh ]; then
    _CATKIN_SETUP_DIR=$SNAP/opt/ros/kinetic . $SNAP/opt/ros/kinetic/setup.sh
fi

eval "set -- $BACKUP_ARGS"

export LD_LIBRARY_PATH="$SNAP/opt/ros/kinetic/lib:$SNAP/usr/lib:$SNAP/usr/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH"
export LD_LIBRARY_PATH=$SNAP_LIBRARY_PATH:$LD_LIBRARY_PATH
exec "roslaunch" "edukit_bot" "edukit_bot.launch" "$@"

The more plugins involved, the nastier it gets. This proposal is to stop dumping everything into a nasty wrapper, and simply provide an executable instead, telling snapd that it must run before the app by using the command-chain keyword.

This comes back to the templates proposal as well: we want to keep templates solely to generating YAML, not throwing more shell code in an opaque, already-nasty wrapper. This empowers it to simply add a command to the chain.

2 Likes

This is true for wrappers unless you use adapter: none

I have since added that to the reference Snapcraft.yaml reference (or https://snapdocs.labix.org/snapcraft-yaml-reference/4276). Implementation was originally discussed on Telling snapcraft to skip generating wrapper scripts

We will need to maybe either factor this into adapter or reconsider its use entirely.

If snapcraft is just adding environment variables with its wrapper script, could it instead just add to the environment: stanza of the meta/snap.yaml?

The template will do whatever it needs, if it needs a command because it does more than setup an environment variable, it will use that; if not, it will just setup environment appropriately. The only gotcha with environment is that it is a dict, and each environment entry has its own semantics on how to cascade them and we are trying to get out of the Harry Potter world with snapcraft and leave the Ministry of Magic to that realm only.

That’s not all it does. See the above post for more examples.