#!/usr/bin/python3 """ Synchronizes enums and their comments from the NeuralNetworks.h to types.hal Workflow: - Don't try to make other changes to types.hal in the same branch, as this will check out and overwrite files - Edit NeuralNetworks.h - run sync_enums_to_hal.py - can be run from anywhere, but ANDROID_BUILD_TOP must be set - this resets 1.[0-2]/types.hal to last commit (so you can run the script multiple times with changes to it in-between), and - overwrites types.hal in-place - Check the output (git diff) - Recreate hashes - commit and upload for review Note: This is somewhat brittle in terms of ordering and formatting of the relevant files. It's the author's opinion that it's not worth spending a lot of time upfront coming up with better mechanisms, but to improve it when needed. For example, currently Operations have differences between 1.0 and 1.1, but Operands do not, so the script is explicit rather than generic. There are asserts in the code to make sure the expectations on the ordering and formatting of the headers are met, so this should fail rather than produce completely unexpected output. The alternative would be to add explicit section markers to the files. """ import os import re import subprocess class HeaderReader(object): """ Simple base class facilitates reading a file into sections and writing it back out """ def __init__(self): self.sections = [] self.current = -1 self.next_section() def put_back(self, no_of_lines=1): assert not self.sections[self.current] for i in range(no_of_lines): line = self.sections[self.current - 1].pop() self.sections[self.current].insert(0, line) def pop_back(self, no_of_lines=1): for i in range(no_of_lines): self.sections[self.current].pop() def next_section(self): self.current = self.current + 1 self.sections.append([]) def get_contents(self): return "".join([ "".join(s) for s in self.sections]) def get_section(self, which): return "".join(self.sections[which]) def handle_line(self, line): assert False def read(self, filename): assert self.current == 0 self.filename = filename with open(filename) as f: lines = f.readlines() for line in lines: self.sections[self.current].append(line) if self.current == self.REST: continue self.handle_line(line) assert self.current == self.REST def write(self): with open(self.filename, "w") as f: f.write(self.get_contents()) class Types10Reader(HeaderReader): """ Reader for 1.0 types.hal The structure of the file is: - preamble - enum OperandType ... { < this becomes the OPERAND section > OEM operands }; - comments - enum OperationType ... { < this becomes the OPERATION section > OEM operarions }; - rest """ BEFORE_OPERAND = 0 OPERAND = 1 BEFORE_OPERATION = 2 OPERATION = 3 REST = 4 def __init__(self): super(Types10Reader, self).__init__() self.read("hardware/interfaces/neuralnetworks/1.0/types.hal") def handle_line(self, line): if "enum OperandType" in line: assert self.current == self.BEFORE_OPERAND self.next_section() elif "enum OperationType" in line: assert self.current == self.BEFORE_OPERATION self.next_section() elif "OEM" in line and self.current == self.OPERAND: self.next_section() self.put_back(2) elif "OEM specific" in line and self.current == self.OPERATION: self.next_section() self.put_back(2) class Types11Reader(HeaderReader): """ Reader for 1.1 types.hal The structure of the file is: - preamble - enum OperationType ... { < this becomes the OPERATION section > }; - rest """ BEFORE_OPERATION = 0 OPERATION = 1 REST = 2 FILENAME = "hardware/interfaces/neuralnetworks/1.1/types.hal" def __init__(self): super(Types11Reader, self).__init__() self.read(self.FILENAME) def handle_line(self, line): if "enum OperationType" in line: assert self.current == self.BEFORE_OPERATION self.next_section() # there is more content after the enums we are interested in so # it cannot be the last line, can match with \n elif line == "};\n": self.next_section() self.put_back() class Types12Reader(Types11Reader): """ Reader for 1.2 types.hal Assumes the structure of the file is the same as in v1.1. """ FILENAME = "hardware/interfaces/neuralnetworks/1.2/types.hal" class NeuralNetworksReader(HeaderReader): """ Reader for NeuralNetworks.h The structure of the file is: - preamble - typedef enum { < this becomes the OPERAND section > } OperandCode; - comments - typedef enum { // Operations below are available since API level 27. < this becomes the OPERATION_V1_0 section > // Operations below are available since API level 28. < this becomes the OPERATION_V1_1 section > // Operations below are available since API level 29. < this becomes the OPERATION_V1_2 section > } OperationCode; - rest """ BEFORE_OPERAND = 0 OPERAND = 1 BEFORE_OPERATION = 2 OPERATION_V1_0 = 3 OPERATION_V1_1 = 4 OPERATION_V1_2 = 5 REST = 6 def __init__(self): super(NeuralNetworksReader, self).__init__() self.read("frameworks/ml/nn/runtime/include/NeuralNetworks.h") def handle_line(self, line): if line == "typedef enum {\n": self.next_section() elif line == "} OperandCode;\n": assert self.current == self.OPERAND self.next_section() self.put_back() elif "// Operations below are available since API level 27." in line: assert self.current == self.OPERATION_V1_0 self.pop_back() elif "// Operations below are available since API level 28." in line: assert self.current == self.OPERATION_V1_0 self.pop_back() self.next_section() elif "// Operations below are available since API level 29." in line: assert self.current == self.OPERATION_V1_1 self.pop_back() self.next_section() elif line == "} OperationCode;\n": assert self.current == self.OPERATION_V1_2 self.next_section() self.put_back() if __name__ == "__main__": # Reset assert os.environ["ANDROID_BUILD_TOP"] os.chdir(os.environ["ANDROID_BUILD_TOP"]) subprocess.run( "cd hardware/interfaces/neuralnetworks && git checkout */types.hal", shell=True) # Read existing contents types10 = Types10Reader() types11 = Types11Reader() types12 = Types12Reader() nn = NeuralNetworksReader() # Rewrite from header syntax to HAL and replace types.hal contents operand = [] for line in nn.sections[nn.OPERAND]: line = line.replace("ANEURALNETWORKS_", "") operand.append(line) types10.sections[types10.OPERAND] = operand def rewrite_operation(from_nn): hal = [] for line in from_nn: if "TODO" in line: continue # Match multiline comment style if re.match("^ */\*\* \w.*[^/]$", line): hal.append(" /**\n") line = line.replace("/** ", " * ") # Match naming changes in HAL vs framework line = line.replace("@link OperandCode", "@link OperandType") line = line.replace("@link ANEURALNETWORKS_", "@link OperandType::") line = line.replace("ANEURALNETWORKS_", "") line = line.replace("FuseCode", "FusedActivationFunc") # PaddingCode is not part of HAL, rewrite line = line.replace("{@link PaddingCode} values", "following values: {0 (NONE), 1 (SAME), 2 (VALID)}") hal.append(line) return hal types10.sections[types10.OPERATION] = rewrite_operation(nn.sections[nn.OPERATION_V1_0]) types11.sections[types11.OPERATION] = rewrite_operation(nn.sections[nn.OPERATION_V1_1]) types12.sections[types12.OPERATION] = rewrite_operation(nn.sections[nn.OPERATION_V1_2]) # Write synced contents types10.write() types11.write() types12.write() print("") print("The files") print(" " + types10.filename + " and") print(" " + types11.filename + " and") print(" " + types12.filename) print("have been rewritten") print("") print("Check that the change matches your expectations and regenerate the hashes") print("")