Tim Edwards | dae621a | 2021-09-08 09:28:02 -0400 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
emayecs | 5966a53 | 2021-07-29 10:07:02 -0400 | [diff] [blame] | 2 | # |
| 3 | # rename_project.py --- Perform all tasks required for renaming a project. |
| 4 | # |
| 5 | # In the context of this script, "renaming" a project means changing the |
| 6 | # 'ip-name' entry in the JSON file and all that that implies. To create a |
| 7 | # new project with an existing ip-name is essentially a trivial process of |
| 8 | # renaming the parent directory. |
| 9 | # |
| 10 | # Note that when a catalog entry is generated in the marketplace, the entry's |
| 11 | # default name is taken from the parent directory name, not the ip-name. The |
| 12 | # resulting entry name is irrelevant to most everything except how and where |
| 13 | # the IP is listed in the catalog. The read-only IP version has the name of |
| 14 | # the ip-name. Implies it is important that ip-name does not collide with |
| 15 | # the ip-name of anything else in the marketplace catalog (but this is not |
| 16 | # currently enforced). |
| 17 | # |
| 18 | # Modified 12/20/2018: New protocol is that the .json file is always called |
| 19 | # 'project.json' and does not take the name of the parent directory. This |
| 20 | # makes projects more portable. |
| 21 | # |
| 22 | import shutil |
| 23 | import json |
| 24 | import stat |
| 25 | import sys |
| 26 | import os |
| 27 | import re |
| 28 | |
| 29 | """ |
| 30 | This module converts an entire project from one ip-name to another, making |
| 31 | sure that all filenames and file contents are updated. |
| 32 | """ |
| 33 | |
| 34 | def copy_meta_with_ownership(src, dst, follow_symlinks=False): |
| 35 | # Copy file metadata using copystat() and preserve ownership through stat calls. |
| 36 | file_stat = os.stat(src) |
| 37 | owner = file_stat[stat.ST_UID] |
| 38 | group = file_stat[stat.ST_GID] |
| 39 | shutil.copystat(src, dst) |
| 40 | os.chown(dst, owner, group, follow_symlinks=follow_symlinks) |
| 41 | |
| 42 | def rename_json(project_path, json_file, new_name, orig_name = ''): |
| 43 | # Make sure we have the full absolute path to the project |
| 44 | fullpath = os.path.abspath(project_path) |
| 45 | # Project directory name is the last component of the full path |
| 46 | dirname = os.path.split(fullpath)[1] |
| 47 | # Get contents of the file, then recast the ip-name and rewrite it. |
| 48 | with open(json_file, 'r') as ifile: |
| 49 | datatop = json.load(ifile) |
| 50 | |
| 51 | # Find ip-name and replace it |
| 52 | if 'data-sheet' in datatop: |
| 53 | dsheet = datatop['data-sheet'] |
| 54 | if 'ip-name' in dsheet: |
| 55 | ipname = dsheet['ip-name'] |
| 56 | if ipname == orig_name: |
| 57 | dsheet['ip-name'] = new_name |
| 58 | elif orig_name == '': |
| 59 | dsheet['ip-name'] = new_name |
| 60 | else: |
| 61 | print('Error: original name ' + orig_name + ' specified in command line,') |
| 62 | print('but ip-name is ' + ipname + ' in the datasheet.') |
| 63 | return ipname |
| 64 | |
| 65 | # Change name of file. This must match the name of the directory whether |
| 66 | # or not the directory name matches the IP name. |
| 67 | # (New protocol from Dec. 2018: JSON file is now always named 'project.json') |
| 68 | |
| 69 | opath = os.path.split(json_file)[0] |
| 70 | # oname = opath + '/' + dirname + '.json' |
| 71 | oname = opath + '/project.json' |
| 72 | |
| 73 | with open(oname, 'w') as ofile: |
| 74 | json.dump(datatop, ofile, indent = 4) |
| 75 | |
| 76 | # Remove original file. Avoid destroying the file if rename_project happens |
| 77 | # to be called with the same name for old and new. |
| 78 | if (json_file != oname): |
| 79 | copy_meta_with_ownership(json_file, oname) |
| 80 | os.remove(json_file) |
| 81 | |
| 82 | # Return the original IP name |
| 83 | return ipname |
| 84 | |
| 85 | def rename_netlist(netlist_path, orig_name, new_name): |
| 86 | # All netlists can be regenerated on the fly, so remove any that are |
| 87 | # from orig_name |
| 88 | if not os.path.exists(netlist_path): |
| 89 | return |
| 90 | |
| 91 | filelist = os.listdir(netlist_path) |
| 92 | for file in filelist: |
| 93 | rootname = os.path.splitext(file)[0] |
| 94 | fullpath = netlist_path + '/' + file |
| 95 | if rootname == orig_name: |
| 96 | if os.path.isdir(fullpath): |
| 97 | rename_netlist(fullpath, orig_name, new_name); |
| 98 | else: |
| 99 | print('Removing netlist file ' + file) |
| 100 | os.remove(fullpath) |
| 101 | |
| 102 | def rename_magic(magic_path, orig_name, new_name): |
| 103 | # remove old files that will get regenerated: comp.out, comp.json, any *.log |
| 104 | # move any file beginnng with orig_name to the same file with new_name |
| 105 | filelist = os.listdir(magic_path) |
| 106 | |
| 107 | # All netlists can be regenerated on the fly, so remove any that are |
| 108 | # from orig_name |
| 109 | for file in filelist: |
| 110 | rootname, fext = os.path.splitext(file) |
| 111 | |
| 112 | if file == 'comp.out' or file == 'comp.json': |
| 113 | os.remove(magic_path + '/' + file) |
| 114 | elif rootname == orig_name: |
| 115 | if fext == '.spc' or fext == '.spice': |
| 116 | print('Removing netlist file ' + file) |
| 117 | os.remove(magic_path + '/' + file) |
| 118 | elif fext == '.ext' or fext == '.lef': |
| 119 | os.remove(magic_path + '/' + file) |
| 120 | else: |
| 121 | shutil.move(magic_path + '/' + file, magic_path + '/' + new_name + fext) |
| 122 | |
| 123 | elif fext == '.log': |
| 124 | os.remove(magic_path + '/' + file) |
| 125 | |
| 126 | def rename_verilog(verilog_path, orig_name, new_name): |
| 127 | filelist = os.listdir(verilog_path) |
| 128 | |
| 129 | # The root module name can remain as the original (may not match orig_name |
| 130 | # anyway), but the verilog file containing it gets renamed. |
| 131 | # To be done: Any file (e.g., simulation testbenches, makefiles) referencing |
| 132 | # the file must be modified to match. These may be in subdirectories, so |
| 133 | # walk the filesystem from verilog_path. |
| 134 | |
| 135 | for file in filelist: |
| 136 | rootname, fext = os.path.splitext(file) |
| 137 | if rootname == orig_name: |
| 138 | if fext == '.v' or fext == '.sv': |
| 139 | shutil.move(verilog_path + '/' + file, verilog_path + '/' + new_name + fext) |
| 140 | |
| 141 | def rename_electric(electric_path, orig_name, new_name): |
| 142 | # <project_name>.delib gets renamed |
| 143 | |
| 144 | filelist = os.listdir(electric_path) |
| 145 | for file in filelist: |
| 146 | rootname, fext = os.path.splitext(file) |
| 147 | if rootname == orig_name: |
| 148 | shutil.move(electric_path + '/' + file, electric_path + '/' + new_name + fext) |
| 149 | |
| 150 | delib_path = electric_path + '/' + new_name + '.delib' |
| 151 | if os.path.exists(delib_path): |
| 152 | filelist = os.listdir(delib_path) |
| 153 | for file in filelist: |
| 154 | if os.path.isdir(file): |
| 155 | continue |
| 156 | rootname, fext = os.path.splitext(file) |
| 157 | if rootname == orig_name: |
| 158 | # Read and do name substitution where orig_name occurs |
| 159 | # in 'H', 'C', and 'L' statements. The top-level should not appear in |
| 160 | # an 'I' (instance) statement. |
| 161 | with open(delib_path + '/' + file, 'r') as ifile: |
| 162 | contents = ifile.read() |
| 163 | contents = re.sub('H' + orig_name + '\|', 'H' + new_name + '|', contents) |
| 164 | contents = re.sub('C' + orig_name + ';', 'C' + new_name + ';', contents) |
| 165 | contents = re.sub('L' + orig_name + '\|' + orig_name, 'L' + new_name + '|' + new_name, contents) |
| 166 | |
| 167 | oname = new_name + fext |
| 168 | with open(delib_path + '/' + oname, 'w') as ofile: |
| 169 | ofile.write(contents) |
| 170 | |
| 171 | # Copy ownership and permissions from the old file |
| 172 | # Remove the original file |
| 173 | copy_meta_with_ownership(delib_path + '/' + file, delib_path + '/' + oname) |
| 174 | os.remove(delib_path + '/' + file) |
| 175 | |
| 176 | elif rootname == 'header': |
| 177 | # Read and do name substitution where orig_name occurs in 'H' statements. |
| 178 | with open(delib_path + '/' + file, 'r') as ifile: |
| 179 | contents = ifile.read() |
| 180 | contents = re.sub('H' + orig_name + '\|', 'H' + new_name + '|', contents) |
| 181 | |
| 182 | with open(delib_path + '/' + file + '.tmp', 'w') as ofile: |
| 183 | ofile.write(contents) |
| 184 | |
| 185 | copy_meta_with_ownership(delib_path + '/' + file, delib_path + '/' + file + '.tmp') |
| 186 | os.remove(delib_path + '/' + file) |
| 187 | shutil.move(delib_path + '/' + file + '.tmp', delib_path + '/' + file) |
| 188 | |
| 189 | # Top level routine (call this one) |
| 190 | |
| 191 | def rename_project_all(project_path, new_name, orig_name=''): |
| 192 | # project_path is the original full path to the project in the user's design space. |
| 193 | # |
| 194 | # new_name is the new name to give to the project. It is assumed to have been |
| 195 | # already checked for uniqueness against existing names |
| 196 | |
| 197 | # Original name is determined from the 'ip-name' field in the JSON file |
| 198 | # unless it is specified as a separate argument. |
| 199 | |
| 200 | proj_name = os.path.split(project_path)[1] |
| 201 | json_path = project_path + '/project.json' |
| 202 | |
| 203 | # The JSON file is assumed to have the name "project.json" always. |
| 204 | # However, if the project directory just got named, or if the project pre-dates |
| 205 | # December 2018, then that may not be true. If json_path does not exist, look |
| 206 | # for any JSON file containing a data-sheet entry. |
| 207 | |
| 208 | if not os.path.exists(json_path): |
| 209 | json_path = '' |
| 210 | filelist = os.listdir(project_path) |
| 211 | for file in filelist: |
| 212 | if os.path.splitext(file)[1] == '.json': |
| 213 | with open(project_path + '/' + file) as ifile: |
| 214 | datatop = json.load(ifile) |
| 215 | if 'data-sheet' in datatop: |
| 216 | json_path = project_path + '/' + file |
| 217 | break |
| 218 | |
| 219 | if os.path.exists(json_path): |
| 220 | if (orig_name == ''): |
| 221 | orig_name = rename_json(project_path, json_path, new_name) |
| 222 | else: |
| 223 | test_name = rename_json(project_path, json_path, new_name, orig_name) |
| 224 | if test_name != orig_name: |
| 225 | # Refusing to make a change because the orig_name didn't match ip-name |
| 226 | return |
| 227 | else: |
| 228 | if (orig_name == ''): |
| 229 | orig_name = proj_name |
| 230 | |
| 231 | if orig_name == new_name: |
| 232 | print('Warning: project old and new names are the same; nothing to change.', file=sys.stderr) |
| 233 | return |
| 234 | |
| 235 | # Each subroutine renames a specific group of files. |
| 236 | electric_path = project_path + '/elec' |
| 237 | if os.path.exists(electric_path): |
| 238 | rename_electric(electric_path, orig_name, new_name) |
| 239 | |
| 240 | magic_path = project_path + '/mag' |
| 241 | if os.path.exists(magic_path): |
| 242 | rename_magic(magic_path, orig_name, new_name) |
| 243 | |
| 244 | verilog_path = project_path + '/verilog' |
| 245 | if os.path.exists(verilog_path): |
| 246 | rename_verilog(verilog_path, orig_name, new_name) |
| 247 | |
| 248 | # Maglef is deprecated in anything but readonly IP and PDKs, but |
| 249 | # handle for backwards compatibility. |
| 250 | maglef_path = project_path + '/maglef' |
| 251 | if os.path.exists(maglef_path): |
| 252 | rename_magic(maglef_path, orig_name, new_name) |
| 253 | |
| 254 | netlist_path = project_path + '/spi' |
| 255 | if os.path.exists(netlist_path): |
| 256 | rename_netlist(netlist_path, orig_name, new_name) |
| 257 | rename_netlist(netlist_path + '/pex', orig_name, new_name) |
| 258 | rename_netlist(netlist_path + '/cdl', orig_name, new_name) |
| 259 | rename_netlist(netlist_path + '/lvs', orig_name, new_name) |
| 260 | |
| 261 | # To be done: handle qflow directory if it exists. |
| 262 | |
| 263 | print('Renamed project ' + orig_name + ' to ' + new_name + '; done.') |
| 264 | |
| 265 | # If called as main, run rename_project_all. |
| 266 | |
| 267 | if __name__ == '__main__': |
| 268 | |
| 269 | # Divide up command line into options and arguments |
| 270 | options = [] |
| 271 | arguments = [] |
| 272 | for item in sys.argv[1:]: |
| 273 | if item.find('-', 0) == 0: |
| 274 | options.append(item) |
| 275 | else: |
| 276 | arguments.append(item) |
| 277 | |
| 278 | # Need two arguments: path to directory, and new project name. |
| 279 | |
| 280 | if len(arguments) < 2: |
| 281 | print("Usage: rename_project.py <project_path> <new_name> [<orig_name>]") |
| 282 | elif len(arguments) >= 2: |
| 283 | project_path = arguments[0] |
| 284 | new_name = arguments[1] |
| 285 | |
| 286 | if len(arguments) == 3: |
| 287 | orig_name = arguments[2] |
| 288 | rename_project_all(project_path, new_name, orig_name) |
| 289 | else: |
| 290 | rename_project_all(project_path, new_name) |
| 291 | |