Added "no-copy" and "include" functions to the existing "exclude"
to handle the process of replacing existing vendor cell views with
custom cell views.  Added a syntax to all three functions that is
<file_path>/<wildcard> and allows names for groups of cells to be
taken from a location other than the source;  e.g., from a custom
directory where replacements are found.  Updated the Makefile for
sky130 to make use of the standard cell replacements (in
particular, corrections to GDS layout), and to better integrate
additions to the libraries.
diff --git a/common/README b/common/README
index decfec1..521f84c 100644
--- a/common/README
+++ b/common/README
@@ -457,8 +457,14 @@
 		    than taking the name of the library.
 
 	       exclude=<file>[,...]
-		    When using "compile" or "compile-only", exclude any
-		    file in the target directory matching the name <file>.
+		    Exclude file(s) from the comma-separated list when
+		    copying from source to target.  The list items may
+		    include glob-style wildcards.
+
+	       excludefrom=<dir>
+		    Exclude all files in the listing of directory <dir>
+		    from the comma-separated list when copying from source
+		    to target.
 
     File conversions handled by foundry_install.py:
 
diff --git a/common/create_lef_library.py b/common/create_lef_library.py
index 4a46e5f..72a5025 100755
--- a/common/create_lef_library.py
+++ b/common/create_lef_library.py
@@ -75,6 +75,9 @@
         with open(alllibname, 'w') as ofile:
             headerdone = False
             for lfile in llist:
+                if not os.path.exists(lfile):
+                    print('Error: File ' + lfile + ' not found (skipping).')
+                    continue
                 with open(lfile, 'r') as ifile:
                     # print('Adding ' + lfile + ' to library.')
                     ltext = ifile.read()
diff --git a/common/create_lib_library.py b/common/create_lib_library.py
index 1f058c7..247f541 100755
--- a/common/create_lib_library.py
+++ b/common/create_lib_library.py
@@ -79,6 +79,9 @@
         with open(alllibname, 'w') as ofile:
             headerdone = False
             for lfile in llist:
+                if not os.path.exists(lfile):
+                    print('Error: File ' + lfile + ' not found (skipping).')
+                    continue
                 with open(lfile, 'r') as ifile:
                     # print('Adding ' + lfile + ' to library.')
                     ltext = ifile.read()
diff --git a/common/create_verilog_library.py b/common/create_verilog_library.py
index 55e1c76..d5b09ea 100755
--- a/common/create_verilog_library.py
+++ b/common/create_verilog_library.py
@@ -78,6 +78,9 @@
         with open(alllibname, 'w') as ofile:
             allmodules = []
             for vfile in vlist:
+                if not os.path.exists(vfile):
+                    print('Error: File ' + vfile + ' not found (skipping).')
+                    continue
                 with open(vfile, 'r') as ifile:
                     # print('Adding ' + vfile + ' to library.')
                     vtext = ifile.read()
diff --git a/common/foundry_install.py b/common/foundry_install.py
index fcb8970..6328491 100755
--- a/common/foundry_install.py
+++ b/common/foundry_install.py
@@ -75,10 +75,12 @@
 #	nospec	:  Remove timing specification before installing
 #		    (used with verilog files;  needs to be extended to
 #		    liberty files)
+#
 #	compile :  Create a single library from all components.  Used
 #		    when a foundry library has inconveniently split
 #		    an IP library (LEF, CDL, verilog, etc.) into
 #		    individual files.
+#
 #	compile-only:	Like "compile" except that the individual
 #		    files are removed after the library file has been
 #		    created.
@@ -93,7 +95,27 @@
 #
 #	exclude :  Followed by "=" and a comma-separated list of names.
 #		    exclude these files/modules/subcircuits.  Names may
-#		    also be wildcarded in "glob" format.
+#		    also be wildcarded in "glob" format.  Cell names are
+#		    excluded from the copy and also from any libraries
+#		    that are generated.  If the pattern contains a directory
+#		    path, then patterns are added from files in the
+#		    specified directory instead of the source.
+#
+#	no-copy :  Followed by "=" and a comma-separated list of names.
+#		    exclude these files/modules/subcircuits.  Names may
+#		    also be wildcarded in "glob" format.  Cell names are
+#		    excluded from the copy only.  If the pattern contains
+#		    a directory path, then patterns are added from files
+#		    in the specified directory instead of the source.
+#
+#	include :  Followed by "=" and a comma-separated list of names.
+#		    include these files/modules/subcircuits.  Names may
+#		    also be wildcarded in "glob" format.  Cell names are
+#		    included in any libraries that are generated if they
+#		    exist in the target directory, even if they were not
+#		    in the source directory being copied.  If the pattern
+#		    contains a directory path, then patterns are added from
+#		    files in the specified directory instead of the source.
 #
 #	rename :   Followed by "=" and an alternative name.  For any
 #		    file that is a single entry, change the name of
@@ -117,7 +139,12 @@
 #		    If not specified, files are sorted by "natural sort"
 #		    order.
 #
-#	noconvert : Install only; do not attempt to convert to other
+#	annotate:  When used with "-lef", the LEF files are used only
+#		    for annotation of pins (use, direction, etc.), but
+#		    the LEF files should not be used and LEF should be
+#		    generated from layout.
+#
+#	noconvert: Install only; do not attempt to convert to other
 #		    formats (applies only to GDS, CDL, and LEF).
 #
 #	options:   Followed by "=" and the name of a script.  Behavior
@@ -738,13 +765,35 @@
         do_remove_spec = 'nospecify' in option or 'nospec' in option
 
         # Option 'exclude' has an argument
+        excludelist = []
         try:
             excludelist = list(item.split('=')[1].split(',') for item in option if item.startswith('excl'))[0]
         except IndexError:
-            excludelist = []
-        else:
+            pass
+
+        if len(excludelist) > 0:
             print('Excluding files: ' + (',').join(excludelist))
 
+        # Option 'no-copy' has an argument
+        nocopylist = []
+        try:
+            nocopylist = list(item.split('=')[1].split(',') for item in option if item.startswith('no-copy'))[0]
+        except IndexError:
+            pass
+
+        if len(nocopylist) > 0:
+            print('Not copying files: ' + (',').join(nocopylist))
+
+        # Option 'include' has an argument
+        includelist = []
+        try:
+            includelist = list(item.split('=')[1].split(',') for item in option if item.startswith('incl'))[0]
+        except IndexError:
+            pass
+
+        if len(includelist) > 0:
+            print('Including files: ' + (',').join(includelist))
+
         # Option 'rename' has an argument
         try:
             newname = list(item.split('=')[1] for item in option if item.startswith('rename'))[0]
@@ -800,7 +849,13 @@
                 liblistnames = list(os.path.split(item)[1] for item in liblist)
                 notliblist = []
                 for exclude in excludelist:
-                    notliblist.extend(fnmatch.filter(liblistnames, exclude))
+                    if '/' in exclude:
+                        # Names come from files in a path that is not the source
+                        excludefiles = os.listdir(os.path.split(exclude)[0])
+                        pattern = os.path.split(exclude)[1]
+                        notliblist.extend(fnmatch.filter(excludefiles, pattern))
+                    else:
+                        notliblist.extend(fnmatch.filter(liblistnames, exclude))
 
                 # Apply exclude list
                 if len(notliblist) > 0:
@@ -813,6 +868,17 @@
                     print('excludelist = ' + str(excludelist))
                     print('destlibdir = ' + destlibdir)
 
+            # Create a list of cell names not to be copied from "nocopylist"
+            nocopynames = []
+            for nocopy in nocopylist:
+                if '/' in nocopy:
+                    # Names come from files in a path that is not the source
+                    nocopyfiles = os.listdir(os.path.split(nocopy)[0])
+                    pattern = os.path.split(nocopy)[1]
+                    nocopynames.extend(fnmatch.filter(nocopyfiles, pattern))
+                else:
+                    nocopynames.extend(fnmatch.filter(liblistnames, nocopy))
+
             # Diagnostic
             print('Collecting files from ' + testpath)
             print('Files to install:')
@@ -831,6 +897,7 @@
 
             destfilelist = []
             for libname in liblist:
+     
                 # Note that there may be a hierarchy to the files in option[1],
                 # say for liberty timing files under different conditions, so
                 # make sure directories have been created as needed.
@@ -844,6 +911,8 @@
                 destpathcomp.reverse()
                 destpath = ''.join(destpathcomp)
 
+                dontcopy = True if libfile in nocopynames else False
+
                 if option[0] == 'verilog':
                     fileext = '.v'
                 elif option[0] == 'liberty' or option[0] == 'lib':
@@ -880,16 +949,20 @@
 
                 # Remove any existing file
                 if os.path.isfile(targname):
-                    os.remove(targname)
+                    if not dontcopy:
+                        os.remove(targname)
                 elif os.path.isdir(targname):
-                    shutil.rmtree(targname)
+                    if not dontcopy:
+                        shutil.rmtree(targname)
 
                 # NOTE:  Diagnostic, probably much too much output.
                 print('   Install:' + libname + ' to ' + targname)
                 if os.path.isfile(libname):
-                    shutil.copy(libname, targname)
+                    if not dontcopy:
+                        shutil.copy(libname, targname)
                 else:
-                    shutil.copytree(libname, targname)
+                    if not dontcopy:
+                        shutil.copytree(libname, targname)
 
                 # File filtering options:  Two options 'stub' and 'nospec' are
                 # handled by scripts in ../common/.  Custom filters can also be
@@ -914,6 +987,19 @@
 
                 destfilelist.append(os.path.split(targname)[1])
 
+            # Add names from "include" list to destfilelist before writing
+            # filelist.txt for library file compiling.
+
+            includenames = []
+            for incname in includelist:
+                if '/' in incname:
+                    # Names come from files in a path that is not the source
+                    incfiles = os.listdir(os.path.split(incname)[0])
+                    pattern = os.path.split(incname)[1]
+                    destfilelist.extend(fnmatch.filter(incfiles, pattern))
+                else:
+                    destfilelist.extend(fnmatch.filter(liblistnames, incname))
+
             if sortscript:
                 with open(destlibdir + '/filelist.txt', 'w') as ofile:
                     for destfile in destfilelist: