Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue730 #226

Draft
wants to merge 44 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
af4e2c8
[issue730] added argcomplete for driver arguments if installed
speckdavid Jul 10, 2024
2a34d67
[issue730] started with logic for tab completion
speckdavid Jul 12, 2024
5be8779
start with completion in search component
FlorianPommerening Jul 15, 2024
c784126
started communiaction between driver and search component
speckdavid Jul 15, 2024
64713a9
some prelim fixes and style
speckdavid Jul 16, 2024
e3aaf18
logic of argcomplete
speckdavid Jul 16, 2024
c0e0b8a
tab completion for translator option when called as standalone and vi…
speckdavid Jul 16, 2024
d5de54f
minor
speckdavid Jul 16, 2024
ddd7f84
some intermediate fixes
speckdavid Jul 17, 2024
3741663
some logic fixes
speckdavid Jul 17, 2024
816f983
forward cursor point to c++
FlorianPommerening Jul 17, 2024
ed83082
always set completer
FlorianPommerening Jul 17, 2024
8083992
reduce code duplication and simplify logic
FlorianPommerening Jul 17, 2024
75b6440
work on broken translate completion via driver
speckdavid Jul 17, 2024
9bda42c
extract functions for forwarding calls and fix bugs
FlorianPommerening Jul 17, 2024
91048da
added file completer to search component for --internal-plan-file op…
speckdavid Jul 18, 2024
05aa478
Make path handling OS-agnostic by using std::filesystem::path::prefer…
speckdavid Jul 18, 2024
b37280b
file completion for search component
speckdavid Jul 18, 2024
43cf4d7
tab completion docu
speckdavid Jul 18, 2024
a1f8b80
removed unncessary variable
speckdavid Jul 18, 2024
b5826a8
missing instructions for tab completion
speckdavid Jul 18, 2024
57e0f4c
removed file completer in search component and rely on a default bash…
speckdavid Jul 18, 2024
6011133
enable tab completion in build.py
FlorianPommerening Jul 18, 2024
228a1f2
remove redundant import
FlorianPommerening Jul 19, 2024
0aec464
handle default differently from aliases
FlorianPommerening Jul 19, 2024
e7151b0
improved tab completion setup instructions
speckdavid Jul 19, 2024
bed863b
fixed build script. no search configuration suggestions
speckdavid Jul 19, 2024
39a5d35
fixed issue with remaining args related to '-j' options
speckdavid Jul 19, 2024
962d2d0
imporved tab completion
speckdavid Jul 19, 2024
3ba6f84
code review of build.py
FlorianPommerening Jul 20, 2024
2276c0b
extract warning function and use BUILDS_DIR
FlorianPommerening Jul 22, 2024
def3f5a
worked on code clarity and fixed issues
speckdavid Jul 22, 2024
5ed1356
minor
speckdavid Jul 23, 2024
7c167dc
call translator standalone for tab completion
FlorianPommerening Jul 25, 2024
5a23b09
reset translator
FlorianPommerening Jul 25, 2024
23ae10a
style
FlorianPommerening Jul 25, 2024
4f5b847
zsh completion with help
FlorianPommerening Jul 26, 2024
fc46d83
style
FlorianPommerening Jul 26, 2024
7a270a0
changes from code review
FlorianPommerening Jul 29, 2024
8564b91
avoid computing default build in two places.
FlorianPommerening Jul 29, 2024
a239859
code review
FlorianPommerening Jul 31, 2024
8f6c57b
fix tests and typo
FlorianPommerening Jul 31, 2024
12b81aa
discuss local and global activation of argcomplete
FlorianPommerening Sep 6, 2024
0762dc2
fixed pip install comment
speckdavid Sep 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ and on macOS we do not test LP solvers (yet).

See [BUILD.md](BUILD.md).

## Tab completion

See [TABCOMPLETION.md](TABCOMPLETION.md).

## Contributors

Expand Down
71 changes: 71 additions & 0 deletions TABCOMPLETION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
## Tab completion for Fast Downward

