| #!/usr/bin/env python3 | |
| # | |
| # rename_project.py --- Perform all tasks required for renaming a project. | |
| # | |
| # In the context of this script, "renaming" a project means changing the | |
| # 'ip-name' entry in the JSON file and all that that implies. To create a | |
| # new project with an existing ip-name is essentially a trivial process of | |
| # renaming the parent directory. | |
| # | |
| # Note that when a catalog entry is generated in the marketplace, the entry's | |
| # default name is taken from the parent directory name, not the ip-name. The | |
| # resulting entry name is irrelevant to most everything except how and where | |
| # the IP is listed in the catalog. The read-only IP version has the name of | |
| # the ip-name. Implies it is important that ip-name does not collide with | |
| # the ip-name of anything else in the marketplace catalog (but this is not | |
| # currently enforced). | |
| # | |
| # Modified 12/20/2018: New protocol is that the .json file is always called | |
| # 'project.json' and does not take the name of the parent directory. This | |
| # makes projects more portable. | |
| # | |
| import shutil | |
| import json | |
| import stat | |
| import sys | |
| import os | |
| import re | |
| """ | |
| This module converts an entire project from one ip-name to another, making | |
| sure that all filenames and file contents are updated. | |
| """ | |
| def copy_meta_with_ownership(src, dst, follow_symlinks=False): | |
| # Copy file metadata using copystat() and preserve ownership through stat calls. | |
| file_stat = os.stat(src) | |
| owner = file_stat[stat.ST_UID] | |
| group = file_stat[stat.ST_GID] | |
| shutil.copystat(src, dst) | |
| os.chown(dst, owner, group, follow_symlinks=follow_symlinks) | |
| def rename_json(project_path, json_file, new_name, orig_name = ''): | |
| # Make sure we have the full absolute path to the project | |
| fullpath = os.path.abspath(project_path) | |
| # Project directory name is the last component of the full path | |
| dirname = os.path.split(fullpath)[1] | |
| # Get contents of the file, then recast the ip-name and rewrite it. | |
| with open(json_file, 'r') as ifile: | |
| datatop = json.load(ifile) | |
| # Find ip-name and replace it | |
| if 'data-sheet' in datatop: | |
| dsheet = datatop['data-sheet'] | |
| if 'ip-name' in dsheet: | |
| ipname = dsheet['ip-name'] | |
| if ipname == orig_name: | |
| dsheet['ip-name'] = new_name | |
| elif orig_name == '': | |
| dsheet['ip-name'] = new_name | |
| else: | |
| print('Error: original name ' + orig_name + ' specified in command line,') | |
| print('but ip-name is ' + ipname + ' in the datasheet.') | |
| return ipname | |
| # Change name of file. This must match the name of the directory whether | |
| # or not the directory name matches the IP name. | |
| # (New protocol from Dec. 2018: JSON file is now always named 'project.json') | |
| opath = os.path.split(json_file)[0] | |
| # oname = opath + '/' + dirname + '.json' | |
| oname = opath + '/project.json' | |
| with open(oname, 'w') as ofile: | |
| json.dump(datatop, ofile, indent = 4) | |
| # Remove original file. Avoid destroying the file if rename_project happens | |
| # to be called with the same name for old and new. | |
| if (json_file != oname): | |
| copy_meta_with_ownership(json_file, oname) | |
| os.remove(json_file) | |
| # Return the original IP name | |
| return ipname | |
| def rename_netlist(netlist_path, orig_name, new_name): | |
| # All netlists can be regenerated on the fly, so remove any that are | |
| # from orig_name | |
| if not os.path.exists(netlist_path): | |
| return | |
| filelist = os.listdir(netlist_path) | |
| for file in filelist: | |
| rootname = os.path.splitext(file)[0] | |
| fullpath = netlist_path + '/' + file | |
| if rootname == orig_name: | |
| if os.path.isdir(fullpath): | |
| rename_netlist(fullpath, orig_name, new_name); | |
| else: | |
| print('Removing netlist file ' + file) | |
| os.remove(fullpath) | |
| def rename_magic(magic_path, orig_name, new_name): | |
| # remove old files that will get regenerated: comp.out, comp.json, any *.log | |
| # move any file beginnng with orig_name to the same file with new_name | |
| filelist = os.listdir(magic_path) | |
| # All netlists can be regenerated on the fly, so remove any that are | |
| # from orig_name | |
| for file in filelist: | |
| rootname, fext = os.path.splitext(file) | |
| if file == 'comp.out' or file == 'comp.json': | |
| os.remove(magic_path + '/' + file) | |
| elif rootname == orig_name: | |
| if fext == '.spc' or fext == '.spice': | |
| print('Removing netlist file ' + file) | |
| os.remove(magic_path + '/' + file) | |
| elif fext == '.ext' or fext == '.lef': | |
| os.remove(magic_path + '/' + file) | |
| else: | |
| shutil.move(magic_path + '/' + file, magic_path + '/' + new_name + fext) | |
| elif fext == '.log': | |
| os.remove(magic_path + '/' + file) | |
| def rename_verilog(verilog_path, orig_name, new_name): | |
| filelist = os.listdir(verilog_path) | |
| # The root module name can remain as the original (may not match orig_name | |
| # anyway), but the verilog file containing it gets renamed. | |
| # To be done: Any file (e.g., simulation testbenches, makefiles) referencing | |
| # the file must be modified to match. These may be in subdirectories, so | |
| # walk the filesystem from verilog_path. | |
| for file in filelist: | |
| rootname, fext = os.path.splitext(file) | |
| if rootname == orig_name: | |
| if fext == '.v' or fext == '.sv': | |
| shutil.move(verilog_path + '/' + file, verilog_path + '/' + new_name + fext) | |
| def rename_electric(electric_path, orig_name, new_name): | |
| # <project_name>.delib gets renamed | |
| filelist = os.listdir(electric_path) | |
| for file in filelist: | |
| rootname, fext = os.path.splitext(file) | |
| if rootname == orig_name: | |
| shutil.move(electric_path + '/' + file, electric_path + '/' + new_name + fext) | |
| delib_path = electric_path + '/' + new_name + '.delib' | |
| if os.path.exists(delib_path): | |
| filelist = os.listdir(delib_path) | |
| for file in filelist: | |
| if os.path.isdir(file): | |
| continue | |
| rootname, fext = os.path.splitext(file) | |
| if rootname == orig_name: | |
| # Read and do name substitution where orig_name occurs | |
| # in 'H', 'C', and 'L' statements. The top-level should not appear in | |
| # an 'I' (instance) statement. | |
| with open(delib_path + '/' + file, 'r') as ifile: | |
| contents = ifile.read() | |
| contents = re.sub('H' + orig_name + '\|', 'H' + new_name + '|', contents) | |
| contents = re.sub('C' + orig_name + ';', 'C' + new_name + ';', contents) | |
| contents = re.sub('L' + orig_name + '\|' + orig_name, 'L' + new_name + '|' + new_name, contents) | |
| oname = new_name + fext | |
| with open(delib_path + '/' + oname, 'w') as ofile: | |
| ofile.write(contents) | |
| # Copy ownership and permissions from the old file | |
| # Remove the original file | |
| copy_meta_with_ownership(delib_path + '/' + file, delib_path + '/' + oname) | |
| os.remove(delib_path + '/' + file) | |
| elif rootname == 'header': | |
| # Read and do name substitution where orig_name occurs in 'H' statements. | |
| with open(delib_path + '/' + file, 'r') as ifile: | |
| contents = ifile.read() | |
| contents = re.sub('H' + orig_name + '\|', 'H' + new_name + '|', contents) | |
| with open(delib_path + '/' + file + '.tmp', 'w') as ofile: | |
| ofile.write(contents) | |
| copy_meta_with_ownership(delib_path + '/' + file, delib_path + '/' + file + '.tmp') | |
| os.remove(delib_path + '/' + file) | |
| shutil.move(delib_path + '/' + file + '.tmp', delib_path + '/' + file) | |
| # Top level routine (call this one) | |
| def rename_project_all(project_path, new_name, orig_name=''): | |
| # project_path is the original full path to the project in the user's design space. | |
| # | |
| # new_name is the new name to give to the project. It is assumed to have been | |
| # already checked for uniqueness against existing names | |
| # Original name is determined from the 'ip-name' field in the JSON file | |
| # unless it is specified as a separate argument. | |
| proj_name = os.path.split(project_path)[1] | |
| json_path = project_path + '/project.json' | |
| # The JSON file is assumed to have the name "project.json" always. | |
| # However, if the project directory just got named, or if the project pre-dates | |
| # December 2018, then that may not be true. If json_path does not exist, look | |
| # for any JSON file containing a data-sheet entry. | |
| if not os.path.exists(json_path): | |
| json_path = '' | |
| filelist = os.listdir(project_path) | |
| for file in filelist: | |
| if os.path.splitext(file)[1] == '.json': | |
| with open(project_path + '/' + file) as ifile: | |
| datatop = json.load(ifile) | |
| if 'data-sheet' in datatop: | |
| json_path = project_path + '/' + file | |
| break | |
| if os.path.exists(json_path): | |
| if (orig_name == ''): | |
| orig_name = rename_json(project_path, json_path, new_name) | |
| else: | |
| test_name = rename_json(project_path, json_path, new_name, orig_name) | |
| if test_name != orig_name: | |
| # Refusing to make a change because the orig_name didn't match ip-name | |
| return | |
| else: | |
| if (orig_name == ''): | |
| orig_name = proj_name | |
| if orig_name == new_name: | |
| print('Warning: project old and new names are the same; nothing to change.', file=sys.stderr) | |
| return | |
| # Each subroutine renames a specific group of files. | |
| electric_path = project_path + '/elec' | |
| if os.path.exists(electric_path): | |
| rename_electric(electric_path, orig_name, new_name) | |
| magic_path = project_path + '/mag' | |
| if os.path.exists(magic_path): | |
| rename_magic(magic_path, orig_name, new_name) | |
| verilog_path = project_path + '/verilog' | |
| if os.path.exists(verilog_path): | |
| rename_verilog(verilog_path, orig_name, new_name) | |
| # Maglef is deprecated in anything but readonly IP and PDKs, but | |
| # handle for backwards compatibility. | |
| maglef_path = project_path + '/maglef' | |
| if os.path.exists(maglef_path): | |
| rename_magic(maglef_path, orig_name, new_name) | |
| netlist_path = project_path + '/spi' | |
| if os.path.exists(netlist_path): | |
| rename_netlist(netlist_path, orig_name, new_name) | |
| rename_netlist(netlist_path + '/pex', orig_name, new_name) | |
| rename_netlist(netlist_path + '/cdl', orig_name, new_name) | |
| rename_netlist(netlist_path + '/lvs', orig_name, new_name) | |
| # To be done: handle qflow directory if it exists. | |
| print('Renamed project ' + orig_name + ' to ' + new_name + '; done.') | |
| # If called as main, run rename_project_all. | |
| if __name__ == '__main__': | |
| # Divide up command line into options and arguments | |
| options = [] | |
| arguments = [] | |
| for item in sys.argv[1:]: | |
| if item.find('-', 0) == 0: | |
| options.append(item) | |
| else: | |
| arguments.append(item) | |
| # Need two arguments: path to directory, and new project name. | |
| if len(arguments) < 2: | |
| print("Usage: rename_project.py <project_path> <new_name> [<orig_name>]") | |
| elif len(arguments) >= 2: | |
| project_path = arguments[0] | |
| new_name = arguments[1] | |
| if len(arguments) == 3: | |
| orig_name = arguments[2] | |
| rename_project_all(project_path, new_name, orig_name) | |
| else: | |
| rename_project_all(project_path, new_name) | |