ICU-21323 Automates the uconfig variation BRS tasks. The test
subsequently sets each of the UCONFIG_NO_XXX flags to '1' (exceptions apply)
and runs the ICU4C unit tests and the header test. Afterwards all
UCONFIG_NO_XXX flags are set to '1' and unit tests and header test
are executed.

To allow concurrent execution and reduce total run time, the script provides
the option to execute only the unit tests or only the header test.

ICU-21323 Split the uconfig.h variation test into two to reduce
execution time. One test now tests the variations with unit tests, the
other tests with header test.
Execution time now ranges between 45 and 58 minutes.

ICU-21323 Works in review comments.

ICU-21323 Factors in more review comments.
diff --git a/.github/workflows/icu_ci.yml b/.github/workflows/icu_ci.yml
index d0936c4..e178fcf 100644
--- a/.github/workflows/icu_ci.yml
+++ b/.github/workflows/icu_ci.yml
@@ -252,3 +252,17 @@
     steps:
       - uses: actions/checkout@v2
       - run: mvn -f tools/release/java/pom.xml package dependency:analyze
+
+  # Run unit tests with UCONFIG_NO_XXX variations.
+  uconfig_variation-check-unit-tests:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - run: python3 tools/scripts/uconfig_vars_test.py -u
+
+# Run header tests with UCONFIG_NO_XXX variations.
+  uconfig_variation-check-all-header-tests:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - run: python3 tools/scripts/uconfig_vars_test.py -p
diff --git a/icu4c/source/test/hdrtst/Makefile.in b/icu4c/source/test/hdrtst/Makefile.in
index af0e848..5e640a4 100644
--- a/icu4c/source/test/hdrtst/Makefile.in
+++ b/icu4c/source/test/hdrtst/Makefile.in
@@ -17,6 +17,10 @@
 ##
 ##    .. etc.  Anything other than zero is an error. (except for the deprecation tests, where '1' is the correct value)
 ##
+##  If the header test is run for a particular UCONFIG_NO_XXX=1 variation (see uconfig.h)
+##  then invoke the test with 'make UCONFIG_NO="-DUCONFIG_NO_XXX=1 check'.
+##  For standard header test run the UCONFIG_NO variable will evaluate to empty string.
+##
 ##  If a header fails the C compile test it is likely because the header is a
 ##  C++ header and it isn't properly guarded by the U_SHOW_CPLUSPLUS_API macro.
 ##
@@ -48,7 +52,7 @@
 		echo "$(NAME.headers) unicode/$$incfile" ; \
 		echo "#include <unicode/$$incfile>" > $$stub ; \
 		echo 'void junk(){}' >> $$stub ; \
-		$(COMPILE.headers) $(cppflags) $(FLAGS.headers) $$stub || FAIL=1 ; \
+		$(COMPILE.headers) $(cppflags) $(FLAGS.headers) $(UCONFIG_NO) $$stub || FAIL=1 ; \
 		rm -f $$stub; \
 	done ; \
 	exit $$FAIL
@@ -121,7 +125,7 @@
 			echo '#if !defined(U_SHOW_CPLUSPLUS_API)' >> $$stub ; \
 			echo "#error The header '$$incfile' refers to the macro U_SHOW_CPLUSPLUS_API (defined in utypes.h) but either does not include utypes.h or does so incorrectly." >> $$stub ; \
 			echo '#endif' >> $$stub ; \
-			$(COMPILE.cc) $(cppflags) $$stub || FAIL=1 ; \
+			$(COMPILE.cc) $(cppflags) $(UCONFIG_NO) $$stub || FAIL=1 ; \
 			rm -f $$stub; \
 		else \
 			echo "$@ skipping unicode/$$incfile" ; \
