| 1 | #!/usr/bin/env python |
|---|
| 2 | import sys |
|---|
| 3 | |
|---|
| 4 | BUF_MAX_SIZE = 512 |
|---|
| 5 | |
|---|
| 6 | |
|---|
| 7 | def _skip_sequence(stream, sequence): |
|---|
| 8 | """ |
|---|
| 9 | Finds the first occurrence of a sequence of bytes in a binary stream and |
|---|
| 10 | sets the streams's current position right after it. Returns True if the |
|---|
| 11 | sequence is found and False otherwise, The length of the sequence must not |
|---|
| 12 | exceed BUF_MAX_SIZE. |
|---|
| 13 | """ |
|---|
| 14 | sequence_size = len(sequence) |
|---|
| 15 | while 1: |
|---|
| 16 | buf = stream.read(BUF_MAX_SIZE) |
|---|
| 17 | idx = buf.find(sequence) |
|---|
| 18 | if idx < 0: |
|---|
| 19 | if len(buf) < BUF_MAX_SIZE: |
|---|
| 20 | return False |
|---|
| 21 | else: |
|---|
| 22 | stream.seek(1 - sequence_size, 1) |
|---|
| 23 | else: |
|---|
| 24 | stream.seek(idx + sequence_size - len(buf), 1) |
|---|
| 25 | return True |
|---|
| 26 | |
|---|
| 27 | |
|---|
| 28 | def _mods_differ_default(stream1, stream2): |
|---|
| 29 | # Simple byte comparison: |
|---|
| 30 | while 1: |
|---|
| 31 | buf1 = stream1.read(BUF_MAX_SIZE) |
|---|
| 32 | buf2 = stream2.read(BUF_MAX_SIZE) |
|---|
| 33 | if buf1 != buf2: |
|---|
| 34 | return True |
|---|
| 35 | if not buf1: |
|---|
| 36 | return False |
|---|
| 37 | |
|---|
| 38 | |
|---|
| 39 | def _mods_differ_intel(stream1, stream2): |
|---|
| 40 | # The first byte encodes the version of the module file format: |
|---|
| 41 | if stream1.read(1) != stream2.read(1): |
|---|
| 42 | return True |
|---|
| 43 | # The block before the following magic sequence might change from |
|---|
| 44 | # compilation to compilation, probably due to a second resolution timestamp |
|---|
| 45 | # in it: |
|---|
| 46 | magic_sequence = b'\x0A\x00' # the same as \n\0 |
|---|
| 47 | if not (_skip_sequence(stream1, magic_sequence) and |
|---|
| 48 | _skip_sequence(stream2, magic_sequence)): |
|---|
| 49 | return True |
|---|
| 50 | return _mods_differ_default(stream1, stream2) |
|---|
| 51 | |
|---|
| 52 | |
|---|
| 53 | def _mods_differ_gnu(stream1, stream2): |
|---|
| 54 | # The magic number of gzip to be found in the module files generated by |
|---|
| 55 | # GFortran 4.9 or later: |
|---|
| 56 | magic_sequence = b'\x1F\x8B' |
|---|
| 57 | stream1_sequence = stream1.read(len(magic_sequence)) |
|---|
| 58 | stream1.seek(0) |
|---|
| 59 | if stream1_sequence != magic_sequence: |
|---|
| 60 | # Older versions of GFortran generate module files in plain ASCII. Also, |
|---|
| 61 | # up to version 4.6.4, the first line of a module file contains a |
|---|
| 62 | # timestamp, therefore we ignore it. |
|---|
| 63 | stream1.readline() |
|---|
| 64 | stream2.readline() |
|---|
| 65 | return _mods_differ_default(stream1, stream2) |
|---|
| 66 | |
|---|
| 67 | |
|---|
| 68 | def _mods_differ_portland(stream1, stream2): |
|---|
| 69 | for _ in range(2): # the first two lines must be identical |
|---|
| 70 | if stream1.readline() != stream2.readline(): |
|---|
| 71 | return True |
|---|
| 72 | # The next line is a timestamp followed by the sequence '\nenduse\n': |
|---|
| 73 | magic_sequence = b'\x0A\x65\x6E\x64\x75\x73\x65\x0A' |
|---|
| 74 | if not (_skip_sequence(stream1, magic_sequence) and |
|---|
| 75 | _skip_sequence(stream2, magic_sequence)): |
|---|
| 76 | return True |
|---|
| 77 | return _mods_differ_default(stream1, stream2) |
|---|
| 78 | |
|---|
| 79 | |
|---|
| 80 | def _mods_differ_amd(stream1, stream2): |
|---|
| 81 | # AOCC is based on the Classic Flang, which has the same format as the PGI |
|---|
| 82 | # compiler |
|---|
| 83 | return _mods_differ_portland(stream1, stream2) |
|---|
| 84 | |
|---|
| 85 | |
|---|
| 86 | def _mods_differ_flang(stream1, stream2): |
|---|
| 87 | # The header of the module files generated by the new Flang compiler, |
|---|
| 88 | # formerly known as F18: |
|---|
| 89 | magic_sequence = (b'\xEF\xBB\xBF\x21' # UTF-8 BOM |
|---|
| 90 | b'\x6D\x6F\x64\x24') # the same as !mod$ |
|---|
| 91 | stream1_sequence = stream1.read(len(magic_sequence)) |
|---|
| 92 | stream1.seek(0) |
|---|
| 93 | if stream1_sequence != magic_sequence: |
|---|
| 94 | # The Classic Flang has the same format as the PGI compiler |
|---|
| 95 | return _mods_differ_portland(stream1, stream2) |
|---|
| 96 | return _mods_differ_default(stream1, stream2) |
|---|
| 97 | |
|---|
| 98 | |
|---|
| 99 | def _mods_differ_omni(stream1, stream2): |
|---|
| 100 | import xml.etree.ElementTree as eT |
|---|
| 101 | # Attributes that either declare or reference the type hashes. Each list |
|---|
| 102 | # contains a group of tags that reference "same things". |
|---|
| 103 | hash_attrs = [["imported_id"], |
|---|
| 104 | ["type", "ref", "return_type", "extends"]] |
|---|
| 105 | |
|---|
| 106 | tree1 = eT.parse(stream1) |
|---|
| 107 | tree2 = eT.parse(stream2) |
|---|
| 108 | |
|---|
| 109 | try: |
|---|
| 110 | it1 = tree1.iter() |
|---|
| 111 | it2 = tree2.iter() |
|---|
| 112 | except AttributeError: |
|---|
| 113 | it1 = iter(tree1.getiterator()) |
|---|
| 114 | it2 = iter(tree2.getiterator()) |
|---|
| 115 | |
|---|
| 116 | type_maps1 = [dict() for _ in hash_attrs] |
|---|
| 117 | type_maps2 = [dict() for _ in hash_attrs] |
|---|
| 118 | |
|---|
| 119 | for node1 in it1: |
|---|
| 120 | try: |
|---|
| 121 | node2 = next(it2) |
|---|
| 122 | except StopIteration: |
|---|
| 123 | # The second file is shorter: |
|---|
| 124 | return True |
|---|
| 125 | |
|---|
| 126 | if node1.tag != node2.tag: |
|---|
| 127 | # The nodes have different tags: |
|---|
| 128 | return True |
|---|
| 129 | |
|---|
| 130 | if node1.text != node2.text: |
|---|
| 131 | # The nodes have different texts: |
|---|
| 132 | return True |
|---|
| 133 | |
|---|
| 134 | for ii, attr_group in enumerate(hash_attrs): |
|---|
| 135 | type_map1 = type_maps1[ii] |
|---|
| 136 | type_map2 = type_maps2[ii] |
|---|
| 137 | |
|---|
| 138 | for attr in attr_group: |
|---|
| 139 | if (attr in node1.attrib) != (attr in node2.attrib): |
|---|
| 140 | # One of the files has the attribute and the second one |
|---|
| 141 | # does not: |
|---|
| 142 | return True |
|---|
| 143 | |
|---|
| 144 | hash1 = node1.attrib.pop(attr, None) |
|---|
| 145 | hash2 = node2.attrib.pop(attr, None) |
|---|
| 146 | |
|---|
| 147 | if hash1 == hash2: |
|---|
| 148 | # Either the attribute is missing in both nodes or they have |
|---|
| 149 | # the same value: |
|---|
| 150 | continue |
|---|
| 151 | elif (hash1 in type_map1) != (hash2 in type_map2): |
|---|
| 152 | # One of the files has already declared the respective hash |
|---|
| 153 | # and the second one has not: |
|---|
| 154 | return True |
|---|
| 155 | elif hash1 in type_map1 and \ |
|---|
| 156 | type_map1[hash1] != type_map2[hash2]: |
|---|
| 157 | # Both files have declared the respective hashes but they |
|---|
| 158 | # refer to different types: |
|---|
| 159 | return True |
|---|
| 160 | else: |
|---|
| 161 | # Declare the respective hashes for both files: |
|---|
| 162 | type_value = len(type_map1) |
|---|
| 163 | type_map1[hash1] = type_value |
|---|
| 164 | type_map2[hash2] = type_value |
|---|
| 165 | |
|---|
| 166 | if node1.attrib != node2.attrib: |
|---|
| 167 | # The rest of the attributes have different values: |
|---|
| 168 | return True |
|---|
| 169 | try: |
|---|
| 170 | next(it2) |
|---|
| 171 | # The first file is shorter: |
|---|
| 172 | return True |
|---|
| 173 | except StopIteration: |
|---|
| 174 | return False |
|---|
| 175 | |
|---|
| 176 | |
|---|
| 177 | def mods_differ(filename1, filename2, compiler_name=None): |
|---|
| 178 | """ |
|---|
| 179 | Checks whether two Fortran module files are essentially different. Some |
|---|
| 180 | compiler-specific logic is required for compilers that generate different |
|---|
| 181 | module files for the same source file (e.g. the module files might contain |
|---|
| 182 | timestamps). This implementation is inspired by CMake. |
|---|
| 183 | """ |
|---|
| 184 | with open(filename1, 'rb') as stream1: |
|---|
| 185 | with open(filename2, 'rb') as stream2: |
|---|
| 186 | if compiler_name == "intel": |
|---|
| 187 | return _mods_differ_intel(stream1, stream2) |
|---|
| 188 | elif compiler_name == "gnu": |
|---|
| 189 | return _mods_differ_gnu(stream1, stream2) |
|---|
| 190 | elif compiler_name == "portland": |
|---|
| 191 | return _mods_differ_portland(stream1, stream2) |
|---|
| 192 | elif compiler_name == "amd": |
|---|
| 193 | return _mods_differ_amd(stream1, stream2) |
|---|
| 194 | elif compiler_name == "flang": |
|---|
| 195 | return _mods_differ_flang(stream1, stream2) |
|---|
| 196 | elif compiler_name == "omni": |
|---|
| 197 | return _mods_differ_omni(stream1, stream2) |
|---|
| 198 | else: |
|---|
| 199 | return _mods_differ_default(stream1, stream2) |
|---|
| 200 | |
|---|
| 201 | |
|---|
| 202 | # We try to make this as fast as possible, therefore we do not parse arguments |
|---|
| 203 | # properly: |
|---|
| 204 | exit(mods_differ(sys.argv[1], sys.argv[2], |
|---|
| 205 | sys.argv[3].lower() if len(sys.argv) > 3 else None)) |
|---|