diff --git a/start.py b/start.py index fc36fa5b8c..0c6c1a87ea 100755 --- a/start.py +++ b/start.py @@ -66,21 +66,22 @@ class ConfChooser(object): # CallBack Functions - def find_conf_files(self): + def find_conf_files(self, combo): conf_files = paparazzi.get_list_of_conf_files(self.exclude_backups) - self.update_combo(self.conf_file_combo, conf_files, self.conf_xml) + self.update_combo(combo, conf_files, self.conf_xml) def find_controlpanel_files(self): controlpanel_files = paparazzi.get_list_of_controlpanel_files(self.exclude_backups) self.update_combo(self.controlpanel_file_combo, controlpanel_files, self.controlpanel_xml) - def count_airframes_in_conf(self): + @staticmethod + def count_airframes_in_conf(combo, conf_airframes): airframes = 0 releases = 0 - if self.conf_file_combo.get_active_text() is None: + if combo.get_active_text() is None: return desc = "" - afile = os.path.join(paparazzi.conf_dir, self.conf_file_combo.get_active_text()) + afile = os.path.join(paparazzi.conf_dir, combo.get_active_text()) if os.path.exists(afile): e = xml.etree.ElementTree.parse(afile).getroot() for atype in e.findall('aircraft'): @@ -94,7 +95,7 @@ class ConfChooser(object): else: desc += atype.get('name') desc = "" + str(airframes) + " airframes: " + desc - self.conf_airframes.set_markup(desc) + conf_airframes.set_markup(desc) return def about(self, widget): @@ -120,13 +121,22 @@ class ConfChooser(object): def set_backups(self, widget): self.exclude_backups = not widget.get_active() - self.find_conf_files() + self.find_conf_files(self.conf_file_combo) self.find_controlpanel_files() def more_info(self, widget): - obj = PaparazziOverview(0) - obj.run() + self.obj.run() + def show_untested(self, widget, data): + self.obj.run(show_af_detail=False, show_untested=True, + show_airframes=data["Airframes"].get_active(), + show_flightplans=data["Flightplans"].get_active(), + show_boards=data["Boards"].get_active(), + show_modules=data["Modules"].get_active()) + + def module_usage(self, widget, data): + selected_conf = data.get_active_text() + self.obj.airframe_module_overview(selected_conf) def launch(self, widget): self.accept(widget) @@ -184,8 +194,8 @@ class ConfChooser(object): if os.path.exists(filename): os.remove(filename) self.update_conf_label() - self.count_airframes_in_conf() - self.find_conf_files() + self.count_airframes_in_conf(self.conf_file_combo, self.conf_airframes) + self.find_conf_files(self.conf_file_combo) self.print_status("Deleted: " + filename) def delete_controlpanel(self, widget): @@ -208,8 +218,8 @@ class ConfChooser(object): os.remove(self.conf_xml) os.symlink(selected, self.conf_xml) self.update_conf_label() - self.count_airframes_in_conf() - self.find_conf_files() + self.count_airframes_in_conf(self.conf_file_combo, self.conf_airframes) + self.find_conf_files(self.conf_file_combo) selected = self.controlpanel_file_combo.get_active_text() if selected == "control_panel.xml": @@ -232,8 +242,8 @@ class ConfChooser(object): os.remove(self.conf_xml) os.symlink(self.conf_personal_name, self.conf_xml) self.update_conf_label() - self.count_airframes_in_conf() - self.find_conf_files() + self.count_airframes_in_conf(self.conf_file_combo, self.conf_airframes) + self.find_conf_files(self.conf_file_combo) def personal_controlpanel(self, widget): if os.path.exists(self.controlpanel_personal): @@ -250,11 +260,94 @@ class ConfChooser(object): def print_status(self, text): self.statusbar.push(self.context_id, text) - def changed_cb(self, entry): - self.count_airframes_in_conf() + def changed_cb(self, widget, data): + self.count_airframes_in_conf(data["combo"], data["list"]) + + def maintenance_window(self, widget): + mtn_window = gtk.Window() + mtn_window.set_position(gtk.WIN_POS_CENTER) + mtn_window.set_size_request(750, 300) + mtn_window.set_title("Maintenance Tools") + + mnt_vbox = gtk.VBox() + + mnt_conf_label = gtk.Label("Conf:") + mnt_conf_label.set_size_request(100, 30) + mnt_conf_file_combo = gtk.combo_box_new_text() + self.find_conf_files(mnt_conf_file_combo) + mnt_conf_file_combo.set_size_request(500, 30) + + mnt_conf_airframes = gtk.Label("") + self.count_airframes_in_conf(mnt_conf_file_combo, mnt_conf_airframes) + mnt_conf_airframes.set_size_request(650, 180) + mnt_conf_airframes.set_line_wrap(True) + + combo_list = {"combo": mnt_conf_file_combo, "list": mnt_conf_airframes} + mnt_conf_file_combo.connect("changed", self.changed_cb, combo_list) + + mnt_confbar = gtk.HBox() + mnt_confbar.pack_start(mnt_conf_label) + mnt_confbar.pack_start(mnt_conf_file_combo) + mnt_vbox.pack_start(mnt_confbar, False) + + btnModule = gtk.Button("Module\nUsage") + btnModule.connect("clicked", self.module_usage, mnt_conf_file_combo) + btnModule.set_tooltip_text("More information on the modules used by these airframes") + + mnt_caexbar = gtk.HBox() + mnt_caexbar.pack_start(mnt_conf_airframes) + mnt_caexbar.pack_start(btnModule) + mnt_vbox.pack_start(mnt_caexbar) + + separator = gtk.HSeparator() + mnt_vbox.pack_start(separator) + + cbtnAirframes = gtk.CheckButton() + cbtnAirframes.set_label("Airframes") + cbtnAirframes.set_active(True) + + cbtnFlightplans = gtk.CheckButton() + cbtnFlightplans.set_label("Flight plans") + cbtnFlightplans.set_active(True) + + cbtnBoards = gtk.CheckButton() + cbtnBoards.set_label("Boards") + cbtnBoards.set_active(True) + + cbtnModules = gtk.CheckButton() + cbtnModules.set_label("Modules") + cbtnModules.set_active(False) + + selectedOptions = {"Airframes": cbtnAirframes, "Flightplans": cbtnFlightplans, + "Boards": cbtnBoards, "Modules": cbtnModules} + + btnUntested = gtk.Button(None, "Show Untested Files") + btnUntested.connect("clicked", self.show_untested, selectedOptions) + btnUntested.set_tooltip_text("For the selected options show the files not tested by any conf") + + untestedHBox = gtk.HBox() + cbtnVBox = gtk.VBox() + cbRowUpper = gtk.HBox() + cbRowLower = gtk.HBox() + + cbRowUpper.pack_start(cbtnAirframes) + cbRowUpper.pack_start(cbtnBoards) + cbRowLower.pack_start(cbtnFlightplans) + cbRowLower.pack_start(cbtnModules) + cbtnVBox.pack_start(cbRowUpper) + cbtnVBox.pack_start(cbRowLower) + untestedHBox.pack_start(cbtnVBox) + untestedHBox.pack_start(btnUntested) + + mnt_vbox.pack_start(untestedHBox) + + mtn_window.add(mnt_vbox) + mtn_window.show_all() # Constructor Functions def __init__(self): + self.obj = PaparazziOverview(0) + # paparazzi process self.pp = None @@ -311,8 +404,7 @@ class ConfChooser(object): self.conf_label.set_size_request(100, 30) self.conf_file_combo = gtk.combo_box_new_text() - self.find_conf_files() - self.conf_file_combo.connect("changed", self.changed_cb) + self.find_conf_files(self.conf_file_combo) self.conf_file_combo.set_size_request(550, 30) self.btnDeleteConf = gtk.Button(None, gtk.STOCK_DELETE) @@ -344,17 +436,27 @@ class ConfChooser(object): # Count Airframes self.conf_airframes = gtk.Label("") - self.count_airframes_in_conf() - self.conf_airframes.set_size_request(650,180) + self.count_airframes_in_conf(self.conf_file_combo, self.conf_airframes) + self.conf_airframes.set_size_request(650, 180) self.conf_airframes.set_line_wrap(True) + self.combo_list = {"combo": self.conf_file_combo, "list": self.conf_airframes} + self.conf_file_combo.connect("changed", self.changed_cb, self.combo_list) + self.btnInfo = gtk.Button(None, "More\nInfo") self.btnInfo.connect("clicked", self.more_info) self.btnInfo.set_tooltip_text("More information on airframe files") + self.btnMaintenance = gtk.Button(None, "Maintenance\n\tTools") + self.btnMaintenance.connect("clicked", self.maintenance_window) + self.btnMaintenance.set_tooltip_text("Show maintenance tools") + self.caexbar = gtk.HBox() self.caexbar.pack_start(self.conf_airframes) - self.caexbar.pack_start(self.btnInfo) + self.caexbar_btns = gtk.VBox() + self.caexbar.pack_start(self.caexbar_btns) + self.caexbar_btns.pack_start(self.btnInfo) + self.caexbar_btns.pack_start(self.btnMaintenance) self.my_vbox.pack_start(self.caexbar, False) diff --git a/sw/lib/python/paparazzi.py b/sw/lib/python/paparazzi.py index 4720c0075d..2b79ad7d0c 100755 --- a/sw/lib/python/paparazzi.py +++ b/sw/lib/python/paparazzi.py @@ -14,7 +14,8 @@ import lxml.etree as ET # if PAPARAZZI_HOME not set, then assume the tree containing this # file is a reasonable substitute -PAPARAZZI_HOME = getenv("PAPARAZZI_HOME", path.normpath(path.join(path.dirname(path.abspath(__file__)), '../../../'))) +PAPARAZZI_SRC = getenv("PAPARAZZI_HOME", path.normpath(path.join(path.dirname(path.abspath(__file__)), '../../../'))) +PAPARAZZI_HOME = getenv("PAPARAZZI_HOME", PAPARAZZI_SRC) # Directories conf_dir = path.join(PAPARAZZI_HOME, "conf/") diff --git a/sw/lib/python/paparazzi_health.py b/sw/lib/python/paparazzi_health.py index 8a5b94070c..2fe049fe83 100755 --- a/sw/lib/python/paparazzi_health.py +++ b/sw/lib/python/paparazzi_health.py @@ -6,19 +6,25 @@ import webbrowser import os import datetime from fnmatch import fnmatch +import re +import numpy as np +from collections import Counter +import paparazzi +import xml.etree.ElementTree +from lxml import etree import subprocess + PIPE = subprocess.PIPE -import paparazzi - -import xml.etree.ElementTree class Airframe: name = "" - id = "" + ac_id = "" xml = "" flight_plan = "" release = "" + modules = [] + def __init__(self): name = "" ac_id = "" @@ -26,6 +32,7 @@ class Airframe: flight_plan = "" release = "" + class AirframeFile: name = "" description = "" @@ -33,6 +40,10 @@ class AirframeFile: boards = [] xml = "" includes = [] + last_commit = "" + commit_processes = [] + modules = [] + def __init__(self): name = "" description = "" @@ -41,44 +52,128 @@ class AirframeFile: xml = "" includes = [] + +class Module: + """ Stores data related to single module + Data class for storage of information related to a single module xml file. + """ + module_airborne_dir = paparazzi.PAPARAZZI_SRC + "/sw/airborne/" + name = "" + last_commit = "" + xml = "" + usage = 0 + files = [] + missing_files = [] + + def __init__(self, name, file_list): + self.files = dict() + self.missing_files = [] + self.name = name + self.xml = paparazzi.modules_dir + name + ".xml" + self.find_files(file_list) + self.last_commit = "" + self.calc_last_commit() + + def find_files(self, file_dict): + """ Finds files mentioned in xml + Finds all files mentioned in the xml and looks them up in the files in the sw folder + """ + parser = etree.XMLParser(recover=True) + e = etree.parse(self.xml, parser) + for atype in e.findall('.//file'): + file_name = atype.get('name') + if (file_name is not None) & (not file_name == ""): + if file_name.rfind('/') != -1: + file_name = file_name[file_name.rfind('/')+1:] + self.files[file_name] = "" + + self.missing_files = list(self.files.viewkeys()) + for mod_file in self.files.viewkeys(): + if mod_file in file_dict: + self.files[mod_file] = file_dict[mod_file] + if mod_file in self.missing_files: + self.missing_files.remove(mod_file) + return + + def calc_last_commit(self): + """ Calculates the most recent commit from any of the files related to this module.""" + commit_processes = [self.open_commit_log_process(self.xml)] + commit_list = [] + for _, value in self.files.items(): + commit_processes.append(self.open_commit_log_process(value)) + for process in commit_processes: + out = process.communicate() + if out[0][:-2]: + commit_list.append(out[0][:-2]) + commit_list = sorted(commit_list, key=lambda x: datetime.datetime.strptime(x, '%d-%m-%Y'), + reverse=True) + self.last_commit = commit_list[0] + return + + @staticmethod + def open_commit_log_process(filename): + """ Opens subprocess to retrieve last commit date.""" + process = subprocess.Popen(['git', 'log', '-1', '--date=format:%d-%m-%Y', '--format=%cd ', filename], + stdout=PIPE, stderr=PIPE) + return process + + def get_comments(self): + """ Formats the comment field which gets printed to the html file.""" + output = "Last commit: " + self.last_commit + "
" + if len(self.missing_files): + output = output + "The following files could not be found: " + for missing_file in self.missing_files: + output = output + missing_file + " " + + return output + + class PaparazziOverview(object): - def RepresentsInt(self, s): + @staticmethod + def represents_int(s): try: - v=int(s) + v = int(s) return v except ValueError: return -1 - def maximize_text_size(self, txt): + @staticmethod + def maximize_text_size(txt): if len(txt) > 500: return txt[:500] + '...' else: return txt def git_behind(self, sha): - process = subprocess.Popen(['git', 'rev-list', sha+"..HEAD", '--count'], stdout=PIPE, stderr=PIPE) + process = subprocess.Popen(['git', 'rev-list', sha+"..HEAD", '--count'], + stdout=PIPE, stderr=PIPE) stdoutput, stderroutput = process.communicate() - return self.RepresentsInt(stdoutput) + return self.represents_int(stdoutput) def git_ahead(self, sha): - process = subprocess.Popen(['git', 'rev-list', "HEAD.."+sha, '--count'], stdout=PIPE, stderr=PIPE) + process = subprocess.Popen(['git', 'rev-list', "HEAD.."+sha, '--count'], + stdout=PIPE, stderr=PIPE) stdoutput, stderroutput = process.communicate() - return self.RepresentsInt(stdoutput) + return self.represents_int(stdoutput) - def get_last_commit_sha(self, file): - process = subprocess.Popen(['git', 'log', '-n', 1, '--pretty=format:%H', '--', sha+"..HEAD"], stdout=PIPE, stderr=PIPE) + @staticmethod + def get_last_commit_date(filename): + process = subprocess.Popen(['git', 'log', '-1', '--date=format:%d-%m-%Y', '--format=%cd ', filename], bufsize=-1, + stdout=PIPE, stderr=PIPE) stdoutput, stderroutput = process.communicate() + if stdoutput == "": + stdoutput = "00-00-0000" return stdoutput - - def find_xml_files(self, folder): + @staticmethod + def find_xml_files(folder): airframe_files = [] pattern = "*.xml" confn = "*conf[._-]*xml" controlpanel = "*control_panel[._-]*xml" - for path, subdirs, files in os.walk(os.path.join(paparazzi.conf_dir,folder)): + for path, subdirs, files in os.walk(os.path.join(paparazzi.conf_dir, folder)): for name in files: if fnmatch(name, confn): continue @@ -91,13 +186,31 @@ class PaparazziOverview(object): airframe_files.sort() return airframe_files + @staticmethod + def find_makefiles(folder): + makefiles = [] + pattern = "*.makefile" + + for path, subdirs, files in os.walk(os.path.join(paparazzi.conf_dir, folder)): + for name in files: + if fnmatch(name, pattern): + filepath = os.path.join(path, name) + entry = os.path.relpath(filepath, paparazzi.conf_dir) + makefiles.append(entry) + makefiles.sort() + return makefiles + def find_airframe_files(self): return self.find_xml_files('airframes/') def find_flightplan_files(self): return self.find_xml_files('flight_plans/') - def list_airframes_in_conf(self, conf): + def find_board_files(self): + return self.find_makefiles('boards/') + + @staticmethod + def list_airframes_in_conf(conf): if conf is None: return [] list_of_airframes = [] @@ -108,13 +221,16 @@ class PaparazziOverview(object): for atype in e.findall('aircraft'): release = "" if (not atype.get('release') is None) & (not atype.get('release') == ""): - release = atype.get('release') + release = atype.get('release') af = Airframe() af.name = atype.get('name') af.ac_id = atype.get('ac_id') af.xml = atype.get('airframe') af.flight_plan = atype.get('flight_plan') af.release = release + if (not atype.get('settings_modules') is None) & (not atype.get('settings_modules') == ""): + modules = atype.get('settings_modules').translate(None, '[]').split() + af.modules = modules list_of_airframes.append(af) return list_of_airframes @@ -123,7 +239,8 @@ class PaparazziOverview(object): airframe.xml = xmlname airframe.firmware = [] airframe.includes = [] - airframe.board = [] + airframe.boards = [] + airframe.modules = [] if xml is None: return airframe afile = os.path.join(paparazzi.conf_dir, xmlname) @@ -131,106 +248,357 @@ class PaparazziOverview(object): try: e = xml.etree.ElementTree.parse(afile).getroot() for atype in e.findall('firmware'): - if (not atype.get('name') is None) & (not atype.get('name') == "") & (not atype.get('name') in airframe.firmware): + for ctype in atype.findall('module'): + module_name_type = self.get_module_name_type(ctype) + airframe.modules.append(module_name_type) + if (not atype.get('name') is None) & (not atype.get('name') == "") \ + & (not atype.get('name') in airframe.firmware): airframe.firmware.append(atype.get('name')) for btype in atype.findall('target'): - if (not btype.get('board') is None) & (not btype.get('board') == "") & (not btype.get('board') in airframe.board): - airframe.board.append( btype.get('board') ) + if (not btype.get('board') is None) & (not btype.get('board') == "") \ + & (not btype.get('board') in airframe.boards): + airframe.boards.append(btype.get('board')) + for ctype in btype.findall('module'): + module_name_type = self.get_module_name_type(ctype) + airframe.modules.append(module_name_type) for atype in e.findall('include'): if (not atype.get('href') is None) & (not atype.get('href') == ""): - airframe.includes.append( atype.get('href') ) + airframe.includes.append(atype.get('href')) for atype in e.findall('description'): airframe.description = atype.text + except xml.etree.ElementTree.ParseError as e: print("Could not parse {}: {}".format(afile, e)) return airframe - def flightplan_includes(self, xmlname): + @staticmethod + def get_module_name_type(ctype): + module_name = re.sub(r'(\.xml)$', "", ctype.get('name')) + if ctype.get('type') is not None: + module_type = re.sub(r'(\.xml)$', "", ctype.get('type')) + else: + module_type = "" + module = (module_name, module_type) + return module + + @staticmethod + def flightplan_includes(xmlname): includes = [] - print(xmlname) if xml is None: return includes afile = os.path.join(paparazzi.conf_dir, xmlname) + parser = etree.XMLParser(recover=True) if os.path.exists(afile): - e = xml.etree.ElementTree.parse(afile).getroot() - for atype in e.findall('include'): - if (not atype.get('procedure') is None) & (not atype.get('procedure') == ""): - includes.append( atype.get('procedure') ) + try: + e = etree.parse(afile, parser).getroot() + for atype in e.findall('include'): + if (not atype.get('procedure') is None) & (not atype.get('procedure') == ""): + if atype.get('procedure')[0:7] == "include_": + includes.append(atype.get('procedure')[8:]) + else: + includes.append(atype.get('procedure')) + for btype in e.findall('includes'): + for atype in btype.findall('include'): + if (not atype.get('procedure') is None) & (not atype.get('procedure') == ""): + if atype.get('procedure')[0:7] == "include_": + includes.append(atype.get('procedure')[8:]) + else: + includes.append(atype.get('procedure')) + except etree.ParseError as e: + print("Could not parse {}: {}".format(afile, e)) return includes + @staticmethod + def remove_path_and_xml(filename): + if filename[-4:] == ".xml": + return os.path.splitext(os.path.split(filename)[1])[0] + else: + return os.path.split(filename)[1] + + def generate_sorted_list(self, lst): + commit_dates = [re.sub(r"( \n)$", "", self.get_last_commit_date(paparazzi.conf_dir + elm)) for elm in lst] + file_date_lst = sorted(zip(lst, commit_dates), key=lambda x: datetime.datetime.strptime(x[1], '%d-%m-%Y'), + reverse=True) + return file_date_lst + + def airframe_module_overview(self, selected_conf): + """ + Creates a nested dictionary of the airframe names and the modules they use to generate an html table + that provides an overview of module usage of the airframes of interest + + :param selected_conf: str of the name of conf file + """ + # Looks at airframes in selected conf (not the currently active conf) + airframes = self.list_airframes_in_conf(selected_conf) + + # Structure of nested dictionary: {af name: {module name: module type}} + afs_mods = {} + for ac in airframes: + afs_mods[ac.name] = {'xml': [ac.xml]} + af = self.airframe_details(ac.xml) + for mod in af.modules: + if mod[0] in afs_mods[ac.name]: + afs_mods[ac.name][mod[0]].append(mod[1]) + else: + afs_mods[ac.name][mod[0]] = [mod[1]] + + # Generates Counter object of all module names used by airframes in conf + unique_mods_ctr = Counter() + for mods in afs_mods.values(): + unique_mods_ctr.update(mods.keys()) + del unique_mods_ctr['xml'] + + # Table initialization, airframe names are ordered alphabetically, module names by most used + ac_mod_table = np.zeros((len(afs_mods.keys()) + 1, len(unique_mods_ctr.keys()) + 1), dtype=object) + ac_mod_table[0, 0] = "Name \\ Modules" + ac_mod_table[1:, 0] = sorted(afs_mods.keys(), key=lambda s: s.lower()) + ac_mod_table[0, 1:] = [i for i, _ in unique_mods_ctr.most_common()] + + for i in range(1, len(ac_mod_table[:, 0])): + for j in range(1, len(ac_mod_table[0, :])): + ac_name = ac_mod_table[i, 0] + module_name = ac_mod_table[0, j] + if ac_mod_table[0, j] not in afs_mods[ac_name]: + ac_mod_table[i, j] = "\\" + else: + ac_mod_table[i, j] = "" + for module_type in afs_mods[ac_name][module_name]: + if module_type == "": + ac_mod_table[i, j] = "default" + else: + ac_mod_table[i, j] += module_type + "\n" + + with open('var/airframe_module_overview.html', 'w') as f: + f.write('\n\n\nModule usage overview\n \ + \n\n') + f.write('\n\n\n') + f.write('\n') + + f.write('\n\t') + for column_header in ac_mod_table[0, :]: + f.write('\n\t'.format(column_header)) + f.write('\n') + for row in ac_mod_table[1:, :]: + f.write('\n\t') + for element in row: + f.write('\n\t'.format(element)) + f.write('\n') + f.write('
{}
{}
\n\n\n') + + webbrowser.open('file://' + os.path.realpath('./var/airframe_module_overview.html')) + + def find_not_tested_by_conf(self, show_airframes, show_flightplans, show_boards, show_modules): + # Find all airframe, flightplan and module XML's + afs = self.find_airframe_files() + fps = self.find_flightplan_files() + bs = self.find_board_files() + mods = dict() + fps_usage = dict() + conf_files = paparazzi.get_list_of_conf_files() + + # Generation of list of all files in the sw directory, for checking with the modules. + if show_modules: + module_sw_dir = paparazzi.PAPARAZZI_SRC + "/sw/" + file_dict = dict() + for root, dirs, dir_files in os.walk(module_sw_dir): + for dir_file in dir_files: + file_dict[dir_file] = os.path.join(root, dir_file) + + mods = {name: Module(name, file_dict) for name in paparazzi.get_list_of_modules()} + + for conf in conf_files: + airframes = self.list_airframes_in_conf(conf) + for ac in airframes: + xml = ac.xml + flight_plan = ac.flight_plan + af = self.airframe_details(xml) + if show_airframes: + if xml in afs: + afs.remove(xml) + if len(af.includes) > 0: + for i in af.includes: + inc_name = i[5:].replace('$AC_ID', ac.ac_id) + if inc_name in afs: + afs.remove(inc_name) + if show_flightplans and flight_plan in fps: + fps.remove(flight_plan) + if show_boards: + for board in af.boards: + pattern = 'boards/' + board + '.makefile' + if pattern in bs: + bs.remove(pattern) + if show_airframes and len(af.includes) > 0: + for i in af.includes: + inc_name = i[5:].replace('$AC_ID', ac.ac_id) + if inc_name in afs: + afs.remove(inc_name) + if show_modules: + for ac_mod in ac.modules: + mod_str = self.remove_path_and_xml(ac_mod) + try: + mods[mod_str].usage = mods[mod_str].usage + 1 + except KeyError: + print(mod_str + " in " + str(conf) + ": Does not seem to have an xml file associated with it") + + # Check if flight plans are included in other flight plans + if show_flightplans: + for fp in fps: + fps_usage[os.path.split(fp)[1]] = "" + for fp in fps: + includes = self.flightplan_includes(fp) + for include in includes: + try: + fps_usage[include] = fps_usage[include] + str(os.path.split(fp)[1]) + ". " + except KeyError: + print(include + " in " + fp + ": Does not seem to have an xml file associated with it") + + # Create usage dictionary for modules + if show_modules: + for ac in afs: + af = self.airframe_details(ac) + for af_mod in af.modules: + if af_mod[1] == "": + mod_str = af_mod[0] + else: + mod_str = af_mod[0] + "_" + af_mod[1] + mod_str = self.remove_path_and_xml(mod_str) + try: + mods[mod_str].usage = mods[mod_str].usage + 1 + except KeyError: + print(mod_str + " in " + af.xml + ": Does not seem to have an xml file associated with it") + + return afs, fps, bs, mods, fps_usage + + def not_tested_html(self, output_html, show_airframes, show_flightplans, show_boards, show_modules): + afs, fps, bs, mods, fps_usage = self.find_not_tested_by_conf(show_airframes, show_flightplans, + show_boards, show_modules) + f = output_html + f.write('\n\n\n' + 'Untested Airframes, Boards, Flight Plans and Modules Overview\n' + '\n' + '\n') + f.write('\n\n\n') + f.write('\n') + + # Generate table with airframe files that are not in any config + if show_airframes: + f.write('

Airframe xml that are not tested by any conf:

') + f.write('') + afs_sorted = self.generate_sorted_list(afs) + for af in afs_sorted: + f.write('') + f.write('
Filename Last commit date
  • ' + af[0] + '
  • ' + af[1] + '
    ') + + # Generate table with flightplan files that are not in any config + if show_flightplans: + f.write('

    Flight_plan xml that are not tested by any conf:

    ') + f.write('This section lists all flightplan xml files not directly tested by a conf xml.
    ' + 'Certain flightplans may be included in other (potentially) tested flightplans (third column).') + f.write('') + fps_sorted = self.generate_sorted_list(fps) + for af in fps_sorted: + name = os.path.split(af[0])[1] + f.write('') + f.write('
    Filename Last commit date Included in:
  • ' + af[0] + '
  • ' + af[1] + '' + fps_usage[name] + '
    ') + + # Generate table with board files that are not in any config + if show_boards: + f.write('

    Board makefiles that are not tested by any conf:

    ') + f.write('') + bs_sorted = self.generate_sorted_list(bs) + for b in bs_sorted: + f.write('') + f.write('
    Filename Last commit date
  • ' + b[0] + '
  • ' + b[1] + '
    ') + + # Generate table with all modules and module usage + if show_modules: + f.write('

    Module xml:

    ') + f.write('This section lists all module xml files (first column),
    ' + 'checks how often they are called in airframe xml files (second column)
    ' + 'and checks if all files mentioned in the module xml exist in the sw directory ' + 'and when they were last modified (third column)') + f.write('') + for name, mod in mods.items(): + f.write('') + f.write('
    Filename Number of airframes used in Comments
  • ' + mod.name + '
  • ' + + str(mod.usage) + '' + + mod.get_comments() + '
    ') # Constructor Functions def __init__(self, verbose): self.exclude_backups = 1 self.verbose = verbose - def run(self): - # find all airframe XML's - afs = self.find_airframe_files() - fps = self.find_flightplan_files() - #brds = self.find_boards() - # write all confs - with open('var/paparazzi.html','w') as f: - f.write('\n\n\nPaparazzi\n\n\n') - f.write('\n\n\n') + def run(self, show_af_detail=True, show_untested=False, + show_airframes=False, show_flightplans=False, show_boards=False, show_modules=False): + with open('var/paparazzi.html', 'w') as f: + f.write('\n\n\n' + 'Paparazzi\n' + '\n' + '\n') + f.write('\n\n\n') f.write('\n') - conf_files = paparazzi.get_list_of_conf_files() - for conf in conf_files: - airframes = self.list_airframes_in_conf(conf) - f.write('

    ' + conf + '

    ') - for ac in airframes: - f.write('

    ' + ac.name + ' (' + ac.ac_id + ')

    ') - sha = ac.release - xml = ac.xml - name = ac.name - # remove airframe xml from list - if xml in afs: - afs.remove(xml) - if ac.flight_plan in fps: - fps.remove(ac.flight_plan) - if (not sha is None) and (not sha == ""): - f.write('

    Last flown with ' + sha[:6] + '...

    ') - behind = self.git_behind(sha) - color = 'orange' - if behind < 200: - color = 'green' - if behind > 2000: - color = 'red' - if behind > 0: - f.write( '

    Is ' + str(behind) + ' commits behind

    ') - else: - f.write( '

    Is not available on this machine

    ') - outside = self.git_ahead(sha) - if outside > 0: - f.write( '

    Using ' + str(outside) + ' commits not in master

    ') - af = self.airframe_details(xml) - f.write('

    ' + ", ".join(af.firmware) + ' on ' + ", ".join(af.board) + ' [E]

    ') - f.write('

    ' + self.maximize_text_size(af.description) + '

    ') - if self.verbose: - f.write('

    ' + ac.xml + '

    ') - if self.verbose: - f.write('

    ' + ac.flight_plan + '

    ') - #fp_inc = self.flightplan_includes(ac.flight_plan) - if len(af.includes) > 0: - for i in af.includes: - inc_name = i[5:].replace('$AC_ID',ac.ac_id) - if inc_name in afs: - afs.remove(inc_name) + if show_af_detail: + conf_files = paparazzi.get_list_of_conf_files() + for conf in conf_files: + airframes = self.list_airframes_in_conf(conf) + f.write('

    ' + conf + '

    ') + for ac in airframes: + f.write('

    ' + ac.name + ' (' + ac.ac_id + ')

    ') + sha = ac.release + xml = ac.xml + if (sha is not None) and (not sha == ""): + f.write('

    Last flown with ' + sha[:6] + '...

    ') + behind = self.git_behind(sha) + color = 'orange' + if behind < 200: + color = 'green' + if behind > 2000: + color = 'red' + if behind > 0: + f.write('

    Is ' + str(behind) + + ' commits behind

    ') + else: + f.write('

    Is not available on this machine

    ') + outside = self.git_ahead(sha) + if outside > 0: + f.write('

    Using ' + str(outside) + + ' commits not in master

    ') + af = self.airframe_details(xml) + f.write('

    ' + ", ".join(af.firmware) + ' on ' + ", ".join(af.boards) + + ' [E]

    ') + f.write('

    ' + + self.maximize_text_size(af.description) + '

    ') if self.verbose: - f.write('

    Includes: ' + ", ".join(af.includes) + '

    ') - f.write('
    \n\n') - f.write('
    \n') - f.write('

    Airframe xml that are not tested by any conf:

    ') - for af in afs: - f.write('
  • ' + af) - f.write('
  • Flight_plan xml that are not tested by any conf:

    ') - for af in fps: - f.write('
  • ' + af) + f.write('

    ' + ac.xml + '

    ') + if self.verbose: + f.write('

    ' + ac.flight_plan + '

    ') + if len(af.includes) > 0: + if self.verbose: + f.write('

    Includes: ' + ", ".join(af.includes) + '

    ') + f.write('
  • \n\n') + f.write('
    \n') + + if show_untested: + self.not_tested_html(f, show_airframes, show_flightplans, show_boards, show_modules) f.write('
    \n\n') webbrowser.open('file://' + os.path.realpath('./var/paparazzi.html')) + if __name__ == "__main__": import sys brief = 0 @@ -239,4 +607,3 @@ if __name__ == "__main__": obj = PaparazziOverview(brief) obj.run() -