public inbox for devel@edk2.groups.io
 help / color / mirror / Atom feed
From: "Leif Lindholm" <leif.lindholm@linaro.org>
To: "Fan, ZhijuX" <zhijux.fan@intel.com>
Cc: "devel@edk2.groups.io" <devel@edk2.groups.io>,
	"Gao, Liming" <liming.gao@intel.com>,
	"Feng, Bob C" <bob.c.feng@intel.com>,
	Ard Biesheuvel <ard.biesheuvel@linaro.org>,
	"Kinney, Michael D" <michael.d.kinney@intel.com>
Subject: Re: [edk2-platform patch 2/2] Platform/Intel:Add UniTool into edk2-platforms/Platform/Intel/Tools
Date: Fri, 21 Jun 2019 10:04:05 +0100	[thread overview]
Message-ID: <20190621090405.fi3gs225afoxyaml@bivouac.eciton.net> (raw)
In-Reply-To: <FAD0D7E0AE0FA54D987F6E72435CAFD50AF84144@SHSMSX101.ccr.corp.intel.com>

On Fri, Jun 21, 2019 at 01:58:57AM +0000, Fan, ZhijuX wrote:
> BZ:https://bugzilla.tianocore.org/show_bug.cgi?id=1855
> 
> UniTool is one python script to generate UQI (Universal Question
> Identifier) unicode string for HII question PROMPT string. UQI
> string can be used to identify each HII question.
> The scripts function will sync up UQI definitions with uni files
> based on vfi/vfr/hfr/sd/sdi in the tree.
> 
> This script can be run in both Py2 and Py3.
> 
> Cc: Liming Gao <liming.gao@intel.com>
> Cc: Bob Feng <bob.c.feng@intel.com>
> Cc: Ard Biesheuvel <ard.biesheuvel@linaro.org>
> Cc: Leif Lindholm <leif.lindholm@linaro.org>
> Cc: Michael D Kinney <michael.d.kinney@intel.com>
> Signed-off-by: Zhiju.Fan <zhijux.fan@intel.com>
> ---
>  Platform/Intel/Tools/UniTool/UniTool.py | 515 ++++++++++++++++++++++++++++++++
>  1 file changed, 515 insertions(+)
>  create mode 100644 Platform/Intel/Tools/UniTool/UniTool.py
> 
> diff --git a/Platform/Intel/Tools/UniTool/UniTool.py b/Platform/Intel/Tools/UniTool/UniTool.py
> new file mode 100644
> index 0000000000..aec25b51c2
> --- /dev/null
> +++ b/Platform/Intel/Tools/UniTool/UniTool.py
> @@ -0,0 +1,515 @@
> +## @file
> +# generate UQI (Universal Question Identifier) unicode string for HII question PROMPT string. UQI string can be used to
> +# identify each HII question.
> +#
> +# Copyright (c) 2019, Intel Corporation. All rights reserved.
> +#
> +# SPDX-License-Identifier: BSD-2-Clause-Patent
> +#
> +
> +import re
> +import sys
> +import os
> +import getopt
> +import codecs
> +import fnmatch
> +import logging
> +
> +# global variable declarations
> +QuestionError = False
> +UqiList = re.compile('^#string[ \t]+([A-Z_0-9]+)[ \t]+#language[ \t]+uqi[ \t\r\n]+"(?:[x\S]{1,2})([0-9a-fA-F]{4,5})"',
> +                     re.M).findall
> +AllUqis = {}
> +StringDict = {}
> +GlobalVarId = {}
> +Options = {}
> +
> +
> +# **********************************************************************
> +# description: Prints help information
> +#
> +# arguments:   none
> +#
> +# returns:     none
> +#
> +
> +def Usage():

Why not use argparse?

/
    Leif

> +    print("Syntax:  %s [-b] [-u] [-l] [-x] [-h] [-d 'rootDirectory1'] [-d 'rootDirectory2'] [-d 'rootDirectory3']... [-q e|w] \
> +'rootDirectory0' 'uqiFile'|'uqiFileDirectory' ['excludedDirectory1'] ['excludedDirectory2'] ['excludedDirectory3']...\n%s" %
> +          (os.path.basename(sys.argv[0]),
> +           """\nFunction will sync up UQI definitions with uni files based on vfi/vfr/hfr/sd/sdi in the tree.\n
> +     Required Arguments:
> +       'rootdirectory0'       path to root directory
> +       'uqiFileDirectory'     path to UQI file(UqiList.uni)
> +       'uqiFile'              UQI file
> +
> +     Options:
> +       -h                    Show this help
> +       -b                    Build option returns error if any new UQI needs assigning
> +                             based on vfi/vfr/hfr/sd/sdi when no -u option is specified
> +       -u                    Create new UQIs that does not already exist in uqiFile for
> +                             any string requiring a UQI based on vfi/vfr/hfr/sd/sdi
> +                             NOTE: 'uqiFile' cannot be readonly!
> +       -l                    Language deletion option (keeps only English and uqi)
> +                             moves all UQIs to 'uqiFile'
> +                             NOTE: Uni files cannot be readonly!
> +       -x                    Exclude 'rootDirectory'/'excludedDirectory1' &
> +                             'rootDirectory'/'excludedDirectory2'... from UQI list build
> +                             NOTE: Cannot be the same as rootDirectory
> +       -d                    Add multiple root directories to process
> +       -q                    Print warning(w) or return error(e) if different HII questions
> +                             are referring same string token
> +
> +     Return error if any duplicated UQI string or value in UQI list or if no definition
> +     for any string referred by HII question when -b or -u is specified
> +
> +     NOTE: Options must be specified before parameters
> +     """))
> +    sys.exit()
> +
> +
> +# **********************************************************************
> +# description: Get uni file encoding
> +#
> +# arguments:   Filename - name of uni file
> +#
> +# returns:     utf-8 or utf-16
> +#
> +def GetUniFileEncoding(Filename):
> +    #
> +    # Detect Byte Order Mark at beginning of file.  Default to UTF-8
> +    #
> +    Encoding = 'utf-8'
> +
> +    #
> +    # Read file
> +    #
> +    try:
> +        with open(Filename, mode='rb') as UniFile:
> +            FileIn = UniFile.read()
> +    except:
> +        return Encoding
> +
> +    if (FileIn.startswith(codecs.BOM_UTF16_BE) or FileIn.startswith(codecs.BOM_UTF16_LE)):
> +        Encoding = 'utf-16'
> +
> +    return Encoding
> +
> +
> +# rewrite function os.path.walk
> +def Walk(Top, Func, Arg):
> +    try:
> +        Names = os.listdir(Top)
> +    except os.error:
> +        return
> +    Func(Arg, Top, Names)
> +    for Name in Names:
> +        Name = os.path.join(Top, Name)
> +        if os.path.isdir(Name):
> +            Walk(Name, Func, Arg)
> +
> +
> +# **********************************************************************
> +# description: Parses commandline arguments and options
> +#              Calls function processUni to build dictionary of strings
> +#              Calls other functions according to user specified options
> +#
> +# arguments:   argv - contains all input from command line
> +#                   - must contain path to root directory
> +#                   - may contain options -h, -u, -l, -b or -x before path
> +#
> +# returns:     none
> +#
> +def main(argv):
> +    ##### Read input arguments and options
> +    global AllUqis, UqiList, QuestionError
> +    try:
> +        Opts, Args = getopt.getopt(argv[1:], "hulbxd:q:")  # each letter is an optional argument
> +    except getopt.GetoptError:
> +        Usage()
> +    try:
> +        DirNameList = [Args[0]]
> +        QuestionOption = None
> +        for EachOpt in Opts:
> +            if EachOpt[0] == '-d':
> +                DirNameList.append(EachOpt[1])
> +            if EachOpt[0] == '-q':
> +                QuestionOption = EachOpt[1]
> +                if (QuestionOption != "e") and (QuestionOption != "w"):
> +                    print("\nERROR: invalid option value for -q option\n")
> +                    return
> +        Destname = Args[1]
> +        if len(Args) > 2:
> +            ExDirList = Args[2:]
> +    except:
> +        Usage()
> +
> +    UpdateUQIs = False
> +    LangOption = False
> +    BuildOption = False
> +    ExcludeOption = False
> +    ExPathList = []
> +
> +    for Opt, Args in Opts:
> +        if Opt == "-h":
> +            Usage()
> +        if Opt == "-b":
> +            BuildOption = True
> +        if Opt == "-u":
> +            BuildOption = True
> +            UpdateUQIs = True
> +        if Opt == "-l":
> +            LangOption = True
> +        if Opt == "-x":
> +            ExcludeOption = True
> +            try:
> +                for EachExDir in ExDirList:
> +                    for EachRootDir in DirNameList:
> +                        if EachExDir == EachRootDir:
> +                            print("\nERROR: excludedDirectory is same as rootDirectory\n")
> +                            return
> +                    ExPathList.append(EachRootDir + os.sep + EachExDir)
> +            except:
> +                Usage()
> +
> +    global Options
> +    Options = {'Destname': Destname, 'DirNameList': DirNameList, 'ExPathList': ExPathList, 'BuildOption': BuildOption,
> +               'UpdateUQIs': UpdateUQIs, 'LangOption': LangOption, 'ExcludeOption': ExcludeOption,
> +               'QuestionOption': QuestionOption}
> +    print("UQI file: %s" % Destname)
> +    for EachDirName in DirNameList:
> +        Walk(EachDirName, processUni, None)
> +    if QuestionError:
> +        return
> +    if os.path.isdir(Options['Destname']):
> +        DestFileName = Options['Destname'] + os.sep + 'UqiList.uni'
> +    else:
> +        DestFileName = Options['Destname']
> +    if os.path.exists(DestFileName) and (DestFileName not in list(AllUqis.keys())):
> +        try:
> +            Encoding = GetUniFileEncoding(DestFileName)
> +            with codecs.open(DestFileName, 'r+', Encoding) as destFile:
> +                DestFileBuffer = destFile.read()
> +        except IOError as e:
> +            print("ERROR: " + e.args[1])
> +            return
> +        AllUqis[DestFileName] = UqiList(DestFileBuffer)
> +    if BuildOption:
> +        ReturnVal = newUqi()
> +        if (ReturnVal == 1):
> +            print('Please fix UQI ERROR(s) above before proceeding.')
> +        else:
> +            print("No UQI issues detected\n")
> +    return
> +
> +
> +# **********************************************************************
> +# description: newUqi collects a list of all currently used uqi values in the tree
> +#              Halt build if any duplicated string or value in UQI list.
> +#              If -u option was specified, creates new UQIs that does not
> +#              already exist in uqiFile for any string requiring a UQI.
> +#
> +# arguments:   none
> +#
> +# returns:     0 on success
> +#              1 on error - this should cause the build to halt
> +#
> +
> +Syntax = "S"
> +SyntaxRE = re.compile('#string[ \t]+[A-Z_0-9]+[ \t]+#language[ \t]+uqi[ \t\r\n]+"([x\S]{1,2}).*', re.DOTALL).findall
> +
> +
> +def newUqi():
> +    global Options, GlobalVarId, AllUqis, Syntax, SyntaxRE
> +    UqiRange = []
> +    UqiStringList = []
> +    CreateUQI = []
> +    ReturnVal = 0
> +    BaseNumSpaces = 47  # Used to line up the UQI values in the resulting uqiFile
> +
> +    # Look for duplication in the current UQIs and collect current range of UQIs
> +    for path in AllUqis.keys():
> +        for UqiString in AllUqis[path]:  # path contains the path and Filename of each uni file
> +            # Checks for duplicated strings in UQI list
> +            for TempString in UqiStringList:
> +                if TempString == UqiString[0]:
> +                    print("ERROR: UQI string %s was assigned more than once and will cause corruption!" % UqiString[0])
> +                    print("Delete one occurrence of the string and rerun tool.")
> +                    ReturnVal = 1  # halt build
> +
> +            UqiStringList.append(UqiString[0])
> +
> +            # Checks for duplicated UQI values in UQI list
> +            if int(UqiString[1], 16) in UqiRange:
> +                print("ERROR: UQI value %04x was assigned more than once and will cause corruption!" % int(UqiString[1],
> +                                                                                                           16))
> +                print("Delete one occurrance of the UQI and rerun tool to create alternate value.")
> +                ReturnVal = 1  # halt build
> +            UqiRange.append(int(UqiString[1], 16))
> +
> +    for StringValue in GlobalVarId.keys():
> +        StringFound = False
> +        for path in StringDict.keys():
> +            for UniString in StringDict[path]:  # path contains the path and Filename of each uni file
> +                if (StringValue == UniString):
> +                    StringFound = True
> +                    break
> +        if not StringFound:
> +            print("ERROR: No definition for %s referred by HII question" % (StringValue))
> +            ReturnVal = 1  # halt build
> +
> +    # Require a UQI for any string in vfr/vfi files
> +    for StringValue in GlobalVarId.keys():
> +        # Ignore strings defined as STRING_TOKEN(0)
> +        if (StringValue != "0"):
> +            # Check if this string already exists in the UQI list
> +            if (StringValue not in UqiStringList) and (StringValue not in CreateUQI):
> +                CreateUQI.append(StringValue)
> +                if not Options['UpdateUQIs']:
> +                    print("ERROR: No UQI for %s referred by HII question" % (StringValue))
> +                    ReturnVal = 1  # halt build after printing all error messages
> +
> +    if (ReturnVal == 1):
> +        return ReturnVal
> +
> +    # Update uqiFile with necessary UQIs
> +    if Options['UpdateUQIs'] and CreateUQI:
> +        if os.path.isdir(Options['Destname']):
> +            DestFileName = Options['Destname'] + os.sep + 'UqiList.uni'
> +        else:
> +            DestFileName = Options['Destname']
> +        try:
> +            Encoding = GetUniFileEncoding(DestFileName)
> +            with codecs.open(DestFileName, 'r+', Encoding) as OutputFile:
> +                PlatformUQI = OutputFile.read()
> +        except IOError as e:
> +            print("ERROR: " + e.args[1])
> +            if (e.args[0] == 2):
> +                try:
> +                    with codecs.open(DestFileName, 'w', Encoding) as OutputFile:
> +                        print(DestFileName + " did not exist.  Creating new file.")
> +                        PlatformUQI = ''
> +                except:
> +                    print("Error creating " + DestFileName + ".")
> +                    return 1
> +            if (e.args[1] == "Permission denied"):
> +                print(
> +                    "\n%s is Readonly.  You must uncheck the ReadOnly attibute to run the -u option.\n" % DestFileName)
> +                return 1
> +
> +        # Determines and sets the UQI number format
> +        # TODO: there is probably a more elegant way to do this...
> +        SyntaxL = SyntaxRE(PlatformUQI)
> +        if len(SyntaxL) != 0:
> +            Syntax = SyntaxL[0]
> +
> +        # script is reading the file in and writing it back instead of appending because the codecs module
> +        # automatically adds a BOM wherever you start writing. This caused build failure.
> +        UqiRange.sort()
> +        if (UqiRange == []):
> +            NextUqi = 0
> +        else:
> +            NextUqi = UqiRange[len(UqiRange) - 1] + 1
> +
> +        for StringValue in CreateUQI:
> +            print("%s will be assigned a new UQI value" % StringValue)
> +            UqiRange.append(NextUqi)
> +            #
> +            # Lines up the UQI values in the resulting uqiFile
> +            #
> +            Spaces = " " * (BaseNumSpaces - len(StringValue))
> +            PlatformUQI += '#string %s%s #language uqi \"%s%04x\"\r\n' % (StringValue, Spaces, Syntax, NextUqi)
> +            print("#string %s%s #language uqi  \"%s%04X\"" % (StringValue, Spaces, Syntax, NextUqi))
> +            NextUqi += 1
> +
> +        with codecs.open(DestFileName, 'r+', Encoding) as OutputFile:
> +            OutputFile.seek(0)
> +            OutputFile.write(PlatformUQI)
> +
> +    return 0
> +
> +
> +# **********************************************************************
> +# description: Parses each uni file to collect dictionary of strings
> +#              Removes additional languages and overwrites current uni files
> +#              if -l option was specified
> +#
> +# arguments:   path - directory location of file including file name
> +#              Filename - name of file to be modified
> +#
> +# returns:     error string if failure occurred;
> +#              none if completed sucessfully
> +#
> +# the following are global so that parsefile is quicker
> +
> +FindUniString = re.compile(
> +    '^#string[ \t]+([A-Z_0-9]+)(?:[ \t\r\n]+#language[ \t]+[a-zA-Z-]{2,5}[ \t\r\n]+".*"[ \t]*[\r]?[\n]?)*',
> +    re.M).findall
> +
> +OtherLang = re.compile(
> +    '^#string[ \t]+[A-Z_0-9]+(?:[ \t\r\n]+#language[ \t]+[a-zA-Z-]{2,5}[ \t\r\n]+".*"[ \t]*[\r]?[\n]?)*', re.M).findall
> +EachLang = re.compile('[ \t\r\n]+#language[ \t]+([a-zA-Z-]{2,5})[ \t\r\n]+".*"[ \t]*[\r]?[\n]?').findall
> +
> +UqiStrings = re.compile('^#string[ \t]+[A-Z_0-9]+[ \t]+#language[ \t]+uqi[ \t\r\n]+".*"[ \t]*[\r]?[\n]?', re.M)
> +
> +
> +def parsefile(path, Filename):
> +    global Options, StringDict, AllUqis, UqiList, FindUniString, OtherLang, EachLang, UqiStrings
> +
> +    FullPath = path + os.sep + Filename
> +
> +    try:
> +        UniEncoding = GetUniFileEncoding(FullPath)
> +        with codecs.open(FullPath, 'r', UniEncoding) as UniFile:
> +            Databuffer = UniFile.read()
> +    except:
> +        print("Error opening " + FullPath + " for reading.")
> +        return
> +    WriteFile = False
> +
> +    if os.path.isdir(Options['Destname']):
> +        DestFileName = Options['Destname'] + os.sep + 'UqiList.uni'
> +    else:
> +        DestFileName = Options['Destname']
> +
> +    if Options['LangOption']:
> +        try:
> +            UqiEncoding = GetUniFileEncoding(DestFileName)
> +            with codecs.open(DestFileName, 'r+', UqiEncoding) as OutputFile:
> +                PlatformUQI = OutputFile.read()
> +        except IOError as e:
> +            print("ERROR: " + e.args[1])
> +            if (e.args[0] == 2):
> +                try:
> +                    with codecs.open(DestFileName, 'w', UqiEncoding) as OutputFile:
> +                        print(DestFileName + " did not exist.  Creating new file.")
> +                        PlatformUQI = ''
> +                except:
> +                    print("Error creating " + DestFileName + ".")
> +                    return
> +            else:
> +                print("Error opening " + DestFileName + " for appending.")
> +                return
> +
> +        if (Filename != DestFileName.split(os.sep)[-1]):
> +            Uqis = re.findall(UqiStrings, Databuffer)
> +            if Uqis:
> +                for Uqi in Uqis:
> +                    PlatformUQI += Uqi
> +                with codecs.open(DestFileName, 'r+', UqiEncoding) as OutputFile:
> +                    OutputFile.seek(0)
> +                    OutputFile.write(PlatformUQI)
> +            Databuffer = re.sub(UqiStrings, '', Databuffer)
> +            if Uqis:
> +                WriteFile = True
> +                print("Deleted uqis from %s" % FullPath)
> +            stringlist = OtherLang(Databuffer)
> +            for stringfound in stringlist:
> +                ThisString = EachLang(stringfound)
> +                for LanguageFound in ThisString:
> +                    if ((LanguageFound != 'en') and (LanguageFound != 'en-US') and (LanguageFound != 'eng') and (
> +                            LanguageFound != 'uqi')):
> +                        Databuffer = re.sub(re.escape(stringfound), '', Databuffer)
> +                        WriteFile = True
> +                        print("Deleted %s from %s" % (LanguageFound, FullPath))
> +    if (Filename != DestFileName.split(os.sep)[-1]):
> +        # adding strings to dictionary
> +        StringDict[r'%s' % FullPath] = FindUniString(Databuffer)
> +    # adding UQIs to dictionary
> +    AllUqis[r'%s' % FullPath] = UqiList(Databuffer)
> +
> +    if WriteFile:
> +        try:
> +            with codecs.open(FullPath, 'w', UniEncoding) as UniFile:
> +                UniFile.write(Databuffer)
> +        except:
> +            print("Error opening " + FullPath + " for writing.")
> +    return
> +
> +
> +# **********************************************************************
> +# description: Searches tree for uni files
> +#              Calls parsefile to collect dictionary of strings in each uni file
> +#              Calls searchVfiFile for each vfi or vfr file found
> +#
> +# arguments:   argument list is built by os.path.walk function call
> +#              arg     - None
> +#              dirname - directory location of files
> +#              names   - specific files to search in directory
> +#
> +# returns:     none
> +#
> +def processUni(args, dirname, names):
> +    global Options
> +    # Remove excludedDirectory
> +    if Options['ExcludeOption']:
> +        for EachExDir in Options['ExPathList']:
> +            for dir in names:
> +                if os.path.join(dirname, dir) == EachExDir:
> +                    names.remove(dir)
> +
> +    for entry in names:
> +        FullPath = dirname + os.sep + entry
> +        if fnmatch.fnmatch(FullPath, '*.uni'):
> +            parsefile(dirname, entry)
> +        if fnmatch.fnmatch(FullPath, '*.vf*'):
> +            searchVfiFile(FullPath)
> +        if fnmatch.fnmatch(FullPath, '*.sd'):
> +            searchVfiFile(FullPath)
> +        if fnmatch.fnmatch(FullPath, '*.sdi'):
> +            searchVfiFile(FullPath)
> +        if fnmatch.fnmatch(FullPath, '*.hfr'):
> +            searchVfiFile(FullPath)
> +    return
> +
> +
> +# **********************************************************************
> +# description: Compose a dictionary of all strings that may need UQIs assigned
> +#              to them and key is the string
> +#
> +# arguments:   Filename - name of file to search for strings
> +#
> +# returns:     none
> +#
> +
> +# separate regexes for readability
> +StringGroups = re.compile(
> +    '^[ \t]*(?:oneof|numeric|checkbox|orderedlist)[ \t]+varid.+?(?:endoneof|endnumeric|endcheckbox|endorderedlist);',
> +    re.DOTALL | re.M).findall
> +StringVarIds = re.compile(
> +    '[ \t]*(?:oneof|numeric|checkbox|orderedlist)[ \t]+varid[ \t]*=[ \t]*([a-zA-Z_0-9]+\.[a-zA-Z_0-9]+)').findall
> +StringTokens = re.compile('prompt[ \t]*=[ \t]*STRING_TOKEN[ \t]*\(([a-zA-Z_0-9]+)\)').findall
> +
> +
> +def searchVfiFile(Filename):
> +    global Options, GlobalVarId, StringGroups, StringVarIds, StringTokens, QuestionError
> +    try:
> +        with open(Filename, 'r') as VfiFile:
> +            Databuffer = VfiFile.read()
> +
> +        # Finds specified lines in file
> +        VfiStringGroup = StringGroups(Databuffer)
> +
> +        # Searches for prompts within specified lines
> +        for EachGroup in VfiStringGroup:
> +            for EachString in StringTokens(EachGroup):
> +                # Ignore strings defined as STRING_TOKEN(0), STRING_TOKEN(STR_EMPTY) or STRING_TOKEN(STR_NULL)
> +                if (EachString != "0") and (EachString != "STR_EMPTY") and (EachString != "STR_NULL"):
> +                    if EachString not in GlobalVarId:
> +                        GlobalVarId[EachString] = StringVarIds(EachGroup)
> +                    else:
> +                        if (GlobalVarId[EachString][0] != StringVarIds(EachGroup)[0]):
> +                            if Options['QuestionOption']:
> +                                if Options['QuestionOption'] == "e":
> +                                    QuestionError = True
> +                                    print("ERROR:"),
> +                                if Options['QuestionOption'] == "w":
> +                                    print("WARNING:"),
> +                                print("%s referred by different HII questions(%s and %s)" % (
> +                                    EachString, GlobalVarId[EachString][0], StringVarIds(EachGroup)[0]))
> +    except:
> +        print("Error opening file at %s for reading." % Filename)
> +
> +
> +if __name__ == '__main__':
> +    sys.exit(main(sys.argv))
> -- 
> 2.14.1.windows.1
> 



  reply	other threads:[~2019-06-21  9:04 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-06-21  1:58 [edk2-platform patch 2/2] Platform/Intel:Add UniTool into edk2-platforms/Platform/Intel/Tools Fan, ZhijuX
2019-06-21  9:04 ` Leif Lindholm [this message]
2019-06-21 10:02   ` Fan, ZhijuX
2019-06-21 10:28     ` [edk2-devel] " Leif Lindholm

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-list from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20190621090405.fi3gs225afoxyaml@bivouac.eciton.net \
    --to=devel@edk2.groups.io \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox