|  | #!/usr/bin/python | 
|  | """Parses a 'git diff --stat' file to extract what files have been changed as | 
|  | shown in the diff. Extracts the directory paths of those files, and decides | 
|  | which components of AKAROS should be compiled and tested, accordingly. | 
|  | """ | 
|  | import json | 
|  | import os | 
|  | import re | 
|  | import sys | 
|  |  | 
|  | REGEX_EXTRACT_PATH_FROM_GIT_DIFF_LINE = r'^(?:\s)*([^\s]*)(?:\s)*\|(?:.*)\n?$' | 
|  | # Path to file with git diff | 
|  | DIFF_FILE = sys.argv[1] | 
|  | # Path to file with JSON definition of akaros components | 
|  | CONFIG_COMP_COMPONENTS_FILE = sys.argv[2] | 
|  |  | 
|  | # Arguments to fill variable paths with. Useful, for example, to define a path | 
|  | # that will vary whether we are compiling an architecture or another. | 
|  | # TODO(alfongj): Get these from Env var or sys.argv | 
|  |  | 
|  |  | 
|  | """The following is the definition of the given components of AKAROS (filled in | 
|  | from CONFIG_COMP_COMPONENTS_FILE (which should be | 
|  | config/compilation_components.json or something). | 
|  |  | 
|  | Each 'component' consists of a name (which will be a unique identifier printed | 
|  | out to console) as key, plus a list of PATHS as content. If any of these paths | 
|  | is modified, then we will consider that the full component is affected, and | 
|  | therefore may need to be compiled and tested again. | 
|  |  | 
|  | Paths should be written from the root repo folder, beginning with './' and not | 
|  | '/', and ending in '/'. | 
|  |  | 
|  | If a path should include any subpaths, then it must be appended with a + symbol. | 
|  | e.g. ./path/with/subpaths/+ | 
|  |  | 
|  | If a path has variable arguments, they should be represented like {{this}}. | 
|  | These arguments will be filled in with the contents of PATH_ARGS. | 
|  | e.g. ./path/with/{{VARIABLE}}/{{ARGUMENTS}}/. | 
|  | """ | 
|  | akaros_components = {} | 
|  |  | 
|  | affected_components = {} | 
|  |  | 
|  | def get_variable_path_args() : | 
|  | """Returns dict of arguments to use in the load_component_config function | 
|  | to generate dynamic paths. Currently it is only being used to change a | 
|  | subdirectory in one of the paths depending on the architecture being | 
|  | tested. | 
|  | """ | 
|  | PATH_ARGS = { | 
|  | "I686": { | 
|  | "arch": "x86" | 
|  | }, | 
|  | "X86_64": { | 
|  | "arch": "x86" | 
|  | }, | 
|  | "RISCV": { | 
|  | "arch": "riscv" | 
|  | } | 
|  | } | 
|  | compilation_arch = os.getenv('COMPILATION_ARCH', 'I686') | 
|  | return PATH_ARGS[compilation_arch] | 
|  |  | 
|  | def load_component_config() : | 
|  | """Loads ../config/compilation_components.json object, which contains a | 
|  | list of all the different AKAROS compilation components along with the paths | 
|  | to look for for compiling them. | 
|  | """ | 
|  | conf_file_contents = "" | 
|  | # Read config file. | 
|  | with open(CONFIG_COMP_COMPONENTS_FILE, 'r') as conf_file : | 
|  | conf_file_contents = conf_file.read().replace('\n', '') | 
|  |  | 
|  | # Replace variable arguments. | 
|  | var_path_args = get_variable_path_args() | 
|  | for arg in var_path_args : | 
|  | wrapped_arg = "{{" + arg + "}}" | 
|  | conf_file_contents = conf_file_contents.replace(wrapped_arg, | 
|  | var_path_args[arg]) | 
|  |  | 
|  | # Parse JSON into python object. | 
|  | global akaros_components | 
|  | akaros_components = json.loads(conf_file_contents)['compilation_components'] | 
|  |  | 
|  | def extract_dir(diff_line) : | 
|  | """Given a line from a "git diff --stat" output, it tries to extract a | 
|  | directory from it. | 
|  |  | 
|  | If a blank or non-change line is passed, it ignores it and returns nothing. | 
|  |  | 
|  | If a 'change' line (e.g. ' path/to/file.ext  |  33++ ') is passed, it strips | 
|  | the path (not including the file name) and prepends a './' to it and returns | 
|  | it. | 
|  | """ | 
|  | match = re.match(REGEX_EXTRACT_PATH_FROM_GIT_DIFF_LINE, diff_line) | 
|  | if (match) : | 
|  | full_path = './' + match.group(1) | 
|  | folder_list = full_path.split('/')[0:-1] | 
|  | folder_path = '/'.join(folder_list) + '/' | 
|  | return folder_path | 
|  |  | 
|  | def includes_subpaths(path) : | 
|  | """Checks if a given path includes subpaths or not. It includes them if it | 
|  | ends in a '+' symbol. | 
|  | """ | 
|  | return path[-1] == '+' | 
|  |  | 
|  | def check_components_affected(path_of_changed_file) : | 
|  | """Checks if a given directory should set the state of one of the components | 
|  | to affected. | 
|  | """ | 
|  | global affected_components | 
|  | for component in akaros_components : | 
|  | affected = component in affected_components | 
|  | paths = akaros_components[component]['PATHS'] | 
|  | if (not affected) : | 
|  | for path in paths : | 
|  | if (includes_subpaths(path)) : | 
|  | # Checks if a given string contains the given path. | 
|  | # e.g., If the path is 'path/to': | 
|  | # The regex will match for: 'path/to/file.txt' or | 
|  | # 'path/to/and/subpath/to/file.txt' | 
|  | # But not for: 'path/file.txt' nor for 'path/tofile.txt' or | 
|  | # 'path/tobananas/file.txt' | 
|  | regex = re.compile('^\%s(?:.*/)*$' % path[:-1]) | 
|  | else : | 
|  | # Checks if a given string contains the given path with no | 
|  | # subpaths. | 
|  | # e.g., If the path is 'path/to': | 
|  | # The regex will match for: 'path/to/file.txt' | 
|  | # But not for: 'path/file.txt' nor for 'path/tofile.txt' or | 
|  | # 'path/tobananas/file.txt' or | 
|  | # 'path/to/and/subpath/to/file.txt' | 
|  | regex = re.compile('^\%s[^/]*$' % path) | 
|  |  | 
|  | if (re.match(regex, path_of_changed_file)) : | 
|  | affected_components[component] = True | 
|  | break | 
|  |  | 
|  | def main() : | 
|  | load_component_config() | 
|  | diff_file = open(DIFF_FILE) | 
|  | for line in diff_file : | 
|  | cur_dir = extract_dir(line) | 
|  | if (cur_dir) : | 
|  | check_components_affected(cur_dir) | 
|  |  | 
|  | print ' '.join(affected_components) | 
|  |  | 
|  | main() |