public inbox for devel@edk2.groups.io
 help / color / mirror / Atom feed
* [edk2-staging/EdkRepo] [PATCH V1 0/3] EdkRepo: Command completion in bash/zsh
@ 2020-04-01 22:34 Nate DeSimone
  2020-04-01 22:34 ` [edk2-staging/EdkRepo] [PATCH V1 1/3] EdkRepo: Generate command completion scripts Nate DeSimone
                   ` (2 more replies)
  0 siblings, 3 replies; 7+ messages in thread
From: Nate DeSimone @ 2020-04-01 22:34 UTC (permalink / raw)
  To: devel
  Cc: Ashley DeSimone, Puja Pandya, Erik Bjorge, Prince Agyeman,
	Bret Barkelew, Philippe Mathieu-Daude

This patch series adds command completions (also referred to as
[TAB] completions) to EdkRepo. It also adds the currently
checked out branch combination to the command prompt. This is a
significant quality of life improvement for EdkRepo's users.

The bash and zsh shells are supported. The reason why bash and
zsh were choosen is based on their popularity in the UNIX world.
zsh is the default shell on macOS and most contemporary Linux
distributions use bash as the default shell. Git for Windows
also uses bash.

Performance was a strong consideration in the design of this
feature. Since EdkRepo has become a fairly large amount of
Python code with lots of dependencies, the "boot time" for
the Python interperter to load and execute the EdkRepo
entrypoint is approximately 1-2 seconds. A 2 second delay
on TAB completion would not be a good user experience.
To mitigate this, instead of enumerating and instantiating
all the command classes to generate the list of EdkRepo
sub-commands every time the user presses the TAB
key, this is done once and stored in a auto-generated shell
script at installation time. This way, the shell has all the
information needed for static completions without having
to invoke the Python interperter at all.

There are cases where TAB completions need some dynamic data.
For example, completing the 3rd parameter after
"edkrepo checkout " requires the shell to know the list of
branch combinations, this requires parsing the manifest XML.
command_completion_edkrepo.py provides a means for the shell
to get that type of data. A new command_completion_edkrepo.py
script has been added for this purpose.
command_completion_edkrepo.py is callable by the shell when
dynamic completion data is needed.
command_completion_edkrepo.py has been tuned to limit imports
as much as possible so that it executes quickly.

command_completion_edkrepo.py is also used to retrieve the
currently checked out branch combination so that it can be
added to the prompt. For performance considerations,
this operation is only done iff the present working directory
was changed by the last command the shell executed.

Cc: Ashley DeSimone <ashley.e.desimone@intel.com>
Cc: Puja Pandya <puja.pandya@intel.com>
Cc: Erik Bjorge <erik.c.bjorge@intel.com>
Cc: Prince Agyeman <prince.agyeman@intel.com>
Cc: Bret Barkelew <Bret.Barkelew@microsoft.com>
Cc: Philippe Mathieu-Daude <philmd@redhat.com>
Signed-off-by: Nate DeSimone <nathaniel.l.desimone@intel.com>

Nate DeSimone (3):
  EdkRepo: Generate command completion scripts
  EdkRepo: Add command completion setup to install.py
  EdkRepo: Add command completion setup to Windows installer

 edkrepo/command_completion_edkrepo.py         |  86 ++++++
 edkrepo/edkrepo_cli.py                        |  61 +++-
 .../EdkRepoInstaller/InstallWorker.cs         | 212 +++++++++++--
 .../EdkRepoInstaller/InstallerStrings.cs      |  44 ++-
 .../Vendor/win_edkrepo_prompt.sh              |  60 ++++
 edkrepo_installer/linux-scripts/install.py    | 285 +++++++++++++++++-
 setup.py                                      |   8 +-
 7 files changed, 729 insertions(+), 27 deletions(-)
 create mode 100644 edkrepo/command_completion_edkrepo.py
 create mode 100644 edkrepo_installer/Vendor/win_edkrepo_prompt.sh

-- 
2.24.0.windows.2


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [edk2-staging/EdkRepo] [PATCH V1 1/3] EdkRepo: Generate command completion scripts
  2020-04-01 22:34 [edk2-staging/EdkRepo] [PATCH V1 0/3] EdkRepo: Command completion in bash/zsh Nate DeSimone
@ 2020-04-01 22:34 ` Nate DeSimone
  2020-04-03 21:33   ` Ashley E Desimone
  2020-04-01 22:34 ` [edk2-staging/EdkRepo] [PATCH V1 2/3] EdkRepo: Add command completion setup to install.py Nate DeSimone
  2020-04-01 22:34 ` [edk2-staging/EdkRepo] [PATCH V1 3/3] EdkRepo: Add command completion setup to Windows installer Nate DeSimone
  2 siblings, 1 reply; 7+ messages in thread
From: Nate DeSimone @ 2020-04-01 22:34 UTC (permalink / raw)
  To: devel
  Cc: Ashley DeSimone, Puja Pandya, Erik Bjorge, Prince Agyeman,
	Bret Barkelew, Philippe Mathieu-Daude

Adds code to edkrepo_cli.py to generate a bash/zsh
compatible command completion script.

Add a new command_completion_edkrepo.py script which
is callable by the shell when needed for dynamic
completion data. For example, providing completion for
"edkrepo checkout" requires the shell to know the list
of possible branch combinations, this requires parsing
the manifest XML. command_completion_edkrepo.py provides
a means for the shell to get that type of data.

Cc: Ashley DeSimone <ashley.e.desimone@intel.com>
Cc: Puja Pandya <puja.pandya@intel.com>
Cc: Erik Bjorge <erik.c.bjorge@intel.com>
Cc: Prince Agyeman <prince.agyeman@intel.com>
Cc: Bret Barkelew <Bret.Barkelew@microsoft.com>
Cc: Philippe Mathieu-Daude <philmd@redhat.com>
Signed-off-by: Nate DeSimone <nathaniel.l.desimone@intel.com>
---
 edkrepo/command_completion_edkrepo.py | 86 +++++++++++++++++++++++++++
 edkrepo/edkrepo_cli.py                | 61 ++++++++++++++++++-
 setup.py                              |  8 +--
 3 files changed, 150 insertions(+), 5 deletions(-)
 create mode 100644 edkrepo/command_completion_edkrepo.py

diff --git a/edkrepo/command_completion_edkrepo.py b/edkrepo/command_completion_edkrepo.py
new file mode 100644
index 0000000..05de9ca
--- /dev/null
+++ b/edkrepo/command_completion_edkrepo.py
@@ -0,0 +1,86 @@
+#!/usr/bin/env python3
+#
+## @file
+# command_completion_edkrepo.py
+#
+# Copyright (c) 2020, Intel Corporation. All rights reserved.<BR>
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+#
+
+import argparse
+import io
+import os
+import sys
+import traceback
+
+from edkrepo_manifest_parser.edk_manifest import ManifestXml
+from edkrepo.config import config_factory
+from edkrepo.config.config_factory import get_workspace_manifest
+
+def checkout(parsed_args, config):
+    manifest = get_workspace_manifest()
+    print(' '.join([c.name for c in manifest.combinations]))
+
+def current_combo(parsed_args, config):
+    manifest = get_workspace_manifest()
+    print(" [{}]".format(manifest.general_config.current_combo))
+
+def checkout_pin(parsed_args, config):
+    pins = []
+    manifest_directory = config['cfg_file'].manifest_repo_abs_local_path
+    manifest = get_workspace_manifest()
+    pin_folder = os.path.normpath(os.path.join(manifest_directory, manifest.general_config.pin_path))
+    for dirpath, _, filenames in os.walk(pin_folder):
+        for file in filenames:
+            pin_file = os.path.join(dirpath, file)
+            # Capture error output from manifest parser stdout so it is hidden unless verbose is enabled
+            stdout = sys.stdout
+            sys.stdout = io.StringIO()
+            pin = ManifestXml(pin_file)
+            parse_output = sys.stdout.getvalue()
+            sys.stdout = stdout
+            if parsed_args.verbose and parse_output.strip() != '':
+                print('Pin {} Parsing Errors: {}\n'.format(file, parse_output.strip()))
+            if pin.project_info.codename == manifest.project_info.codename:
+                pins.append(file)
+    print(' '.join(pins))
+
+# To add command completions for a new command, add an entry to this dictionary.
+command_completions = {
+    'current-combo': current_combo,
+    'checkout': checkout,
+    'checkout-pin': checkout_pin,
+    'chp': checkout_pin
+}
+
+def main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument("-v", "--verbose", action="store_true", help='Increases command verbosity')
+    subparsers = parser.add_subparsers(dest='subparser_name')
+    for command_completion in command_completions:
+        subparsers.add_parser(command_completion, formatter_class=argparse.RawTextHelpFormatter)
+    if len(sys.argv) <= 1:
+        return 0
+    parsed_args = parser.parse_args()
+    try:
+        command_name = parsed_args.subparser_name
+        config = {}
+        config["cfg_file"] = config_factory.GlobalConfig()
+        config["user_cfg_file"] = config_factory.GlobalUserConfig()
+        if command_name not in command_completions:
+            return 1
+        command_completions[command_name](parsed_args, config)
+        return 0
+    except Exception as e:
+        if parsed_args.verbose:
+            traceback.print_exc()
+            print("Error: {}".format(str(e)))
+        return 1
+    return 0
+
+if __name__ == "__main__":
+    try:
+        sys.exit(main())
+    except Exception as e:
+        traceback.print_exc()
+        sys.exit(1)
diff --git a/edkrepo/edkrepo_cli.py b/edkrepo/edkrepo_cli.py
index 0b69860..03061c9 100644
--- a/edkrepo/edkrepo_cli.py
+++ b/edkrepo/edkrepo_cli.py
@@ -3,7 +3,7 @@
 ## @file
 # edkrepo_cli.py
 #
-# Copyright (c) 2017- 2019, Intel Corporation. All rights reserved.<BR>
+# Copyright (c) 2017 - 2020, Intel Corporation. All rights reserved.<BR>
 # SPDX-License-Identifier: BSD-2-Clause-Patent
 #
 
@@ -29,6 +29,7 @@ from edkrepo.common.edkrepo_exception import EdkrepoException, EdkrepoGlobalConf
 from edkrepo.common.edkrepo_exception import EdkrepoWarningException
 from edkrepo.common.edkrepo_exception import EdkrepoConfigFileInvalidException
 from edkrepo.common.humble import KEYBOARD_INTERRUPT, GIT_CMD_ERROR
+from edkrepo.common.pathfix import get_actual_path
 
 def generate_command_line(command):
     parser = argparse.ArgumentParser()
@@ -100,6 +101,61 @@ def generate_command_line(command):
                 subparser_name.add_argument(('--' + arg.get('name')), action=arg_action, help=arg.get('help-text'))
     return parser
 
+command_completion_script_header='''#!/usr/bin/env bash
+#
+## @file edkrepo_completions.sh
+#
+# Automatically generated please DO NOT modify !!!
+#
+
+'''
+def generate_command_completion_script(script_filename, parser):
+    import edkrepo.command_completion_edkrepo as completion
+    commands = []
+    for action in parser._positionals._group_actions:
+        if action.choices is not None:
+            commands = [c for c in action.choices]
+            break
+    commands = sorted(commands)
+    commands_with_3rd_param_completion = [c for c in completion.command_completions if c in commands]
+    commands_with_3rd_param_completion = sorted(commands_with_3rd_param_completion)
+    with open(script_filename, 'w') as f:
+        f.write(command_completion_script_header)
+        if sys.platform == "win32":
+            command_completion_path = os.path.dirname(sys.executable)
+            command_completion_path = os.path.join(command_completion_path, 'Scripts', "command_completion_edkrepo.exe")
+            if not os.path.isfile(command_completion_path):
+                print('command_completion_edkrepo.exe not found')
+                return
+            command_completion_path = get_actual_path(command_completion_path)
+            (drive, path) = os.path.splitdrive(command_completion_path)
+            command_completion_path = '/{}{}'.format(drive.replace(':','').lower(), path.replace('\\','/'))
+            f.write("export command_completion_edkrepo_file='{}'\n".format(command_completion_path))
+            f.write('alias command_completion_edkrepo="$command_completion_edkrepo_file"\n')
+        f.write('_edkrepo_completions() {\n    if [ "${#COMP_WORDS[@]}" -eq "2" ]; then\n')
+        f.write('        COMPREPLY=($(compgen -W "{}" -- "${{COMP_WORDS[1]}}"))\n'.format(' '.join(commands)))
+        if len(commands_with_3rd_param_completion) > 0:
+            f.write('    elif [ "${#COMP_WORDS[@]}" -eq "3" ]; then\n')
+        first_loop = True
+        for command in commands_with_3rd_param_completion:
+            if first_loop:
+                f.write('        if [ "${{COMP_WORDS[1]}}" == "{}" ]; then\n'.format(command))
+                first_loop = False
+            else:
+                f.write('        elif [ "${{COMP_WORDS[1]}}" == "{}" ]; then\n'.format(command))
+            f.write('            COMPREPLY=($(compgen -W "$(command_completion_edkrepo ${COMP_WORDS[1]})" -- "${COMP_WORDS[2]}"))\n')
+        if len(commands_with_3rd_param_completion) > 0:
+            f.write('        fi\n')
+        f.write('    fi\n}\n\n')
+        if len(commands_with_3rd_param_completion) > 0:
+            if sys.platform == "win32":
+                f.write('if [ -x "$(command -v edkrepo)" ] && [ -x "$(command -v $command_completion_edkrepo_file)" ]; then\n')
+            else:
+                f.write('if [ -x "$(command -v edkrepo)" ] && [ -x "$(command -v command_completion_edkrepo)" ]; then\n')
+        else:
+            f.write('if [ -x "$(command -v edkrepo)" ]; then\n')
+        f.write('    complete -F _edkrepo_completions edkrepo\nfi\n')
+
 def main():
     command = command_factory.create_composite_command()
     config = {}
@@ -117,6 +173,9 @@ def main():
     if len(sys.argv) <= 1:
         parser.print_help()
         return 1
+    if sys.argv[1] == 'generate-command-completion-script' and len(sys.argv) >= 3:
+        generate_command_completion_script(sys.argv[2], parser)
+        return 0
     parsed_args = parser.parse_args()
     command_name = parsed_args.subparser_name
     try:
diff --git a/setup.py b/setup.py
index e14aed1..e7e6ce8 100755
--- a/setup.py
+++ b/setup.py
@@ -1,9 +1,8 @@
 #!/usr/bin/env python3
 #
-## @file
-# setup.py
+## @file setup.py
 #
-# Copyright (c) 2017 - 2019, Intel Corporation. All rights reserved.<BR>
+# Copyright (c) 2017 - 2020, Intel Corporation. All rights reserved.<BR>
 # SPDX-License-Identifier: BSD-2-Clause-Patent
 #
 
@@ -20,7 +19,8 @@ setup(name='edkrepo',
       include_package_data=True,
       entry_points={
           'console_scripts': [
-              'edkrepo = edkrepo.edkrepo_entry_point:main'
+              'edkrepo = edkrepo.edkrepo_entry_point:main',
+              'command_completion_edkrepo = edkrepo.command_completion_edkrepo:main'
               ]
           }
       )
-- 
2.24.0.windows.2


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [edk2-staging/EdkRepo] [PATCH V1 2/3] EdkRepo: Add command completion setup to install.py
  2020-04-01 22:34 [edk2-staging/EdkRepo] [PATCH V1 0/3] EdkRepo: Command completion in bash/zsh Nate DeSimone
  2020-04-01 22:34 ` [edk2-staging/EdkRepo] [PATCH V1 1/3] EdkRepo: Generate command completion scripts Nate DeSimone
@ 2020-04-01 22:34 ` Nate DeSimone
  2020-04-03 21:51   ` Ashley E Desimone
  2020-04-01 22:34 ` [edk2-staging/EdkRepo] [PATCH V1 3/3] EdkRepo: Add command completion setup to Windows installer Nate DeSimone
  2 siblings, 1 reply; 7+ messages in thread
From: Nate DeSimone @ 2020-04-01 22:34 UTC (permalink / raw)
  To: devel
  Cc: Ashley DeSimone, Puja Pandya, Erik Bjorge, Prince Agyeman,
	Bret Barkelew, Philippe Mathieu-Daude

Add configuration of command completion scripts
to install.py This enables edkrepo command completions
to work "out of box" on most Linux systems by appending
to the user's .bashrc and .zshrc startup scripts
inclusion of the EdkRepo command completion scripts.

Cc: Ashley DeSimone <ashley.e.desimone@intel.com>
Cc: Puja Pandya <puja.pandya@intel.com>
Cc: Erik Bjorge <erik.c.bjorge@intel.com>
Cc: Prince Agyeman <prince.agyeman@intel.com>
Cc: Bret Barkelew <Bret.Barkelew@microsoft.com>
Cc: Philippe Mathieu-Daude <philmd@redhat.com>
Signed-off-by: Nate DeSimone <nathaniel.l.desimone@intel.com>
---
 edkrepo_installer/linux-scripts/install.py | 285 ++++++++++++++++++++-
 1 file changed, 283 insertions(+), 2 deletions(-)

diff --git a/edkrepo_installer/linux-scripts/install.py b/edkrepo_installer/linux-scripts/install.py
index b2cdfed..52f0c52 100755
--- a/edkrepo_installer/linux-scripts/install.py
+++ b/edkrepo_installer/linux-scripts/install.py
@@ -15,6 +15,7 @@ import importlib.util
 import logging
 import os
 import platform
+import re
 import stat
 import shutil
 import subprocess
@@ -22,7 +23,7 @@ import sys
 import traceback
 import xml.etree.ElementTree as et
 
-tool_sign_on = 'Installer for edkrepo version {}\nCopyright(c) Intel Corporation, 2019'
+tool_sign_on = 'Installer for edkrepo version {}\nCopyright(c) Intel Corporation, 2020'
 
 # Data here should be maintained in a configuration file
 cfg_dir = '.edkrepo'
@@ -31,6 +32,21 @@ cfg_src_dir = os.path.abspath('config')
 whl_src_dir = os.path.abspath('wheels')
 def_python = 'python3'
 
+# ZSH Configuration options
+prompt_regex = re.compile(r"#\s+[Aa][Dd][Dd]\s+[Ee][Dd][Kk][Rr][Ee][Pp][Oo]\s+&\s+[Gg][Ii][Tt]\s+[Tt][Oo]\s+[Tt][Hh][Ee]\s+[Pp][Rr][Oo][Mm][Pp][Tt]")
+zsh_autoload_compinit_regex = re.compile(r"autoload\s+-U\s+compinit")
+zsh_autoload_bashcompinit_regex = re.compile(r"autoload\s+-U\s+bashcompinit")
+zsh_autoload_colors_regex = re.compile(r"autoload\s+-U\s+colors")
+zsh_colors_regex = re.compile(r"\n\s*colors\n")
+zsh_compinit_regex = re.compile(r"compinit\s+-u")
+zsh_bashcompinit_regex = re.compile(r"\n\s*bashcompinit\n")
+zsh_autoload_compinit = 'autoload -U compinit'
+zsh_autoload_bashcompinit = 'autoload -U bashcompinit'
+zsh_autoload_colors = 'autoload -U colors'
+zsh_colors = 'colors'
+zsh_compinit = 'compinit -u'
+zsh_bashcompinit = 'bashcompinit'
+
 def default_run(cmd):
     return subprocess.run(cmd, universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=True)
 
@@ -51,8 +67,85 @@ def get_args():
     parser.add_argument('-p', '--py', action='store', default=None, help='Specify the python command to use when installing')
     parser.add_argument('-u', '--user', action='store', default=None, help='Specify user account to install edkrepo support on')
     parser.add_argument('-v', '--verbose', action='store_true', default=False, help='Enables verbose output')
+    parser.add_argument('--no-prompt', action='store_true', default=False, help='Do NOT add EdkRepo combo and git branch to the shell prompt')
+    parser.add_argument('--prompt', action='store_true', default=False, help='Add EdkRepo combo and git branch to the shell prompt')
     return parser.parse_args()
 
+def is_prompt_customization_installed(user_home_dir):
+    script_files = [os.path.join(user_home_dir, '.bashrc'), os.path.join(user_home_dir, '.zshrc')]
+    customization_installed = True
+    for script_file in script_files:
+        if os.path.isfile(script_file):
+            with open(script_file, 'r') as f:
+                script = f.read().strip()
+        data = prompt_regex.search(script)
+        if not data:
+            customization_installed = False
+            break
+    return customization_installed
+
+__add_prompt_customization = None
+def get_add_prompt_customization(args, user_home_dir):
+    global __add_prompt_customization
+    if __add_prompt_customization is not None:
+        return __add_prompt_customization
+    if args.no_prompt:
+        __add_prompt_customization = False
+        return False
+    elif args.prompt:
+        __add_prompt_customization = True
+        return True
+    #Check if the prompt customization has already been installed
+    if is_prompt_customization_installed(user_home_dir):
+        __add_prompt_customization = False
+        return False
+    #If the prompt has not been installed and EdkRepo >= 2.0.0 is installed, then don't install the prompt customization
+    if shutil.which('edkrepo') is not None:
+        res = default_run(['edkrepo', '--version'])
+        if _check_version(res.stdout.replace('edkrepo ', '').strip(), '2.0.0') >= 0:
+            __add_prompt_customization = False
+            return False
+    #Show the user an advertisement to see if they want the prompt customization
+    from select import select
+    import termios
+    import tty
+    def get_key(timeout=-1):
+        key = None
+        old_settings = termios.tcgetattr(sys.stdin.fileno())
+        try:
+            tty.setraw(sys.stdin.fileno())
+            if timeout != -1:
+                rlist, _, _ = select([sys.stdin], [], [], timeout)
+                if rlist:
+                    key = sys.stdin.read(1)
+            else:
+                key = sys.stdin.read(1)
+        finally:
+            termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, old_settings)
+        return key
+    print('\nEdkRepo can show the checked out \033[32mcombo\033[00m and \033[36mbranch\033[00m as part of the command prompt')
+    print('For example, instead of:\n')
+    print('\033[01;32muser@machine\033[00m:\033[01;34mEdk2\033[00m$ ')
+    print('\nThe command prompt would look like:\n')
+    print('\033[01;32muser@machine\033[00m:\033[01;34mEdk2\033[00m \033[32m[Edk2Master]\033[36m (master)\033[00m$ ')
+    print('')
+    while True:
+        print('Would you like the combo and branch shown on the command prompt? [y/N] ')
+        key = get_key(120)
+        if key:
+            if key == 'y' or key == 'Y':
+                print('Y')
+                __add_prompt_customization = True
+                return True
+            if key == 'n' or key == 'N':
+                print('N')
+                __add_prompt_customization = False
+                return False
+        else:
+            print('No response after 2min... assuming no.')
+            __add_prompt_customization = False
+            return False
+
 def get_installed_packages(python_command):
     pip_cmd = [def_python, '-m', 'pip', 'list', '--legacy']
     try:
@@ -146,6 +239,176 @@ def set_execute_permissions():
                     stat_data = os.stat(py_file)
                     os.chmod(py_file, stat_data.st_mode | stat.S_IEXEC)
 
+bash_prompt_customization = r'''
+# Add EdkRepo & git to the prompt
+ps1len="${#PS1}"
+let "pos3 = ps1len - 3"
+let "pos2 = ps1len - 2"
+if [ "${PS1:pos3}" == "\\$ " ]; then
+  newps1="${PS1:0:pos3}"
+  prompt_suffix="\\$ "
+elif [ "${PS1:pos3}" == " $ " ]; then
+  newps1="${PS1:0:pos3}"
+  prompt_suffix=" $ "
+elif [ "${PS1:pos2}" == "$ " ]; then
+  newps1="${PS1:0:pos2}"
+  prompt_suffix="$ "
+else
+  newps1="$PS1"
+  prompt_suffix=""
+fi
+
+# EdkRepo combo in prompt.
+if [ -x "$(command -v edkrepo)" ] && [ -x "$(command -v command_completion_edkrepo)" ]; then
+  newps1="$newps1\[\033[32m\]\$current_edkrepo_combo"
+  current_edkrepo_combo=$(command_completion_edkrepo current-combo)
+
+  # Determining the current Edkrepo combo requires invoking Python and parsing
+  # manifest XML, which is a relatively expensive operation to do every time
+  # the user presses <Enter>.
+  # As a performance optimization, only do this if the present working directory
+  # changed
+  if [[ ! -z ${PROMPT_COMMAND+x} ]] && [[ "$PROMPT_COMMAND" != "edkrepo_combo_chpwd" ]]; then
+    old_prompt_command=$PROMPT_COMMAND
+  fi
+  old_pwd=$(pwd)
+  edkrepo_combo_chpwd() {
+      if [[ "$(pwd)" != "$old_pwd" ]]; then
+        old_pwd=$(pwd)
+        current_edkrepo_combo=$(command_completion_edkrepo current-combo)
+      fi
+      if [[ ! -z ${PROMPT_COMMAND+x} ]]; then
+        eval $old_prompt_command
+      fi
+  }
+  PROMPT_COMMAND=edkrepo_combo_chpwd
+fi
+
+# Git branch in prompt.
+parse_git_branch() {
+  git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/ (\1)/'
+}
+
+PS1="$newps1\[\033[36m\]\$(parse_git_branch)\[\033[00m\]$prompt_suffix"
+'''
+
+zsh_prompt_customization = r'''
+# Add EdkRepo & git to the prompt
+prompt_length="${#PROMPT}"
+let "pos4 = prompt_length - 3"
+let "pos3 = prompt_length - 2"
+if [ "${PROMPT[$pos4,$prompt_length]}" = ' %# ' ]; then
+  new_prompt="${PROMPT[1,$pos4-1]}"
+  prompt_suffix=" %# "
+elif [ "${PROMPT[$pos3,$prompt_length]}" = "%# " ]; then
+  new_prompt="${PROMPT[1,$pos3-1]}"
+  prompt_suffix="%# "
+else
+  new_prompt="$PROMPT"
+  prompt_suffix=""
+fi
+
+# EdkRepo combo in prompt.
+if [ -x "$(command -v edkrepo)" ] && [ -x "$(command -v command_completion_edkrepo)" ]; then
+  new_prompt="$new_prompt%{$fg[green]%}\$current_edkrepo_combo%{$reset_color%}"
+  current_edkrepo_combo=$(command_completion_edkrepo current-combo)
+
+  # Determining the current Edkrepo combo requires invoking Python and parsing
+  # manifest XML, which is a relatively expensive operation to do every time
+  # the user presses <Enter>.
+  # As a performance optimization, only do this if the present working directory
+  # changed
+  function edkrepo_combo_chpwd() {
+    current_edkrepo_combo=$(command_completion_edkrepo current-combo)
+  }
+  chpwd_functions=(${chpwd_functions[@]} "edkrepo_combo_chpwd")
+fi
+
+# Load version control information
+autoload -Uz vcs_info
+precmd() { vcs_info }
+
+# Format the vcs_info_msg_0_ variable
+zstyle ':vcs_info:git:*' formats " %{$fg[cyan]%}(%b)%{$reset_color%}"
+
+# Set up the prompt (with git branch name)
+setopt PROMPT_SUBST
+eval "PROMPT='$new_prompt\${vcs_info_msg_0_}\$prompt_suffix'"
+'''
+
+def add_command_to_startup_script(script_file, regex, command, username):
+    script = ''
+    if os.path.isfile(script_file):
+        with open(script_file, 'r') as f:
+            script = f.read().strip()
+    data = regex.search(script)
+    if not data:
+        if script == '':
+            script = command
+        else:
+            script = '{}\n{}'.format(script, command)
+        with open(script_file, 'w') as f:
+            f.write(script)
+
+def add_command_comment_to_startup_script(script_file, regex, command, comment, username):
+    script = ''
+    if os.path.isfile(script_file):
+        with open(script_file, 'r') as f:
+            script = f.read().strip()
+    (new_script, subs) = re.subn(regex, command, script)
+    if subs == 0:
+        command = '\n{1}\n{0}\n'.format(command, comment)
+        if script == '':
+            new_script = command
+        else:
+            new_script = '{}\n{}'.format(script, command)
+    if new_script != script:
+        with open(script_file, 'w') as f:
+            f.write(new_script)
+        shutil.chown(script_file, user=username)
+        os.chmod(script_file, 0o644)
+
+def add_command_completions_to_shell(command_completion_script, args, username, user_home_dir):
+    # Add "source ~/.bashrc" to ~/.bash_profile if it does not have it already
+    bash_profile_file = os.path.join(user_home_dir, '.bash_profile')
+    bash_profile = ''
+    if os.path.isfile(bash_profile_file):
+        with open(bash_profile_file, 'r') as f:
+            bash_profile = f.read().strip()
+    profile_source_regex = re.compile(r"source\s+~/\.bashrc")
+    profile_source_regex2 = re.compile(r".\s+~/\.bashrc")
+    data = profile_source_regex.search(bash_profile)
+    if not data:
+        data = profile_source_regex2.search(bash_profile)
+        if not data:
+            if bash_profile == '':
+                bash_profile = 'source ~/.bashrc\n'
+            else:
+                bash_profile = '{}\nsource ~/.bashrc\n'.format(bash_profile)
+            with open(bash_profile_file, 'w') as f:
+                f.write(bash_profile)
+            shutil.chown(bash_profile_file, user=username)
+            os.chmod(bash_profile_file, 0o644)
+
+    # Add edkrepo command completion to ~/.bashrc if it does not have it already
+    regex = r"\[\[\s+-r\s+\"\S*edkrepo_completions.sh\"\s+\]\]\s+&&\s+.\s+\"\S*edkrepo_completions.sh\""
+    new_source_line = '[[ -r "{0}" ]] && . "{0}"'.format(command_completion_script)
+    comment = '\n# Add EdkRepo command completions'
+    bash_rc_file = os.path.join(user_home_dir, '.bashrc')
+    add_command_comment_to_startup_script(bash_rc_file, regex, new_source_line, comment, username)
+    if get_add_prompt_customization(args, user_home_dir):
+        add_command_to_startup_script(bash_rc_file, prompt_regex, bash_prompt_customization, username)
+    zsh_rc_file = os.path.join(user_home_dir, '.zshrc')
+    add_command_to_startup_script(zsh_rc_file, zsh_autoload_compinit_regex, zsh_autoload_compinit, username)
+    add_command_to_startup_script(zsh_rc_file, zsh_autoload_bashcompinit_regex, zsh_autoload_bashcompinit, username)
+    add_command_to_startup_script(zsh_rc_file, zsh_autoload_colors_regex, zsh_autoload_colors, username)
+    add_command_to_startup_script(zsh_rc_file, zsh_colors_regex, zsh_colors, username)
+    add_command_to_startup_script(zsh_rc_file, zsh_compinit_regex, zsh_compinit, username)
+    add_command_to_startup_script(zsh_rc_file, zsh_bashcompinit_regex, zsh_bashcompinit, username)
+    add_command_comment_to_startup_script(zsh_rc_file, regex, new_source_line, comment, username)
+    if get_add_prompt_customization(args, user_home_dir):
+        add_command_to_startup_script(zsh_rc_file, prompt_regex, zsh_prompt_customization, username)
+
 def do_install():
     global def_python
     org_python = None
