[dvsim] Move clean_odirs to `util.py`

Output directory cleaner does not need to be tied to a Launcher instance
- moving it to a generic utils module.

Signed-off-by: Srikrishna Iyer <sriyer@google.com>
diff --git a/util/dvsim/Deploy.py b/util/dvsim/Deploy.py
index d6fc4d2..b0114a8 100644
--- a/util/dvsim/Deploy.py
+++ b/util/dvsim/Deploy.py
@@ -9,7 +9,8 @@
 from LocalLauncher import LocalLauncher
 from sim_utils import get_cov_summary_table
 from tabulate import tabulate
-from utils import VERBOSE, find_and_substitute_wildcards, rm_path
+from utils import (VERBOSE, clean_odirs, find_and_substitute_wildcards,
+                   rm_path, subst_wildcards)
 
 
 class Deploy():
@@ -475,23 +476,24 @@
             if run.cov_db_dir not in self.cov_db_dirs:
                 self.cov_db_dirs.append(run.cov_db_dir)
 
-        super().__init__(sim_cfg)
+        # Early lookup the cov_merge_db_dir, which is a mandatory misc
+        # attribute anyway. We need it to compute additional cov db dirs.
+        self.cov_merge_db_dir = subst_wildcards("{cov_merge_db_dir}",
+                                                sim_cfg.__dict__)
 
+        # Prune previous merged cov directories, keeping past 7 dbs.
+        prev_cov_db_dirs = clean_odirs(odir=self.cov_merge_db_dir, max_odirs=7)
+
+        # If the --cov-merge-previous command line switch is passed, then
+        # merge coverage with the previous runs.
+        if sim_cfg.cov_merge_previous:
+            self.cov_db_dirs += [str(item) for item in prev_cov_db_dirs]
+
+        super().__init__(sim_cfg)
         self.dependencies += run_items
         # Run coverage merge even if one test passes.
         self.needs_all_dependencies_passing = False
 
-        # TODO: need to move this up.
-        # Prune previous merged cov directories.
-        prev_cov_db_dirs = self.launcher.clean_odirs(
-            odir=self.cov_merge_db_dir)
-
-        # If a merged cov data base exists from a previous run, then consider
-        # that as well for merging, if the --cov-merge-previous command line
-        # switch is passed.
-        if self.sim_cfg.cov_merge_previous:
-            self.cov_db_dirs += [str(item) for item in prev_cov_db_dirs]
-
         # Append cov_db_dirs to the list of exports.
         self.exports["cov_db_dirs"] = "\"{}\"".format(" ".join(
             self.cov_db_dirs))
diff --git a/util/dvsim/Launcher.py b/util/dvsim/Launcher.py
index fff1891..c3d7200 100644
--- a/util/dvsim/Launcher.py
+++ b/util/dvsim/Launcher.py
@@ -5,11 +5,9 @@
 import logging as log
 import os
 import re
-import shutil
-from datetime import datetime
 from pathlib import Path
 
-from utils import TS_FORMAT, VERBOSE, rm_path
+from utils import VERBOSE, clean_odirs, rm_path
 
 
 class LauncherError(Exception):
@@ -58,7 +56,7 @@
 
         # If renew_odir flag is True - then move it.
         if self.renew_odir:
-            self.clean_odirs(odir=self.deploy.odir)
+            clean_odirs(odir=self.deploy.odir, max_odirs=self.max_odirs)
         os.makedirs(self.deploy.odir, exist_ok=True)
 
     def _dump_env_vars(self, exports):
@@ -232,32 +230,3 @@
         if status != "D":
             old = Path(self.deploy.sim_cfg.links['D'], self.deploy.qual_name)
             rm_path(old)
-
-    def clean_odirs(self, odir):
-        """Clean previous output directories.
-
-        When running jobs, we may want to maintain a limited history of
-        previous invocations. This method finds and deletes the output
-        directories at the base of input arg 'odir' with the oldest timestamps,
-        if that limit is reached. It returns a list of directories that
-        remain after deletion.
-        """
-
-        if not os.path.exists(odir):
-            return []
-
-        # If output directory exists, back it up.
-        ts = datetime.fromtimestamp(os.stat(odir).st_ctime)
-        ts = ts.strftime(TS_FORMAT)
-        shutil.move(odir, odir + "_" + ts)
-
-        # Get list of past output directories sorted by creation time.
-        pdir = Path(odir).resolve().parent
-        dirs = sorted([old for old in pdir.iterdir() if old.is_dir()],
-                      key=os.path.getctime,
-                      reverse=True)
-
-        for old in dirs[self.max_odirs - 1:]:
-            rm_path(old)
-
-        return dirs[0:self.max_odirs - 2]
diff --git a/util/dvsim/utils.py b/util/dvsim/utils.py
index 476a624..82afaa9 100644
--- a/util/dvsim/utils.py
+++ b/util/dvsim/utils.py
@@ -14,6 +14,8 @@
 import sys
 import time
 from collections import OrderedDict
+from datetime import datetime
+from pathlib import Path
 
 import hjson
 import mistletoe
@@ -547,3 +549,34 @@
         log.error("Failed to remove {}:\n{}.".format(path, e))
         if not ignore_error:
             raise e
+
+
+def clean_odirs(odir, max_odirs, ts_format=TS_FORMAT):
+    """Clean previous output directories.
+
+    When running jobs, we may want to maintain a limited history of
+    previous invocations. This method finds and deletes the output
+    directories at the base of input arg 'odir' with the oldest timestamps,
+    if that limit is reached. It returns a list of directories that
+    remain after deletion.
+    """
+
+    if os.path.exists(odir):
+        # If output directory exists, back it up.
+        ts = datetime.fromtimestamp(os.stat(odir).st_ctime).strftime(ts_format)
+        shutil.move(odir, "{}_{}".format(odir, ts))
+
+    # Get list of past output directories sorted by creation time.
+    pdir = Path(odir).resolve().parent
+    if not pdir.exists():
+        return []
+
+    dirs = sorted([old for old in pdir.iterdir() if old.is_dir()],
+                  key=os.path.getctime,
+                  reverse=True)
+
+    for old in dirs[max_odirs - 1:]:
+        if os.path.exists(old):
+            shutil.rmtree(old, ignore_errors=True)
+
+    return dirs[0:max_odirs - 2]