Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.
Path: blob/master/Tools/scripts/build_binaries.py
Views: 1798
#!/usr/bin/env python312"""3script to build the latest binaries for each vehicle type, ready to upload4Peter Barker, August 20175based on build_binaries.sh by Andrew Tridgell, March 201367AP_FLAKE8_CLEAN8"""910from __future__ import print_function1112import datetime13import optparse14import os15import re16import shutil17import time18import string19import subprocess20import sys21import traceback2223# local imports24import generate_manifest25import gen_stable26import build_binaries_history2728import board_list29from board_list import AP_PERIPH_BOARDS3031if sys.version_info[0] < 3:32running_python3 = False33else:34running_python3 = True353637def topdir():38'''return path to ardupilot checkout directory. This is to cope with39running on developer's machines (where autotest is typically40invoked from the root directory), and on the autotest server where41it is invoked in the checkout's parent directory.42'''43for path in [44os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", ".."),45"",46]:47if os.path.exists(os.path.join(path, "libraries", "AP_HAL_ChibiOS")):48return path49raise Exception("Unable to find ardupilot checkout dir")505152def is_chibios_build(board):53'''see if a board is using HAL_ChibiOS'''54# cope with both running from Tools/scripts or running from cwd55hwdef_dir = os.path.join(topdir(), "libraries", "AP_HAL_ChibiOS", "hwdef")5657return os.path.exists(os.path.join(hwdef_dir, board, "hwdef.dat"))585960def get_required_compiler(vehicle, tag, board):61'''return required compiler for a build tag.62return format is the version string that waf configure will detect.63You should setup a link from this name in $HOME/arm-gcc directory pointing at the64appropriate compiler65'''66if not is_chibios_build(board):67# only override compiler for ChibiOS builds68return None69# use 10.2.1 compiler for all other builds70return "g++-10.2.1"717273class build_binaries(object):74def __init__(self, tags):75self.tags = tags76self.dirty = False77self.board_list = board_list.BoardList()7879def progress(self, string):80'''pretty-print progress'''81print("BB: %s" % string)8283def run_git(self, args):84'''run git with args git_args; returns git's output'''85cmd_list = ["git"]86cmd_list.extend(args)87return self.run_program("BB-GIT", cmd_list)8889def board_branch_bit(self, board):90'''return a fragment which might modify the branch name.91this was previously used to have a master-AVR branch etc92if the board type was apm1 or apm2'''93return None9495def board_options(self, board):96'''return board-specific options'''97if board in ["bebop", "disco"]:98return ["--static"]99return []100101def run_waf(self, args, compiler=None):102if os.path.exists("waf"):103waf = "./waf"104else:105waf = os.path.join(".", "modules", "waf", "waf-light")106cmd_list = ["python3", waf]107cmd_list.extend(args)108env = None109if compiler is not None:110# default to $HOME/arm-gcc, but allow for any path with AP_GCC_HOME environment variable111gcc_home = os.environ.get("AP_GCC_HOME", os.path.join(os.environ["HOME"], "arm-gcc"))112gcc_path = os.path.join(gcc_home, compiler, "bin")113if os.path.exists(gcc_path):114# setup PATH to point at the right compiler, and setup to use ccache115env = os.environ.copy()116env["PATH"] = gcc_path + ":" + env["PATH"]117env["CC"] = "ccache arm-none-eabi-gcc"118env["CXX"] = "ccache arm-none-eabi-g++"119else:120raise Exception("BB-WAF: Missing compiler %s" % gcc_path)121self.run_program("BB-WAF", cmd_list, env=env)122123def run_program(self, prefix, cmd_list, show_output=True, env=None, force_success=False):124if show_output:125self.progress("Running (%s)" % " ".join(cmd_list))126p = subprocess.Popen(cmd_list, stdin=None,127stdout=subprocess.PIPE, close_fds=True,128stderr=subprocess.STDOUT, env=env)129output = ""130while True:131x = p.stdout.readline()132if len(x) == 0:133returncode = os.waitpid(p.pid, 0)134if returncode:135break136# select not available on Windows... probably...137time.sleep(0.1)138continue139if running_python3:140x = bytearray(x)141x = filter(lambda x : chr(x) in string.printable, x)142x = "".join([chr(c) for c in x])143output += x144x = x.rstrip()145if show_output:146print("%s: %s" % (prefix, x))147(_, status) = returncode148if status != 0 and not force_success:149self.progress("Process failed (%s)" %150str(returncode))151raise subprocess.CalledProcessError(152returncode, cmd_list)153return output154155def run_make(self, args):156cmd_list = ["make"]157cmd_list.extend(args)158self.run_program("BB-MAKE", cmd_list)159160def run_git_update_submodules(self):161'''if submodules are present initialise and update them'''162if os.path.exists(os.path.join(self.basedir, ".gitmodules")):163self.run_git(["submodule",164"update",165"--init",166"--recursive",167"-f"])168169def checkout(self, vehicle, ctag, cboard=None, cframe=None, submodule_update=True):170'''attempt to check out a git tree. Various permutations are171attempted based on ctag - for examplle, if the board is avr and ctag172is bob we will attempt to checkout bob-AVR'''173if self.dirty:174self.progress("Skipping checkout for dirty build")175return True176177self.progress("Trying checkout %s %s %s %s" %178(vehicle, ctag, cboard, cframe))179self.run_git(['stash'])180if ctag == "latest":181vtag = "master"182else:183tagvehicle = vehicle184if tagvehicle == "Rover":185# FIXME: Rover tags in git still named APMrover2 :-(186tagvehicle = "APMrover2"187vtag = "%s-%s" % (tagvehicle, ctag)188189branches = []190if cframe is not None:191# try frame specific tag192branches.append("%s-%s" % (vtag, cframe))193if cboard is not None:194bbb = self.board_branch_bit(cboard)195if bbb is not None:196# try board type specific branch extension197branches.append("".join([vtag, bbb]))198branches.append(vtag)199200for branch in branches:201try:202self.progress("Trying branch %s" % branch)203self.run_git(["checkout", "-f", branch])204if submodule_update:205self.run_git_update_submodules()206self.run_git(["log", "-1"])207return True208except subprocess.CalledProcessError:209self.progress("Checkout branch %s failed" % branch)210211self.progress("Failed to find tag for %s %s %s %s" %212(vehicle, ctag, cboard, cframe))213return False214215def skip_board_waf(self, board):216'''check if we should skip this build because we do not support the217board in this release218'''219220try:221out = self.run_program(222'waf',223["python3", './waf', 'configure', '--board=BOARDTEST'],224show_output=False,225force_success=True226)227lines = out.split('\n')228needles = ["BOARDTEST' (choose from", "BOARDTEST': choices are"]229for line in lines:230for needle in needles:231idx = line.find(needle)232if idx != -1:233break234if idx != -1:235line = line[idx+len(needle):-1]236line = line.replace("'", "")237line = line.replace(" ", "")238boards = line.split(",")239ret = board not in boards240if ret:241self.progress("Skipping board (%s) - not in board list" % board)242return ret243except IOError as e:244if e.errno != 2:245raise246247self.progress("Skipping unsupported board %s" % (board,))248return True249250def skip_frame(self, board, frame):251'''returns true if this board/frame combination should not be built'''252if frame == "heli":253if board in ["bebop", "aerofc-v1", "skyviper-v2450", "CubeSolo", "CubeGreen-solo", 'skyviper-journey']:254self.progress("Skipping heli build for %s" % board)255return True256return False257258def first_line_of_filepath(self, filepath):259'''returns the first (text) line from filepath'''260with open(filepath) as fh:261line = fh.readline()262return line263264def skip_build(self, buildtag, builddir):265'''check if we should skip this build because we have already built266this version267'''268269if os.getenv("FORCE_BUILD", False):270return False271272if not os.path.exists(os.path.join(self.basedir, '.gitmodules')):273self.progress("Skipping build without submodules")274return True275276bname = os.path.basename(builddir)277ldir = os.path.join(os.path.dirname(os.path.dirname(278os.path.dirname(builddir))), buildtag, bname) # FIXME: WTF279280oldversion_filepath = os.path.join(ldir, "git-version.txt")281if not os.path.exists(oldversion_filepath):282self.progress("%s doesn't exist - building" % oldversion_filepath)283return False284285oldversion = self.first_line_of_filepath(oldversion_filepath)286newversion = self.run_git(["log", "-1"])287newversion = newversion.splitlines()[0]288oldversion = oldversion.rstrip()289newversion = newversion.rstrip()290self.progress("oldversion=%s newversion=%s" %291(oldversion, newversion,))292if oldversion == newversion:293self.progress("Skipping build - version match (%s)" %294(newversion,))295return True296297self.progress("%s needs rebuild" % (ldir,))298return False299300def write_string_to_filepath(self, string, filepath):301'''writes the entirety of string to filepath'''302with open(filepath, "w") as x:303x.write(string)304305def version_h_path(self, src):306'''return path to version.h'''307if src == 'AP_Periph':308return os.path.join('Tools', src, "version.h")309return os.path.join(src, "version.h")310311def addfwversion_gitversion(self, destdir, src):312# create git-version.txt:313gitlog = self.run_git(["log", "-1"])314gitversion_filepath = os.path.join(destdir, "git-version.txt")315gitversion_content = gitlog316versionfile = self.version_h_path(src)317if os.path.exists(versionfile):318content = self.read_string_from_filepath(versionfile)319match = re.search('define.THISFIRMWARE "([^"]+)"', content)320if match is None:321self.progress("Failed to retrieve THISFIRMWARE from version.h")322self.progress("Content: (%s)" % content)323self.progress("Writing version info to %s" %324(gitversion_filepath,))325gitversion_content += "\nAPMVERSION: %s\n" % (match.group(1))326else:327self.progress("%s does not exist" % versionfile)328329self.write_string_to_filepath(gitversion_content, gitversion_filepath)330331def addfwversion_firmwareversiontxt(self, destdir, src):332# create firmware-version.txt333versionfile = self.version_h_path(src)334if not os.path.exists(versionfile):335self.progress("%s does not exist" % (versionfile,))336return337ss = r".*define +FIRMWARE_VERSION[ ]+(?P<major>\d+)[ ]*,[ ]*" \338r"(?P<minor>\d+)[ ]*,[ ]*(?P<point>\d+)[ ]*,[ ]*" \339r"(?P<type>[A-Z_]+)[ ]*"340content = self.read_string_from_filepath(versionfile)341match = re.search(ss, content)342if match is None:343self.progress("Failed to retrieve FIRMWARE_VERSION from version.h")344self.progress("Content: (%s)" % content)345return346ver = "%d.%d.%d-%s\n" % (int(match.group("major")),347int(match.group("minor")),348int(match.group("point")),349match.group("type"))350firmware_version_filepath = "firmware-version.txt"351self.progress("Writing version (%s) to %s" %352(ver, firmware_version_filepath,))353self.write_string_to_filepath(354ver, os.path.join(destdir, firmware_version_filepath))355356def addfwversion(self, destdir, src):357'''write version information into destdir'''358self.addfwversion_gitversion(destdir, src)359self.addfwversion_firmwareversiontxt(destdir, src)360361def read_string_from_filepath(self, filepath):362'''returns content of filepath as a string'''363with open(filepath, 'rb') as fh:364content = fh.read()365366if running_python3:367return content.decode('ascii')368369return content370371def string_in_filepath(self, string, filepath):372'''returns true if string exists in the contents of filepath'''373return string in self.read_string_from_filepath(filepath)374375def mkpath(self, path):376'''make directory path and all elements leading to it'''377'''distutils.dir_util.mkpath was playing up'''378try:379os.makedirs(path)380except OSError as e:381if e.errno != 17: # EEXIST382raise e383384def touch_filepath(self, filepath):385'''creates a file at filepath, or updates the timestamp on filepath'''386if os.path.exists(filepath):387os.utime(filepath, None)388else:389with open(filepath, "a"):390pass391392def build_vehicle(self, tag, vehicle, boards, vehicle_binaries_subdir,393binaryname, frames=[None]):394'''build vehicle binaries'''395self.progress("Building %s %s binaries (cwd=%s)" %396(vehicle, tag, os.getcwd()))397398board_count = len(boards)399count = 0400for board in sorted(boards, key=str.lower):401now = datetime.datetime.now()402count += 1403self.progress("[%u/%u] Building board: %s at %s" %404(count, board_count, board, str(now)))405for frame in frames:406if frame is not None:407self.progress("Considering frame %s for board %s" %408(frame, board))409if frame is None:410framesuffix = ""411else:412framesuffix = "-%s" % frame413if not self.checkout(vehicle, tag, board, frame, submodule_update=False):414msg = ("Failed checkout of %s %s %s %s" %415(vehicle, board, tag, frame,))416self.progress(msg)417self.error_strings.append(msg)418continue419420self.progress("Building %s %s %s binaries %s" %421(vehicle, tag, board, frame))422ddir = os.path.join(self.binaries,423vehicle_binaries_subdir,424self.hdate_ym,425self.hdate_ymdhm,426"".join([board, framesuffix]))427if self.skip_build(tag, ddir):428continue429if self.skip_frame(board, frame):430continue431432# we do the submodule update after the skip_board_waf check to avoid doing it on433# builds we will not be running434self.run_git_update_submodules()435436if self.skip_board_waf(board):437continue438439if os.path.exists(self.buildroot):440shutil.rmtree(self.buildroot)441442self.remove_tmpdir()443444githash = self.run_git(["rev-parse", "HEAD"]).rstrip()445446t0 = time.time()447448self.progress("Configuring for %s in %s" %449(board, self.buildroot))450try:451waf_opts = ["configure",452"--board", board,453"--out", self.buildroot,454"clean"]455gccstring = get_required_compiler(vehicle, tag, board)456if gccstring is not None and gccstring.find("g++-6.3") == -1:457# versions using the old compiler don't have the --assert-cc-version option458waf_opts += ["--assert-cc-version", gccstring]459460waf_opts.extend(self.board_options(board))461self.run_waf(waf_opts, compiler=gccstring)462except subprocess.CalledProcessError:463self.progress("waf configure failed")464continue465466time_taken_to_configure = time.time() - t0467468try:469target = os.path.join("bin",470"".join([binaryname, framesuffix]))471self.run_waf(["build", "--targets", target], compiler=gccstring)472except subprocess.CalledProcessError:473msg = ("Failed build of %s %s%s %s" %474(vehicle, board, framesuffix, tag))475self.progress(msg)476self.error_strings.append(msg)477# record some history about this build478t1 = time.time()479time_taken_to_build = t1-t0480self.history.record_build(githash, tag, vehicle, board, frame, None, t0, time_taken_to_build)481continue482483time_taken_to_build = (time.time()-t0) - time_taken_to_configure484485time_taken = time.time()-t0486self.progress("Making %s %s %s %s took %u seconds (configure=%u build=%u)" %487(vehicle, tag, board, frame, time_taken, time_taken_to_configure, time_taken_to_build))488489bare_path = os.path.join(self.buildroot,490board,491"bin",492"".join([binaryname, framesuffix]))493files_to_copy = []494extensions = [".apj", ".abin", "_with_bl.hex", ".hex"]495if vehicle == 'AP_Periph' or board == "Here4FC":496# need bin file for uavcan-gui-tool and MissionPlanner497extensions.append('.bin')498for extension in extensions:499filepath = "".join([bare_path, extension])500if os.path.exists(filepath):501files_to_copy.append((filepath, os.path.basename(filepath)))502if not os.path.exists(bare_path):503raise Exception("No elf file?!")504505# attempt to run an extract_features.py to create features.txt:506features_text = None507ef_path = os.path.join(topdir(), "Tools", "scripts", "extract_features.py")508if os.path.exists(ef_path):509try:510features_text = self.run_program("EF", [ef_path, bare_path], show_output=False)511except Exception as e:512self.print_exception_caught(e)513self.progress("Failed to extract features")514pass515else:516self.progress("Not extracting features as (%s) does not exist" % (ef_path,))517518# only rename the elf if we have have other files to519# copy. So linux gets "arducopter" and stm32 gets520# "arducopter.elf"521target_elf_filename = os.path.basename(bare_path)522if len(files_to_copy) > 0:523target_elf_filename += ".elf"524files_to_copy.append((bare_path, target_elf_filename))525526for (path, target_filename) in files_to_copy:527try:528'''copy path into various places, adding metadata'''529bname = os.path.basename(ddir)530tdir = os.path.join(os.path.dirname(os.path.dirname(531os.path.dirname(ddir))), tag, bname)532if tag == "latest":533# we keep a permanent archive of all534# "latest" builds, their path including a535# build timestamp:536if not os.path.exists(ddir):537self.mkpath(ddir)538self.addfwversion(ddir, vehicle)539features_filepath = os.path.join(ddir, "features.txt",)540if features_text is not None:541self.progress("Writing (%s)" % features_filepath)542self.write_string_to_filepath(features_text, features_filepath)543self.progress("Copying %s to %s" % (path, ddir,))544shutil.copy(path, os.path.join(ddir, target_filename))545# the most recent build of every tag is kept around:546self.progress("Copying %s to %s" % (path, tdir))547if not os.path.exists(tdir):548self.mkpath(tdir)549# must addfwversion even if path already550# exists as we re-use the "beta" directories551self.addfwversion(tdir, vehicle)552features_filepath = os.path.join(tdir, "features.txt")553if features_text is not None:554self.progress("Writing (%s)" % features_filepath)555self.write_string_to_filepath(features_text, features_filepath)556shutil.copy(path, os.path.join(tdir, target_filename))557except Exception as e:558self.print_exception_caught(e)559self.progress("Failed to copy %s to %s: %s" % (path, tdir, str(e)))560# why is touching this important? -pb20170816561self.touch_filepath(os.path.join(self.binaries,562vehicle_binaries_subdir, tag))563564# record some history about this build565self.history.record_build(githash, tag, vehicle, board, frame, bare_path, t0, time_taken_to_build)566567self.checkout(vehicle, "latest")568569def _get_exception_stacktrace(self, e):570if sys.version_info[0] >= 3:571ret = "%s\n" % e572ret += ''.join(traceback.format_exception(type(e),573e,574tb=e.__traceback__))575return ret576577# Python2:578return traceback.format_exc(e)579580def get_exception_stacktrace(self, e):581try:582return self._get_exception_stacktrace(e)583except Exception:584return "FAILED TO GET EXCEPTION STACKTRACE"585586def print_exception_caught(self, e, send_statustext=True):587self.progress("Exception caught: %s" %588self.get_exception_stacktrace(e))589590def AP_Periph_boards(self):591return AP_PERIPH_BOARDS592593def build_arducopter(self, tag):594'''build Copter binaries'''595596boards = []597boards.extend(["aerofc-v1", "bebop"])598boards.extend(self.board_list.find_autobuild_boards('Copter'))599self.build_vehicle(tag,600"ArduCopter",601boards,602"Copter",603"arducopter",604frames=[None, "heli"])605606def build_arduplane(self, tag):607'''build Plane binaries'''608boards = self.board_list.find_autobuild_boards('Plane')[:]609boards.append("disco")610self.build_vehicle(tag,611"ArduPlane",612boards,613"Plane",614"arduplane")615616def build_antennatracker(self, tag):617'''build Tracker binaries'''618self.build_vehicle(tag,619"AntennaTracker",620self.board_list.find_autobuild_boards('Tracker')[:],621"AntennaTracker",622"antennatracker")623624def build_rover(self, tag):625'''build Rover binaries'''626self.build_vehicle(tag,627"Rover",628self.board_list.find_autobuild_boards('Rover')[:],629"Rover",630"ardurover")631632def build_ardusub(self, tag):633'''build Sub binaries'''634self.build_vehicle(tag,635"ArduSub",636self.board_list.find_autobuild_boards('Sub')[:],637"Sub",638"ardusub")639640def build_AP_Periph(self, tag):641'''build AP_Periph binaries'''642boards = self.AP_Periph_boards()643self.build_vehicle(tag,644"AP_Periph",645boards,646"AP_Periph",647"AP_Periph")648649def build_blimp(self, tag):650'''build Blimp binaries'''651self.build_vehicle(tag,652"Blimp",653self.board_list.find_autobuild_boards('Blimp')[:],654"Blimp",655"blimp")656657def generate_manifest(self):658'''generate manigest files for GCS to download'''659self.progress("Generating manifest")660base_url = 'https://firmware.ardupilot.org'661generator = generate_manifest.ManifestGenerator(self.binaries,662base_url)663generator.run()664665generator.write_manifest_json(os.path.join(self.binaries, "manifest.json"))666generator.write_features_json(os.path.join(self.binaries, "features.json"))667self.progress("Manifest generation successful")668669self.progress("Generating stable releases")670gen_stable.make_all_stable(self.binaries)671self.progress("Generate stable releases done")672673def validate(self):674'''run pre-run validation checks'''675if "dirty" in self.tags:676if len(self.tags) > 1:677raise ValueError("dirty must be only tag if present (%s)" %678(str(self.tags)))679self.dirty = True680681def remove_tmpdir(self):682if os.path.exists(self.tmpdir):683self.progress("Removing (%s)" % (self.tmpdir,))684shutil.rmtree(self.tmpdir)685686def buildlogs_dirpath(self):687return os.getenv("BUILDLOGS",688os.path.join(os.getcwd(), "..", "buildlogs"))689690def run(self):691self.validate()692693self.mkpath(self.buildlogs_dirpath())694695binaries_history_filepath = os.path.join(696self.buildlogs_dirpath(), "build_binaries_history.sqlite")697self.history = build_binaries_history.BuildBinariesHistory(binaries_history_filepath)698699prefix_bin_dirpath = os.path.join(os.environ.get('HOME'),700"prefix", "bin")701origin_env_path = os.environ.get("PATH")702os.environ["PATH"] = ':'.join([prefix_bin_dirpath, origin_env_path,703"/bin", "/usr/bin"])704if 'BUILD_BINARIES_PATH' in os.environ:705self.tmpdir = os.environ['BUILD_BINARIES_PATH']706else:707self.tmpdir = os.path.join(os.getcwd(), 'build.tmp.binaries')708os.environ["TMPDIR"] = self.tmpdir709710print(self.tmpdir)711self.remove_tmpdir()712713self.progress("Building in %s" % self.tmpdir)714715now = datetime.datetime.now()716self.progress(now)717718if not self.dirty:719self.run_git(["checkout", "-f", "master"])720githash = self.run_git(["rev-parse", "HEAD"])721githash = githash.rstrip()722self.progress("git hash: %s" % str(githash))723724self.hdate_ym = now.strftime("%Y-%m")725self.hdate_ymdhm = now.strftime("%Y-%m-%d-%H:%m")726727self.mkpath(os.path.join("binaries", self.hdate_ym,728self.hdate_ymdhm))729self.binaries = os.path.join(self.buildlogs_dirpath(), "binaries")730self.basedir = os.getcwd()731self.error_strings = []732733if not self.dirty:734self.run_git_update_submodules()735self.buildroot = os.path.join(os.environ.get("TMPDIR"),736"binaries.build")737738for tag in self.tags:739t0 = time.time()740self.build_arducopter(tag)741self.build_arduplane(tag)742self.build_rover(tag)743self.build_antennatracker(tag)744self.build_ardusub(tag)745self.build_AP_Periph(tag)746self.build_blimp(tag)747self.history.record_run(githash, tag, t0, time.time()-t0)748749if os.path.exists(self.tmpdir):750shutil.rmtree(self.tmpdir)751752self.generate_manifest()753754for error_string in self.error_strings:755self.progress("%s" % error_string)756sys.exit(len(self.error_strings))757758759if __name__ == '__main__':760parser = optparse.OptionParser("build_binaries.py")761762parser.add_option("", "--tags", action="append", type="string",763default=[], help="tags to build")764cmd_opts, cmd_args = parser.parse_args()765766tags = cmd_opts.tags767if len(tags) == 0:768# FIXME: wedge this defaulting into parser somehow769tags = ["stable", "beta-4.3", "beta", "latest"]770771bb = build_binaries(tags)772bb.run()773774775