We have the capability to exclude certain OpBuilder/OpSupportChecker from the build
if they are not used when we are building with reduced ops.

To perform build time op registration reduction, certain build scripts need to be modified.
Please also see the commits within this PR, https://github.com/microsoft/onnxruntime/pull/5841

- tools/ci_build/exclude_unused_ops.py

# class extends BaseExcludeOpsRegistrationProcessor, which has shared line processing functionalities
# the __init__ and _should_exclude_op is different in this and ExcludeOpsRegistrationProcessor
# Note, BaseExcludeOpsRegistrationProcessor is created based on ExcludeOpsRegistrationProcessor with
# its process_registration and process_other_line functions
class ExcludeNNAPIOpsRegistrationProcessor(BaseExcludeOpsRegistrationProcessor):
    def __init__(self, required_ops, output_file):
        # we only care about op_type here in nnapi, put all the op_type into a set
        self.required_ops = set()
        for opsets in required_ops.values():
            for op_types in opsets.values():
                for op_type in op_types:
                    self.required_ops.add(op_type)

        self.output_file = output_file

    def _should_exclude_op(self, domain, operator, start_version, end_version):
        return operator not in self.required_ops


- tools/ci_build/op_registration_utils.py

# lines with OpBuilder/OpSupportChecker registration will start with 'NNAPI_EP_ADD_'
# such as
# NNAPI_EP_ADD_SHARED_OP_BUILDER("Abs", UnaryOpBuilder);
# NNAPI_EP_ADD_SINGLE_OP_BUILDER("Concat", ConcatOpBuilder);
# NNAPI_EP_ADD_SHARED_OP_SUPPORT_CHECKER("Conv", ConvOpSupportChecker);
# NNAPI_EP_ADD_SINGLE_OP_SUPPORT_CHECKER("Cast", CastOpSupportChecker);

# this function will find the paths of NNAPI OpBuilder/OpSupportChecker registration files
def get_nnapi_registration_files(ort_root=None):
    '''
    Return paths to files containing operator registrations for NNAPI EP.
    :param ort_root: ORT repository root directory. Inferred from the location of this script if not provided.
    :return: list[str] containing the operator registration filenames.
    '''
    if not ort_root:
        ort_root = os.path.dirname(os.path.abspath(__file__)) + '/../..'

    nnapi_path = ort_root + '/onnxruntime/core/providers/nnapi/nnapi_builtin/builders/'
    nnapi_provider_paths = [nnapi_path + 'op_builder.cc',
                            nnapi_path + 'op_support_checker.cc']

    nnapi_provider_paths = [os.path.abspath(p) for p in nnapi_provider_paths]
    return nnapi_provider_paths

# this function will process the lines of NNAPI OpBuilder/OpSupportChecker registration files
def _process_nnapi_lines(lines: typing.List[str], offset: int, registration_processor: RegistrationProcessor):
    end_mark = ';'
    lines_to_process = []

    # merge line if split over multiple.
    # original lines will be in lines_to_process. merged and stripped line will be in code_line
    while True:
        lines_to_process.append(lines[offset])
        stripped = lines[offset].strip()

        if stripped.endswith(end_mark):
            break

        offset += 1
        if offset > len(lines):
            log.error('Past end of input lines looking for line terminator.')
            sys.exit(-1)

    code_line = ''.join([line.strip() for line in lines_to_process])

    line_match_pattern = ('^NNAPI_EP_ADD_(SHARED_OP_BUILDER|SINGLE_OP_BUILDER|'
                          'SHARED_OP_SUPPORT_CHECKER|SINGLE_OP_SUPPORT_CHECKER)\\(\\"\\s*(\\w+)\\"')

    match = re.match(line_match_pattern, code_line)
    if match:
        # extract the op_type from the regex match
        op_type = match.group(2)
        registration_processor.process_registration(lines_to_process, None, op_type, None, None, None)

    return offset + 1
