| #!/usr/bin/env python3 | |
| #-------------------------------------------------------------------- | |
| # | |
| # save_commit_refs.py | |
| # | |
| # This file is used to annotate a standard tech information JSON | |
| # file. Each JSON file should have a section called "reference" | |
| # (if not, this script exits without doing anything). The | |
| # "reference" section contains the commit hashes of repositories | |
| # used by the developer for generating the PDK, creating a | |
| # reference against which issues can be compared. The "reference" | |
| # section is fixed and is not modified by replacement like the | |
| # rest of the JSON file. It is the duty of the PDK developer to | |
| # update the references section periodically by running "make | |
| # reference". | |
| # | |
| #-------------------------------------------------------------------- | |
| # Usage: | |
| # | |
| # save_commit_refs.py <filename> [-D<variable> ...] | |
| # | |
| # Where <filename> is the name of a technology JSON file, and | |
| # <variable> may be a <keyword>=<hash_value> pair only. | |
| # <keyword> corresponds to the variable name in a technology Makefile | |
| # for a commit hash that appears somewhere in <filename>. <value> | |
| # is the commit hash value corresponding to <keyword>. | |
| # | |
| # The JSON file "reference" section is expected to have entries of | |
| # the form | |
| # "<name>": "<commit_hash>" | |
| # | |
| # where <name> must also appear elsewhere in <filename> in an entry | |
| # of the form | |
| # "<name>": "<keyword>" | |
| # | |
| # Then, where "-D<keyword>=<hash_value>" has been passed to the | |
| # script on the command line, the entry in the "reference" section | |
| # is changed by replacing "<commit_hash>" with "<hash_value>". | |
| # | |
| # The output of the script overwrites <filename> with the modified | |
| # contents. | |
| # | |
| # Example: | |
| # sky130.json has an entry in "reference": | |
| # "magic": "fe2eb6d3906ed15ade0e7a51daea80dd4e3846e2" | |
| # reflecting the git commit number of the program "magic" at | |
| # the time the developer last ran save_commit_refs.py. | |
| # Elsewhere in sky130.json, the following line appears: | |
| # "magic": "MAGIC_COMMIT" | |
| # If save_commit_refs.py is called as: | |
| # save_commit_refs.py sky130.json -DMAGIC_COMMIT=abcdef | |
| # then the line in "reference" will be changed to: | |
| # "magic": "abcdef" | |
| # | |
| #-------------------------------------------------------------------- | |
| import os | |
| import re | |
| import sys | |
| def runsubs(keys, defines, inputfile, outputfile): | |
| ifile = False | |
| ifile = open(inputfile, 'r') | |
| if not ifile: | |
| print("Error: Cannot open file " + inputfile + " for reading.\n", file=sys.stderr) | |
| return | |
| indist = False | |
| filetext = ifile.readlines() | |
| lastline = [] | |
| # Input file has been read, so close it. | |
| ifile.close() | |
| # Open output file | |
| if not outputfile: | |
| outputfile = inputfile | |
| ofile = open(outputfile, 'w') | |
| if not ofile: | |
| print("Error: Cannot open file " + outputfile + " for writing.\n", file=sys.stderr) | |
| return | |
| keyrex = re.compile(r'[ \t]*"([^"]+)":[ \t]*"[^"]+"') | |
| valuerex = re.compile(r'[ \t]*"[^"]+":[ \t]*"([a-z0-9]+)"') | |
| distdefs = {} | |
| defs = [] | |
| for line in filetext: | |
| newline = line | |
| if indist == False: | |
| if '"reference":' in line: | |
| indist = True | |
| else: | |
| # Find values matching keywords | |
| for key in keys: | |
| if key in line: | |
| kmatch = keyrex.match(line) | |
| if kmatch: | |
| print('Found match "' + kmatch.group(1) + '" for key "' + key + '"') | |
| distdefs[kmatch.group(1)] = defines[key] | |
| defs.append(kmatch.group(1)) | |
| else: | |
| # Find definitions matching keywords | |
| for defx in defs: | |
| if defx in line: | |
| vmatch = valuerex.match(line) | |
| if vmatch: | |
| value = distdefs[defx] | |
| newline = line.replace(vmatch.group(1), value) | |
| print(newline, file=ofile, end='') | |
| ofile.close() | |
| return | |
| def printusage(progname): | |
| print('Usage: ' + progname + ' filename [-options]') | |
| print(' Options are:') | |
| print(' -help Print this help text.') | |
| print(' -quiet Stop without error if input file is not found.') | |
| print(' -ccomm Remove C comments in /* ... */ delimiters.') | |
| print(' -D<def> Define word <def> and set its value to 1.') | |
| print(' -D<def>=<val> Define word <def> and set its value to <val>.') | |
| print(' -I<dir> Add <dir> to search path for input files.') | |
| return | |
| if __name__ == '__main__': | |
| # Parse command line for options and arguments | |
| options = [] | |
| arguments = [] | |
| for item in sys.argv[1:]: | |
| if item.find('-', 0) == 0: | |
| options.append(item) | |
| else: | |
| arguments.append(item) | |
| if len(arguments) > 0: | |
| inputfile = arguments[0] | |
| if len(arguments) > 1: | |
| outputfile = arguments[1] | |
| else: | |
| outputfile = [] | |
| else: | |
| printusage(sys.argv[0]) | |
| sys.exit(0) | |
| defines = {} | |
| keys = [] | |
| for item in options: | |
| result = item.split('=') | |
| if result[0] == '-help': | |
| printusage(sys.argv[0]) | |
| sys.exit(0) | |
| elif result[0][0:2] == '-D': | |
| keyword = result[0][2:] | |
| value = result[1] | |
| defines[keyword] = value | |
| keys.append(keyword) | |
| else: | |
| print('Bad option ' + item + ', options are -help, -D<def>\n') | |
| sys.exit(1) | |
| if not os.path.isfile(inputfile): | |
| if not quiet: | |
| print("Error: No input file " + inputfile + " found.") | |
| else: | |
| sys.exit(0) | |
| runsubs(keys, defines, inputfile, outputfile) | |
| # Set mode of outputfile to be equal to that of inputfile (if not stdout) | |
| if outputfile: | |
| statinfo = os.stat(inputfile) | |
| mode = statinfo.st_mode | |
| os.chmod(outputfile, mode) | |
| sys.exit(0) |