This is a strange thing I've run into, and I don't know really where to start, so I'm just going to start rambling and hope that you guys are smart enough to make some sense out of it.

  • I have a module called Foo, let's say, that is enabled on a Drupal 6 site.
  • I have two separate drush command files in this module, foo.drush.inc and bar.drush.inc.
  • I have commands in bar.drush.inc that use the drush_COMMANDNAME() naming convention, not drush_COMMANDFILE_COMMANDNAME(). That is, the function for the command baz is drush_baz().
  • This has worked fine on older versions of drush, but now when I call drush baz, drush gives me the silent treatment. It doesn't tell me anything is wrong with the callback, and the exit code of the command is zero as if everything was fine and dandy.
  • If I change the command callback to drush_bar_baz(), the command gets executed properly.

It used to be that drush would at least tell you that it couldn't find a function to call. Have I stumbled on some weird edge case, or is that just no longer a feature in drush?

Comments

q0rban’s picture

Category: bug » support
apotek’s picture

Category: support » bug

Glad to find this. Was scratching my head with a very similar issue. The drush command (with --debug and -v) says it finds the command

Found command: bar (commandfile=foo) [0.9 sec, 87.96 MB]                                                                                                                                                                                         [bootstrap]
Command dispatch complete [0.95 sec, 87.97 MB]                                                                                                                                                                                                                          [notice]
 Timer  Cum (sec)  Count  Avg (msec) 
 page   0.873      1      873.05     

Peak memory usage was 89.7 MB [0.95 sec, 87.98 MB]                           

But the callback for that command is never executed. If I error_log(__FUNCTION__); in there, I get nothing. I can even put a call in to an undefined function "nofunction();" and there is no PHP crash. So the command is found, but the callback is never actually called. Odd that there is no debugging or error output for this situation.

q0rban’s picture

Category: bug » support

cross post. :)

greg.1.anderson’s picture

Category: support » feature
Status: Active » Patch (to be ported)

In Drush-5.x, under the conditions you describe, the following code should fire:

  // If no hook functions were found, print a warning.
  if (empty($all_available_hooks)) {
    drush_log(dt("No hook functions were found for !command.", array('!command' => $command)), 'warning');
    drush_log(dt("Available drush_invoke() hooks for !command: !available", array('!command' => $command, '!available' => "\n" . implode("\n", $all_available_hooks))), 'warning');
  }

In other words, if you do not define any valid hooks, then Drush will print all of the available hooks that were considered so that you can choose one to implement.

Marking this as 'patch to be ported' for consideration for backporting to 4.x. Probably won't do this myself, so please close if you don't want to do it.

q0rban’s picture

Version: 7.x-4.5 »
Category: feature » bug
Status: Patch (to be ported) » Active

Interesting. Looks like that code exists in drush 4.5. If I print out $all_available_hooks there, I get a huge array of functions that don't exist. Looks like this code is the culprit (notice the else statement):

      if (function_exists($func)) {
        $functions[] = $func;
        $all_available_hooks[] = $func . ' [* Defined in ' . $filename . ']';
        $available_rollbacks[] = $func . '_rollback';
        $completed[] = $func;
        $result = call_user_func_array($func, $args);
        _drush_log_drupal_messages();
        if (drush_get_error() || ($result === FALSE)) {
          $rollback = TRUE;
          // break out of the foreach variations and foreach list
          break 2;
        }
      }
      else {
        $all_available_hooks[] = $func;
      }

In light of this, switching to a bug report, since it sounds like this is supposed to work as I expected it to. Also, if I'm reading things right, it looks like this might be a bug in the 5.x branch as well.

q0rban’s picture

Title: Drush command fails silently in 4.5 » Drush command fails silently if no callback is present
Assigned: Unassigned » q0rban

Updating title, and working on a patch now.

q0rban’s picture

Status: Active » Needs review
StatusFileSize
new1 KB
new1019 bytes

Attached patches fix it for me.

greg.1.anderson’s picture

Title: Drush command fails silently if no callback is present » Drush command fails silently in 4.5
Assigned: q0rban » Unassigned
StatusFileSize
new1.81 KB

You are right. The condition (in 5.x) should have been $completed, not $all_available_hooks.

Try the following fix for Drush-5.x. I also improved the output so that it shows only the most interesting function name unless --show-invoke is specified.

q0rban’s picture

Hmm, I tried running the tests against this, and I get errors for all sorts of core commands. For example:

No hook functions were found for pm-download.                                              [warning]
Available drush_invoke() hooks for pm-download:                                            [warning]
drush_archive_pm_download_validate
drush_cache_pm_download_validate
drush_core_pm_download_validate
drush_docs_pm_download_validate
drush_field_pm_download_validate
drush_help_pm_download_validate
drush_image_pm_download_validate
drush_pm_download_validate [* Defined in /usr/local/share/drush/commands/pm/pm.drush.inc]
drush_search_pm_download_validate
drush_site_install_pm_download_validate
drush_sitealias_pm_download_validate
drush_sql_pm_download_validate
drush_test_pm_download_validate
drush_topic_pm_download_validate
drush_upgrade_pm_download_validate
drush_user_pm_download_validate
drush_variable_pm_download_validate
drush_watchdog_pm_download_validate
drush_archive_pre_pm_download
drush_cache_pre_pm_download
drush_core_pre_pm_download
drush_docs_pre_pm_download
drush_field_pre_pm_download
drush_help_pre_pm_download
drush_image_pre_pm_download
drush_pm_pre_pm_download
q0rban’s picture

Also, what is the difference between $functions and $completed? I don't see them being used anywhere else. It seems like $completed should only contain functions that didn't fall into here:

  if (drush_get_error() || ($result === FALSE)) {

But, I don't see that happening anywhere. If I'm reading it right, $functions and $completed would contain the exact same thing.

q0rban’s picture

Status: Needs review » Needs work

Yeah, now that I'm using this patch more, it seems there's something else wrong with the logic in here. Here's some output on a drush cc all call:

'all' cache was cleared                                                                  [success]
No hook functions were found for cache-clear.                                            [warning]
Available drush_invoke() hooks for cache-clear:                                          [warning]
drush_archive_cache_clear_validate
drush_cache_clear_validate
drush_core_cache_clear_validate
drush_devel_cache_clear_validate
drush_devel_generate_cache_clear_validate
drush_docs_cache_clear_validate
drush_features_cache_clear_validate
...
greg.1.anderson’s picture

Status: Needs work » Needs review

All tests pass for me. This patch is for Drush-5; are you perhaps applying it to Drush-4?

The practical difference between $functions and $completed is that $functions has an inner scope, whereas $completed is still in-scope where it is needed. You are correct that $functions is residual, though, and currently unused. It is residual, and could go away.

moshe weitzman’s picture

Code looks good. Lets remove residual code. drush_invoke() in particular needs to stay grokkable

q0rban’s picture

Ah, nevermind, I see the difference between $functions and $completed, and I see why my patch isn't working.

q0rban’s picture

Assigned: Unassigned » q0rban
Status: Needs review » Needs work

Setting to needs work, per moshe: "Lets remove residual code."

Also, I think we should add in a test for this, so this doesn't happen again. I'm going to work on this some today.

greg.1.anderson’s picture

Status: Needs work » Fixed

Committed with residual code removed. Please re-open or make a new issue to submit tests.

q0rban’s picture

Version: » All-versions-4.x-dev
Status: Fixed » Patch (to be ported)

Umm.. that kinda sucked. :)

I had assigned this to me, and said I was working on a patch. Then I go to create the patch and get conflicts b/c you already committed a patch. The commit message also only says your name in the credits.

If you're going to work on something that someone has assigned to them, I would at least try to find them in IRC and tell them to stop working on it. :)

q0rban’s picture

Version: All-versions-4.x-dev »
Status: Patch (to be ported) » Needs work

Now that I'm getting into the test, I'm thinking the exit code should be 1 when this happens. Setting this to needs work again based on that. I'm working on a patch for this. No really, I am. :P

q0rban’s picture

Status: Needs work » Needs review
StatusFileSize
new2.69 KB

Ok, here goes.

q0rban’s picture

Assigned: q0rban » Unassigned
moshe weitzman’s picture

I wish I could remember why, but I think it is a warning for a reason. I'm not eager to make this an error, until drush6 at the earliest.

q0rban’s picture

At a very basic level, why wouldn't the exit code be 1 for a drush command that didn't actually run?

Also, I'm not sure how to write the test without testing on the exit code. $this->getOutput() doesn't have anything in it, so I can't assert anything there.

moshe weitzman’s picture

Drush commands can all other drush commands. You don't know that the outermost command failed.

q0rban’s picture

Yeah, I understand. That's actually what got me here in the first place, though. The command that started this issue is actually a command that calls other commands. If commands call other commands and those commands fail, the outer command should determine how to deal with the failure. I understand that may be a lot to ask, and maybe that's why you say "Drush 6". In my opinion, a command that can't find a callback function is a very big problem, and may be even more so when you are talking about nested commands. If I have an outer custom migration command (which is how this all started), and one of those commands fails, that can be the cause of some SERIOUS problems down the line. :)

But aside from that, can someone give me some tips on how I would write a test for this if exit status is 0?

greg.1.anderson’s picture

@q0rban: Sorry about that. I should slow down in the queue -- making some errors of fact and judgment lately.

I can't think of any reason why this could not be an error; no function should ever exist without at least one callback function. I think there was a tmie when --show-invoke returned an error; that did cause a problem, because sometimes you want to use --show-invoke with a functional command. I think it's safe to return drush_set_error if $completed is empty.

That said, if the command does not return an error when there is no command callback, you could still test it in a unit test by calling the empty command via backend invoke and checking the contents of $values['output'] for the error message.

greg.1.anderson’s picture

Status: Needs review » Needs work

drush_invoke_hooks should return FALSE if you call drush_set_error. Try $return = drush_set_error(...) to effect that. It would also be an improvement to roll the two adjacent messages into the drush_set_error, so that you don't have a drush_set_error immediately followed by a drush_log on the next line (minor).

I do think it is best for drush_invoke_hooks to call drush_set_error when the command has no hooks; this is a degenerate case, and it seems like when this happens, we should force the outermost command to fail. In the rare case that the outermost command wants to suppress this error, it can reset the drush error explicitly. Also, there is some chance that the value of 'integrate' might affect how errors are propagated in backend invoke, but I didn't really investigate this; setting the error just seems like the right thing to do.

moshe weitzman’s picture

You guys are right - we should throw an error when there are no hooks. Greg can commit this when he is satisfied.

q0rban’s picture

Assigned: Unassigned » q0rban

Greg, thanks for the awesome feedback, and no worries about the earlier commit. You can't win if you have people complaining about committing something too fast, eh? :)

I'll work on this and re-roll the patch.

q0rban’s picture

Status: Needs work » Needs review
StatusFileSize
new3.06 KB

Changes include setting $return to drush_set_error(), and combining the subsequent warning into the error message.

q0rban’s picture

Any feedback on this latest patch?

greg.1.anderson’s picture

Assigned: q0rban » greg.1.anderson

Yeah, it is good; I will test and commit. I've just been really busy lately.

greg.1.anderson’s picture

Status: Needs review » Fixed

Tested and committed.

q0rban’s picture

Version: » All-versions-4.x-dev
Assigned: greg.1.anderson » q0rban
Status: Fixed » Patch (to be ported)
greg.1.anderson’s picture

Yes, this could go in Drush-4.

msonnabaum’s picture

Tried cherry-picking these commits but the test didn't pass. I'll try to look into it further tomorrow, but if someone wants to provide a 4.x patch it will have a better chance of making the 4.6 release.

greg.1.anderson’s picture

I thought about it, but I'm just not that interested in seeing this in 4.x. Sure, it's useful, but it's primarily of educational / informative value. Now that Drush-5 is out, folks writing new Drush commands (esp. people not already familiar w/ writing Drush commands) should be doing it on 5.x, not 4.x, and this patch doesn't help any other class of user. Unless a patch comes from another quarter, I'd say just skip it.

msonnabaum’s picture

Status: Patch (to be ported) » Fixed

I think I agree. Marking as fixed for now unless someone wants to reopen with a patch.

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.

Anonymous’s picture

Issue summary: View changes

Clarified a sentence.