blob: 4d541f01874d68123ed8cd2f94cd8261637d2381 [file] [log] [blame]
#!/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)