Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
check_release.py
00001 # Script to check a new mbed 2 release by compiling a set of specified test apps 00002 # for all currently supported platforms. Each test app must include an mbed library. 00003 # This can either be the pre-compiled version 'mbed' or the source version 'mbed-dev'. 00004 # 00005 # Setup: 00006 # 1. Set up your global .hgrc file 00007 # 00008 # If you don't already have a .hgrc file in your $HOME directory, create one there. 00009 # Then add the following section: 00010 # 00011 # [auth] 00012 # x.prefix = * 00013 # x.username = <put your mbed org username here> 00014 # x.password = <put your mbed org password here> 00015 # 00016 # This has 2 purposes, the first means you don't get prompted for your password 00017 # whenever you run hg commands on the commandline. The second is that this script 00018 # reads these details in order to fully automate the Mercurial commands. 00019 # 00020 # Edit "check_release.json". This has the following structure: 00021 #{ 00022 # "config" : { 00023 # "mbed_repo_path" : "C:/Users/annbri01/Work/Mercurial" 00024 # }, 00025 # "test_list" : [ 00026 # { 00027 # "name" : "test_compile_mbed_lib", 00028 # "lib" : "mbed" 00029 # }, 00030 # { 00031 # "name" : "test_compile_mbed_dev", 00032 # "lib" : "mbed-dev" 00033 # } 00034 # ], 00035 # "target_list" : [] 00036 #} 00037 # 00038 # The mbed_repo_path field should be changed to point to where your local 00039 # working directory is for Mercurial repositories. 00040 # For each test app you wish to run, add an entry to the test list. The example 00041 # above has 2 test apps 00042 # "test_compile_mbed_lib" and "test_compile_mbed_dev" 00043 # The lib field in each says which type of mbed 2 library the app contains. 00044 # These test apps MUST be available as repos in the user's online Mercurial area. 00045 # The target_list allows the user to override the set of targets/platforms used 00046 # for the compilation. 00047 # E.g to just compile for 2 targets, K64F and K22F : 00048 # "target_list" : ["K64F", "K22F"] 00049 # 00050 # Run the script from the mbed-os directory as follows: 00051 # > python tools/check_release.py 00052 # 00053 # It will look for local clones of the test app repos. If they don't exist 00054 # it will clone them. It will then read the latest versions of mbed and mbed-dev 00055 # (an assumption is made that both of these are already cloned in your Mercurial area). 00056 # The lib files within the test apps are then updated to the corresponding version in 00057 # the associated lib itself. The test apps are then committed and pushed back to the users 00058 # fork. 00059 # The test apps will then be compiled for all supported targets and a % result output at 00060 # the end. 00061 # 00062 # Uses the online compiler API at https://mbed.org/handbook/Compile-API 00063 # Based on the example from https://mbed.org/teams/mbed/code/mbed-API-helper/ 00064 00065 00066 import os, getpass, sys, json, time, requests, logging 00067 from os.path import dirname, abspath, basename, join 00068 import argparse 00069 import subprocess 00070 import re 00071 import hglib 00072 import argparse 00073 00074 # Be sure that the tools directory is in the search path 00075 ROOT = abspath(join(dirname(__file__), "..")) 00076 sys.path.insert(0, ROOT) 00077 00078 from tools.build_api import get_mbed_official_release 00079 00080 OFFICIAL_MBED_LIBRARY_BUILD = get_mbed_official_release('2') 00081 00082 def get_compilation_failure(messages): 00083 """ Reads the json formatted 'messages' and checks for compilation errors. 00084 If there is a genuine compilation error then there should be a new 00085 message containing a severity field = Error and an accompanying message 00086 with the compile error text. Any other combination is considered an 00087 internal compile engine failure 00088 Args: 00089 messages - json formatted text returned by the online compiler API. 00090 00091 Returns: 00092 Either "Error" or "Internal" to indicate an actual compilation error or an 00093 internal IDE API fault. 00094 00095 """ 00096 for m in messages: 00097 # Get message text if it exists 00098 try: 00099 message = m['message'] 00100 message = message + "\n" 00101 except KeyError: 00102 # Skip this message as it has no 'message' field 00103 continue 00104 00105 # Get type of message text 00106 try: 00107 msg_type = m['type'] 00108 except KeyError: 00109 # Skip this message as it has no 'type' field 00110 continue 00111 00112 if msg_type == 'error' or msg_type == 'tool_error': 00113 rel_log.error(message) 00114 return "Error" 00115 else: 00116 rel_log.debug(message) 00117 00118 return "Internal" 00119 00120 def invoke_api(payload, url, auth, polls, begin="start/"): 00121 """ Sends an API command request to the online IDE. Waits for a task completed 00122 response before returning the results. 00123 00124 Args: 00125 payload - Configuration parameters to be passed to the API 00126 url - THe URL for the online compiler API 00127 auth - Tuple containing authentication credentials 00128 polls - Number of times to poll for results 00129 begin - Default value = "start/", start command to be appended to URL 00130 00131 Returns: 00132 result - True/False indicating the success/failure of the compilation 00133 fail_type - the failure text if the compilation failed, else None 00134 """ 00135 00136 # send task to api 00137 rel_log.debug(url + begin + "| data: " + str(payload)) 00138 r = requests.post(url + begin, data=payload, auth=auth) 00139 rel_log.debug(r.request.body) 00140 00141 if r.status_code != 200: 00142 rel_log.error("HTTP code %d reported.", r.status_code) 00143 return False, "Internal" 00144 00145 response = r.json() 00146 rel_log.debug(response) 00147 uuid = response['result']['data']['task_id'] 00148 rel_log.debug("Task accepted and given ID: %s", uuid) 00149 result = False 00150 fail_type = None 00151 00152 # It currently seems to take the onlide IDE API ~30s to process the compile 00153 # request and provide a response. Set the poll time to half that in case it 00154 # does manage to compile quicker. 00155 poll_delay = 15 00156 rel_log.debug("Running with a poll for response delay of: %ss", poll_delay) 00157 00158 # poll for output 00159 for check in range(polls): 00160 time.sleep(poll_delay) 00161 00162 try: 00163 r = requests.get(url + "output/%s" % uuid, auth=auth) 00164 00165 except ConnectionError: 00166 return "Internal" 00167 00168 response = r.json() 00169 00170 data = response['result']['data'] 00171 if data['task_complete']: 00172 # Task completed. Now determine the result. Should be one of : 00173 # 1) Successful compilation 00174 # 2) Failed compilation with an error message 00175 # 3) Internal failure of the online compiler 00176 result = bool(data['compilation_success']) 00177 if result: 00178 rel_log.info("COMPILATION SUCCESSFUL\n") 00179 else: 00180 # Did this fail due to a genuine compilation error or a failue of 00181 # the api itself ? 00182 rel_log.info("COMPILATION FAILURE\n") 00183 fail_type = get_compilation_failure(data['new_messages']) 00184 break 00185 else: 00186 rel_log.info("COMPILATION FAILURE\n") 00187 00188 if not result and fail_type == None: 00189 fail_type = "Internal" 00190 00191 return result, fail_type 00192 00193 00194 def build_repo(target, program, user, pw, polls=25, 00195 url="https://developer.mbed.org/api/v2/tasks/compiler/"): 00196 """ Wrapper for sending an API command request to the online IDE. Sends a 00197 build request. 00198 00199 Args: 00200 target - Target to be built 00201 program - Test program to build 00202 user - mbed username 00203 pw - mbed password 00204 polls - Number of times to poll for results 00205 url - THe URL for the online compiler API 00206 00207 Returns: 00208 result - True/False indicating the success/failure of the compilation 00209 fail_type - the failure text if the compilation failed, else None 00210 """ 00211 payload = {'clean':True, 'target':target, 'program':program} 00212 auth = (user, pw) 00213 return invoke_api(payload, url, auth, polls) 00214 00215 def run_cmd(command, exit_on_failure=False): 00216 """ Passes a command to the system and returns a True/False result once the 00217 command has been executed, indicating success/failure. Commands are passed 00218 as a list of tokens. 00219 E.g. The command 'git remote -v' would be passed in as ['git', 'remote', '-v'] 00220 00221 Args: 00222 command - system command as a list of tokens 00223 exit_on_failure - If True exit the program on failure (default = False) 00224 00225 Returns: 00226 result - True/False indicating the success/failure of the command 00227 """ 00228 rel_log.debug('[Exec] %s', ' '.join(command)) 00229 return_code = subprocess.call(command, shell=True) 00230 00231 if return_code: 00232 rel_log.warning("The command '%s' failed with return code: %s", 00233 (' '.join(command), return_code)) 00234 if exit_on_failure: 00235 sys.exit(1) 00236 00237 return return_code 00238 00239 def run_cmd_with_output(command, exit_on_failure=False): 00240 """ Passes a command to the system and returns a True/False result once the 00241 command has been executed, indicating success/failure. If the command was 00242 successful then the output from the command is returned to the caller. 00243 Commands are passed as a list of tokens. 00244 E.g. The command 'git remote -v' would be passed in as ['git', 'remote', '-v'] 00245 00246 Args: 00247 command - system command as a list of tokens 00248 exit_on_failure - If True exit the program on failure (default = False) 00249 00250 Returns: 00251 result - True/False indicating the success/failure of the command 00252 output - The output of the command if it was successful, else empty string 00253 """ 00254 rel_log.debug('[Exec] %s', ' '.join(command)) 00255 returncode = 0 00256 output = "" 00257 try: 00258 output = subprocess.check_output(command, shell=True) 00259 except subprocess.CalledProcessError as e: 00260 rel_log.warning("The command '%s' failed with return code: %s", 00261 (' '.join(command), e.returncode)) 00262 returncode = e.returncode 00263 if exit_on_failure: 00264 sys.exit(1) 00265 return returncode, output 00266 00267 def upgrade_test_repo(test, user, library, ref, repo_path): 00268 """ Upgrades a local version of a test repo to the latest version of its 00269 embedded library. 00270 If the test repo is not present in the user area specified in the json 00271 config file, then it will first be cloned. 00272 Args: 00273 test - Mercurial test repo name 00274 user - Mercurial user name 00275 library - library name 00276 ref - SHA corresponding to the latest version of the library 00277 repo_path - path to the user's repo area 00278 00279 Returns: 00280 updated - True if library was updated, False otherwise 00281 """ 00282 rel_log.info("Updating test repo: '%s' to SHA: %s", test, ref) 00283 cwd = os.getcwd() 00284 00285 repo = "https://" + user + '@developer.mbed.org/users/' + user + '/code/' + test 00286 00287 # Clone the repo if it doesn't already exist 00288 path = abspath(repo_path + '/' + test) 00289 if not os.path.exists(path): 00290 rel_log.info("Test repo doesn't exist, cloning...") 00291 os.chdir(abspath(repo_path)) 00292 clone_cmd = ['hg', 'clone', repo] 00293 run_cmd(clone_cmd, exit_on_failure=True) 00294 00295 os.chdir(path) 00296 00297 client = hglib.open(path) 00298 00299 lib_file = library + '.lib' 00300 if os.path.isfile(lib_file): 00301 # Rename command will fail on some OS's if the target file already exist, 00302 # so ensure if it does, it is deleted first. 00303 bak_file = library + '_bak' 00304 if os.path.isfile(bak_file): 00305 os.remove(bak_file) 00306 00307 os.rename(lib_file, bak_file) 00308 else: 00309 rel_log.error("Failure to backup lib file prior to updating.") 00310 return False 00311 00312 # mbed 2 style lib file contains one line with the following format 00313 # e.g. https://developer.mbed.org/users/<user>/code/mbed-dev/#156823d33999 00314 exp = 'https://developer.mbed.org/users/' + user + '/code/' + library + '/#[A-Za-z0-9]+' 00315 lib_re = re.compile(exp) 00316 updated = False 00317 00318 # Scan through mbed-os.lib line by line, looking for lib version and update 00319 # it if found 00320 with open(bak_file, 'r') as ip, open(lib_file, 'w') as op: 00321 for line in ip: 00322 00323 opline = line 00324 00325 regexp = lib_re.match(line) 00326 if regexp: 00327 opline = 'https://developer.mbed.org/users/' + user + '/code/' + library + '/#' + ref 00328 updated = True 00329 00330 op.write(opline) 00331 00332 if updated: 00333 00334 # Setup the default commit message 00335 commit_message = '"Updating ' + library + ' to ' + ref + '"' 00336 00337 # Setup and run the commit command. Need to use the rawcommand in the hglib 00338 # for this in order to pass the string value to the -m option. run_cmd using 00339 # subprocess does not like this syntax. 00340 try: 00341 client.rawcommand(['commit','-m '+commit_message, lib_file]) 00342 00343 cmd = ['hg', 'push', '-f', repo] 00344 run_cmd(cmd, exit_on_failure=True) 00345 00346 except: 00347 rel_log.info("Lib file already up to date and thus nothing to commit") 00348 00349 os.chdir(cwd) 00350 return updated 00351 00352 def get_sha(repo_path, library): 00353 """ Gets the latest SHA for the library specified. The library is assumed to be 00354 located at the repo_path. If a SHA cannot be obtained this script will exit. 00355 00356 Args: 00357 library - library name 00358 repo_path - path to the user's repo area 00359 00360 Returns: 00361 sha - last commit SHA 00362 """ 00363 cwd = os.getcwd() 00364 sha = None 00365 os.chdir(abspath(repo_path + '/' + library)) 00366 00367 cmd = ['hg', 'log', '-l', '1'] 00368 ret, output = run_cmd_with_output(cmd, exit_on_failure=True) 00369 00370 # Output should contain a 4 line string of the form: 00371 # changeset: 135:176b8275d35d 00372 # tag: tip 00373 # user: <> 00374 # date: Thu Feb 02 16:02:30 2017 +0000 00375 # summary: Release 135 of the mbed library 00376 # All we want is the changeset string after version number 00377 00378 lines = output.split('\n') 00379 fields = lines[0].split(':') 00380 sha = fields[2] 00381 00382 os.chdir(cwd) 00383 return sha 00384 00385 def get_latest_library_versions(repo_path): 00386 """ Returns the latest library versions (SHAs) for 'mbed' and 'mbed-dev'. 00387 If the SHAs cannot be obtained this script will exit. 00388 00389 Args: 00390 repo_path - path to the user's repo area 00391 00392 Returns: 00393 mbed - last commit SHA for mbed library 00394 mbed_dev - last commit SHA for mbed_dev library 00395 00396 """ 00397 00398 mbed = get_sha(repo_path, 'mbed') 00399 mbed_dev = get_sha(repo_path, 'mbed-dev') 00400 00401 return mbed, mbed_dev 00402 00403 def log_results(lst, title): 00404 if len(lst) == 0: 00405 rel_log.info("%s - None", title) 00406 else: 00407 for entry in lst: 00408 rel_log.info("%s - Test: %s, Target: %s", title, entry[0], entry[1]) 00409 00410 00411 if __name__ == '__main__': 00412 00413 parser = argparse.ArgumentParser(description=__doc__, 00414 formatter_class=argparse.RawDescriptionHelpFormatter) 00415 parser.add_argument('-l', '--log-level', 00416 help="Level for providing logging output", 00417 default='INFO') 00418 args = parser.parse_args() 00419 00420 default = getattr(logging, 'INFO') 00421 level = getattr(logging, args.log_level.upper(), default) 00422 00423 # Set logging level 00424 logging.basicConfig(level=level) 00425 rel_log = logging.getLogger("check-release") 00426 00427 # Read configuration data 00428 with open(os.path.join(os.path.dirname(__file__), "check_release.json")) as config: 00429 json_data = json.load(config) 00430 00431 supported_targets = [] 00432 00433 if len(json_data["target_list"]) > 0: 00434 # Compile user supplied subset of targets 00435 supported_targets = json_data["target_list"] 00436 else: 00437 # Get a list of the officially supported mbed-os 2 targets 00438 for tgt in OFFICIAL_MBED_LIBRARY_BUILD: 00439 supported_targets.append(tgt[0]) 00440 00441 ignore_list = [] 00442 00443 if len(json_data["ignore_list"]) > 0: 00444 # List of tuples of (test, target) to be ignored in this test 00445 ignore_list = json_data["ignore_list"] 00446 00447 config = json_data["config"] 00448 test_list = json_data["test_list"] 00449 repo_path = config["mbed_repo_path"] 00450 tests = [] 00451 00452 # get username 00453 cmd = ['hg', 'config', 'auth.x.username'] 00454 ret, output = run_cmd_with_output(cmd, exit_on_failure=True) 00455 output = output.split('\n') 00456 user = output[0] 00457 00458 # get password 00459 cmd = ['hg', 'config', 'auth.x.password'] 00460 ret, output = run_cmd_with_output(cmd, exit_on_failure=True) 00461 output = output.split('\n') 00462 password = output[0] 00463 00464 mbed, mbed_dev = get_latest_library_versions(repo_path) 00465 00466 if not mbed or not mbed_dev: 00467 rel_log.error("Could not obtain latest versions of library files!!") 00468 exit(1) 00469 00470 rel_log.info("Latest mbed lib version = %s", mbed) 00471 rel_log.info("Latest mbed-dev lib version = %s", mbed_dev) 00472 00473 # First update test repos to latest versions of their embedded libraries 00474 for test in test_list: 00475 tests.append(test['name']) 00476 upgrade_test_repo(test['name'], user, test['lib'], 00477 mbed if test['lib'] == "mbed" else mbed_dev, 00478 repo_path) 00479 00480 total = len(supported_targets) * len(tests) 00481 current = 0 00482 retries = 10 00483 passes = 0 00484 failures = [] 00485 skipped = [] 00486 00487 # Compile each test for each supported target 00488 for test in tests: 00489 for target in supported_targets: 00490 00491 combo = [test, target] 00492 00493 if combo in ignore_list: 00494 rel_log.info("SKIPPING TEST: %s, TARGET: %s", test, target) 00495 total -= 1 00496 skipped.append(combo) 00497 continue 00498 00499 current += 1 00500 for retry in range(0, retries): 00501 rel_log.info("COMPILING (%d/%d): TEST %s, TARGET: %s , attempt %u\n", current, total, test, target, retry) 00502 result, mesg = build_repo(target, test, user, password) 00503 if not result: 00504 if mesg == 'Internal': 00505 # Internal compiler error thus retry 00506 continue 00507 else: 00508 # Actual error thus move on to next compilation 00509 failures.append(combo) 00510 break 00511 00512 passes += (int)(result) 00513 break 00514 else: 00515 rel_log.error("Compilation failed due to internal errors.") 00516 rel_log.error("Skipping test/target combination.") 00517 total -= 1 00518 skipped.append(combo) 00519 00520 rel_log.info(" SUMMARY OF COMPILATION RESULTS") 00521 rel_log.info(" ------------------------------") 00522 rel_log.info(" NUMBER OF TEST APPS: %d, NUMBER OF TARGETS: %d", 00523 len(tests), len(supported_targets)) 00524 log_results(failures, " FAILED") 00525 log_results(skipped, " SKIPPED") 00526 00527 # Output a % pass rate, indicate a failure if not 100% successful 00528 pass_rate = (float(passes) / float(total)) * 100.0 00529 rel_log.info(" PASS RATE %.1f %%\n", pass_rate) 00530 sys.exit(not (pass_rate == 100))
Generated on Tue Jul 12 2022 14:23:31 by
