Modified sky130 Makefile to remove a blockage to parallelism that
was preventing the Makefile from building the different PDK vendor
libraries in parallel.  Also:  Made some revisions to the project
manager (work in progress).  Includes breaking out the python
"natural sort" routine into its own file, and not relying on the
"natsort" package, which is generally not included with python3
distributions and needs to be pip installed.
diff --git a/Makefile.in b/Makefile.in
index 8628b9c..1c59993 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -85,9 +85,9 @@
 datadir = @datadir@
 
 # NOTE:  All scripts used by the project and design flow management
-# system are in the "runtime" directory, except for cdl2spi.py, which
-# is the one file used by scripts in both the common/ and runtime/
-# directories.
+# system are in the "runtime" directory, except for cdl2spi.py and
+# natural_sort.py, which are the two files used by scripts in both the
+# common/ and runtime/ directories.
 
 common_install:
 	@if test -w $(datadir) ; then \
@@ -103,6 +103,7 @@
 		done ;\
 		mv $(datadir)/pdk/runtime/* $(datadir)/pdk/scripts ;\
 		${CPP} -DPREFIX=$(datadir) common/cdl2spi.py $(datadir)/pdk/scripts/cdl2spi.py ;\
+		${CPP} -DPREFIX=$(datadir) common/natural_sort.py $(datadir)/pdk/scripts/natural_sort.py ;\
 		rm -r -f $(datadir)/pdk/runtime ;\
 		echo "Common install:  Done." ;\
 	else \
diff --git a/VERSION b/VERSION
index e8b71fe..2cde13a 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.0.282
+1.0.283
diff --git a/common/create_gds_library.py b/common/create_gds_library.py
index 2dfa111..32d1636 100755
--- a/common/create_gds_library.py
+++ b/common/create_gds_library.py
@@ -14,7 +14,7 @@
 import glob
 import fnmatch
 import subprocess
-from sort_pdkfiles import natural_sort
+import natural_sort
 
 #----------------------------------------------------------------------------
 
@@ -57,7 +57,7 @@
         glist = glob.glob(destlibdir + '/*.gds')
         glist.extend(glob.glob(destlibdir + '/*.gdsii'))
         glist.extend(glob.glob(destlibdir + '/*.gds2'))
-        glist = natural_sort(glist)
+        glist = natural_sort.natural_sort(glist)
 
     if alllibname in glist:
         glist.remove(alllibname)
diff --git a/common/create_lef_library.py b/common/create_lef_library.py
index 6c9899c..3834296 100755
--- a/common/create_lef_library.py
+++ b/common/create_lef_library.py
@@ -13,7 +13,7 @@
 import os
 import glob
 import fnmatch
-from sort_pdkfiles import natural_sort
+import natural_sort
 
 #----------------------------------------------------------------------------
 
@@ -54,7 +54,7 @@
                 llist.append(destlibdir + '/' + rfile)
     else:
         llist = glob.glob(destlibdir + '/*.lef')
-        llist = natural_sort(llist)
+        llist = natural_sort.natural_sort(llist)
 
     if alllibname in llist:
         llist.remove(alllibname)
diff --git a/common/create_lib_library.py b/common/create_lib_library.py
index ccb58bf..6fbef49 100755
--- a/common/create_lib_library.py
+++ b/common/create_lib_library.py
@@ -13,7 +13,7 @@
 import os
 import glob
 import fnmatch
-from sort_pdkfiles import natural_sort
+import natural_sort
 
 #----------------------------------------------------------------------------
 
@@ -61,7 +61,7 @@
                 llist.append(destlibdir + '/' + rfile)
     else:
         llist = glob.glob(destlibdir + '/*.lib')
-        llist = natural_sort(llist)
+        llist = natural_sort.natural_sort(llist)
 
     # Create exclude list with glob-style matching using fnmatch
     if len(llist) > 0:
diff --git a/common/create_spice_library.py b/common/create_spice_library.py
index df2f197..d26eddb 100755
--- a/common/create_spice_library.py
+++ b/common/create_spice_library.py
@@ -14,7 +14,7 @@
 import re
 import glob
 import fnmatch
-from sort_pdkfiles import natural_sort
+import natural_sort
 
 #----------------------------------------------------------------------------
 
@@ -74,7 +74,7 @@
             slist.extend(glob.glob(destlibdir + '/*.ckt'))
             slist.extend(glob.glob(destlibdir + '/*.cir'))
             slist.extend(glob.glob(destlibdir + '/*' + spiext))
-        slist = natural_sort(slist)
+        slist = natural_sort.natural_sort(slist)
 
     if alllibname in slist:
         slist.remove(alllibname)
diff --git a/common/create_verilog_library.py b/common/create_verilog_library.py
index e1bc4af..baa6624 100755
--- a/common/create_verilog_library.py
+++ b/common/create_verilog_library.py
@@ -14,7 +14,7 @@
 import re
 import glob
 import fnmatch
-from sort_pdkfiles import natural_sort
+import natural_sort
 
 #----------------------------------------------------------------------------
 
@@ -57,7 +57,7 @@
                 vlist.append(destlibdir + '/' + rfile)
     else:
         vlist = glob.glob(destlibdir + '/*.v')
-        vlist = natural_sort(vlist)
+        vlist = natural_sort.natural_sort(vlist)
 
     if alllibname in vlist:
         vlist.remove(alllibname)
diff --git a/common/foundry_install.py b/common/foundry_install.py
index 68a02da..e27009c 100755
--- a/common/foundry_install.py
+++ b/common/foundry_install.py
@@ -179,12 +179,12 @@
 import subprocess
 
 # Import local routines
+import natural_sort
 from create_gds_library import create_gds_library
 from create_spice_library import create_spice_library
 from create_lef_library import create_lef_library
 from create_lib_library import create_lib_library
 from create_verilog_library import create_verilog_library
-from sort_pdkfiles import natural_sort
 
 def usage():
     print("foundry_install.py [options...]")
@@ -896,7 +896,7 @@
 
             testpath = substitute(sourcedir + '/' + option[1], library[1])
             liblist = glob.glob(testpath)
-            liblist = natural_sort(liblist)
+            liblist = natural_sort.natural_sort(liblist)
 
             # Create a file "sources.txt" (or append to it if it exists)
             # and add the source directory name so that the staging install
@@ -1927,7 +1927,7 @@
                         # in it.
                         try:
                             cdlfiles = glob.glob(cdllibdir + '/*.cdl')
-                            cdlfiles = natural_sort(cdlfiles)
+                            cdlfiles = natural_sort.natural_sort(cdlfiles)
                         except:
                             pass
                     if len(cdlfiles) > 0:
@@ -2154,12 +2154,12 @@
                 glist = glob.glob(srclibdir + '/*.gds')
                 glist.extend(glob.glob(srclibdir + '/*.gdsii'))
                 glist.extend(glob.glob(srclibdir + '/*.gds2'))
-                glist = natural_sort(glist)
+                glist = natural_sort.natural_sort(glist)
 
             allleflibname = leflibdir + '/' + destlib + '.lef'
             if not os.path.isfile(allleflibname):
                 llist = glob.glob(leflibdir + '/*.lef')
-                llist = natural_sort(llist)
+                llist = natural_sort.natural_sort(llist)
 
             print('Creating magic generation script to generate SPICE library.') 
             with open(destlibdir + '/generate_magic.tcl', 'w') as ofile:
diff --git a/common/natural_sort.py b/common/natural_sort.py
new file mode 100755
index 0000000..7ad1e8c
--- /dev/null
+++ b/common/natural_sort.py
@@ -0,0 +1,11 @@
+#!/usr/bin/env python3
+#
+# natural_sort.py
+# Natural sort thanks to Mark Byers in StackOverflow
+
+import re
+
+def natural_sort(l):
+    convert = lambda text: int(text) if text.isdigit() else text.lower()
+    alphanum_key= lambda key: [ convert(c) for c in re.split('([0-9]+)', key) ]
+    return sorted(l, key = alphanum_key)
diff --git a/common/sort_pdkfiles.py b/common/sort_pdkfiles.py
index ac0a107..9dc5492 100755
--- a/common/sort_pdkfiles.py
+++ b/common/sort_pdkfiles.py
@@ -14,12 +14,7 @@
 import re
 import os
 import sys
-
-# Natural sort thanks to Mark Byers in StackOverflow
-def natural_sort(l):
-    convert = lambda text: int(text) if text.isdigit() else text.lower()
-    alphanum_key= lambda key: [ convert(c) for c in re.split('([0-9]+)', key) ]
-    return sorted(l, key = alphanum_key)
+import natural_sort
 
 def pdk_sort(destdir):
     if not os.path.isfile(destdir + '/filelist.txt'):
@@ -29,7 +24,7 @@
     with open(destdir + '/filelist.txt', 'r') as ifile:
         vlist = ifile.read().splitlines()
 
-    vlist = natural_sort(vlist)
+    vlist = natural_sort.natural_sort(vlist)
     
     with open(destdir + '/filelist.txt', 'w') as ofile:
         for vfile in vlist:
diff --git a/runtime/config.py b/runtime/config.py
deleted file mode 100755
index 7485171..0000000
--- a/runtime/config.py
+++ /dev/null
@@ -1,32 +0,0 @@
-# Configuration values for the OpenGalaxy machines
-# Select config-file directed by OPTIONAL (INI file): /etc/sysconfig/ef-config ef_variant= line.
-# File should: have no [section] header, use "# " comments, var="val" (no spaces around =),
-# no dash in var-names, for good compatibility between python and bash.
-#
-# default if fail to read/parse the etc file is STAGING. These values:
-#          ef_variant=DEV       ef_variant=STAGING       ef_variant=PROD
-# yield respectively:
-#    import config_DEV import config_STAGING import config_PROD
-#
-# Survive (try:) missing,improper,unreadable /etc/sysconfig/ef-config.
-# DO NOT survive (no try:) failed imports (non-existent file, bad syntax, etc.).
-
-#
-# look-up ef_variant=... in optional etc file, default to STAGING
-#
-import configparser
-#TODO: replace path with PREFIX
-apps_path="PREFIX/pdk/bin"
-
-config = configparser.ConfigParser(strict=False, allow_no_value=True)
-try:
-    config.read_string("[null]\n"+open("/etc/sysconfig/ef-config").read())
-except:
-    pass
-ef_variant = config.get('null','ef_variant', fallback='STAGING').strip('\'"')
-
-#
-# emulate: import config_<ef_variant>
-#
-#cfgModule = __import__("config_"+ef_variant)
-#globals().update(vars(cfgModule))
diff --git a/runtime/project_manager.py b/runtime/project_manager.py
index e3ef491..4d26945 100755
--- a/runtime/project_manager.py
+++ b/runtime/project_manager.py
@@ -1,98 +1,46 @@
 #!/usr/bin/env python3
 #
 #--------------------------------------------------------
-# Open Galaxy Project Manager GUI.
+# Efabless Open Galaxy Project Manager GUI ported to
+# open_pdks.
 #
 # This is a Python tkinter script that handles local
-# project management.  It is meant as a replacement for
-# appsel_zenity.sh
+# project management.
 #
 #--------------------------------------------------------
 # Written by Tim Edwards
 # efabless, inc.
 # September 9, 2016
 # Modifications 2017, 2018
-# Version 1.0
+# Updates 2021 by Max Chen
+# Version 1.1
 #--------------------------------------------------------
 
 import sys
-# Require python 3.5.x (and not python 3.6.x). Without this trap here, in several
-# instances of VMs where /usr/bin/python3 symlinked to 3.6.x by mistake, it manifests
-# as (misleading) errors like: ImportError: No module named 'yaml'
-#
-# '%x' % sys.hexversion  ->  '30502f0'
+import io
+import os
+import re
+import glob
+import json
+import signal
+import shutil
+import tarfile
+import datetime
+import tempfile
+import subprocess
+import contextlib
+import faulthandler
 
 import tkinter
 from tkinter import ttk, StringVar, Listbox, END
 from tkinter import filedialog
 
-# globals
-theProg = sys.argv[0]
-root = tkinter.Tk()      # WARNING: must be exactly one instance of Tk; don't call again elsewhere
-
-# 4 configurations based on booleans: splash,defer
-# n,n:  no splash, show only form when completed: LEGACY MODE, user confused by visual lag.
-# n,y:  no splash but defer projLoad: show an empty form ASAP
-# y,n:  yes splash, and wait for projLoad before showing completed form
-# y,y:  yes splash, but also defer projLoad: show empty form ASAP
-
-# deferLoad = False        # LEGACY: no splash, and wait for completed form
-# doSplash = False
-
-deferLoad = True         # True: display GUI before (slow) loading of projects, so no splash:
-doSplash = not deferLoad # splash IFF GUI-construction includes slow loading of projects
-
-# deferLoad = False        # load projects before showing form, so need splash:
-# doSplash = not deferLoad # splash IFF GUI-construction includes slow loading of projects
-
-# deferLoad = True         # here keep splash also, despite also deferred-loading
-# doSplash = True
-
-#------------------------------------------------------
-# Splash screen: display ASAP: BEFORE bulk of imports.
-#------------------------------------------------------
-
-class SplashScreen(tkinter.Toplevel):
-    """Project Management Splash Screen"""
-
-    def __init__(self, parent, *args, **kwargs):
-        super().__init__(parent, *args, **kwargs)
-        parent.withdraw()
-        #EFABLESS PLATFORM
-        #image = tkinter.PhotoImage(file="/ef/efabless/opengalaxy/og_splashscreen50.gif")
-        label = ttk.Label(self, image=image)
-        label.pack()
-
-        # required to make window show before the program gets to the mainloop
-        self.update_idletasks()
-
-import faulthandler
-import signal
-
-# SplashScreen here. fyi: there's a 2nd/later __main__ section for main app
-splash = None     # a global
-if __name__ == '__main__':
-    faulthandler.register(signal.SIGUSR2)
-    if doSplash:
-        splash = SplashScreen(root)
-
-import io
-import os
-import re
-import json
-import yaml
-import shutil
-import tarfile
-import datetime
-import subprocess
-import contextlib
-import tempfile
-import glob
+# Local imports
 
 import tksimpledialog
 import tooltip
+
 from rename_project import rename_project_all
-#from fix_libdirs import fix_libdirs
 from consoletext import ConsoleText
 from helpwindow import HelpWindow
 from treeviewchoice import TreeViewChoice
@@ -100,21 +48,40 @@
 from make_icon_from_soft import create_symbol
 from profile import Profile
 
-import config
+# Global variables
 
-# Global name for design directory
+theProg = sys.argv[0]
+
+deferLoad = True         # True: display GUI before (slow) loading of projects
+
+# There must be exactly one instance of Tk; don't call again elsewhere.
+root = tkinter.Tk()
+
+# Name for design directory
 designdir = 'design'
-# Global name for import directory
+
+# Name for import directory
 importdir = 'import'
-# Global name for cloudv directory
+
+# Name for cloudv directory
 cloudvdir = 'cloudv'
-# Global name for archived imports project sub-directory
+
+# Name for archived imports project sub-directory
 archiveimportdir = 'imported'
-# Global name for current design file
-#EFABLESS PLATFORM
+
+# Name for current design file
 currdesign = '~/.open_pdks/currdesign'
+
+# Name for personal preferences file
 prefsfile = '~/.open_pdks/prefs.json'
 
+# Application directory.
+try:
+    pdk_root = os.environ('PDK_ROOT')
+except:
+    pdk_root = 'PREFIX/pdk'
+
+apps_path = pdk_root + '/scripts'
 
 #---------------------------------------------------------------
 # Watch a directory for modified time change.  Repeat every two
@@ -195,7 +162,7 @@
 
 class PadFrameCellNameDialog(tksimpledialog.Dialog):
     def body(self, master, warning, seed=''):
-        description='PadFrame'       # TODO: make this an extra optional parameter of a generic CellNameDialog?
+        description = 'PadFrame'       # TODO: make this an extra optional parameter of a generic CellNameDialog?
         if warning:
             ttk.Label(master, text=warning).grid(row = 0, columnspan = 2, sticky = 'wns')
         if description:
@@ -247,6 +214,7 @@
 
 class NewProjectDialog(tksimpledialog.Dialog):
     def body(self, master, warning, seed='', importnode=None, development=False, parent_pdk=''):
+        global pdk_root
         if warning:
             ttk.Label(master, text=warning).grid(row = 0, columnspan = 2, sticky = 'wns')
         ttk.Label(master, text="Enter new project name:").grid(row = 1, column = 0)
@@ -274,7 +242,7 @@
         # TODO: stop hardwired default EFXH035B: get from an overall flow /ef/tech/.ef-config/plist.json
         # (or get it from the currently selected project)
         #EFABLESS PLATFORM
-        for pdkdir_lr in glob.glob('PREFIX/*/libs.tech/'):
+        for pdkdir_lr in glob.glob(pdk_root + '/*/libs.tech/'):
             pdkdir = os.path.split( os.path.split( pdkdir_lr )[0])[0]    # discard final .../libs.tech/
             (foundry, foundry_name, node, desc, status) = ProjectManager.pdkdir2fnd( pdkdir )
             if not foundry or not node:
@@ -781,12 +749,9 @@
         super().__init__(parent, *args, **kwargs)
         self.root = parent
         parent.withdraw()
-        # self.update()
         self.update_idletasks()     # erase small initial frame asap
         self.init_gui()
         parent.protocol("WM_DELETE_WINDOW", self.on_quit)
-        if splash:
-            splash.destroy()
         parent.deiconify()
 
     def on_quit(self):
@@ -801,6 +766,7 @@
         global currdesign
         global theProg
         global deferLoad
+        global apps_path
 
         message = []
         allPaneOpen = False
@@ -808,9 +774,6 @@
         iplPaneMinh = 4
         impPaneMinh = 4
 
-        # if deferLoad:         # temp. for testing... open all panes
-        #     allPaneOpen = True
-
         # Read user preferences
         self.prefs = {}
         self.read_prefs()
@@ -847,7 +810,7 @@
         self.help = HelpWindow(self, fontsize=fontsize)
         
         with io.StringIO() as buf, contextlib.redirect_stdout(buf):
-            self.help.add_pages_from_file(config.apps_path + '/manager_help.txt')
+            self.help.add_pages_from_file(apps_path + '/manager_help.txt')
             message = buf.getvalue()
         
 
@@ -892,17 +855,6 @@
         self.toppane.user_frame = ttk.Frame(self.toppane)
         self.toppane.user_frame.grid(row = 0, sticky = 'news')
 
-        # Put logo image in corner.  Ignore if something goes wrong, as this
-        # is only decorative.  Note: ef_logo must be kept as a record in self,
-        # or else it gets garbage collected.
-        try:
-            #EFABLESS PLATFORM
-            self.ef_logo = tkinter.PhotoImage(file='/ef/efabless/opengalaxy/efabless_logo_small.gif')
-            self.toppane.user_frame.logo = ttk.Label(self.toppane.user_frame, image=self.ef_logo)
-            self.toppane.user_frame.logo.pack(side = 'left', padx = 5)
-        except:
-            pass
-
         self.toppane.user_frame.title = ttk.Label(self.toppane.user_frame, text='User:', style='red.TLabel')
         self.toppane.user_frame.user = ttk.Label(self.toppane.user_frame, text=username, style='blue.TLabel')
 
@@ -936,16 +888,16 @@
         # Create listbox of projects
         projectlist = self.get_project_list() if not deferLoad else []
         height = min(10, max(prjPaneMinh, 2 + len(projectlist)))
-        self.projectselect = TreeViewChoice(self.toppane, fontsize=fontsize, deferLoad=deferLoad, selectVal=pdirCur, natSort=True)
+        self.projectselect = TreeViewChoice(self.toppane, fontsize = fontsize,
+		    deferLoad = deferLoad, selectVal = pdirCur, natSort = True)
         self.projectselect.populate("Available Projects:", projectlist,
 			[["New", True, self.createproject],
 			 ["Import", True, self.importproject],
 			 ["Flow", False, self.startflow],
 			 ["Copy", False, self.copyproject],
 			 ["Rename", False, self.renameproject],
-			 ["Delete", False, self.deleteproject],
-			 ],
-			height=height, columns=[0, 1])
+			 ["Delete", False, self.deleteproject]],
+			height = height, columns = [0, 1])
         self.projectselect.grid(row = 3, sticky = 'news')
         self.projectselect.bindselect(self.setcurrent)
 
@@ -1262,6 +1214,7 @@
 
     def read_prefs(self):
         global prefsfile
+        global apps_path
 
         # Set all known defaults even if they are not in the JSON file so
         # that it is not necessary to check for the existence of the keyword
@@ -1279,7 +1232,7 @@
             # 
             #EFABLESS PLATFORM
             p = subprocess.run(['/ef/apps/bin/withnet' ,
-			config.apps_path + '/og_uid_service.py', userid],
+			apps_path + '/og_uid_service.py', userid],
 			stdout = subprocess.PIPE)
             if p.stdout:
                 uid_string = p.stdout.splitlines()[0].decode('utf-8')
@@ -1349,7 +1302,7 @@
         def add_projects(projectpath):
             # Recursively add subprojects to projectlist
             projectlist.append(projectpath)
-            #check for subprojects and add them
+            # Check for subprojects and add them
             if os.path.isdir(projectpath + '/subcells'):
                 for subproj in os.listdir(projectpath + '/subcells'):
                     if os.path.isdir(projectpath + '/subcells/' + subproj):
@@ -3106,6 +3059,7 @@
 
     def createproject(self, value, seedname=None, importnode=None):
         global currdesign
+        global apps_path
         # Note:  value is current selection, if any, and is ignored
         # Require new project location and confirmation
         badrex1 = re.compile("^\.")
@@ -3169,7 +3123,7 @@
             os.makedirs(parent_path + '/subcells')
         
         try:
-            subprocess.Popen([config.apps_path + '/create_project.py', newproject, newpdk]).wait()
+            subprocess.Popen([apps_path + '/create_project.py', newproject, newpdk]).wait()
             
             # Show subproject in project view
             if parent_pdk != '':
@@ -3829,6 +3783,7 @@
     #----------------------------------------------------------------------
 
     def characterize(self):
+        global apps_path
         value = self.projectselect.selected()
         if value:
             design = value['values'][0]
@@ -3839,7 +3794,7 @@
             if datasheet:
                 # use Popen, not run, so that application does not wait for it to exit.
                 dsheetroot = os.path.splitext(datasheet)[0]
-                subprocess.Popen([config.apps_path + '/cace.py',
+                subprocess.Popen([apps_path + '/cace.py',
 				datasheet])
         else:
             print("You must first select a project.", file=sys.stderr)
@@ -4428,6 +4383,7 @@
 
     def upload(self):
         '''
+        global apps_path
         value = self.projectselect.selected()
         if value:
             design = value['values'][0]
@@ -4435,18 +4391,11 @@
             designname = self.project_name
             print('Upload design ' + designname + ' (' + design + ' )')
             subprocess.run(['/ef/apps/bin/withnet',
-			config.apps_path + '/cace_design_upload.py',
+			apps_path + '/cace_design_upload.py',
 			design, '-test'])
 	'''
 
     #--------------------------------------------------------------------------
-    # Upload a datasheet to the marketplace (Administrative use only, for now)
-    #--------------------------------------------------------------------------
-
-    # def make_challenge(self):
-    #      importp = self.cur_import
-    #      print("Make a Challenge from import " + importp + "!")
-    #      # subprocess.run([config.apps_path + '/cace_import_upload.py', importp, '-test'])
 
     # Runs whenever a user selects a project
     def setcurrent(self, value):
@@ -4563,11 +4512,17 @@
         else:
             self.toppane.appbar.padframeCalc_button.config(state='disabled')
 
-# main app. fyi: there's a 2nd/earlier __main__ section for splashscreen
+#----------------------------------------------------------------------------
+# Main application
+#----------------------------------------------------------------------------
+
 if __name__ == '__main__':
+    faulthandler.register(signal.SIGUSR2)
+
     ProjectManager(root)
     if deferLoad:
-        # Without this, mainloop may find&run very short clock-delayed events BEFORE main form display.
-        # With it 1st project-load can be scheduled using after-time=0 (needn't tune a delay like 100ms).
+        # Without this, mainloop may find and run very short clock-delayed
+        # events before the main form displays. With it, the first project
+        # load can be scheduled using after-time=0
         root.update_idletasks()
     root.mainloop()
diff --git a/runtime/treeviewchoice.py b/runtime/treeviewchoice.py
index ef1e580..67b07c2 100755
--- a/runtime/treeviewchoice.py
+++ b/runtime/treeviewchoice.py
@@ -7,7 +7,8 @@
 
 import tkinter
 from tkinter import ttk
-import natsort
+import natural_sort
+
 #------------------------------------------------------
 # Tree view used as a multi-column list box
 #------------------------------------------------------
@@ -112,10 +113,7 @@
         self.treeView.delete(*self.treeView.get_children())
 
         if self.natSort:
-            self.itemlist = natsort.natsorted( itemlist,
-                                               alg=natsort.ns.INT |
-                                               natsort.ns.UNSIGNED |
-                                               natsort.ns.IGNORECASE )
+            self.itemlist = natural_sort.natural_sort(itemlist)
         else:
             self.itemlist = itemlist[:]
             self.itemlist.sort()
diff --git a/sky130/Makefile.in b/sky130/Makefile.in
index a5a7b84..d89cfdf 100644
--- a/sky130/Makefile.in
+++ b/sky130/Makefile.in
@@ -914,7 +914,11 @@
 	${CPP} -quiet ${SKY130$*_DEFS} openlane/sky130_osu_sc_t18/tracks.info \
 		${OPENLANE_STAGING_$*}/sky130_osu_sc_t18/tracks.info
 
-vendor-%:
+vendor-A: primitive-build-A io-build-A sram-build-A digital-hd-build-A digital-hvl-build-A digital-hdll-build-A digital-lp-build-A digital-hs-build-A digital-ms-build-A digital-ls-build-A alpha-build-A osu-t12-build-A osu-t15-build-A osu-t18-build-A
+
+vendor-B: primitive-build-B io-build-B sram-build-B digital-hd-build-B digital-hvl-build-B digital-hdll-build-B digital-lp-build-B digital-hs-build-B digital-ms-build-B digital-ls-build-B alpha-build-B osu-t12-build-B osu-t15-build-B osu-t18-build-B
+
+primitive-build-%:
 	# Build targets conditionally based on what repositories or submodules
 	# were selected or initialized.  To be done:  Allow a library version
 	# to be specified that overrides "latest".
@@ -922,10 +926,14 @@
 		echo "Building primitives library and simulation models" ;\
 		make primitive-$*;\
 	fi
+
+io-build-%:
 	if test -d ${SKYWATER_LIBS_PATH}/sky130_fd_io/latest/cells ; then \
 		echo "Building padframe I/O libraries" ;\
 		make io-$* ;\
 	fi
+
+digital-hd-build-%:
 	# This assumes that at least the HD library submodule exists.  Also it
 	# assumes that the same library version is used for all libraries, which
 	# is fine with "latest" but otherwise probably invalid.
@@ -933,46 +941,68 @@
 		echo "Building digital high-density standard cell library" ;\
 		make digital-hd-$* ;\
 	fi
+
+digital-hvl-build-%:
 	if test -d ${SKYWATER_LIBS_PATH}/sky130_fd_sc_hvl/latest/cells ; then \
 		echo "Building digital high-voltage standard cell library" ;\
 		make digital-hvl-$* ;\
 	fi
+
+digital-hdll-build-%:
 	if test -d ${SKYWATER_LIBS_PATH}/sky130_fd_sc_hdll/latest/cells ; then \
 		echo "Building digital high-density low-leakage standard cell library" ;\
 		make digital-hdll-$* ;\
 	fi
+
+digital-lp-build-%:
 	if test -d ${SKYWATER_LIBS_PATH}/sky130_fd_sc_lp/latest/cells ; then \
 		echo "Building digital low-power standard cell library" ;\
 		make digital-lp-$* ;\
 	fi
+
+digital-hs-build-%:
 	if test -d ${SKYWATER_LIBS_PATH}/sky130_fd_sc_hs/latest/cells ; then \
 		echo "Building digital high-speed standard cell library" ;\
 		make digital-hs-$* ;\
 	fi
+
+digital-ms-build-%:
 	if test -d ${SKYWATER_LIBS_PATH}/sky130_fd_sc_ms/latest/cells ; then \
 		echo "Building digital medium-speed standard cell library" ;\
 		make digital-ms-$* ;\
 	fi
+
+digital-ls-build-%:
 	if test -d ${SKYWATER_LIBS_PATH}/sky130_fd_sc_ls/latest/cells ; then \
 		echo "Building digital low-speed standard cell library" ;\
 		make digital-ls-$* ;\
 	fi
+
+alpha-build-%:
 	if test "x${ALPHA_PATH}" != "x" ; then \
 		echo "Building alphanumeric layout libraries" ;\
 		make alpha-$* ;\
 	fi
+
+sram-build-%:
 	if test "x${SRAM_PATH}" != "x" ; then \
 		echo "Building SRAM macro library" ;\
 		make sram-$* ;\
 	fi
+
+osu-t12-build-%:
 	if test "x${OSU_T12_PATH}" != "x" ; then \
 		echo "Building OSU 12-track-high standard cell library" ;\
 		make osu-t12-$* ;\
 	fi
+
+osu-t15-build-%:
 	if test "x${OSU_T15_PATH}" != "x" ; then \
 		echo "Building OSU 15-track-high standard cell library" ;\
 		make osu-t15-$* ;\
 	fi
+
+osu-t18-build-%:
 	if test "x${OSU_T18_PATH}" != "x" ; then \
 		echo "Building OSU 18-track-high standard cell library" ;\
 		make osu-t18-$* ;\