Tim Edwards | 5ed94ff | 2022-06-16 15:07:53 -0400 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | #-------------------------------------------------------------------- |
| 3 | # |
| 4 | # save_commit_refs.py |
| 5 | # |
| 6 | # This file is used to annotate a standard tech information JSON |
| 7 | # file. Each JSON file should have a section called "reference" |
| 8 | # (if not, this script exits without doing anything). The |
| 9 | # "reference" section contains the commit hashes of repositories |
| 10 | # used by the developer for generating the PDK, creating a |
| 11 | # reference against which issues can be compared. The "reference" |
| 12 | # section is fixed and is not modified by replacement like the |
| 13 | # rest of the JSON file. It is the duty of the PDK developer to |
| 14 | # update the references section periodically by running "make |
Tim Edwards | cc0029b | 2022-08-01 18:11:30 -0400 | [diff] [blame] | 15 | # reference". |
Tim Edwards | 5ed94ff | 2022-06-16 15:07:53 -0400 | [diff] [blame] | 16 | # |
| 17 | #-------------------------------------------------------------------- |
| 18 | # Usage: |
| 19 | # |
| 20 | # save_commit_refs.py <filename> [-D<variable> ...] |
| 21 | # |
| 22 | # Where <filename> is the name of a technology JSON file, and |
| 23 | # <variable> may be a <keyword>=<hash_value> pair only. |
| 24 | # <keyword> corresponds to the variable name in a technology Makefile |
| 25 | # for a commit hash that appears somewhere in <filename>. <value> |
| 26 | # is the commit hash value corresponding to <keyword>. |
| 27 | # |
| 28 | # The JSON file "reference" section is expected to have entries of |
| 29 | # the form |
| 30 | # "<name>": "<commit_hash>" |
| 31 | # |
| 32 | # where <name> must also appear elsewhere in <filename> in an entry |
| 33 | # of the form |
| 34 | # "<name>": "<keyword>" |
| 35 | # |
| 36 | # Then, where "-D<keyword>=<hash_value>" has been passed to the |
| 37 | # script on the command line, the entry in the "reference" section |
| 38 | # is changed by replacing "<commit_hash>" with "<hash_value>". |
| 39 | # |
| 40 | # The output of the script overwrites <filename> with the modified |
| 41 | # contents. |
| 42 | # |
| 43 | # Example: |
Tim Edwards | cc0029b | 2022-08-01 18:11:30 -0400 | [diff] [blame] | 44 | # sky130.json has an entry in "reference": |
Tim Edwards | 5ed94ff | 2022-06-16 15:07:53 -0400 | [diff] [blame] | 45 | # "magic": "fe2eb6d3906ed15ade0e7a51daea80dd4e3846e2" |
| 46 | # reflecting the git commit number of the program "magic" at |
| 47 | # the time the developer last ran save_commit_refs.py. |
| 48 | # Elsewhere in sky130.json, the following line appears: |
| 49 | # "magic": "MAGIC_COMMIT" |
| 50 | # If save_commit_refs.py is called as: |
| 51 | # save_commit_refs.py sky130.json -DMAGIC_COMMIT=abcdef |
Tim Edwards | cc0029b | 2022-08-01 18:11:30 -0400 | [diff] [blame] | 52 | # then the line in "reference" will be changed to: |
Tim Edwards | 5ed94ff | 2022-06-16 15:07:53 -0400 | [diff] [blame] | 53 | # "magic": "abcdef" |
| 54 | # |
| 55 | #-------------------------------------------------------------------- |
| 56 | |
| 57 | import os |
| 58 | import re |
| 59 | import sys |
| 60 | |
| 61 | def runsubs(keys, defines, inputfile, outputfile): |
| 62 | |
| 63 | ifile = False |
| 64 | ifile = open(inputfile, 'r') |
| 65 | if not ifile: |
| 66 | print("Error: Cannot open file " + inputfile + " for reading.\n", file=sys.stderr) |
| 67 | return |
| 68 | |
| 69 | indist = False |
| 70 | |
| 71 | filetext = ifile.readlines() |
| 72 | lastline = [] |
| 73 | |
| 74 | # Input file has been read, so close it. |
| 75 | ifile.close() |
| 76 | |
| 77 | # Open output file |
| 78 | if not outputfile: |
| 79 | outputfile = inputfile |
| 80 | |
| 81 | ofile = open(outputfile, 'w') |
| 82 | if not ofile: |
| 83 | print("Error: Cannot open file " + outputfile + " for writing.\n", file=sys.stderr) |
| 84 | return |
| 85 | |
| 86 | keyrex = re.compile('[ \t]*"([^"]+)":[ \t]*"[^"]+"') |
| 87 | valuerex = re.compile('[ \t]*"[^"]+":[ \t]*"([a-z0-9]+)"') |
| 88 | distdefs = {} |
| 89 | defs = [] |
| 90 | |
| 91 | for line in filetext: |
| 92 | newline = line |
| 93 | |
| 94 | if indist == False: |
Tim Edwards | cc0029b | 2022-08-01 18:11:30 -0400 | [diff] [blame] | 95 | if '"reference":' in line: |
Tim Edwards | 5ed94ff | 2022-06-16 15:07:53 -0400 | [diff] [blame] | 96 | indist = True |
| 97 | else: |
| 98 | # Find values matching keywords |
| 99 | for key in keys: |
| 100 | if key in line: |
| 101 | kmatch = keyrex.match(line) |
| 102 | if kmatch: |
| 103 | print('Found match "' + kmatch.group(1) + '" for key "' + key + '"') |
| 104 | distdefs[kmatch.group(1)] = defines[key] |
| 105 | defs.append(kmatch.group(1)) |
| 106 | else: |
| 107 | # Find definitions matching keywords |
| 108 | for defx in defs: |
| 109 | if defx in line: |
| 110 | vmatch = valuerex.match(line) |
| 111 | if vmatch: |
| 112 | value = distdefs[defx] |
| 113 | newline = line.replace(vmatch.group(1), value) |
| 114 | |
| 115 | print(newline, file=ofile, end='') |
| 116 | |
| 117 | ofile.close() |
| 118 | return |
| 119 | |
| 120 | def printusage(progname): |
| 121 | print('Usage: ' + progname + ' filename [-options]') |
| 122 | print(' Options are:') |
| 123 | print(' -help Print this help text.') |
| 124 | print(' -quiet Stop without error if input file is not found.') |
| 125 | print(' -ccomm Remove C comments in /* ... */ delimiters.') |
| 126 | print(' -D<def> Define word <def> and set its value to 1.') |
| 127 | print(' -D<def>=<val> Define word <def> and set its value to <val>.') |
| 128 | print(' -I<dir> Add <dir> to search path for input files.') |
| 129 | return |
| 130 | |
| 131 | if __name__ == '__main__': |
| 132 | |
| 133 | # Parse command line for options and arguments |
| 134 | options = [] |
| 135 | arguments = [] |
| 136 | for item in sys.argv[1:]: |
| 137 | if item.find('-', 0) == 0: |
| 138 | options.append(item) |
| 139 | else: |
| 140 | arguments.append(item) |
| 141 | |
| 142 | if len(arguments) > 0: |
| 143 | inputfile = arguments[0] |
| 144 | if len(arguments) > 1: |
| 145 | outputfile = arguments[1] |
| 146 | else: |
| 147 | outputfile = [] |
| 148 | else: |
| 149 | printusage(sys.argv[0]) |
| 150 | sys.exit(0) |
| 151 | |
| 152 | defines = {} |
| 153 | keys = [] |
| 154 | for item in options: |
| 155 | result = item.split('=') |
| 156 | if result[0] == '-help': |
| 157 | printusage(sys.argv[0]) |
| 158 | sys.exit(0) |
| 159 | elif result[0][0:2] == '-D': |
| 160 | keyword = result[0][2:] |
| 161 | value = result[1] |
| 162 | defines[keyword] = value |
| 163 | keys.append(keyword) |
| 164 | else: |
| 165 | print('Bad option ' + item + ', options are -help, -D<def>\n') |
| 166 | sys.exit(1) |
| 167 | |
| 168 | if not os.path.isfile(inputfile): |
| 169 | if not quiet: |
| 170 | print("Error: No input file " + inputfile + " found.") |
| 171 | else: |
| 172 | sys.exit(0) |
| 173 | |
| 174 | runsubs(keys, defines, inputfile, outputfile) |
| 175 | |
| 176 | # Set mode of outputfile to be equal to that of inputfile (if not stdout) |
| 177 | if outputfile: |
| 178 | statinfo = os.stat(inputfile) |
| 179 | mode = statinfo.st_mode |
| 180 | os.chmod(outputfile, mode) |
| 181 | |
| 182 | sys.exit(0) |