We support tab completion for bash and zsh based on the python package [argcomplete](https://pypi.org/project/argcomplete/). For full support, use at least version 3.3 which can be installed via `pip`.

```bash
pip install "argcomplete>=3.3"
```

After the installation, tab completion has to be enabled in one of two ways.


### Activating tab completion globally

The global activation will enable tab completion for *all* Python files that support argcomplete (not only files related to Fast Downward). To activate the global tab completion execute the following command. Depending on your installation replace `activate-global-python-argcomplete` with `activate-global-python-argcomplete3`.

```bash
activate-global-python-argcomplete
FlorianPommerening marked this conversation as resolved.
Show resolved Hide resolved
```


### Activating tab completion locally

In contrast to global activation, local activation only enables tab completion for files called `fast-downward.py`, `build.py`, or `translate.py`. However, activation is not limited to files that support argcomplete. This means that pressing tab on older version of Fast Downward files or unrelated files with the same name may have unintended side effects. For example, with older version of Fast Downward `build.py <TAB>` will start a build without printing the output.

To activate the local tab completion, add the following commands to your `.bashrc` or `.zshrc`. Depending on your installation replace `register-python-argcomplete` with `register-python-argcomplete3`.

```bash
eval "$(register-python-argcomplete fast-downward.py)"
eval "$(register-python-argcomplete build.py)"
eval "$(register-python-argcomplete translate.py)"
```

### Activating tab completion for the search binary

If you are working with the search binary `downward` directly, adding the following commands to your `.bashrc` or `.zshrc` will enable tab completion. This is only necessary if you are not using the driver script `fast-downward.py`.

```bash
function _downward_complete() {
local IFS=$'\013'
if [[ -n "${ZSH_VERSION-}" ]]; then
local DFS=":"
local completions
local COMP_CWORD=$((CURRENT - 1))
completions=( $( "${words[1]}" --bash-complete \
"$IFS" "$DFS" "$CURSOR" "$BUFFER" "$COMP_CWORD" ${words[@]}))
if [[ $? == 0 ]]; then
_describe "${words[1]}" completions -o nosort
fi
else
local DFS=""
COMPREPLY=( $( "$1" --bash-complete \
"$IFS" "$DFS" "$COMP_POINT" "$COMP_LINE" "$COMP_CWORD" ${COMP_WORDS[@]}))
if [[ $? != 0 ]]; then
unset COMPREPLY
fi
fi
}

if [[ -n "${ZSH_VERSION-}" ]]; then
compdef _downward_complete downward
else
complete -o nosort -F _downward_complete downward
fi
```

Restart your shell afterwards.
FlorianPommerening marked this conversation as resolved.
Show resolved Hide resolved


### Limitations

The search configuration following the `--search` option is not yet covered by tab completion. For example, `fast-downward.py problem.pddl --search "ast<TAB>"` will not suggest `astar`.
161 changes: 97 additions & 64 deletions build.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
#!/usr/bin/env python3
# PYTHON_ARGCOMPLETE_OK

import argparse
import errno
import os
from pathlib import Path
import subprocess
import sys

import build_configs

try:
import argcomplete
HAS_ARGCOMPLETE = True
except ImportError:
HAS_ARGCOMPLETE = False

PROJECT_ROOT_PATH = Path(__file__).parent
SRC_PATH = PROJECT_ROOT_PATH / "src"

CONFIGS = {config: params for config, params in build_configs.__dict__.items()
if not config.startswith("_")}
DEFAULT_CONFIG_NAME = CONFIGS.pop("DEFAULT")
Expand All @@ -24,7 +36,27 @@
# Number of available CPUs as a fall-back (may be None)
NUM_CPUS = os.cpu_count()

def print_usage():

class RawHelpFormatter(argparse.HelpFormatter):
"""Preserve newlines and spacing."""
def _fill_text(self, text, width, indent):
return "".join([indent + line for line in text.splitlines(True)])

def _format_args(self, action, default_metavar):
"""Show explicit help for remaining args instead of "..."."""
if action.nargs == argparse.REMAINDER:
return "[BUILD ...] [CMAKE_OPTION ...]"
else:
return argparse.HelpFormatter._format_args(self, action, default_metavar)


def parse_args():
description = """Build one or more predefined build configurations of Fast Downward. Each build
uses CMake to compile the code. Build configurations differ in the parameters
they pass to CMake. By default, the build uses all available cores if this
number can be determined. Use the "-j" option for CMake to override this
default behaviour.
"""
script_name = os.path.basename(__file__)
configs = []
for name, args in sorted(CONFIGS.items()):
Expand All @@ -34,56 +66,54 @@ def print_usage():
name += " (default with --debug)"
configs.append(name + "\n " + " ".join(args))
configs_string = "\n ".join(configs)
cmake_name = os.path.basename(CMAKE)
generator_name = CMAKE_GENERATOR.lower()
default_config_name = DEFAULT_CONFIG_NAME
debug_config_name = DEBUG_CONFIG_NAME
print(f"""Usage: {script_name} [BUILD [BUILD ...]] [--all] [--debug] [MAKE_OPTIONS]

Build one or more predefined build configurations of Fast Downward. Each build
uses {cmake_name} to compile the code using {generator_name} . Build configurations
differ in the parameters they pass to {cmake_name}. By default, the build uses all
available cores if this number can be determined. Use the "-j" option for
{cmake_name} to override this default behaviour.

Build configurations
epilog = f"""build configurations:
{configs_string}

--all Alias to build all build configurations.
--debug Alias to build the default debug build configuration.
--help Print this message and exit.

Make options
All other parameters are forwarded to the build step.

Example usage:
./{script_name} # build {default_config_name} in #cores threads
./{script_name} -j4 # build {default_config_name} in 4 threads
./{script_name} debug # build debug
./{script_name} --debug # build {debug_config_name}
./{script_name} release debug # build release and debug configs
./{script_name} --all VERBOSE=true # build all build configs with detailed logs
""")


def get_project_root_path():
import __main__
return os.path.dirname(__main__.__file__)


def get_builds_path():
return os.path.join(get_project_root_path(), "builds")


def get_src_path():
return os.path.join(get_project_root_path(), "src")
example usage:
{script_name} # build {DEFAULT_CONFIG_NAME} with #cores parallel processes
{script_name} -j4 # build {DEFAULT_CONFIG_NAME} with 4 parallel processes
speckdavid marked this conversation as resolved.
Show resolved Hide resolved
{script_name} debug # build debug
{script_name} --debug # build {DEBUG_CONFIG_NAME}
{script_name} release debug # build release and debug configs
{script_name} --all VERBOSE=true # build all build configs with detailed logs
"""

parser = argparse.ArgumentParser(
description=description, epilog=epilog, formatter_class=RawHelpFormatter)
parser.add_argument("--debug", action="store_true",
help="alias to build the default debug build configuration")
parser.add_argument("--all", action="store_true",
help="alias to build all build configurations")
remaining_args = parser.add_argument("arguments", nargs=argparse.REMAINDER,
help="build configurations (see below) or build options")
remaining_args.completer = complete_arguments

if HAS_ARGCOMPLETE:
argcomplete.autocomplete(parser)

# With calls like 'build.py -j4' the parser would try to interpret '-j4' as
# an option and fail to parse. We use parse_known_args to parse everything
# we recognize and add everything else to the list of arguments.
args, unparsed_args = parser.parse_known_args()
args.arguments = unparsed_args + args.arguments

return args


def complete_arguments(prefix, parsed_args, **kwargs):
# This will modify parsed_args in place. This is not a problem because the
# process will stop after generating suggestions for the tab completion.
split_args(parsed_args)
FlorianPommerening marked this conversation as resolved.
Show resolved Hide resolved
unused_configs = set(CONFIGS) - set(parsed_args.config_names)
return sorted([c for c in unused_configs if c.startswith(prefix)])


def get_build_path(config_name):
return os.path.join(get_builds_path(), config_name)
return PROJECT_ROOT_PATH / "builds" / config_name


def try_run(cmd):
print(f'Executing command "{" ".join(cmd)}"')
print(f"""Executing command '{" ".join(cmd)}'""")
try:
subprocess.check_call(cmd)
except OSError as exc:
Expand All @@ -94,17 +124,18 @@ def try_run(cmd):
else:
raise


def build(config_name, configure_parameters, build_parameters):
print(f"Building configuration {config_name}.")

build_path = get_build_path(config_name)
generator_cmd = [CMAKE, "-S", get_src_path(), "-B", build_path]
generator_cmd = [CMAKE, "-S", str(SRC_PATH), "-B", str(build_path)]
if CMAKE_GENERATOR:
generator_cmd += ["-G", CMAKE_GENERATOR]
generator_cmd += configure_parameters
try_run(generator_cmd)

build_cmd = [CMAKE, "--build", build_path]
build_cmd = [CMAKE, "--build", str(build_path)]
if NUM_CPUS:
build_cmd += ["-j", f"{NUM_CPUS}"]
if build_parameters:
Expand All @@ -114,25 +145,27 @@ def build(config_name, configure_parameters, build_parameters):
print(f"Built configuration {config_name} successfully.")


def split_args(args):
args.config_names = []
args.build_parameters = []
for arg in args.arguments:
if arg in CONFIGS:
args.config_names.append(arg)
elif arg != "--":
args.build_parameters.append(arg)

if args.debug:
args.config_names.append(DEBUG_CONFIG_NAME)
if args.all:
args.config_names.extend(sorted(CONFIGS.keys()))

speckdavid marked this conversation as resolved.
Show resolved Hide resolved

def main():
config_names = []
build_parameters = []
for arg in sys.argv[1:]:
if arg == "--help" or arg == "-h":
print_usage()
sys.exit(0)
elif arg == "--debug":
config_names.append(DEBUG_CONFIG_NAME)
elif arg == "--all":
config_names.extend(sorted(CONFIGS.keys()))
elif arg in CONFIGS:
config_names.append(arg)
else:
build_parameters.append(arg)
if not config_names:
config_names.append(DEFAULT_CONFIG_NAME)
for config_name in config_names:
build(config_name, CONFIGS[config_name], build_parameters)
args = parse_args()
split_args(args)
for config_name in args.config_names or [DEFAULT_CONFIG_NAME]:
build(config_name, CONFIGS[config_name],
args.build_parameters)


if __name__ == "__main__":
Expand Down
Loading
Loading