@@ -209,6 +472,7 @@ def do_install():
         log.info('- Unable to determine users home directory')
         return 1
     default_cfg_dir = os.path.join(user_home_dir, cfg_dir)
+    get_add_prompt_customization(args, user_home_dir)
     log.info('+ System information collected')
 
     # Display current system information.
@@ -348,7 +612,7 @@ def do_install():
         #Delete obsolete dependencies
         if updating_edkrepo:
             installed_packages = get_installed_packages(def_python)
-            for whl_name in ['smmap2', 'gitdb2']:
+            for whl_name in ['smmap2', 'gitdb2', 'edkrepo-internal']:
                 if whl_name in installed_packages:
                     try:
                         res = default_run([def_python, '-m', 'pip', 'uninstall', '--yes', whl_name])
@@ -375,6 +639,23 @@ def do_install():
     set_execute_permissions()
     log.info('+ Marked scripts as executable')
 
+
+    #Install the command completion script
+    if shutil.which('edkrepo') is not None:
+        if args.local:
+            command_completion_script = os.path.join(default_cfg_dir, 'edkrepo_completions.sh')
+        else:
+            command_completion_script = os.path.join('/', 'etc', 'profile.d', 'edkrepo_completions.sh')
+        try:
+            res = default_run(['edkrepo', 'generate-command-completion-script', command_completion_script])
+            if args.local:
+                shutil.chown(command_completion_script, user=username)
+                os.chmod(command_completion_script, 0o644)
+            add_command_completions_to_shell(command_completion_script, args, username, user_home_dir)
+        except:
+            log.info('- Failed to configure edkrepo command completion')
+            if args.verbose:
+                traceback.print_exc()
     log.log(logging.PRINT, '\nInstallation complete\n')
 
     return 0
-- 
2.24.0.windows.2


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [edk2-staging/EdkRepo] [PATCH V1 3/3] EdkRepo: Add command completion setup to Windows installer
  2020-04-01 22:34 [edk2-staging/EdkRepo] [PATCH V1 0/3] EdkRepo: Command completion in bash/zsh Nate DeSimone
  2020-04-01 22:34 ` [edk2-staging/EdkRepo] [PATCH V1 1/3] EdkRepo: Generate command completion scripts Nate DeSimone
  2020-04-01 22:34 ` [edk2-staging/EdkRepo] [PATCH V1 2/3] EdkRepo: Add command completion setup to install.py Nate DeSimone
@ 2020-04-01 22:34 ` Nate DeSimone
  2020-04-03 21:55   ` Ashley E Desimone
  2 siblings, 1 reply; 7+ messages in thread
From: Nate DeSimone @ 2020-04-01 22:34 UTC (permalink / raw)
  To: devel
  Cc: Ashley DeSimone, Puja Pandya, Erik Bjorge, Prince Agyeman,
	Bret Barkelew, Philippe Mathieu-Daude

Add configuration of command completion scripts
to the Windows installer. This enables edkrepo command
completions to work "out of box" on Git for Windows by
adding the edkrepo command completions scripts to
the Git for Windows /etc/profile.d directory

Cc: Ashley DeSimone <ashley.e.desimone@intel.com>
Cc: Puja Pandya <puja.pandya@intel.com>
Cc: Erik Bjorge <erik.c.bjorge@intel.com>
Cc: Prince Agyeman <prince.agyeman@intel.com>
Cc: Bret Barkelew <Bret.Barkelew@microsoft.com>
Cc: Philippe Mathieu-Daude <philmd@redhat.com>
Signed-off-by: Nate DeSimone <nathaniel.l.desimone@intel.com>
---
 .../EdkRepoInstaller/InstallWorker.cs         | 212 ++++++++++++++++--
 .../EdkRepoInstaller/InstallerStrings.cs      |  44 +++-
 .../Vendor/win_edkrepo_prompt.sh              |  60 +++++
 3 files changed, 296 insertions(+), 20 deletions(-)
 create mode 100644 edkrepo_installer/Vendor/win_edkrepo_prompt.sh

diff --git a/edkrepo_installer/EdkRepoInstaller/InstallWorker.cs b/edkrepo_installer/EdkRepoInstaller/InstallWorker.cs
index 472a6c8..679b4f4 100644
--- a/edkrepo_installer/EdkRepoInstaller/InstallWorker.cs
+++ b/edkrepo_installer/EdkRepoInstaller/InstallWorker.cs
@@ -19,6 +19,7 @@ using System.Security.AccessControl;
 using System.Security.Cryptography;
 using System.Security.Principal;
 using System.Text;
+using System.Text.RegularExpressions;
 using System.Threading;
 
 namespace TianoCore.EdkRepoInstaller
@@ -611,7 +612,7 @@ namespace TianoCore.EdkRepoInstaller
             SilentProcess process = SilentProcess.StartConsoleProcessSilently(GitPath, "--version", dataCapture.DataReceivedHandler);
             process.WaitForExit();
             PythonVersion gitVersion = new PythonVersion(dataCapture.GetData().Trim());
