blob: 4d541f01874d68123ed8cd2f94cd8261637d2381 [file] [log] [blame]
Tim Edwardsdae621a2021-09-08 09:28:02 -04001#!/usr/bin/env python3
emayecs5966a532021-07-29 10:07:02 -04002#
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#
22import shutil
23import json
24import stat
25import sys
26import os
27import 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
34def 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
42def 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
85def 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
102def 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
126def 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
141def 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
191def 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
267if __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