diff --git a/tools/scripts/uconfig_vars_test.py b/tools/scripts/uconfig_vars_test.py
new file mode 100755
index 0000000..226ff1d
--- /dev/null
+++ b/tools/scripts/uconfig_vars_test.py
@@ -0,0 +1,219 @@
+# © 2021 and later: Unicode, Inc. and others.
+# License & terms of use: http://www.unicode.org/copyright.html
+
+"""Executes uconfig variations check.
+
+See
+http://site.icu-project.org/processes/release/tasks/healthy-code#TOC-Test-uconfig.h-variations
+for more information.
+"""
+
+import getopt
+import os
+import re
+import subprocess
+import sys
+
+excluded_unit_test_flags = ['UCONFIG_NO_CONVERSION', 'UCONFIG_NO_FILE_IO'];
+
+def ReadFile(filename):
+    """Reads a file and returns the content of the file
+
+    Args:
+      command: string with the filename.
+
+    Returns:
+      Content of file.
+    """
+
+    with open(filename, 'r') as file_handle:
+      return file_handle.read()
+
+def RunCmd(command):
+    """Executes the command, returns output and exit code, writes output to log
+
+    Args:
+      command: string with the command.
+
+    Returns:
+      stdout and exit code of command execution.
+    """
+
+    command += ' >> uconfig_test.log 2>&1'
+    print(command)
+    p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE,
+                         stderr=subprocess.STDOUT, close_fds=True)
+    stdout, _ = p.communicate()
+    return stdout, p.returncode
+
+def ExtractUConfigNoXXX(uconfig_file):
+    """Parses uconfig.h and returns a list of UCONFIG_NO_XXX labels.
+    Initializes test result structure.
+
+    Args:
+      uconfig_file: common/unicode/uconfig.h as string.
+
+    Returns:
+      List of all UCONFIG_NO_XXX flags found in uconfig.h, initialized test
+      result structure.
+    """
+
+    uconfig_no_flags_all = []
+    test_results = {}
+    uconfig_no_regex = r'UCONFIG_NO_[A-Z_]*'
+
+    # Collect all distinct occurences of UCONFIG_NO_XXX matches in uconfig.h.
+    for uconfig_no_flag in re.finditer(uconfig_no_regex, uconfig_file):
+        if uconfig_no_flag.group(0) not in uconfig_no_flags_all:
+            uconfig_no_flags_all.append(uconfig_no_flag.group(0))
+
+    # All UCONFIG_NO_XXX flags found in uconfig.h come in form of a guarded
+    # definition. Verify the existence, report error if not found.
+    for uconfig_no_flag in uconfig_no_flags_all:
+        uconfig_no_def_regex = r'(?m)#ifndef %s\n#\s+define %s\s+0\n#endif$' % (
+        uconfig_no_flag, uconfig_no_flag)
+        uconfig_no_def_match = re.search(uconfig_no_def_regex, uconfig_file)
+        if not uconfig_no_def_match:
+            print('No definition for flag %s found!\n' % uconfig_no_flag)
+            sys.exit(1)
+
+    test_results = {f: {'unit_test': False, 'hdr_test': False} for f in uconfig_no_flags_all}
+    test_results['all_flags'] = {'unit_test': False, 'hdr_test' : False}
+
+    return uconfig_no_flags_all, test_results
+
+def BuildAllFlags(uconfig_no_list):
+    """Builds sequence of -Dflag=1 with each flag from the list."""
+
+    flag_list = ['-D' + uconfig_no + '=1' for uconfig_no in uconfig_no_list]
+
+    return ' '.join(flag_list)
+
+def RunUnitTests(uconfig_no_list, test_results):
+    """Iterates over all flags, sets each individually during ICU configuration
+       and executes the ICU4C unit tests.
+
+    Args:
+      uconfig_no_list: list of all UCONFIG_NO_XXX flags to test with.
+      test_results: dictionary to record test run results.
+
+    Returns:
+      test_results: updated test result entries.
+    """
+
+    for uconfig_no in uconfig_no_list:
+        _, exit_code = RunCmd(
+                './runConfigureICU Linux CPPFLAGS=\"-D%s=1"' % uconfig_no)
+        if exit_code != 0:
+            print('ICU4C configuration for flag %s failed' % uconfig_no)
+            sys.exit(1)
+        print('Running unit tests with %s set to 1.' % uconfig_no)
+        _, exit_code = RunCmd('make -j2 check')
+        test_results[uconfig_no]['unit_test'] = (exit_code == 0)
+        RunCmd('make clean')
+
+    # Configure ICU with all UCONFIG_NO_XXX flags set to 1 and execute
+    # the ICU4C unit tests.
+    all_unit_test_config_no = BuildAllFlags(uconfig_no_list)
+    _, exit_code = RunCmd(
+        'CPPFLAGS=\"%s\" ./runConfigureICU Linux' % all_unit_test_config_no)
+    if exit_code != 0:
+        print('ICU configuration with all flags set failed')
+        sys.exit(1)
+    print('Running unit tests with all flags set to 1.')
+    _, exit_code = RunCmd('make -j2 check')
+    test_results['all_flags']['unit_test'] = (exit_code == 0)
+    RunCmd('make clean')
+
+    return test_results
+
+def RunHeaderTests(uconfig_no_list, test_results):
+    """Iterates over all flags and executes the header test.
+
+    Args:
+      uconfig_no_list: list of all UCONFIG_NO_XXX flags to test with.
+      test_results: dictionary to record test run results.
+
+    Returns:
+      test_results: updated test result entries.
+    """
+
+    # Header tests needs different setup.
+    RunCmd('mkdir /tmp/icu_cnfg')
+    out, exit_code = RunCmd('./runConfigureICU Linux --prefix=/tmp/icu_cnfg')
+    if exit_code != 0:
+        print('ICU4C configuration for header test failed!')
+        print(out)
+        sys.exit(1)
+
+    _, exit_code = RunCmd('make -j2 install')
+    if exit_code != 0:
+        print('make install failed!')
+        sys.exit(1)
+
+    for uconfig_no in uconfig_no_list:
+        print('Running header tests with %s set to 1.' % uconfig_no)
+        _, exit_code = RunCmd(
+            'PATH=/tmp/icu_cnfg/bin:$PATH make -C test/hdrtst UCONFIG_NO=\"-D%s=1\" check' % uconfig_no)
+        test_results[uconfig_no]['hdr_test'] = (exit_code == 0)
+
+    all_hdr_test_flags = BuildAllFlags(uconfig_no_list)
+    print('Running header tests with all flags set to 1.')
+    _, exit_code = RunCmd(
+        'PATH=/tmp/icu_cnfg/bin:$PATH make -C test/hdrtst UCONFIG_NO=\"%s\" check' % all_hdr_test_flags)
+    test_results['all_flags']['hdr_test'] = (exit_code == 0)
+
+    return test_results
+
+def main():
+    # Read the options and determine what to run.
+    run_hdr = False
+    run_unit = False
+    optlist, _ = getopt.getopt(sys.argv[1:], "pu")
+    for o, _ in optlist:
+        if o == "-p":
+            run_hdr = True
+        elif o == "-u":
+            run_unit = True
+
+    os.chdir('icu4c/source')
+    orig_uconfig_file = ReadFile('common/unicode/uconfig.h')
+
+    all_uconfig_no_flags, test_results = ExtractUConfigNoXXX(orig_uconfig_file)
+    if not all_uconfig_no_flags:
+        print('No UCONFIG_NO_XXX flags found!\n')
+        sys.exit(1)
+
+    if run_unit:
+        RunUnitTests(
+            [u for u in all_uconfig_no_flags if u not in excluded_unit_test_flags],
+            test_results)
+    if run_hdr:
+        RunHeaderTests(all_uconfig_no_flags, test_results)
+
+    # Review test results and report any failures.
+    # 'outcome' will be returned by sys.exit(); 0 indicates success, any
+    # other value indicates failure.
+    outcome = 0
+    print('Summary:\n')
+    for uconfig_no in all_uconfig_no_flags:
+        if run_unit and (uconfig_no not in excluded_unit_test_flags):
+            if not test_results[uconfig_no]['unit_test']:
+                outcome = 1
+                print('%s: unit tests fail' % uconfig_no)
+        if run_hdr and not test_results[uconfig_no]['hdr_test']:
+            outcome = 1
+            print('%s: header tests fails' % uconfig_no)
+    if run_unit and not test_results['all_flags']['unit_test']:
+        outcome = 1
+        print('all flags to 1: unit tests fail!')
+    if run_hdr and not test_results['all_flags']['hdr_test']:
+        outcome = 1
+        print('all flags to 1: header tests fail!')
+    if outcome == 0:
+        print('Tests pass for all uconfig variations!')
+    sys.exit(outcome)
+
+
+if __name__ == '__main__':
+  main()