-            if (gitVersion < new PythonVersion(2,13,0))
+            if (gitVersion < new PythonVersion(2, 13, 0))
             {
                 InstallLogger.Log(string.Format("Git Version 2.13 or later is required. You have version {0}, please upgrade to a newer version of Git.", gitVersion));
                 ReportComplete(false, false);
@@ -624,7 +625,7 @@ namespace TianoCore.EdkRepoInstaller
             List<Tuple<string, PythonVersion>> ExclusivePackages = new List<Tuple<string, PythonVersion>>();
             foreach (PythonInstance PyInstance in PythonWheelsToInstall)
             {
-                foreach(PythonWheel Wheel in PyInstance.Wheels)
+                foreach (PythonWheel Wheel in PyInstance.Wheels)
                 {
                     if (Wheel.UninstallAllOtherCopies)
                     {
@@ -668,13 +669,13 @@ namespace TianoCore.EdkRepoInstaller
             //
             foreach (PythonVersion Obsolete in ObsoletedPythonVersions)
             {
-                if(ExistingEdkRepoPaths.Select(p => p.Item2).Contains(Obsolete))
+                if (ExistingEdkRepoPaths.Select(p => p.Item2).Contains(Obsolete))
                 {
                     foreach (string ExistingEdkrepoPythonPath in ExistingEdkRepoPaths.Where(p => p.Item2 == Obsolete).Select(p => p.Item1))
                     {
                         string UninstallerPath = Path.Combine(Path.GetDirectoryName(ExistingEdkrepoPythonPath), "Lib", "site-packages");
                         UninstallerPath = Path.Combine(UninstallerPath, Path.GetFileName(WindowsHelpers.GetApplicationPath()));
-                        if(File.Exists(UninstallerPath))
+                        if (File.Exists(UninstallerPath))
                         {
                             InstallLogger.Log(string.Format("Uninstalling {0}...", UninstallerPath));
                             string UninstallString = string.Format("\"{0}\" /Uninstall /Passive", UninstallerPath);
@@ -788,14 +789,15 @@ namespace TianoCore.EdkRepoInstaller
             //
             // Step 10 - Setup symlink to edkrepo and bash script to launch edkrepo from git bash
             //
+            string EdkrepoSymlinkPath = null;
             if (!string.IsNullOrEmpty(EdkrepoPythonPath))
             {
                 string EdkrepoScriptPath = Path.Combine(Path.GetDirectoryName(EdkrepoPythonPath), "Scripts", InstallerStrings.EdkrepoCliExecutable);
-                string EdkrepoSymlinkPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), InstallerStrings.EdkrepoCliExecutable);
+                EdkrepoSymlinkPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), InstallerStrings.EdkrepoCliExecutable);
                 if (File.Exists(EdkrepoScriptPath))
                 {
                     bool CreateSymlink = true;
-                    if(File.Exists(EdkrepoSymlinkPath))
+                    if (File.Exists(EdkrepoSymlinkPath))
                     {
                         try
                         {
@@ -805,22 +807,22 @@ namespace TianoCore.EdkRepoInstaller
                             }
                         }
                         catch (NotASymlinkException) { }
-                        if(CreateSymlink)
+                        if (CreateSymlink)
                         {
                             File.Delete(EdkrepoSymlinkPath);
                         }
                     }
-                    if(CreateSymlink)
+                    if (CreateSymlink)
                     {
                         InstallLogger.Log("Creating Symbolic Link for edkrepo.exe...");
                         WindowsHelpers.CreateSymbolicLink(EdkrepoSymlinkPath, EdkrepoScriptPath, WindowsHelpers.SYMBOLIC_LINK_FLAG.File);
                     }
                     string GitBashBinPath = Path.Combine(Path.GetDirectoryName(Path.GetDirectoryName(GitPath)), "usr", "bin");
-                    if(Directory.Exists(GitBashBinPath))
+                    if (Directory.Exists(GitBashBinPath))
                     {
                         InstallLogger.Log("Creating edkrepo launcher in Git Bash...");
                         string EdkrepoBashScriptPath = Path.Combine(GitBashBinPath, InstallerStrings.EdkrepoBashLauncherScript);
-                        if(File.Exists(EdkrepoBashScriptPath))
+                        if (File.Exists(EdkrepoBashScriptPath))
                         {
                             File.Delete(EdkrepoBashScriptPath);
                         }
@@ -838,7 +840,7 @@ namespace TianoCore.EdkRepoInstaller
             // Step 11 - Copy edkrepo config file to the edkrepo global data directory
             //
             string EdkrepoCfg = Path.Combine(Path.GetDirectoryName(WindowsHelpers.GetApplicationPath()), InstallerStrings.EdkrepoCfg);
-            if(File.Exists(EdkrepoCfg))
+            if (File.Exists(EdkrepoCfg))
             {
                 CreateEdkrepoGlobalDataDirectory();
                 string EdkrepoCfgDir = Path.Combine(WindowsHelpers.GetAllUsersAppDataPath(), InstallerStrings.EdkrepoGlobalDirectoryName);
@@ -853,7 +855,7 @@ namespace TianoCore.EdkRepoInstaller
                 {
                     string NewCfgHash = ComputeSha256(ReadFile(EdkrepoCfg));
                     string OldCfgHash = ComputeSha256(ReadFile(EdkrepoCfgTarget));
-                    if(NewCfgHash != OldCfgHash)
+                    if (NewCfgHash != OldCfgHash)
                     {
                         if (GetPreviousEdkrepoCfgFileHashes().Contains(OldCfgHash))
                         {
@@ -908,7 +910,119 @@ namespace TianoCore.EdkRepoInstaller
             }
 
             //
-            // Step 13 - Create Programs and Features uninstall links
+            // Step 13 - Copy win_edkrepo_prompt.sh and generate edkrepo_completions.sh
+            //
+            string edkrepoPromptSource = Path.Combine(Path.GetDirectoryName(WindowsHelpers.GetApplicationPath()), InstallerStrings.EdkrepoPrompt);
+
+            if (File.Exists(edkrepoPromptSource) && !string.IsNullOrEmpty(EdkrepoSymlinkPath))
+            {
+                string gitBashEtcPath = Path.Combine(Path.GetDirectoryName(Path.GetDirectoryName(GitPath)), "etc");
+                string gitBashEtcProfileDPath = Path.Combine(gitBashEtcPath, "profile.d");
+                if (Directory.Exists(gitBashEtcPath) && Directory.Exists(gitBashEtcProfileDPath))
+                {
+                    InstallLogger.Log("Installing EdkRepo command completion...");
+
+                    //Copy win_edkrepo_prompt.sh
+                    string edkrepoPromptDest = Path.Combine(gitBashEtcProfileDPath, InstallerStrings.EdkrepoPrompt);
+                    if (File.Exists(edkrepoPromptDest))
+                    {
+                        File.Delete(edkrepoPromptDest);
+                    }
+                    File.Copy(edkrepoPromptSource, edkrepoPromptDest);
+                    DirectoryInfo info = new DirectoryInfo(edkrepoPromptDest);
+                    DirectorySecurity security = info.GetAccessControl();
+                    security.AddAccessRule(new FileSystemAccessRule(
+                        new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null),
+                        FileSystemRights.FullControl,
+                        InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit,
+                        PropagationFlags.NoPropagateInherit,
+                        AccessControlType.Allow
+                        ));
+                    info.SetAccessControl(security);
+                    InstallLogger.Log(string.Format("Copied {0}", InstallerStrings.EdkrepoPrompt));
+
+                    //Generate edkrepo_completions.sh
+                    string edkrepoCompletionDest = Path.Combine(gitBashEtcProfileDPath, InstallerStrings.EdkrepoCompletion);
+                    if (File.Exists(edkrepoCompletionDest))
+                    {
+                        File.Delete(edkrepoCompletionDest);
+                    }
+                    dataCapture = new SilentProcess.StdoutDataCapture();
+                    process = SilentProcess.StartConsoleProcessSilently(
+                        EdkrepoSymlinkPath,
+                        string.Format(
+                            "generate-command-completion-script \"{0}\"",
+                            edkrepoCompletionDest),
+                        dataCapture.DataReceivedHandler);
+                    process.WaitForExit();
+                    InstallLogger.Log(EdkrepoSymlinkPath);
+                    InstallLogger.Log(edkrepoCompletionDest);
+                    InstallLogger.Log(dataCapture.GetData().Trim());
+                    if (process.ExitCode != 0)
+                    {
+                        throw new InvalidOperationException(string.Format("generate-command-completion-script failed with status {0}", process.ExitCode));
+                    }
+                    if (!File.Exists(edkrepoCompletionDest))
+                    {
+                        throw new InvalidOperationException(string.Format("generate-command-completion-script did not create {0}", InstallerStrings.EdkrepoCompletion));
+                    }
+                    info = new DirectoryInfo(edkrepoCompletionDest);
+                    security = info.GetAccessControl();
+                    security.AddAccessRule(new FileSystemAccessRule(
+                        new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null),
+                        FileSystemRights.FullControl,
+                        InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit,
+                        PropagationFlags.NoPropagateInherit,
+                        AccessControlType.Allow
+                        ));
+                    info.SetAccessControl(security);
+                    InstallLogger.Log(string.Format("Generated {0}", InstallerStrings.EdkrepoCompletion));
+
+                    //Call win_edkrepo_prompt.sh from bash.bashrc so edkrepo completions are available for "interactive non-login" bash shells
+                    string bashrcPath = Path.Combine(gitBashEtcPath, "bash.bashrc");
+                    if (File.Exists(bashrcPath))
+                    {
+                        string bashrc = Encoding.UTF8.GetString(ReadFile(bashrcPath));
+                        Match match = Regex.Match(bashrc, InstallerStrings.BashrcEdkrepoPromptCallPattern);
+                        if (match.Success)
+                        {
+                            InstallLogger.Log("EdkRepo prompt is already in bash.bashrc");
+                        }
+                        else
+                        {
+                            bashrc = string.Format("{0}{1}", bashrc, InstallerStrings.BashrcEdkRepoPromptCall);
+                            using (BinaryWriter writer = new BinaryWriter(File.Open(bashrcPath, FileMode.Truncate, FileAccess.Write)))
+                            {
+                                string sanitized = bashrc.Replace("\r\n", "\n");
+                                writer.Write(Encoding.UTF8.GetBytes(sanitized));
+                            }
+                            InstallLogger.Log("EdkRepo prompt added to bash.bashrc");
+                        }
+                    }
+                    else
+                    {
+                        InstallLogger.Log(string.Format("{0} not found", bashrcPath));
+                    }
+                }
+                else
+                {
+                    InstallLogger.Log("Git for Windows /etc/profile.d not found");
+                }
+            }
+            else
+            {
+                if (string.IsNullOrEmpty(EdkrepoSymlinkPath))
+                {
+                    InstallLogger.Log("EdkRepo symlink not found");
+                }
+                if (!File.Exists(edkrepoPromptSource))
+                {
+                    InstallLogger.Log(string.Format("{0} not found", InstallerStrings.EdkrepoPrompt));
+                }
+            }
+
+            //
+            // Step 14 - Create Programs and Features uninstall links
             //
             if (!string.IsNullOrEmpty(EdkrepoPythonPath))
             {
@@ -977,7 +1091,7 @@ namespace TianoCore.EdkRepoInstaller
             string GitPath = PythonOperations.GetFullPath("git.exe");
 
             //
-            // Step 2 - Delete symlink to edkrepo and bash script to launch it from git bash
+            // Step 2 - Delete symlink to edkrepo
             //
             string EdkrepoSymlinkPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), InstallerStrings.EdkrepoCliExecutable);
             if (File.Exists(EdkrepoSymlinkPath))
@@ -1003,7 +1117,11 @@ namespace TianoCore.EdkRepoInstaller
                     File.Delete(EdkrepoSymlinkPath);
                 }
             }
-            if (GitPath != null)
+
+            //
+            // Step 3 - Delete scripts to launch edkrepo and Python from git bash, and edkrepo command completion scripts
+            //
+            if (!string.IsNullOrWhiteSpace(GitPath))
             {
                 string GitBashBinPath = Path.Combine(Path.GetDirectoryName(Path.GetDirectoryName(GitPath)), "usr", "bin");
                 if (Directory.Exists(GitBashBinPath))
@@ -1057,10 +1175,68 @@ namespace TianoCore.EdkRepoInstaller
                         File.Delete(EdkrepoPython2ScriptPath);
                     }
                 }
+                string gitBashEtcPath = Path.Combine(Path.GetDirectoryName(Path.GetDirectoryName(GitPath)), "etc");
+                string gitBashEtcProfileDPath = Path.Combine(gitBashEtcPath, "profile.d");
+                if (Directory.Exists(gitBashEtcPath) && Directory.Exists(gitBashEtcProfileDPath))
+                {
+                    string edkrepoPromptDest = Path.Combine(gitBashEtcProfileDPath, InstallerStrings.EdkrepoPrompt);
+                    if (File.Exists(edkrepoPromptDest))
+                    {
+                        AllowCancel(false);
+                        if (CancelPending())
+                        {
+                            ReportComplete(true, true);
+                            return;
+                        }
+                        InstallLogger.Log(string.Format("Deleting {0}...", InstallerStrings.EdkrepoPrompt));
+                        File.Delete(edkrepoPromptDest);
+                    }
+
+                    string edkrepoCompletionDest = Path.Combine(gitBashEtcProfileDPath, InstallerStrings.EdkrepoCompletion);
+                    if (File.Exists(edkrepoCompletionDest))
+                    {
+                        AllowCancel(false);
+                        if (CancelPending())
+                        {
+                            ReportComplete(true, true);
+                            return;
+                        }
+                        InstallLogger.Log(string.Format("Deleting {0}...", InstallerStrings.EdkrepoCompletion));
+                        File.Delete(edkrepoCompletionDest);
+                    }
+
+                    //Remove call win_edkrepo_prompt.sh from bash.bashrc
+                    string bashrcPath = Path.Combine(gitBashEtcPath, "bash.bashrc");
+                    if (File.Exists(bashrcPath))
+                    {
+                        string original_bashrc = Encoding.UTF8.GetString(ReadFile(bashrcPath));
+
+                        string new_bashrc = Regex.Replace(original_bashrc, InstallerStrings.BashrcEdkrepoPromptCommentPattern, "");
+                        new_bashrc = Regex.Replace(new_bashrc, InstallerStrings.BashrcEdkrepoPromptCallPattern, "");
+                        if (new_bashrc == original_bashrc)
+                        {
+                            InstallLogger.Log("EdkRepo not found in bash.bashrc");
+                        }
+                        else
+                        {
+                            new_bashrc = new_bashrc.TrimEnd();
+                            using (BinaryWriter writer = new BinaryWriter(File.Open(bashrcPath, FileMode.Truncate, FileAccess.Write)))
+                            {
+                                string sanitized = new_bashrc.Replace("\r\n", "\n");
+                                writer.Write(Encoding.UTF8.GetBytes(sanitized));
+                            }
+                            InstallLogger.Log("EdkRepo prompt removed from bash.bashrc");
+                        }
+                    }
+                    else
+                    {
+                        InstallLogger.Log(string.Format("{0} not found", bashrcPath));
+                    }
+                }
             }
 
             //
-            // Step 3 - Uninstall any instances of edkrepo
+            // Step 4 - Uninstall any instances of edkrepo
             //
             IEnumerable<string> PackagesToUninstall = GetPythonWheelsToUninstall();
             InstallLogger.Log("Determining currently installed Python packages...");
@@ -1109,7 +1285,7 @@ namespace TianoCore.EdkRepoInstaller
             }
 
             //
-            // Step 4 - Invoke the Finish Uninstall Event
+            // Step 5 - Invoke the Finish Uninstall Event
             //
             if (VendorCustomizer.Instance != null)
             {
@@ -1122,7 +1298,7 @@ namespace TianoCore.EdkRepoInstaller
             }
 
             //
-            // Step 5 - Delete Programs and Feature uninstall link
+            // Step 6 - Delete Programs and Feature uninstall link
             //
             RegistryKey hklm = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);
             RegistryKey winUninstallRegistryKey = hklm.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", true);
diff --git a/edkrepo_installer/EdkRepoInstaller/InstallerStrings.cs b/edkrepo_installer/EdkRepoInstaller/InstallerStrings.cs
index 6aa90ee..1542641 100644
--- a/edkrepo_installer/EdkRepoInstaller/InstallerStrings.cs
+++ b/edkrepo_installer/EdkRepoInstaller/InstallerStrings.cs
@@ -2,7 +2,7 @@
   InstallerStrings.cs
 
 @copyright
-  Copyright 2016 - 2019 Intel Corporation. All rights reserved.<BR>
+  Copyright 2016 - 2020 Intel Corporation. All rights reserved.<BR>
   SPDX-License-Identifier: BSD-2-Clause-Patent
 
 @par Specification Reference:
@@ -43,7 +43,7 @@ namespace TianoCore.EdkRepoInstaller
         }
 
         public static string EdkrepoPackageName
-        { 
+        {
             get
             {
                 return "edkrepo";
@@ -113,6 +113,46 @@ namespace TianoCore.EdkRepoInstaller
             }
         }
 
+        public static string EdkrepoPrompt
+        {
+            get
+            {
+                return "win_edkrepo_prompt.sh";
+            }
+        }
+
+        public static string EdkrepoCompletion
+        {
+            get
+            {
+                return "edkrepo_completions.sh";
+            }
+        }
+
+        public static string BashrcEdkrepoPromptCommentPattern
+        {
+            get
+            {
+                return @"#\s+Install\s+EdkRepo\s+command\s+completions";
+            }
+        }
+
+        public static string BashrcEdkrepoPromptCallPattern
+        {
+            get
+            {
+                return @"shopt\s+-q\s+login_shell\s+\|\|\s+\.\s+/etc/profile\.d/win_edkrepo_prompt\.sh";
+            }
+        }
+
+        public static string BashrcEdkRepoPromptCall
+        {
+            get
+            {
+                return "\n\n# Install EdkRepo command completions\nshopt -q login_shell || . /etc/profile.d/win_edkrepo_prompt.sh";
+            }
+        }
+
         public static string InstallerName
         {
             get
diff --git a/edkrepo_installer/Vendor/win_edkrepo_prompt.sh b/edkrepo_installer/Vendor/win_edkrepo_prompt.sh
new file mode 100644
index 0000000..5404175
--- /dev/null
+++ b/edkrepo_installer/Vendor/win_edkrepo_prompt.sh
@@ -0,0 +1,60 @@
+## @file win_edkrepo_prompt.sh
+# Note: For use on Git for Windows/MSYS2 ONLY.
+# UNIX version is in install.py
+#
+# Copyright (c) 2020, Intel Corporation. All rights reserved.<BR>
+# SPDX-License-Identifier: BSD-2-Clause-Patent
+#
+
+# Add EdkRepo command completions
+[[ -r "/etc/profile.d/edkrepo_completions.sh" ]] && . "/etc/profile.d/edkrepo_completions.sh"
+
+# Add EdkRepo to the prompt
+ps1len="${#PS1}"
+let "pos38 = ps1len - 38"
+let "pos3 = ps1len - 3"
+let "pos2 = ps1len - 2"
+if [ "${PS1:pos38}" == '\[\033[36m\]`__git_ps1`\[\033[0m\]\n$ ' ]; then
+  newps1="${PS1:0:pos38}"
+  prompt_suffix='\[\033[36m\]`__git_ps1`\[\033[0m\]\n$ '
+elif [ "${PS1:pos3}" == "\\$ " ]; then
+  newps1="${PS1:0:pos3}"
+  prompt_suffix="\\$ "
+elif [ "${PS1:pos3}" == " $ " ]; then
+  newps1="${PS1:0:pos3}"
+  prompt_suffix=" $ "
+elif [ "${PS1:pos2}" == "$ " ]; then
+  newps1="${PS1:0:pos2}"
+  prompt_suffix="$ "
+else
+  newps1="$PS1"
+  prompt_suffix=""
+fi
+
+if [ -x "$(command -v edkrepo)" ] && [ -x "$(command -v $command_completion_edkrepo_file)" ]; then
+  newps1="$newps1\[\033[32m\]\$current_edkrepo_combo\[\033[00m\]"
+  current_edkrepo_combo=$(command_completion_edkrepo current-combo)
+
+  # Determining the current Edkrepo combo requires invoking Python and parsing
+  # manifest XML, which is a relatively expensive operation to do every time
+  # the user presses <Enter>.
+  # As a performance optimization, only do this if the present working directory
+  # changed
+  if [[ ! -z ${PROMPT_COMMAND+x} ]] && [[ "$PROMPT_COMMAND" != "edkrepo_combo_chpwd" ]]; then
+    old_prompt_command=$PROMPT_COMMAND
+  fi
+  old_pwd=$(pwd)
+  edkrepo_combo_chpwd() {
+      if [[ "$(pwd)" != "$old_pwd" ]]; then
+        old_pwd=$(pwd)
+        current_edkrepo_combo=$(command_completion_edkrepo current-combo)
+      fi
+      if [[ ! -z ${PROMPT_COMMAND+x} ]]; then
+        eval $old_prompt_command
+      fi
+  }
+  PROMPT_COMMAND=edkrepo_combo_chpwd
+fi
+
+PS1="$newps1$prompt_suffix"
+MSYS2_PS1="$PS1"  # for detection by MSYS2 SDK's bash.basrc
-- 
2.24.0.windows.2


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [edk2-staging/EdkRepo] [PATCH V1 1/3] EdkRepo: Generate command completion scripts
  2020-04-01 22:34 ` [edk2-staging/EdkRepo] [PATCH V1 1/3] EdkRepo: Generate command completion scripts Nate DeSimone
@ 2020-04-03 21:33   ` Ashley E Desimone
  0 siblings, 0 replies; 7+ messages in thread
From: Ashley E Desimone @ 2020-04-03 21:33 UTC (permalink / raw)
  To: Desimone, Nathaniel L, devel@edk2.groups.io
  Cc: Pandya, Puja, Bjorge, Erik C, Agyeman, Prince, Bret Barkelew,
	Philippe Mathieu-Daude

The following section will need to be updated to take into account Erik's recent addition of support for archived combinations. 

+def checkout(parsed_args, config):
+    manifest = get_workspace_manifest()
+    print(' '.join([c.name for c in manifest.combinations]))

Thanks,
Ashley 

-----Original Message-----
From: Desimone, Nathaniel L <nathaniel.l.desimone@intel.com> 
Sent: Wednesday, April 1, 2020 3:35 PM
To: devel@edk2.groups.io
Cc: Desimone, Ashley E <ashley.e.desimone@intel.com>; Pandya, Puja <puja.pandya@intel.com>; Bjorge, Erik C <erik.c.bjorge@intel.com>; Agyeman, Prince <prince.agyeman@intel.com>; Bret Barkelew <Bret.Barkelew@microsoft.com>; Philippe Mathieu-Daude <philmd@redhat.com>
Subject: [edk2-staging/EdkRepo] [PATCH V1 1/3] EdkRepo: Generate command completion scripts

Adds code to edkrepo_cli.py to generate a bash/zsh compatible command completion script.

Add a new command_completion_edkrepo.py script which is callable by the shell when needed for dynamic completion data. For example, providing completion for "edkrepo checkout" requires the shell to know the list of possible branch combinations, this requires parsing the manifest XML. command_completion_edkrepo.py provides a means for the shell to get that type of data.

Cc: Ashley DeSimone <ashley.e.desimone@intel.com>
Cc: Puja Pandya <puja.pandya@intel.com>
Cc: Erik Bjorge <erik.c.bjorge@intel.com>
Cc: Prince Agyeman <prince.agyeman@intel.com>
Cc: Bret Barkelew <Bret.Barkelew@microsoft.com>
Cc: Philippe Mathieu-Daude <philmd@redhat.com>
Signed-off-by: Nate DeSimone <nathaniel.l.desimone@intel.com>
---
 edkrepo/command_completion_edkrepo.py | 86 +++++++++++++++++++++++++++
 edkrepo/edkrepo_cli.py                | 61 ++++++++++++++++++-
 setup.py                              |  8 +--
 3 files changed, 150 insertions(+), 5 deletions(-)  create mode 100644 edkrepo/command_completion_edkrepo.py

diff --git a/edkrepo/command_completion_edkrepo.py b/edkrepo/command_completion_edkrepo.py
new file mode 100644
index 0000000..05de9ca
--- /dev/null
+++ b/edkrepo/command_completion_edkrepo.py
@@ -0,0 +1,86 @@
+#!/usr/bin/env python3
+#
+## @file
+# command_completion_edkrepo.py
+#
+# Copyright (c) 2020, Intel Corporation. All rights reserved.<BR> # 
+SPDX-License-Identifier: BSD-2-Clause-Patent #
+
+import argparse
+import io
+import os
+import sys
+import traceback
+
+from edkrepo_manifest_parser.edk_manifest import ManifestXml from 
+edkrepo.config import config_factory from edkrepo.config.config_factory 
+import get_workspace_manifest
+
+def checkout(parsed_args, config):
+    manifest = get_workspace_manifest()
+    print(' '.join([c.name for c in manifest.combinations]))
+
+def current_combo(parsed_args, config):
+    manifest = get_workspace_manifest()
+    print(" [{}]".format(manifest.general_config.current_combo))
+
+def checkout_pin(parsed_args, config):
+    pins = []
+    manifest_directory = config['cfg_file'].manifest_repo_abs_local_path
+    manifest = get_workspace_manifest()
+    pin_folder = os.path.normpath(os.path.join(manifest_directory, manifest.general_config.pin_path))
+    for dirpath, _, filenames in os.walk(pin_folder):
+        for file in filenames:
+            pin_file = os.path.join(dirpath, file)
+            # Capture error output from manifest parser stdout so it is hidden unless verbose is enabled
+            stdout = sys.stdout
+            sys.stdout = io.StringIO()
+            pin = ManifestXml(pin_file)
+            parse_output = sys.stdout.getvalue()
+            sys.stdout = stdout
+            if parsed_args.verbose and parse_output.strip() != '':
+                print('Pin {} Parsing Errors: {}\n'.format(file, parse_output.strip()))
+            if pin.project_info.codename == manifest.project_info.codename:
+                pins.append(file)
+    print(' '.join(pins))
+
+# To add command completions for a new command, add an entry to this dictionary.
+command_completions = {
+    'current-combo': current_combo,
+    'checkout': checkout,
+    'checkout-pin': checkout_pin,
+    'chp': checkout_pin
+}
+
+def main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument("-v", "--verbose", action="store_true", help='Increases command verbosity')
+    subparsers = parser.add_subparsers(dest='subparser_name')
+    for command_completion in command_completions:
+        subparsers.add_parser(command_completion, formatter_class=argparse.RawTextHelpFormatter)
+    if len(sys.argv) <= 1:
+        return 0
+    parsed_args = parser.parse_args()
+    try:
+        command_name = parsed_args.subparser_name
+        config = {}
+        config["cfg_file"] = config_factory.GlobalConfig()
+        config["user_cfg_file"] = config_factory.GlobalUserConfig()
+        if command_name not in command_completions:
+            return 1
+        command_completions[command_name](parsed_args, config)
+        return 0
+    except Exception as e:
+        if parsed_args.verbose:
+            traceback.print_exc()
+            print("Error: {}".format(str(e)))
+        return 1
+    return 0
+
+if __name__ == "__main__":
+    try:
+        sys.exit(main())
+    except Exception as e:
+        traceback.print_exc()
+        sys.exit(1)
diff --git a/edkrepo/edkrepo_cli.py b/edkrepo/edkrepo_cli.py index 0b69860..03061c9 100644
--- a/edkrepo/edkrepo_cli.py
+++ b/edkrepo/edkrepo_cli.py
@@ -3,7 +3,7 @@
 ## @file
 # edkrepo_cli.py
 #
-# Copyright (c) 2017- 2019, Intel Corporation. All rights reserved.<BR>
+# Copyright (c) 2017 - 2020, Intel Corporation. All rights 
+reserved.<BR>
 # SPDX-License-Identifier: BSD-2-Clause-Patent  #
 
@@ -29,6 +29,7 @@ from edkrepo.common.edkrepo_exception import EdkrepoException, EdkrepoGlobalConf  from edkrepo.common.edkrepo_exception import EdkrepoWarningException  from edkrepo.common.edkrepo_exception import EdkrepoConfigFileInvalidException  from edkrepo.common.humble import KEYBOARD_INTERRUPT, GIT_CMD_ERROR
+from edkrepo.common.pathfix import get_actual_path
 
 def generate_command_line(command):
     parser = argparse.ArgumentParser()
@@ -100,6 +101,61 @@ def generate_command_line(command):
                 subparser_name.add_argument(('--' + arg.get('name')), action=arg_action, help=arg.get('help-text'))
     return parser
 
+command_completion_script_header='''#!/usr/bin/env bash # ## @file 
+edkrepo_completions.sh # # Automatically generated please DO NOT modify 
+!!!
+#
+
+'''
+def generate_command_completion_script(script_filename, parser):
+    import edkrepo.command_completion_edkrepo as completion
+    commands = []
+    for action in parser._positionals._group_actions:
+        if action.choices is not None:
+            commands = [c for c in action.choices]
+            break
+    commands = sorted(commands)
+    commands_with_3rd_param_completion = [c for c in completion.command_completions if c in commands]
+    commands_with_3rd_param_completion = sorted(commands_with_3rd_param_completion)
+    with open(script_filename, 'w') as f:
+        f.write(command_completion_script_header)
+        if sys.platform == "win32":
+            command_completion_path = os.path.dirname(sys.executable)
+            command_completion_path = os.path.join(command_completion_path, 'Scripts', "command_completion_edkrepo.exe")
+            if not os.path.isfile(command_completion_path):
+                print('command_completion_edkrepo.exe not found')
+                return
+            command_completion_path = get_actual_path(command_completion_path)
+            (drive, path) = os.path.splitdrive(command_completion_path)
+            command_completion_path = '/{}{}'.format(drive.replace(':','').lower(), path.replace('\\','/'))
+            f.write("export command_completion_edkrepo_file='{}'\n".format(command_completion_path))
+            f.write('alias command_completion_edkrepo="$command_completion_edkrepo_file"\n')
+        f.write('_edkrepo_completions() {\n    if [ "${#COMP_WORDS[@]}" -eq "2" ]; then\n')
+        f.write('        COMPREPLY=($(compgen -W "{}" -- "${{COMP_WORDS[1]}}"))\n'.format(' '.join(commands)))
+        if len(commands_with_3rd_param_completion) > 0:
+            f.write('    elif [ "${#COMP_WORDS[@]}" -eq "3" ]; then\n')
+        first_loop = True
+        for command in commands_with_3rd_param_completion:
+            if first_loop:
+                f.write('        if [ "${{COMP_WORDS[1]}}" == "{}" ]; then\n'.format(command))
+                first_loop = False
+            else:
+                f.write('        elif [ "${{COMP_WORDS[1]}}" == "{}" ]; then\n'.format(command))
+            f.write('            COMPREPLY=($(compgen -W "$(command_completion_edkrepo ${COMP_WORDS[1]})" -- "${COMP_WORDS[2]}"))\n')
+        if len(commands_with_3rd_param_completion) > 0:
+            f.write('        fi\n')
+        f.write('    fi\n}\n\n')
+        if len(commands_with_3rd_param_completion) > 0:
+            if sys.platform == "win32":
+                f.write('if [ -x "$(command -v edkrepo)" ] && [ -x "$(command -v $command_completion_edkrepo_file)" ]; then\n')
+            else:
+                f.write('if [ -x "$(command -v edkrepo)" ] && [ -x "$(command -v command_completion_edkrepo)" ]; then\n')
+        else:
+            f.write('if [ -x "$(command -v edkrepo)" ]; then\n')
+        f.write('    complete -F _edkrepo_completions edkrepo\nfi\n')
+
 def main():
     command = command_factory.create_composite_command()
     config = {}
@@ -117,6 +173,9 @@ def main():
     if len(sys.argv) <= 1:
         parser.print_help()
         return 1
+    if sys.argv[1] == 'generate-command-completion-script' and len(sys.argv) >= 3:
+        generate_command_completion_script(sys.argv[2], parser)
+        return 0
     parsed_args = parser.parse_args()
     command_name = parsed_args.subparser_name
     try:
diff --git a/setup.py b/setup.py
index e14aed1..e7e6ce8 100755
--- a/setup.py
+++ b/setup.py
@@ -1,9 +1,8 @@
 #!/usr/bin/env python3
 #
-## @file
-# setup.py
+## @file setup.py
 #
-# Copyright (c) 2017 - 2019, Intel Corporation. All rights reserved.<BR>
+# Copyright (c) 2017 - 2020, Intel Corporation. All rights 
+reserved.<BR>
 # SPDX-License-Identifier: BSD-2-Clause-Patent  #
 
@@ -20,7 +19,8 @@ setup(name='edkrepo',
       include_package_data=True,
       entry_points={
           'console_scripts': [
-              'edkrepo = edkrepo.edkrepo_entry_point:main'
+              'edkrepo = edkrepo.edkrepo_entry_point:main',
+              'command_completion_edkrepo = edkrepo.command_completion_edkrepo:main'
               ]
           }
       )
--
2.24.0.windows.2


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [edk2-staging/EdkRepo] [PATCH V1 2/3] EdkRepo: Add command completion setup to install.py
  2020-04-01 22:34 ` [edk2-staging/EdkRepo] [PATCH V1 2/3] EdkRepo: Add command completion setup to install.py Nate DeSimone
@ 2020-04-03 21:51   ` Ashley E Desimone
  0 siblings, 0 replies; 7+ messages in thread
From: Ashley E Desimone @ 2020-04-03 21:51 UTC (permalink / raw)
  To: Desimone, Nathaniel L, devel@edk2.groups.io
  Cc: Pandya, Puja, Bjorge, Erik C, Agyeman, Prince, Bret Barkelew,
	Philippe Mathieu-Daude

Reviewed-by: Ashley DeSimone <ashley.e.desimone@intel.com>

-----Original Message-----
From: Desimone, Nathaniel L <nathaniel.l.desimone@intel.com> 
Sent: Wednesday, April 1, 2020 3:35 PM
To: devel@edk2.groups.io
Cc: Desimone, Ashley E <ashley.e.desimone@intel.com>; Pandya, Puja <puja.pandya@intel.com>; Bjorge, Erik C <erik.c.bjorge@intel.com>; Agyeman, Prince <prince.agyeman@intel.com>; Bret Barkelew <Bret.Barkelew@microsoft.com>; Philippe Mathieu-Daude <philmd@redhat.com>
Subject: [edk2-staging/EdkRepo] [PATCH V1 2/3] EdkRepo: Add command completion setup to install.py

Add configuration of command completion scripts to install.py This enables edkrepo command completions to work "out of box" on most Linux systems by appending to the user's .bashrc and .zshrc startup scripts inclusion of the EdkRepo command completion scripts.

Cc: Ashley DeSimone <ashley.e.desimone@intel.com>
Cc: Puja Pandya <puja.pandya@intel.com>
Cc: Erik Bjorge <erik.c.bjorge@intel.com>
Cc: Prince Agyeman <prince.agyeman@intel.com>
Cc: Bret Barkelew <Bret.Barkelew@microsoft.com>
Cc: Philippe Mathieu-Daude <philmd@redhat.com>
Signed-off-by: Nate DeSimone <nathaniel.l.desimone@intel.com>
---
 edkrepo_installer/linux-scripts/install.py | 285 ++++++++++++++++++++-
 1 file changed, 283 insertions(+), 2 deletions(-)

diff --git a/edkrepo_installer/linux-scripts/install.py b/edkrepo_installer/linux-scripts/install.py
index b2cdfed..52f0c52 100755
--- a/edkrepo_installer/linux-scripts/install.py
+++ b/edkrepo_installer/linux-scripts/install.py
@@ -15,6 +15,7 @@ import importlib.util
 import logging
 import os
 import platform
+import re
 import stat
 import shutil
 import subprocess
@@ -22,7 +23,7 @@ import sys
 import traceback
 import xml.etree.ElementTree as et
 
-tool_sign_on = 'Installer for edkrepo version {}\nCopyright(c) Intel Corporation, 2019'
+tool_sign_on = 'Installer for edkrepo version {}\nCopyright(c) Intel Corporation, 2020'
 
 # Data here should be maintained in a configuration file  cfg_dir = '.edkrepo'
@@ -31,6 +32,21 @@ cfg_src_dir = os.path.abspath('config')  whl_src_dir = os.path.abspath('wheels')  def_python = 'python3'
 
+# ZSH Configuration options
+prompt_regex = 
+re.compile(r"#\s+[Aa][Dd][Dd]\s+[Ee][Dd][Kk][Rr][Ee][Pp][Oo]\s+&\s+[Gg]
+[Ii][Tt]\s+[Tt][Oo]\s+[Tt][Hh][Ee]\s+[Pp][Rr][Oo][Mm][Pp][Tt]")
+zsh_autoload_compinit_regex = re.compile(r"autoload\s+-U\s+compinit")
+zsh_autoload_bashcompinit_regex = 
+re.compile(r"autoload\s+-U\s+bashcompinit")
+zsh_autoload_colors_regex = re.compile(r"autoload\s+-U\s+colors")
+zsh_colors_regex = re.compile(r"\n\s*colors\n") zsh_compinit_regex = 
+re.compile(r"compinit\s+-u") zsh_bashcompinit_regex = 
+re.compile(r"\n\s*bashcompinit\n")
+zsh_autoload_compinit = 'autoload -U compinit'
+zsh_autoload_bashcompinit = 'autoload -U bashcompinit'
+zsh_autoload_colors = 'autoload -U colors'
+zsh_colors = 'colors'
+zsh_compinit = 'compinit -u'
+zsh_bashcompinit = 'bashcompinit'
+
 def default_run(cmd):
     return subprocess.run(cmd, universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=True)
 
@@ -51,8 +67,85 @@ def get_args():
     parser.add_argument('-p', '--py', action='store', default=None, help='Specify the python command to use when installing')
     parser.add_argument('-u', '--user', action='store', default=None, help='Specify user account to install edkrepo support on')
     parser.add_argument('-v', '--verbose', action='store_true', default=False, help='Enables verbose output')
+    parser.add_argument('--no-prompt', action='store_true', default=False, help='Do NOT add EdkRepo combo and git branch to the shell prompt')
+    parser.add_argument('--prompt', action='store_true', default=False, 
+ help='Add EdkRepo combo and git branch to the shell prompt')
     return parser.parse_args()
 
+def is_prompt_customization_installed(user_home_dir):
+    script_files = [os.path.join(user_home_dir, '.bashrc'), os.path.join(user_home_dir, '.zshrc')]
+    customization_installed = True
+    for script_file in script_files:
+        if os.path.isfile(script_file):
+            with open(script_file, 'r') as f:
+                script = f.read().strip()
+        data = prompt_regex.search(script)
+        if not data:
+            customization_installed = False
+            break
+    return customization_installed
+
+__add_prompt_customization = None
+def get_add_prompt_customization(args, user_home_dir):
+    global __add_prompt_customization
+    if __add_prompt_customization is not None:
+        return __add_prompt_customization
+    if args.no_prompt:
+        __add_prompt_customization = False
+        return False
+    elif args.prompt:
+        __add_prompt_customization = True
+        return True
+    #Check if the prompt customization has already been installed
+    if is_prompt_customization_installed(user_home_dir):
+        __add_prompt_customization = False
+        return False
+    #If the prompt has not been installed and EdkRepo >= 2.0.0 is installed, then don't install the prompt customization
+    if shutil.which('edkrepo') is not None:
+        res = default_run(['edkrepo', '--version'])
+        if _check_version(res.stdout.replace('edkrepo ', '').strip(), '2.0.0') >= 0:
+            __add_prompt_customization = False
+            return False
+    #Show the user an advertisement to see if they want the prompt customization
+    from select import select
+    import termios
+    import tty
+    def get_key(timeout=-1):
+        key = None
+        old_settings = termios.tcgetattr(sys.stdin.fileno())
+        try:
+            tty.setraw(sys.stdin.fileno())
+            if timeout != -1:
+                rlist, _, _ = select([sys.stdin], [], [], timeout)
+                if rlist:
+                    key = sys.stdin.read(1)
+            else:
+                key = sys.stdin.read(1)
+        finally:
+            termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, old_settings)
+        return key
+    print('\nEdkRepo can show the checked out \033[32mcombo\033[00m and \033[36mbranch\033[00m as part of the command prompt')
+    print('For example, instead of:\n')
+    print('\033[01;32muser@machine\033[00m:\033[01;34mEdk2\033[00m$ ')
+    print('\nThe command prompt would look like:\n')
+    print('\033[01;32muser@machine\033[00m:\033[01;34mEdk2\033[00m \033[32m[Edk2Master]\033[36m (master)\033[00m$ ')
+    print('')
+    while True:
+        print('Would you like the combo and branch shown on the command prompt? [y/N] ')
+        key = get_key(120)
+        if key:
+            if key == 'y' or key == 'Y':
+                print('Y')
+                __add_prompt_customization = True
+                return True
+            if key == 'n' or key == 'N':
+                print('N')
+                __add_prompt_customization = False
+                return False
+        else:
+            print('No response after 2min... assuming no.')
+            __add_prompt_customization = False
+            return False
+
 def get_installed_packages(python_command):
     pip_cmd = [def_python, '-m', 'pip', 'list', '--legacy']
     try:
@@ -146,6 +239,176 @@ def set_execute_permissions():
                     stat_data = os.stat(py_file)
                     os.chmod(py_file, stat_data.st_mode | stat.S_IEXEC)
 
+bash_prompt_customization = r'''
+# Add EdkRepo & git to the prompt
+ps1len="${#PS1}"
+let "pos3 = ps1len - 3"
+let "pos2 = ps1len - 2"
+if [ "${PS1:pos3}" == "\\$ " ]; then
+  newps1="${PS1:0:pos3}"
+  prompt_suffix="\\$ "
+elif [ "${PS1:pos3}" == " $ " ]; then
+  newps1="${PS1:0:pos3}"
+  prompt_suffix=" $ "
+elif [ "${PS1:pos2}" == "$ " ]; then
+  newps1="${PS1:0:pos2}"
+  prompt_suffix="$ "
+else
+  newps1="$PS1"
+  prompt_suffix=""
+fi
+
+# EdkRepo combo in prompt.
+if [ -x "$(command -v edkrepo)" ] && [ -x "$(command -v 
+command_completion_edkrepo)" ]; then
+  newps1="$newps1\[\033[32m\]\$current_edkrepo_combo"
+  current_edkrepo_combo=$(command_completion_edkrepo current-combo)
+
+  # Determining the current Edkrepo combo requires invoking Python and 
+parsing
+  # manifest XML, which is a relatively expensive operation to do every 
+time
+  # the user presses <Enter>.
+  # As a performance optimization, only do this if the present working 
+directory
+  # changed
+  if [[ ! -z ${PROMPT_COMMAND+x} ]] && [[ "$PROMPT_COMMAND" != "edkrepo_combo_chpwd" ]]; then
+    old_prompt_command=$PROMPT_COMMAND
+  fi
+  old_pwd=$(pwd)
+  edkrepo_combo_chpwd() {
+      if [[ "$(pwd)" != "$old_pwd" ]]; then
+        old_pwd=$(pwd)
+        current_edkrepo_combo=$(command_completion_edkrepo current-combo)
+      fi
+      if [[ ! -z ${PROMPT_COMMAND+x} ]]; then
+        eval $old_prompt_command
+      fi
+  }
+  PROMPT_COMMAND=edkrepo_combo_chpwd
+fi
+
+# Git branch in prompt.
+parse_git_branch() {
+  git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/ (\1)/'
+}
+
+PS1="$newps1\[\033[36m\]\$(parse_git_branch)\[\033[00m\]$prompt_suffix"
+'''
+
+zsh_prompt_customization = r'''
+# Add EdkRepo & git to the prompt
+prompt_length="${#PROMPT}"
+let "pos4 = prompt_length - 3"
+let "pos3 = prompt_length - 2"
+if [ "${PROMPT[$pos4,$prompt_length]}" = ' %# ' ]; then
+  new_prompt="${PROMPT[1,$pos4-1]}"
+  prompt_suffix=" %# "
+elif [ "${PROMPT[$pos3,$prompt_length]}" = "%# " ]; then
+  new_prompt="${PROMPT[1,$pos3-1]}"
+  prompt_suffix="%# "
+else
+  new_prompt="$PROMPT"
+  prompt_suffix=""
+fi
+
+# EdkRepo combo in prompt.
+if [ -x "$(command -v edkrepo)" ] && [ -x "$(command -v 
+command_completion_edkrepo)" ]; then
+  new_prompt="$new_prompt%{$fg[green]%}\$current_edkrepo_combo%{$reset_color%}"
+  current_edkrepo_combo=$(command_completion_edkrepo current-combo)
+
+  # Determining the current Edkrepo combo requires invoking Python and 
+parsing
+  # manifest XML, which is a relatively expensive operation to do every 
+time
+  # the user presses <Enter>.
+  # As a performance optimization, only do this if the present working 
+directory
+  # changed
+  function edkrepo_combo_chpwd() {
+    current_edkrepo_combo=$(command_completion_edkrepo current-combo)
+  }
+  chpwd_functions=(${chpwd_functions[@]} "edkrepo_combo_chpwd") fi
+
+# Load version control information
+autoload -Uz vcs_info
+precmd() { vcs_info }
+
+# Format the vcs_info_msg_0_ variable
+zstyle ':vcs_info:git:*' formats " %{$fg[cyan]%}(%b)%{$reset_color%}"
+
+# Set up the prompt (with git branch name) setopt PROMPT_SUBST eval 
+"PROMPT='$new_prompt\${vcs_info_msg_0_}\$prompt_suffix'"
+'''
+
+def add_command_to_startup_script(script_file, regex, command, username):
+    script = ''
+    if os.path.isfile(script_file):
+        with open(script_file, 'r') as f:
+            script = f.read().strip()
+    data = regex.search(script)
+    if not data:
+        if script == '':
+            script = command
+        else:
+            script = '{}\n{}'.format(script, command)
+        with open(script_file, 'w') as f:
+            f.write(script)
+
+def add_command_comment_to_startup_script(script_file, regex, command, comment, username):
+    script = ''
+    if os.path.isfile(script_file):
+        with open(script_file, 'r') as f:
+            script = f.read().strip()
+    (new_script, subs) = re.subn(regex, command, script)
+    if subs == 0:
+        command = '\n{1}\n{0}\n'.format(command, comment)
+        if script == '':
+            new_script = command
+        else:
+            new_script = '{}\n{}'.format(script, command)
+    if new_script != script:
+        with open(script_file, 'w') as f:
+            f.write(new_script)
+        shutil.chown(script_file, user=username)
+        os.chmod(script_file, 0o644)
+
+def add_command_completions_to_shell(command_completion_script, args, username, user_home_dir):
+    # Add "source ~/.bashrc" to ~/.bash_profile if it does not have it already
+    bash_profile_file = os.path.join(user_home_dir, '.bash_profile')
+    bash_profile = ''
+    if os.path.isfile(bash_profile_file):
+        with open(bash_profile_file, 'r') as f:
+            bash_profile = f.read().strip()
+    profile_source_regex = re.compile(r"source\s+~/\.bashrc")
+    profile_source_regex2 = re.compile(r".\s+~/\.bashrc")
+    data = profile_source_regex.search(bash_profile)
+    if not data:
+        data = profile_source_regex2.search(bash_profile)
+        if not data:
+            if bash_profile == '':
+                bash_profile = 'source ~/.bashrc\n'
+            else:
+                bash_profile = '{}\nsource ~/.bashrc\n'.format(bash_profile)
+            with open(bash_profile_file, 'w') as f:
+                f.write(bash_profile)
+            shutil.chown(bash_profile_file, user=username)
+            os.chmod(bash_profile_file, 0o644)
+
+    # Add edkrepo command completion to ~/.bashrc if it does not have it already
+    regex = r"\[\[\s+-r\s+\"\S*edkrepo_completions.sh\"\s+\]\]\s+&&\s+.\s+\"\S*edkrepo_completions.sh\""
+    new_source_line = '[[ -r "{0}" ]] && . "{0}"'.format(command_completion_script)
+    comment = '\n# Add EdkRepo command completions'
+    bash_rc_file = os.path.join(user_home_dir, '.bashrc')
+    add_command_comment_to_startup_script(bash_rc_file, regex, new_source_line, comment, username)
+    if get_add_prompt_customization(args, user_home_dir):
+        add_command_to_startup_script(bash_rc_file, prompt_regex, bash_prompt_customization, username)
+    zsh_rc_file = os.path.join(user_home_dir, '.zshrc')
+    add_command_to_startup_script(zsh_rc_file, zsh_autoload_compinit_regex, zsh_autoload_compinit, username)
+    add_command_to_startup_script(zsh_rc_file, zsh_autoload_bashcompinit_regex, zsh_autoload_bashcompinit, username)
+    add_command_to_startup_script(zsh_rc_file, zsh_autoload_colors_regex, zsh_autoload_colors, username)
+    add_command_to_startup_script(zsh_rc_file, zsh_colors_regex, zsh_colors, username)
+    add_command_to_startup_script(zsh_rc_file, zsh_compinit_regex, zsh_compinit, username)
+    add_command_to_startup_script(zsh_rc_file, zsh_bashcompinit_regex, zsh_bashcompinit, username)
+    add_command_comment_to_startup_script(zsh_rc_file, regex, new_source_line, comment, username)
+    if get_add_prompt_customization(args, user_home_dir):
+        add_command_to_startup_script(zsh_rc_file, prompt_regex, 
+ zsh_prompt_customization, username)
+
 def do_install():
     global def_python
     org_python = None
@@ -209,6 +472,7 @@ def do_install():
         log.info('- Unable to determine users home directory')
         return 1
     default_cfg_dir = os.path.join(user_home_dir, cfg_dir)
+    get_add_prompt_customization(args, user_home_dir)
     log.info('+ System information collected')
 
     # Display current system information.
@@ -348,7 +612,7 @@ def do_install():
         #Delete obsolete dependencies
         if updating_edkrepo:
             installed_packages = get_installed_packages(def_python)
-            for whl_name in ['smmap2', 'gitdb2']:
+            for whl_name in ['smmap2', 'gitdb2', 'edkrepo-internal']:
                 if whl_name in installed_packages:
                     try:
                         res = default_run([def_python, '-m', 'pip', 'uninstall', '--yes', whl_name]) @@ -375,6 +639,23 @@ def do_install():
     set_execute_permissions()
     log.info('+ Marked scripts as executable')
 
+
+    #Install the command completion script
+    if shutil.which('edkrepo') is not None:
+        if args.local:
+            command_completion_script = os.path.join(default_cfg_dir, 'edkrepo_completions.sh')
+        else:
+            command_completion_script = os.path.join('/', 'etc', 'profile.d', 'edkrepo_completions.sh')
+        try:
+            res = default_run(['edkrepo', 'generate-command-completion-script', command_completion_script])
+            if args.local:
+                shutil.chown(command_completion_script, user=username)
+                os.chmod(command_completion_script, 0o644)
+            add_command_completions_to_shell(command_completion_script, args, username, user_home_dir)
+        except:
+            log.info('- Failed to configure edkrepo command completion')
+            if args.verbose:
+                traceback.print_exc()
     log.log(logging.PRINT, '\nInstallation complete\n')
 
     return 0
--
2.24.0.windows.2


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [edk2-staging/EdkRepo] [PATCH V1 3/3] EdkRepo: Add command completion setup to Windows installer
  2020-04-01 22:34 ` [edk2-staging/EdkRepo] [PATCH V1 3/3] EdkRepo: Add command completion setup to Windows installer Nate DeSimone
@ 2020-04-03 21:55   ` Ashley E Desimone
  0 siblings, 0 replies; 7+ messages in thread
From: Ashley E Desimone @ 2020-04-03 21:55 UTC (permalink / raw)
  To: Desimone, Nathaniel L, devel@edk2.groups.io
  Cc: Pandya, Puja, Bjorge, Erik C, Agyeman, Prince, Bret Barkelew,
	Philippe Mathieu-Daude

Reviewed-by: Ashley DeSimone <ashley.e.desimone@intel.com>

-----Original Message-----
From: Desimone, Nathaniel L <nathaniel.l.desimone@intel.com> 
Sent: Wednesday, April 1, 2020 3:35 PM
To: devel@edk2.groups.io
Cc: Desimone, Ashley E <ashley.e.desimone@intel.com>; Pandya, Puja <puja.pandya@intel.com>; Bjorge, Erik C <erik.c.bjorge@intel.com>; Agyeman, Prince <prince.agyeman@intel.com>; Bret Barkelew <Bret.Barkelew@microsoft.com>; Philippe Mathieu-Daude <philmd@redhat.com>
Subject: [edk2-staging/EdkRepo] [PATCH V1 3/3] EdkRepo: Add command completion setup to Windows installer

Add configuration of command completion scripts to the Windows installer. This enables edkrepo command completions to work "out of box" on Git for Windows by adding the edkrepo command completions scripts to the Git for Windows /etc/profile.d directory

Cc: Ashley DeSimone <ashley.e.desimone@intel.com>
Cc: Puja Pandya <puja.pandya@intel.com>
Cc: Erik Bjorge <erik.c.bjorge@intel.com>
Cc: Prince Agyeman <prince.agyeman@intel.com>
Cc: Bret Barkelew <Bret.Barkelew@microsoft.com>
Cc: Philippe Mathieu-Daude <philmd@redhat.com>
Signed-off-by: Nate DeSimone <nathaniel.l.desimone@intel.com>
---
 .../EdkRepoInstaller/InstallWorker.cs         | 212 ++++++++++++++++--
 .../EdkRepoInstaller/InstallerStrings.cs      |  44 +++-
 .../Vendor/win_edkrepo_prompt.sh              |  60 +++++
 3 files changed, 296 insertions(+), 20 deletions(-)  create mode 100644 edkrepo_installer/Vendor/win_edkrepo_prompt.sh

diff --git a/edkrepo_installer/EdkRepoInstaller/InstallWorker.cs b/edkrepo_installer/EdkRepoInstaller/InstallWorker.cs
index 472a6c8..679b4f4 100644
--- a/edkrepo_installer/EdkRepoInstaller/InstallWorker.cs
+++ b/edkrepo_installer/EdkRepoInstaller/InstallWorker.cs
@@ -19,6 +19,7 @@ using System.Security.AccessControl;  using System.Security.Cryptography;  using System.Security.Principal;  using System.Text;
+using System.Text.RegularExpressions;
 using System.Threading;
 
 namespace TianoCore.EdkRepoInstaller
@@ -611,7 +612,7 @@ namespace TianoCore.EdkRepoInstaller
             SilentProcess process = SilentProcess.StartConsoleProcessSilently(GitPath, "--version", dataCapture.DataReceivedHandler);
             process.WaitForExit();
             PythonVersion gitVersion = new PythonVersion(dataCapture.GetData().Trim());
-            if (gitVersion < new PythonVersion(2,13,0))
+            if (gitVersion < new PythonVersion(2, 13, 0))
             {
                 InstallLogger.Log(string.Format("Git Version 2.13 or later is required. You have version {0}, please upgrade to a newer version of Git.", gitVersion));
                 ReportComplete(false, false); @@ -624,7 +625,7 @@ namespace TianoCore.EdkRepoInstaller
             List<Tuple<string, PythonVersion>> ExclusivePackages = new List<Tuple<string, PythonVersion>>();
             foreach (PythonInstance PyInstance in PythonWheelsToInstall)
             {
-                foreach(PythonWheel Wheel in PyInstance.Wheels)
+                foreach (PythonWheel Wheel in PyInstance.Wheels)
                 {
                     if (Wheel.UninstallAllOtherCopies)
                     {
@@ -668,13 +669,13 @@ namespace TianoCore.EdkRepoInstaller
             //
             foreach (PythonVersion Obsolete in ObsoletedPythonVersions)
             {
-                if(ExistingEdkRepoPaths.Select(p => p.Item2).Contains(Obsolete))
+                if (ExistingEdkRepoPaths.Select(p => 
+ p.Item2).Contains(Obsolete))
                 {
                     foreach (string ExistingEdkrepoPythonPath in ExistingEdkRepoPaths.Where(p => p.Item2 == Obsolete).Select(p => p.Item1))
                     {
                         string UninstallerPath = Path.Combine(Path.GetDirectoryName(ExistingEdkrepoPythonPath), "Lib", "site-packages");
                         UninstallerPath = Path.Combine(UninstallerPath, Path.GetFileName(WindowsHelpers.GetApplicationPath()));
-                        if(File.Exists(UninstallerPath))
+                        if (File.Exists(UninstallerPath))
                         {
                             InstallLogger.Log(string.Format("Uninstalling {0}...", UninstallerPath));
                             string UninstallString = string.Format("\"{0}\" /Uninstall /Passive", UninstallerPath); @@ -788,14 +789,15 @@ namespace TianoCore.EdkRepoInstaller
             //
             // Step 10 - Setup symlink to edkrepo and bash script to launch edkrepo from git bash
             //
+            string EdkrepoSymlinkPath = null;
             if (!string.IsNullOrEmpty(EdkrepoPythonPath))
             {
                 string EdkrepoScriptPath = Path.Combine(Path.GetDirectoryName(EdkrepoPythonPath), "Scripts", InstallerStrings.EdkrepoCliExecutable);
-                string EdkrepoSymlinkPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), InstallerStrings.EdkrepoCliExecutable);
+                EdkrepoSymlinkPath = 
+ Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windo
+ ws), InstallerStrings.EdkrepoCliExecutable);
                 if (File.Exists(EdkrepoScriptPath))
                 {
                     bool CreateSymlink = true;
-                    if(File.Exists(EdkrepoSymlinkPath))
+                    if (File.Exists(EdkrepoSymlinkPath))
                     {
                         try
                         {
@@ -805,22 +807,22 @@ namespace TianoCore.EdkRepoInstaller
                             }
                         }
                         catch (NotASymlinkException) { }
-                        if(CreateSymlink)
+                        if (CreateSymlink)
                         {
                             File.Delete(EdkrepoSymlinkPath);
                         }
                     }
-                    if(CreateSymlink)
+                    if (CreateSymlink)
                     {
                         InstallLogger.Log("Creating Symbolic Link for edkrepo.exe...");
                         WindowsHelpers.CreateSymbolicLink(EdkrepoSymlinkPath, EdkrepoScriptPath, WindowsHelpers.SYMBOLIC_LINK_FLAG.File);
                     }
                     string GitBashBinPath = Path.Combine(Path.GetDirectoryName(Path.GetDirectoryName(GitPath)), "usr", "bin");
-                    if(Directory.Exists(GitBashBinPath))
+                    if (Directory.Exists(GitBashBinPath))
                     {
                         InstallLogger.Log("Creating edkrepo launcher in Git Bash...");
                         string EdkrepoBashScriptPath = Path.Combine(GitBashBinPath, InstallerStrings.EdkrepoBashLauncherScript);
-                        if(File.Exists(EdkrepoBashScriptPath))
+                        if (File.Exists(EdkrepoBashScriptPath))
                         {
                             File.Delete(EdkrepoBashScriptPath);
                         }
@@ -838,7 +840,7 @@ namespace TianoCore.EdkRepoInstaller
             // Step 11 - Copy edkrepo config file to the edkrepo global data directory
             //
             string EdkrepoCfg = Path.Combine(Path.GetDirectoryName(WindowsHelpers.GetApplicationPath()), InstallerStrings.EdkrepoCfg);
-            if(File.Exists(EdkrepoCfg))
+            if (File.Exists(EdkrepoCfg))
             {
                 CreateEdkrepoGlobalDataDirectory();
                 string EdkrepoCfgDir = Path.Combine(WindowsHelpers.GetAllUsersAppDataPath(), InstallerStrings.EdkrepoGlobalDirectoryName);
@@ -853,7 +855,7 @@ namespace TianoCore.EdkRepoInstaller
                 {
                     string NewCfgHash = ComputeSha256(ReadFile(EdkrepoCfg));
                     string OldCfgHash = ComputeSha256(ReadFile(EdkrepoCfgTarget));
-                    if(NewCfgHash != OldCfgHash)
+                    if (NewCfgHash != OldCfgHash)
                     {
                         if (GetPreviousEdkrepoCfgFileHashes().Contains(OldCfgHash))
                         {
@@ -908,7 +910,119 @@ namespace TianoCore.EdkRepoInstaller
             }
 
             //
-            // Step 13 - Create Programs and Features uninstall links
+            // Step 13 - Copy win_edkrepo_prompt.sh and generate edkrepo_completions.sh
+            //
+            string edkrepoPromptSource = 
+ Path.Combine(Path.GetDirectoryName(WindowsHelpers.GetApplicationPath()
+ ), InstallerStrings.EdkrepoPrompt);
+
+            if (File.Exists(edkrepoPromptSource) && !string.IsNullOrEmpty(EdkrepoSymlinkPath))
+            {
+                string gitBashEtcPath = Path.Combine(Path.GetDirectoryName(Path.GetDirectoryName(GitPath)), "etc");
+                string gitBashEtcProfileDPath = Path.Combine(gitBashEtcPath, "profile.d");
+                if (Directory.Exists(gitBashEtcPath) && Directory.Exists(gitBashEtcProfileDPath))
+                {
+                    InstallLogger.Log("Installing EdkRepo command 
+ completion...");
+
+                    //Copy win_edkrepo_prompt.sh
+                    string edkrepoPromptDest = Path.Combine(gitBashEtcProfileDPath, InstallerStrings.EdkrepoPrompt);
+                    if (File.Exists(edkrepoPromptDest))
+                    {
+                        File.Delete(edkrepoPromptDest);
+                    }
+                    File.Copy(edkrepoPromptSource, edkrepoPromptDest);
+                    DirectoryInfo info = new DirectoryInfo(edkrepoPromptDest);
+                    DirectorySecurity security = info.GetAccessControl();
+                    security.AddAccessRule(new FileSystemAccessRule(
+                        new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null),
+                        FileSystemRights.FullControl,
+                        InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit,
+                        PropagationFlags.NoPropagateInherit,
+                        AccessControlType.Allow
+                        ));
+                    info.SetAccessControl(security);
+                    InstallLogger.Log(string.Format("Copied {0}", 
+ InstallerStrings.EdkrepoPrompt));
+
+                    //Generate edkrepo_completions.sh
+                    string edkrepoCompletionDest = Path.Combine(gitBashEtcProfileDPath, InstallerStrings.EdkrepoCompletion);
+                    if (File.Exists(edkrepoCompletionDest))
+                    {
+                        File.Delete(edkrepoCompletionDest);
+                    }
+                    dataCapture = new SilentProcess.StdoutDataCapture();
+                    process = SilentProcess.StartConsoleProcessSilently(
+                        EdkrepoSymlinkPath,
+                        string.Format(
+                            "generate-command-completion-script \"{0}\"",
+                            edkrepoCompletionDest),
+                        dataCapture.DataReceivedHandler);
+                    process.WaitForExit();
+                    InstallLogger.Log(EdkrepoSymlinkPath);
+                    InstallLogger.Log(edkrepoCompletionDest);
+                    InstallLogger.Log(dataCapture.GetData().Trim());
+                    if (process.ExitCode != 0)
+                    {
+                        throw new InvalidOperationException(string.Format("generate-command-completion-script failed with status {0}", process.ExitCode));
+                    }
+                    if (!File.Exists(edkrepoCompletionDest))
+                    {
+                        throw new InvalidOperationException(string.Format("generate-command-completion-script did not create {0}", InstallerStrings.EdkrepoCompletion));
+                    }
+                    info = new DirectoryInfo(edkrepoCompletionDest);
+                    security = info.GetAccessControl();
+                    security.AddAccessRule(new FileSystemAccessRule(
+                        new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null),
+                        FileSystemRights.FullControl,
+                        InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit,
+                        PropagationFlags.NoPropagateInherit,
+                        AccessControlType.Allow
+                        ));
+                    info.SetAccessControl(security);
+                    InstallLogger.Log(string.Format("Generated {0}", 
+ InstallerStrings.EdkrepoCompletion));
+
+                    //Call win_edkrepo_prompt.sh from bash.bashrc so edkrepo completions are available for "interactive non-login" bash shells
+                    string bashrcPath = Path.Combine(gitBashEtcPath, "bash.bashrc");
+                    if (File.Exists(bashrcPath))
+                    {
+                        string bashrc = Encoding.UTF8.GetString(ReadFile(bashrcPath));
+                        Match match = Regex.Match(bashrc, InstallerStrings.BashrcEdkrepoPromptCallPattern);
+                        if (match.Success)
+                        {
+                            InstallLogger.Log("EdkRepo prompt is already in bash.bashrc");
+                        }
+                        else
+                        {
+                            bashrc = string.Format("{0}{1}", bashrc, InstallerStrings.BashrcEdkRepoPromptCall);
+                            using (BinaryWriter writer = new BinaryWriter(File.Open(bashrcPath, FileMode.Truncate, FileAccess.Write)))
+                            {
+                                string sanitized = bashrc.Replace("\r\n", "\n");
+                                writer.Write(Encoding.UTF8.GetBytes(sanitized));
+                            }
+                            InstallLogger.Log("EdkRepo prompt added to bash.bashrc");
+                        }
+                    }
+                    else
+                    {
+                        InstallLogger.Log(string.Format("{0} not found", bashrcPath));
+                    }
+                }
+                else
+                {
+                    InstallLogger.Log("Git for Windows /etc/profile.d not found");
+                }
+            }
+            else
+            {
+                if (string.IsNullOrEmpty(EdkrepoSymlinkPath))
+                {
+                    InstallLogger.Log("EdkRepo symlink not found");
+                }
+                if (!File.Exists(edkrepoPromptSource))
+                {
+                    InstallLogger.Log(string.Format("{0} not found", InstallerStrings.EdkrepoPrompt));
+                }
+            }
+
+            //
+            // Step 14 - Create Programs and Features uninstall links
             //
             if (!string.IsNullOrEmpty(EdkrepoPythonPath))
             {
@@ -977,7 +1091,7 @@ namespace TianoCore.EdkRepoInstaller
             string GitPath = PythonOperations.GetFullPath("git.exe");
 
             //
-            // Step 2 - Delete symlink to edkrepo and bash script to launch it from git bash
+            // Step 2 - Delete symlink to edkrepo
             //
             string EdkrepoSymlinkPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), InstallerStrings.EdkrepoCliExecutable);
             if (File.Exists(EdkrepoSymlinkPath)) @@ -1003,7 +1117,11 @@ namespace TianoCore.EdkRepoInstaller
                     File.Delete(EdkrepoSymlinkPath);
                 }
             }
-            if (GitPath != null)
+
+            //
+            // Step 3 - Delete scripts to launch edkrepo and Python from git bash, and edkrepo command completion scripts
+            //
+            if (!string.IsNullOrWhiteSpace(GitPath))
             {
                 string GitBashBinPath = Path.Combine(Path.GetDirectoryName(Path.GetDirectoryName(GitPath)), "usr", "bin");
                 if (Directory.Exists(GitBashBinPath))
@@ -1057,10 +1175,68 @@ namespace TianoCore.EdkRepoInstaller
                         File.Delete(EdkrepoPython2ScriptPath);
                     }
                 }
+                string gitBashEtcPath = Path.Combine(Path.GetDirectoryName(Path.GetDirectoryName(GitPath)), "etc");
+                string gitBashEtcProfileDPath = Path.Combine(gitBashEtcPath, "profile.d");
+                if (Directory.Exists(gitBashEtcPath) && Directory.Exists(gitBashEtcProfileDPath))
+                {
+                    string edkrepoPromptDest = Path.Combine(gitBashEtcProfileDPath, InstallerStrings.EdkrepoPrompt);
+                    if (File.Exists(edkrepoPromptDest))
+                    {
+                        AllowCancel(false);
+                        if (CancelPending())
+                        {
+                            ReportComplete(true, true);
+                            return;
+                        }
+                        InstallLogger.Log(string.Format("Deleting {0}...", InstallerStrings.EdkrepoPrompt));
+                        File.Delete(edkrepoPromptDest);
+                    }
+
+                    string edkrepoCompletionDest = Path.Combine(gitBashEtcProfileDPath, InstallerStrings.EdkrepoCompletion);
+                    if (File.Exists(edkrepoCompletionDest))
+                    {
+                        AllowCancel(false);
+                        if (CancelPending())
+                        {
+                            ReportComplete(true, true);
+                            return;
+                        }
+                        InstallLogger.Log(string.Format("Deleting {0}...", InstallerStrings.EdkrepoCompletion));
+                        File.Delete(edkrepoCompletionDest);
+                    }
+
+                    //Remove call win_edkrepo_prompt.sh from bash.bashrc
+                    string bashrcPath = Path.Combine(gitBashEtcPath, "bash.bashrc");
+                    if (File.Exists(bashrcPath))
+                    {
+                        string original_bashrc = 
+ Encoding.UTF8.GetString(ReadFile(bashrcPath));
+
+                        string new_bashrc = Regex.Replace(original_bashrc, InstallerStrings.BashrcEdkrepoPromptCommentPattern, "");
+                        new_bashrc = Regex.Replace(new_bashrc, InstallerStrings.BashrcEdkrepoPromptCallPattern, "");
+                        if (new_bashrc == original_bashrc)
+                        {
+                            InstallLogger.Log("EdkRepo not found in bash.bashrc");
+                        }
+                        else
+                        {
+                            new_bashrc = new_bashrc.TrimEnd();
+                            using (BinaryWriter writer = new BinaryWriter(File.Open(bashrcPath, FileMode.Truncate, FileAccess.Write)))
+                            {
+                                string sanitized = new_bashrc.Replace("\r\n", "\n");
+                                writer.Write(Encoding.UTF8.GetBytes(sanitized));
+                            }
+                            InstallLogger.Log("EdkRepo prompt removed from bash.bashrc");
+                        }
+                    }
+                    else
+                    {
+                        InstallLogger.Log(string.Format("{0} not found", bashrcPath));
+                    }
+                }
             }
 
             //
-            // Step 3 - Uninstall any instances of edkrepo
+            // Step 4 - Uninstall any instances of edkrepo
             //
             IEnumerable<string> PackagesToUninstall = GetPythonWheelsToUninstall();
             InstallLogger.Log("Determining currently installed Python packages..."); @@ -1109,7 +1285,7 @@ namespace TianoCore.EdkRepoInstaller
             }
 
             //
-            // Step 4 - Invoke the Finish Uninstall Event
+            // Step 5 - Invoke the Finish Uninstall Event
             //
             if (VendorCustomizer.Instance != null)
             {
@@ -1122,7 +1298,7 @@ namespace TianoCore.EdkRepoInstaller
             }
 
             //
-            // Step 5 - Delete Programs and Feature uninstall link
+            // Step 6 - Delete Programs and Feature uninstall link
             //
             RegistryKey hklm = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);
             RegistryKey winUninstallRegistryKey = hklm.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", true); diff --git a/edkrepo_installer/EdkRepoInstaller/InstallerStrings.cs b/edkrepo_installer/EdkRepoInstaller/InstallerStrings.cs
index 6aa90ee..1542641 100644
--- a/edkrepo_installer/EdkRepoInstaller/InstallerStrings.cs
+++ b/edkrepo_installer/EdkRepoInstaller/InstallerStrings.cs
@@ -2,7 +2,7 @@
   InstallerStrings.cs
 
 @copyright
-  Copyright 2016 - 2019 Intel Corporation. All rights reserved.<BR>
+  Copyright 2016 - 2020 Intel Corporation. All rights reserved.<BR>
   SPDX-License-Identifier: BSD-2-Clause-Patent
 
 @par Specification Reference:
@@ -43,7 +43,7 @@ namespace TianoCore.EdkRepoInstaller
         }
 
         public static string EdkrepoPackageName
-        { 
+        {
             get
             {
                 return "edkrepo";
@@ -113,6 +113,46 @@ namespace TianoCore.EdkRepoInstaller
             }
         }
 
+        public static string EdkrepoPrompt
+        {
+            get
+            {
+                return "win_edkrepo_prompt.sh";
+            }
+        }
+
+        public static string EdkrepoCompletion
+        {
+            get
+            {
+                return "edkrepo_completions.sh";
+            }
+        }
+
+        public static string BashrcEdkrepoPromptCommentPattern
+        {
+            get
+            {
+                return @"#\s+Install\s+EdkRepo\s+command\s+completions";
+            }
+        }
+
+        public static string BashrcEdkrepoPromptCallPattern
+        {
+            get
+            {
+                return @"shopt\s+-q\s+login_shell\s+\|\|\s+\.\s+/etc/profile\.d/win_edkrepo_prompt\.sh";
+            }
+        }
+
+        public static string BashrcEdkRepoPromptCall
+        {
+            get
+            {
+                return "\n\n# Install EdkRepo command completions\nshopt -q login_shell || . /etc/profile.d/win_edkrepo_prompt.sh";
+            }
+        }
+
         public static string InstallerName
         {
             get
diff --git a/edkrepo_installer/Vendor/win_edkrepo_prompt.sh b/edkrepo_installer/Vendor/win_edkrepo_prompt.sh
new file mode 100644
index 0000000..5404175
--- /dev/null
+++ b/edkrepo_installer/Vendor/win_edkrepo_prompt.sh
@@ -0,0 +1,60 @@
+## @file win_edkrepo_prompt.sh
+# Note: For use on Git for Windows/MSYS2 ONLY.
+# UNIX version is in install.py
+#
+# Copyright (c) 2020, Intel Corporation. All rights reserved.<BR> # 
+SPDX-License-Identifier: BSD-2-Clause-Patent #
+
+# Add EdkRepo command completions
+[[ -r "/etc/profile.d/edkrepo_completions.sh" ]] && . "/etc/profile.d/edkrepo_completions.sh"
+
+# Add EdkRepo to the prompt
+ps1len="${#PS1}"
+let "pos38 = ps1len - 38"
+let "pos3 = ps1len - 3"
+let "pos2 = ps1len - 2"
+if [ "${PS1:pos38}" == '\[\033[36m\]`__git_ps1`\[\033[0m\]\n$ ' ]; then
+  newps1="${PS1:0:pos38}"
+  prompt_suffix='\[\033[36m\]`__git_ps1`\[\033[0m\]\n$ '
+elif [ "${PS1:pos3}" == "\\$ " ]; then
+  newps1="${PS1:0:pos3}"
+  prompt_suffix="\\$ "
+elif [ "${PS1:pos3}" == " $ " ]; then
+  newps1="${PS1:0:pos3}"
+  prompt_suffix=" $ "
+elif [ "${PS1:pos2}" == "$ " ]; then
+  newps1="${PS1:0:pos2}"
+  prompt_suffix="$ "
+else
+  newps1="$PS1"
+  prompt_suffix=""
+fi
+
+if [ -x "$(command -v edkrepo)" ] && [ -x "$(command -v 
+$command_completion_edkrepo_file)" ]; then
+  newps1="$newps1\[\033[32m\]\$current_edkrepo_combo\[\033[00m\]"
+  current_edkrepo_combo=$(command_completion_edkrepo current-combo)
+
+  # Determining the current Edkrepo combo requires invoking Python and 
+parsing
+  # manifest XML, which is a relatively expensive operation to do every 
+time
+  # the user presses <Enter>.
+  # As a performance optimization, only do this if the present working 
+directory
+  # changed
+  if [[ ! -z ${PROMPT_COMMAND+x} ]] && [[ "$PROMPT_COMMAND" != "edkrepo_combo_chpwd" ]]; then
+    old_prompt_command=$PROMPT_COMMAND
+  fi
+  old_pwd=$(pwd)
+  edkrepo_combo_chpwd() {
+      if [[ "$(pwd)" != "$old_pwd" ]]; then
+        old_pwd=$(pwd)
+        current_edkrepo_combo=$(command_completion_edkrepo current-combo)
+      fi
+      if [[ ! -z ${PROMPT_COMMAND+x} ]]; then
+        eval $old_prompt_command
+      fi
+  }
+  PROMPT_COMMAND=edkrepo_combo_chpwd
+fi
+
+PS1="$newps1$prompt_suffix"
+MSYS2_PS1="$PS1"  # for detection by MSYS2 SDK's bash.basrc
--
2.24.0.windows.2


^ permalink raw reply related	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2020-04-03 21:55 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2020-04-01 22:34 [edk2-staging/EdkRepo] [PATCH V1 0/3] EdkRepo: Command completion in bash/zsh Nate DeSimone
2020-04-01 22:34 ` [edk2-staging/EdkRepo] [PATCH V1 1/3] EdkRepo: Generate command completion scripts Nate DeSimone
2020-04-03 21:33   ` Ashley E Desimone
2020-04-01 22:34 ` [edk2-staging/EdkRepo] [PATCH V1 2/3] EdkRepo: Add command completion setup to install.py Nate DeSimone
2020-04-03 21:51   ` Ashley E Desimone
2020-04-01 22:34 ` [edk2-staging/EdkRepo] [PATCH V1 3/3] EdkRepo: Add command completion setup to Windows installer Nate DeSimone
2020-04-03 21:55   ` Ashley E Desimone

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox