drush icon indicating copy to clipboard operation
drush copied to clipboard

Document how to most easily make `vendor/bin/drush` available as `drush`

Open gitressa opened this issue 2 years ago • 21 comments

Is your feature request related to a problem? Please describe.

The vast majority of Drupal sites use Drush on a Linux server. After installing Drush, you need to manually add it to the $PATH, or else you need to use vendor/bin/drush, which is not ideal.

Describe the solution you'd like

After installing Drush, it would be awesome if you could use the command drush, without any extra steps, but this is not possible, since the Drush folder needs to be added to the $PATH manually. But we could look for alternative solutions, or expand the documentation.

Additional context

drupal.org issue: https://www.drupal.org/project/ideas/issues/3405516

Possible solutions

Add in a Bash shell script file such as ~/.bashrc, and source it to take effect.

Using $PATH Add Drush folder to $PATH (reset with source /etc/profile)

PATH=$PATH:./vendor/bin

If there's only one Drupal installation on the system, you can add the absolute path to the Drush folder, and now call drush from anywhere.

PATH=$PATH:/path/to/project/vendor/bin

Function with Git Use Git with a function, to allow running drush inside sub-folders, shared by chx. May only work with absolute Drush path registered in $PATH (remove with unset -f drush).

function drush () {
  $(git rev-parse --show-toplevel)/vendor/bin/drush "$@"
}

Not recommended (alias)

alias drush='vendor/bin/drush': This may seem like a simple and good solution, but Drush will sometimes call itself via exec. When it does this, Drush expects that drush will be in the $PATH. If you use an alias, this redispatch might not work correctly.

gitressa avatar Dec 01 '23 17:12 gitressa

It looks like better documentation is the best way forward, so adding some alternatives in the issue summary under "Possible solutions".

Alias vs. $PATH What are the downsides to using an alias, as opposed to adding to $PATH? They share the same requirement, that they are to be used from the project root. As I see it, adding an alias is easier to deal with, and more immediately understandable.

But maybe there are good reasons to use $PATH instead?

gitressa avatar Dec 02 '23 09:12 gitressa

Drush will sometimes call itself via exec. When it does this, Drush expects that drush will be in the $PATH. If you use an alias, this redispatch might not work correctly. In the past, Drush took some pains to look at $argv[0] and etc to try and call Drush with the same path and ini settings & etc as it was launched with; this code was simplified some time ago, with the stipulation that now, drush must be in the $PATH.

If you use an alias, perhaps it could find the drush it wants and then set a new PATH that includes its directory. I don't think I've seen any techniques try to do that, but in theory it might work.

greg-1-anderson avatar Dec 03 '23 03:12 greg-1-anderson

Thanks for clarifying @greg-1-anderson, that's an important piece of the puzzle.

While updating the $PATH as part of calling an alias might work, the added complexity defeats the ease of using alias in the first place, I think ... but maybe it's doable?

I'll move using an alias out of the possible solutions, and add your explanation why it's probably not a great idea.

I suppose the function drush () solution have the same problem? Or maybe, if you both add the function and register the absolute path in $PATH, all bases are covered?

PATH=$PATH:/path/to/project/vendor/bin
function drush () {
  $(git rev-parse --show-toplevel)/vendor/bin/drush "$@"
}

This raises another question for me: Is it actually required to add the Drush folder to $PATH for those occasions where it calls itself via exec, even if you're using vendor/bin/drush?

gitressa avatar Dec 03 '23 09:12 gitressa

My thought was to use a function, and add Drush to the PATH inside the body. Something like this:

PATH=$PATH:/path/to/project/vendor/bin
function drush () {
  DRUSH_BIN="$(git rev-parse --show-toplevel)/vendor/bin"
  PATH="$DRUSH_BIN:$PATH" "$DRUSH_BIN/drush" "$@"
}

Untested, but should work in concept.

greg-1-anderson avatar Dec 03 '23 13:12 greg-1-anderson

Here's a git free function, could be useful on production servers where git could not be available :

drush() {
  cur_path=$(pwd)
  while [[ "$cur_path" != "" && ! -e "$cur_path/vendor/bin/drush" ]]; do
    cur_path="${cur_path%/*}"
  done

  if [ -e "$cur_path/vendor/bin/drush" ]; then
    eval "$cur_path/vendor/bin/drush $@"
  else
    echo "Could not found drush, maybe your not in a drupal folder or drush is not installed. See https://www.drupal.org/docs/develop/development-tools/drush"
  fi
}

obriat avatar Dec 04 '23 10:12 obriat

Thanks @greg-1-anderson! though your suggestion is using the Git function, not alias ...

Also, do you know the answer to this question?

[...] Is it actually required to add the Drush folder to $PATH for those occasions where it calls itself via exec, even if you're using vendor/bin/drush?

And thanks @obriat! That seems to work well. Perhaps you can briefly describe how it works? Mostly this bit: cur_path="${cur_path%/*}"

gitressa avatar Dec 04 '23 11:12 gitressa

Hi, Sorry, this is stackoverflow copy/past :), after some research, it's called "pattern filtering", "/*" removes the rightist folder of the path string (just like .. while do).

Here's the explanation: https://stackoverflow.com/questions/10535985/how-to-remove-filename-prefix-with-a-posix-shell/25536935#25536935

But I don't know how to add the Drush folder to $PATH, specially without polluting the main $PATH and if eval is the best way to execute drush

obriat avatar Dec 04 '23 13:12 obriat

@gitressa: As I mentioned in https://github.com/drush-ops/drush/issues/5828#issuecomment-1837320691, it is actually necessary to add Drush to the PATH.

Sorry for being indistinct about alias vs function. They are about the same, behavior-wise. Above, when I was talking about the limitations of aliases, I meant that these limitations also applied to functions. The main advantage of functions over aliases is that you can express them over multiple lines, so they are easier to read.

@obriat: See https://github.com/drush-ops/drush/issues/5828#issuecomment-1837478344 for an example of adding Drush to the PATH. I haven't tried this function, but it might work.

greg-1-anderson avatar Dec 04 '23 13:12 greg-1-anderson

@greg-1-anderson I'm afraid that your proposition will add the drush path to the main $PATH each time it is executed and it could be worst if you have several projet on the same machine.

Here's a proposition that should leave the $PATH untouched (but I'm not a shell expert so there should be room for improvement):

drush() {
  PROJECT_PATH=$(pwd)
  while [[ "$PROJECT_PATH" != "" && ! -e "$PROJECT_PATH/vendor/bin/drush" ]]; do
    PROJECT_PATH="${PROJECT_PATH%/*}"
  done

  if [ -e "$PROJECT_PATH/vendor/bin/drush" ]; then
    SAVE_PATH=$PATH
    PATH="$PROJECT_PATH/vendor/bin/drush:$PATH"
    $PROJECT_PATH/vendor/bin/drush $@
    PATH="$SAVE_PATH"
  else
    echo "Could not found drush, maybe your not in a drupal folder or drush is not installed. See https://www.drupal.org/docs/develop/development-tools/drush"
  fi
}

BONUS: This function should be added to the default #/.*shrc with a drush command (drush shell:alias (--disable), similar to drush completion). It should be auto-installed with a post-install composer script.

obriat avatar Dec 04 '23 14:12 obriat

Thanks for confirming that it's always necessary to add Drush to the PATH @greg-1-anderson.

And thanks for experimenting with functions @obriat :)

Since development environments such as DDEV and Lando offer ddev drush or lando drush by default, it is not necessary to configure PATH doing work locally, on your own machine with these tools.

It then follows that the documentation is mostly needed for configuring servers, where in my opinion, there is for the most situations no need to work from anything else than the root of the project, to run git pull, drush config:import, drush updatedb, etc. From that follows that a simple PATH=$PATH:./vendor/binshould be the recommended solution, possibly using the full path to Drush, if there's only one installation on the system.

This is pretty much what is already documented on https://www.drush.org/latest/install/ under "2. Execution". I originally thought that maybe an alternative (and simple?) solution would be possible, which could be added to a "How to run simply drush"-documentation page -- but it doesn't seem like it, so far ...

gitressa avatar Dec 04 '23 17:12 gitressa

@obriat You are correct; however, you can fix that problem by adding parenthesis to my original suggestion:

function drush () {
  (
    DRUSH_BIN="$(git rev-parse --show-toplevel)/vendor/bin"
    PATH="$DRUSH_BIN:$PATH" "$DRUSH_BIN/drush" "$@"
  )
}

I thought that the function isolated the PATH variable, since I didn't export it, but I was wrong. The parenthesis fork a subshell, which isolates the change to the PATH variable.

greg-1-anderson avatar Dec 04 '23 22:12 greg-1-anderson

@gitressa Outside from "containered" dev tools, I've seen a lot of servers where more than one drupal site are running. So IMHO, a 15 lines function that auto detect the correct drush to execute and doesn't depend on third party tools like git is a nice feature for sys admin (eg: editing settings.php and running drush cr)

@greg-1-anderson thanks for the tip, here's my new version (much longer but without git dependency):

drush() {
  (
   PROJECT_PATH=$(pwd)
    while [[ "$PROJECT_PATH" != "" && ! -e "$PROJECT_PATH/vendor/bin/drush" ]]; do
      PROJECT_PATH="${PROJECT_PATH%/*}"
    done

    if [ -e "$PROJECT_PATH/vendor/bin/drush" ]; then
      PATH="$PROJECT_PATH/vendor/bin/drush:$PATH" $PROJECT_PATH/vendor/bin/drush $@
    else
      echo "Could not found drush, maybe your not in a drupal folder or drush is not installed. See https://www.drupal.org/docs/develop/development-tools/drush"
    fi
  )
}

obriat avatar Dec 04 '23 23:12 obriat

Sure @obriat, but if you're always running commands from the project root, I just can't see why it's needed ... Maybe you can expand with some more context, and clarify this?

gitressa avatar Dec 05 '23 10:12 gitressa

Here's my really short version that makes git optional:

function drush () {
  (
    DRUSH_BIN="$(git rev-parse --show-toplevel 2>/dev/null || echo ".")/vendor/bin"
    PATH="$DRUSH_BIN:$PATH" "$DRUSH_BIN/drush" "$@"
  )
}

Works only at the project root if you don't have git, or anywhere in the git project if you do.

greg-1-anderson avatar Dec 05 '23 14:12 greg-1-anderson

Just posting some (random) solutions that are worked on:

I think whatever solution we land on, it should be maintained here under "drush-ops". I think we should have something ready instead of too many options in the install instructions.

hansfn avatar Dec 05 '23 17:12 hansfn

@gitressa As I said, as a sysadmin I had to do some tasks on random subfolders or files (cache settings,…) and run drush (cr mostly). So in this case it could be useful to have a command that does not depend on a specific location.

It’s this kind of “install and forget” setup that could bring a bit of magic that will make drush more easy to use, specially for people who are not Drupal specialists.

obriat avatar Dec 05 '23 17:12 obriat

I created dasginganinja/drush-launcher for a system-level drush wrapper.

There was a strong need for something in my $PATH. I didn't want a bunch of dependencies. We don't have git directories on production either. I wasn't a fan of the bash based profile solutions either. And, we have multiple installations on our systems. We like running Drush contextually, too.

I'm open to PRs and want to help push this issue further. I personally feel there is still a space for the drush launcher. If it was maintained by drush-ops, then that'd be best for the community. 💯

dasginganinja avatar Dec 19 '23 17:12 dasginganinja