Skip to content

force_callback prevents subcommands with excludes from running #1002

@Demonslay335

Description

@Demonslay335

While adding a new subcommand to our app and using excludes to setup mutual exclusivity with the other subcommands, we found the subcommands refuse to run, even when only one was specified at command line.

This seems to happen when force_callback is used, which sets a variable on the subcommand before the exclusion tests are performed.

Here is a minimal app to reproduce the issue. subcommand_1 has a forced callback, and prevents subcommand_2 to run, despite no parameters being passed for subcommand_1.

#include <iostream>
#include "CLI11.hpp" // v2.4.0

int main(int argc, char** argv)
{
    CLI::App app{"CLI11-excludes-bug"};
    
    auto subcommand_1 = app.add_subcommand("subcommand_1", "subcommand 1");
    subcommand_1
        ->add_flag_function(
            "-f",
            [](bool f) {
                std::cout << "subcommand_1 callback " << f << std::endl;
            },
            "subcommand_1 flag")
        ->force_callback();

    auto subcommand_2 = app.add_subcommand("subcommand_2", "subcommand 2");

    subcommand_1->excludes(subcommand_2);
    
    CLI11_PARSE(app, argc, argv);
    return 0;
}

Expected output:

>CLI11-excludes-bug.exe subcommand_2
subcomand_1 callback 0
[Exit Code 0]

Actual output:

>CLI11-excludes-bug.exe subcommand_2
subcommand_1 callback 0
subcommand_2 excludes subcommand_1
[Exit Code 108]

Basically, CLI11 is processing the forced callback for subcommand_1 first, which is setting the flag. Then, the requirements for subcommand_2 are processed; when it reaches the exclusion for subcommand_1, it sees a variable is set, and believes it is a conflict.

for(const auto &subc : exclude_subcommands_) {
if(subc->count_all() > 0) {
excluded = true;
excluder = subc->get_display_name();
}
}

I don't know the best way of fixing this in the library, other than perhaps marking the variable as being set by a forced callback (and not explicitly set by the user/program otherwise), and ignore those when counting the variables only for the exclusion check?

Our workaround has involved removing the flag lambda entirely, and putting the logic into app.parse_complete_callback() instead. This works, but decouples the flag logic a bit.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions