From 6ae0b632be5bdfaadaa0873451848f549bc60fa8 Mon Sep 17 00:00:00 2001 From: Christophe De Wagter Date: Thu, 19 Oct 2017 18:20:08 +0200 Subject: [PATCH] [SHA] Store the SHA of a successful airframe test --- conf/airframes/TUDELFT/tudelft_conf.xml | 15 +- report.py | 246 ++++++++++++++++++++++++ start.py | 43 ++++- sw/supervision/paparazzicenter.glade | 98 ++++++++++ sw/supervision/pc_aircraft.ml | 67 ++++++- 5 files changed, 463 insertions(+), 6 deletions(-) create mode 100755 report.py diff --git a/conf/airframes/TUDELFT/tudelft_conf.xml b/conf/airframes/TUDELFT/tudelft_conf.xml index 92b4f4cda2d..0119ff072b7 100644 --- a/conf/airframes/TUDELFT/tudelft_conf.xml +++ b/conf/airframes/TUDELFT/tudelft_conf.xml @@ -152,6 +152,7 @@ settings="settings/rotorcraft_basic.xml" settings_modules="modules/gps_ubx_ucenter.xml modules/logger_sd_spi_direct.xml modules/temp_adc.xml modules/air_data.xml modules/geo_mag.xml modules/opa_controller.xml modules/ahrs_int_cmpl_quat.xml modules/stabilization_rate.xml modules/stabilization_int_quat.xml modules/nav_basic_rotorcraft.xml modules/guidance_rotorcraft.xml modules/gps.xml modules/imu_common.xml" gui_color="#ffffdffac31f" + release="dc8a90d35290784bcc817edff08d8da2df64bcef" /> diff --git a/report.py b/report.py new file mode 100755 index 00000000000..698a96607e9 --- /dev/null +++ b/report.py @@ -0,0 +1,246 @@ +#!/usr/bin/env python + +from __future__ import print_function + +import webbrowser +import os +import shutil +import datetime +from fnmatch import fnmatch +import subprocess +PIPE = subprocess.PIPE + + + +import xml.etree.ElementTree + +class Airframe: + name = "" + id = "" + xml = "" + flight_plan = "" + release = "" + def __init__(self): + name = "" + ac_id = "" + xml = "" + flight_plan = "" + release = "" + +class AirframeFile: + name = "" + firmware = [] + boards = [] + xml = "" + includes = [] + def __init__(self): + name = "" + firmware = [] + boards = [] + xml = "" + includes = [] + +class PaparazziOverview(object): + + def RepresentsInt(self, s): + try: + v=int(s) + return v + except ValueError: + return -1 + + def git_behind(self, sha): + process = subprocess.Popen(['git', 'rev-list', sha+"..HEAD", '--count'], stdout=PIPE, stderr=PIPE) + stdoutput, stderroutput = process.communicate() + return self.RepresentsInt(stdoutput) + + def git_ahead(self, sha): + process = subprocess.Popen(['git', 'rev-list', "HEAD.."+sha, '--count'], stdout=PIPE, stderr=PIPE) + stdoutput, stderroutput = process.communicate() + return self.RepresentsInt(stdoutput) + + def gif_last_commit(self, file): + process = subprocess.Popen(['git', 'log', '-n', 1, '--pretty=format:%H', '--', sha+"..HEAD"], stdout=PIPE, stderr=PIPE) + stdoutput, stderroutput = process.communicate() + return stdoutput + + def find_conf_files(self): + conf_files = [] + pattern = "*conf[._-]*xml" + backup_pattern = "*conf[._-]*xml.20[0-9][0-9]-[01][0-9]-[0-3][0-9]_*" + excludes = ["%gconf.xml"] + + for path, subdirs, files in os.walk(self.conf_dir): + for name in files: + if self.exclude_backups and fnmatch(name, backup_pattern): + continue + if fnmatch(name, pattern): + filepath = os.path.join(path, name) + entry = os.path.relpath(filepath, self.conf_dir) + if not os.path.islink(filepath) and entry not in excludes: + conf_files.append(entry) + conf_files.sort() + return conf_files + + def find_xml_files(self, folder): + airframe_files = [] + pattern = "*.xml" + confn = "*conf[._-]*xml" + controlpanel = "*control_panel[._-]*xml" + + for path, subdirs, files in os.walk(os.path.join(self.conf_dir,folder)): + for name in files: + if fnmatch(name, confn): + continue + if fnmatch(name, controlpanel): + continue + if fnmatch(name, pattern): + filepath = os.path.join(path, name) + entry = os.path.relpath(filepath, self.conf_dir) + airframe_files.append(entry) + airframe_files.sort() + return airframe_files + + 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): + if conf is None: + return [] + list_of_airframes = [] + + afile = os.path.join(self.conf_dir, conf) + if os.path.exists(afile): + e = xml.etree.ElementTree.parse(afile).getroot() + for atype in e.findall('aircraft'): + release = "" + if (not atype.get('release') is None) & (not 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 + list_of_airframes.append(af) + return list_of_airframes + + def airframe_details(self, xmlname): + airframe = AirframeFile() + airframe.xml = xmlname + airframe.firmware = [] + airframe.includes = [] + airframe.board = [] + if xml is None: + return airframe + afile = os.path.join(self.conf_dir, xmlname) + if os.path.exists(afile): + 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): + 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') ) + for atype in e.findall('include'): + if (not atype.get('href') is None) & (not atype.get('href') == ""): + airframe.includes.append( atype.get('href') ) + return airframe + + def flightplan_includes(self, xmlname): + includes = [] + print(xmlname) + if xml is None: + return includes + afile = os.path.join(self.conf_dir, xmlname) + 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') ) + return includes + + + # Constructor Functions + def __init__(self, verbose): + # if PAPARAZZI_HOME not set, then assume the tree containing this + # file is a reasonable substitute + self.paparazzi_home = os.getenv("PAPARAZZI_HOME", os.path.dirname(os.path.abspath(__file__))) + self.conf_dir = os.path.join(self.paparazzi_home, "conf") + 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('paparazzi.html','w') as f: + f.write('\n\n\nPaparazzi\n\n\n') + f.write('\n\n\n') + f.write('\n') + conf_files = self.find_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' + f.write( '

Is ' + str(behind) + ' commits behind

') + 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) + '

') + 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 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('
  • \n\n') + +if __name__ == "__main__": + import sys + brief = 0 + if len(sys.argv) > 1: + brief = 1 + + obj = PaparazziOverview(brief) + obj.run() + webbrowser.open('file://' + os.path.realpath('./paparazzi.html')) + diff --git a/start.py b/start.py index 923a2b4ca0e..2e5963cf787 100755 --- a/start.py +++ b/start.py @@ -13,6 +13,7 @@ from fnmatch import fnmatch import subprocess +import xml.etree.ElementTree class ConfChooser(object): @@ -97,6 +98,29 @@ def find_controlpanel_files(self): controlpanel_files.sort() self.update_combo(self.controlpanel_file_combo, controlpanel_files, self.controlpanel_xml) + def count_airframes_in_conf(self): + airframes = 0 + releases = 0 + if self.conf_file_combo.get_active_text() is None: + return + desc = "" + afile = os.path.join(self.conf_dir, self.conf_file_combo.get_active_text()) + if os.path.exists(afile): + e = xml.etree.ElementTree.parse(afile).getroot() + for atype in e.findall('aircraft'): + if airframes > 0: + desc += ", " + print(atype.get('name')) + airframes += 1 + if (not atype.get('release') is None) & (not atype.get('release') == ""): + releases += 1 + desc += '' + atype.get('name') + "" + else: + desc += atype.get('name') + desc = "" + str(airframes) + " airframes: " + desc + self.conf_airframes.set_markup(desc) + return + def about(self, widget): about_d = gtk.AboutDialog() about_d.set_program_name("Paparazzi Configuration Selector") @@ -179,6 +203,7 @@ def delete_conf(self, widget): if os.path.exists(filename): os.remove(filename) self.update_conf_label() + self.count_airframes_in_conf() self.find_conf_files() self.print_status("Deleted: " + filename) @@ -202,6 +227,7 @@ def accept(self, widget): os.remove(self.conf_xml) os.symlink(selected, self.conf_xml) self.update_conf_label() + self.count_airframes_in_conf() self.find_conf_files() selected = self.controlpanel_file_combo.get_active_text() @@ -225,6 +251,7 @@ def personal_conf(self, widget): 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() def personal_controlpanel(self, widget): @@ -242,6 +269,9 @@ def personal_controlpanel(self, widget): def print_status(self, text): self.statusbar.push(self.context_id, text) + def changed_cb(self, entry): + self.count_airframes_in_conf() + # Constructor Functions def __init__(self): # paparazzi process @@ -305,7 +335,7 @@ def __init__(self): self.conf_file_combo = gtk.combo_box_new_text() self.find_conf_files() - # self.firmwares_combo.connect("changed", self.parse_list_of_airframes) + self.conf_file_combo.connect("changed", self.changed_cb) self.conf_file_combo.set_size_request(550, 30) self.btnDeleteConf = gtk.Button(None, gtk.STOCK_DELETE) @@ -323,6 +353,17 @@ def __init__(self): self.confbar.pack_start(self.btnPersonalConf) self.my_vbox.pack_start(self.confbar, False) + # Count Airframes + self.conf_airframes = gtk.Label("") + self.count_airframes_in_conf() + self.conf_airframes.set_size_request(550,180) + self.conf_airframes.set_line_wrap(True) + + self.caexbar = gtk.HBox() + self.caexbar.pack_start(self.conf_airframes) + + self.my_vbox.pack_start(self.caexbar, False) + # Explain current conf config self.conf_explain = gtk.Label("") diff --git a/sw/supervision/paparazzicenter.glade b/sw/supervision/paparazzicenter.glade index 9babbec7c2f..543d72fbe33 100644 --- a/sw/supervision/paparazzicenter.glade +++ b/sw/supervision/paparazzicenter.glade @@ -1037,6 +1037,104 @@ 4 + + + + + True + False + 0 + + + True + False + 12 + + + True + False + + + True + False + _________________ + start + + + True + True + 0 + + + + + Compare + True + True + False + False + True + + + False + False + 1 + + + + + Checkout + True + True + False + False + True + + + False + False + 2 + + + + + Tag + True + True + False + False + True + + + False + False + 3 + + + + + + + + + True + False + <b>Release</b> + True + + + label_item + + + + + False + True + 5 + + + + diff --git a/sw/supervision/pc_aircraft.ml b/sw/supervision/pc_aircraft.ml index 2fe6ed5273a..74f515d94c5 100644 --- a/sw/supervision/pc_aircraft.ml +++ b/sw/supervision/pc_aircraft.ml @@ -45,7 +45,8 @@ let aircraft_sample = fun name ac_id -> "flight_plan", "flight_plans/basic.xml"; "settings", "settings/fixedwing_basic.xml"; "settings_modules", ""; - "gui_color", "blue" ], + "gui_color", "blue"; + "release", "" ], []) @@ -104,6 +105,32 @@ let gcs_or_edit = fun file -> | 2 -> ignore (Sys.command (sprintf "%s -edit '%s'&" gcs file)) | _ -> failwith "Internal error: gcs_or_edit" +let checkout_git_version = fun sha -> + (Sys.command (sprintf "git checkout '%s'" sha)); + () + + +let execute_cmd_and_return_text = fun cmd -> + let tmp_file = Filename.temp_file "" ".txt" in + let _ = Sys.command @@ cmd ^ " > " ^ tmp_file in + let chan = open_in tmp_file in + let s = input_line chan in + close_in chan; + s + +let tag_this_version = fun _ -> + (execute_cmd_and_return_text "git rev-parse HEAD") + +let get_commits_after_version = fun sha -> + (execute_cmd_and_return_text (sprintf "git rev-list %s..HEAD --count" sha)) + +let get_commits_outside_version = fun sha -> + (execute_cmd_and_return_text (sprintf "git rev-list HEAD..%s --count" sha)) + +let show_gitk_of_version = fun sha -> + GToolbox.message_box ~title:"Compare" ("There have been " ^ (get_commits_after_version sha ) ^ " commits since the last reported test.\n The last reported test used " ^ (get_commits_outside_version sha ) ^ " commits that are not in this branch."); + (execute_cmd_and_return_text (sprintf "gitk %s..HEAD & gitk HEAD..%s &" sha sha)) + type ac_data = Label of GMisc.label | Tree of Gtk_tools.tree @@ -154,7 +181,8 @@ let save_callback = fun ?user_save gui ac_combo tree tree_modules () -> "flight_plan", gui#label_flight_plan#text; "settings", Gtk_tools.tree_values ~only_checked:false tree; "settings_modules", Gtk_tools.tree_values ~only_checked:false tree_modules; - "gui_color", color ], + "gui_color", color; + "release", gui#label_release#text ], []) in begin try Hashtbl.remove Utils.aircrafts ac_name with _ -> () end; Hashtbl.add Utils.aircrafts ac_name aircraft @@ -295,7 +323,8 @@ let ac_combo_handler = fun gui (ac_combo:Gtk_tools.combo) target_combo flash_com "settings", "settings", Tree tree_set, Some gui#button_browse_settings, Some gui#button_edit_settings, edit, Some gui#button_remove_settings; "settings_modules", "settings", Tree tree_set_mod, None, None, (fun _ -> ()), None; "radio", "radios", Label gui#label_radio, Some gui#button_browse_radio, Some gui#button_edit_radio, edit, None; - "telemetry", "telemetry", Label gui#label_telemetry, Some gui#button_browse_telemetry, Some gui#button_edit_telemetry, edit, None] + "telemetry", "telemetry", Label gui#label_telemetry, Some gui#button_browse_telemetry, Some gui#button_edit_telemetry, edit, None; + "release", "release", Label gui#label_release, None, None, edit, None] in (* Update_params callback *) @@ -532,6 +561,38 @@ let ac_combo_handler = fun gui (ac_combo:Gtk_tools.combo) target_combo flash_com ) ac_files; + (* Tag Current Commit-Aircraft *) + let callback = fun _ -> + match GToolbox.question_box ~title:"Mark Test-flight Successfull" ~default:2 ~buttons:["Yes"; "Cancel"] "Are you sure you tested this airframe in all its modes (e.g. GPS) and confirm all works well." with + | 1 -> + begin + gui#label_release#set_text (tag_this_version () ); + save_callback gui ac_combo tree_set tree_set_mod (); + let ac_name = Gtk_tools.combo_value ac_combo in + update_params ac_name + + end + | _ -> () + in + ignore (gui#button_store_release#connect#clicked ~callback); + + (* Compare *) + let callback = fun _ -> + show_gitk_of_version gui#label_release#text; + () + in + ignore (gui#button_compare_release#connect#clicked ~callback); + + (* Checkout *) + let callback = fun _ -> + match GToolbox.question_box ~title:"Checkout code of last successfull flight" ~default:2 ~buttons:["Yes"; "Cancel"] "Are you sure you have no uncommited changes?" with + | 1 -> (checkout_git_version gui#label_release#text ) + | _ -> () + in + ignore (gui#button_checkout_release#connect#clicked ~callback); + + + (* Save button *) ignore(gui#menu_item_save_ac#connect#activate ~callback:(save_callback ~user_save:true gui ac_combo tree_set tree_set_mod))