Repository: ESMCI/cime

Issue:
case.build should set BUILD_COMPLETE=FALSE if a subsequent build fails
I see this in cime5.1.1. And assume unless someone's done something about it that it still exists.

If I setup and run a case successfully, so now BUILD_COMPLETE==TRUE, but then change code and run case.build over again, but this time I screwed something up so the build fails (BUILD_COMPLETE still is TRUE so I can submit and run my case [but it will use the exe before my changes]). I'd really like it to set BUILD_COMPLETE=FALSE so that I know it didn't compile.

Also as part of this, it's not obvious that the build step fails as it is currently. I can see it only with

stat=1

reported somewhere in all the standard output. I'd like it to report this in a more obvious manner at the end of the case.build.



Discussion:
I have not been able to reproduce the problem you reported of BUILD_COMPLETE not being reset to FALSE on a build failure - please provide a specific example that I can try to reproduce.  

And we've done some work on the build since cime5.1.1 so please use the current trunk.


Resolving patch:
diff --git a/utils/python/CIME/build.py b/utils/python/CIME/build.py
--- a/utils/python/CIME/build.py
+++ b/utils/python/CIME/build.py
@@ -2,7 +2,7 @@
 functions for building CIME models
 """
 from CIME.XML.standard_module_setup  import *
-from CIME.utils                 import get_model, append_status
+from CIME.utils                 import get_model, append_status, analyze_build_log
 from CIME.provenance            import save_build_provenance
 from CIME.preview_namelists     import create_namelists
 import glob, shutil, time, threading, gzip, subprocess
@@ -16,7 +16,7 @@ def stringify_bool(val):
 
 ###############################################################################
 def build_model(build_threaded, exeroot, clm_config_opts, incroot, complist,
-                lid, caseroot, cimeroot):
+                lid, caseroot, cimeroot, compiler):
 ###############################################################################
 
     logs = []
@@ -56,9 +56,11 @@ def build_model(build_threaded, exeroot, clm_config_opts, incroot, complist,
                 os.makedirs(build_dir)
 
         # build the component library
+        # thread_bad_results captures error output from thread (expected to be empty)
+        # logs is a list of log files to be compressed and added to the case logs/bld directory
         t = threading.Thread(target=_build_model_thread,
             args=(config_dir, model, caseroot, bldroot, libroot, incroot, file_build,
-                  thread_bad_results, smp))
+                  thread_bad_results, smp, compiler))
         t.start()
 
         for mod_file in glob.glob(os.path.join(bldroot, "*_[Cc][Oo][Mm][Pp]_*.mod")):
@@ -76,9 +78,11 @@ def build_model(build_threaded, exeroot, clm_config_opts, incroot, complist,
         smp = nthrds > 1 or build_threaded
         if comp == "aquap":
             logger.debug("Now build aquap ocn component")
+            # thread_bad_results captures error output from thread (expected to be empty)
+            # logs is a list of log files to be compressed and added to the case logs/bld directory
             _build_model_thread(config_dir, comp, caseroot, bldroot, libroot, incroot, file_build,
-                                thread_bad_results, smp)
-
+                                thread_bad_results, smp, compiler)
+            logs.append(file_build)
     expect(not thread_bad_results, "\n".join(thread_bad_results))
 
     #
@@ -95,7 +99,7 @@ def build_model(build_threaded, exeroot, clm_config_opts, incroot, complist,
                    from_dir=bldroot, verbose=True, arg_stdout=f,
                    arg_stderr=subprocess.STDOUT)[0]
     f.close()
-
+    analyze_build_log("%s exe"%cime_model, file_build, compiler)
     expect(stat == 0, "ERROR: buildexe failed, cat %s" % file_build)
 
     # Copy the just-built ${MODEL}.exe to ${MODEL}.exe.$LID
@@ -284,11 +288,11 @@ def case_build(caseroot, case, sharedlib_only=False, model_only=False):
 
     if not model_only:
         logs = build_libraries(case, exeroot, sharedpath, caseroot,
-                               cimeroot, libroot, lid)
+                               cimeroot, libroot, lid, compiler)
 
     if not sharedlib_only:
         logs.extend(build_model(build_threaded, exeroot, clm_config_opts, incroot, complist,
-                                lid, caseroot, cimeroot))
+                                lid, caseroot, cimeroot, compiler))
 
     if not sharedlib_only:
         post_build(case, logs)
@@ -407,7 +411,7 @@ def build_checks(case, build_threaded, comp_interface, use_esmf_lib,
     return sharedpath
 
 ###############################################################################
-def build_libraries(case, exeroot, sharedpath, caseroot, cimeroot, libroot, lid):
+def build_libraries(case, exeroot, sharedpath, caseroot, cimeroot, libroot, lid, compiler):
 ###############################################################################
 
     shared_lib = os.path.join(exeroot, sharedpath, "lib")
@@ -440,6 +444,7 @@ def build_libraries(case, exeroot, sharedpath, caseroot, cimeroot, libroot, lid)
                            from_dir=exeroot,
                            verbose=True, arg_stdout=fd,
                            arg_stderr=subprocess.STDOUT)[0]
+        analyze_build_log(lib, file_build, compiler)
         expect(stat == 0, "ERROR: buildlib.%s failed, cat %s" % (lib, file_build))
         logs.append(file_build)
         if lib == "pio":
@@ -465,19 +470,26 @@ def build_libraries(case, exeroot, sharedpath, caseroot, cimeroot, libroot, lid)
                 os.makedirs(ndir)
 
         smp = "SMP" in os.environ and os.environ["SMP"] == "TRUE"
-        _build_model_thread(config_lnd_dir, "lnd", caseroot, bldroot, libroot, incroot, file_build, logs, smp)
+        # thread_bad_results captures error output from thread (expected to be empty)
+        # logs is a list of log files to be compressed and added to the case logs/bld directory
+        thread_bad_results = []
+        _build_model_thread(config_lnd_dir, "lnd", caseroot, bldroot, libroot, incroot,
+                            file_build, thread_bad_results, smp, compiler)
+        logs.append(file_build)
+        expect(not thread_bad_results, "\n".join(thread_bad_results))
 
     return logs
 
 ###############################################################################
 def _build_model_thread(config_dir, compclass, caseroot, bldroot, libroot, incroot, file_build,
-                        thread_bad_results, smp):
+                        thread_bad_results, smp, compiler):
 ###############################################################################
     with open(file_build, "w") as fd:
         stat = run_cmd("MODEL=%s SMP=%s %s/buildlib %s %s %s " %
                        (compclass, stringify_bool(smp), config_dir, caseroot, bldroot, libroot),
                        from_dir=bldroot, verbose=True, arg_stdout=fd,
                        arg_stderr=subprocess.STDOUT)[0]
+    analyze_build_log(compclass, file_build, compiler)
     if (stat != 0):
         thread_bad_results.append("ERROR: %s.buildlib failed, see %s" % (compclass, file_build))
 
diff --git a/utils/python/CIME/utils.py b/utils/python/CIME/utils.py
--- a/utils/python/CIME/utils.py
+++ b/utils/python/CIME/utils.py
@@ -954,3 +954,33 @@ def new_lid():
     lid = time.strftime("%y%m%d-%H%M%S")
     os.environ["LID"] = lid
     return lid
+
+def analyze_build_log(comp, log, compiler):
+    """
+    Capture and report warning count,
+    capture and report errors and undefined references.
+    """
+    warncnt = 0
+    if "intel" in compiler:
+        warn_re = re.compile(r" warning #")
+        error_re = re.compile(r" error #")
+        undefined_re = re.compile(r" undefined reference to ")
+    elif "gnu" in compiler or "nag" in compiler:
+        warn_re = re.compile(r"^Warning: ")
+        error_re = re.compile(r"^Error: ")
+        undefined_re = re.compile(r" undefined reference to ")
+    else:
+        # don't know enough about this compiler
+        return
+
+    with open(log,"r") as fd:
+        for line in fd:
+            if re.search(warn_re, line):
+                warncnt += 1
+            if re.search(error_re, line):
+                logger.warn(line)
+            if re.search(undefined_re, line):
+                logger.warn(line)
+
+    if warncnt > 0:
+        logger.info("Component %s build complete with %s warnings"%(comp,warncnt))


Test patch: