|
37 | 37 | from Queue import Queue, Empty
|
38 | 38 | from os.path import join, exists, basename, relpath
|
39 | 39 | from threading import Thread, Lock
|
| 40 | +from multiprocessing import Pool, cpu_count |
40 | 41 | from subprocess import Popen, PIPE
|
41 | 42 |
|
42 | 43 | # Imports related to mbed build api
|
@@ -2068,6 +2069,48 @@ def norm_relative_path(path, start):
|
2068 | 2069 | path = path.replace("\\", "/")
|
2069 | 2070 | return path
|
2070 | 2071 |
|
| 2072 | + |
| 2073 | +def build_test_worker(*args, **kwargs): |
| 2074 | + """This is a worker function for the parallel building of tests. The `args` |
| 2075 | + and `kwargs` are passed directly to `build_project`. It returns a dictionary |
| 2076 | + with the following structure: |
| 2077 | +
|
| 2078 | + { |
| 2079 | + 'result': `True` if no exceptions were thrown, `False` otherwise |
| 2080 | + 'reason': Instance of exception that was thrown on failure |
| 2081 | + 'bin_file': Path to the created binary if `build_project` was |
| 2082 | + successful. Not present otherwise |
| 2083 | + 'kwargs': The keyword arguments that were passed to `build_project`. |
| 2084 | + This includes arguments that were modified (ex. report) |
| 2085 | + } |
| 2086 | + """ |
| 2087 | + bin_file = None |
| 2088 | + ret = { |
| 2089 | + 'result': False, |
| 2090 | + 'args': args, |
| 2091 | + 'kwargs': kwargs |
| 2092 | + } |
| 2093 | + |
| 2094 | + try: |
| 2095 | + bin_file = build_project(*args, **kwargs) |
| 2096 | + ret['result'] = True |
| 2097 | + ret['bin_file'] = bin_file |
| 2098 | + ret['kwargs'] = kwargs |
| 2099 | + |
| 2100 | + except NotSupportedException, e: |
| 2101 | + ret['reason'] = e |
| 2102 | + except ToolException, e: |
| 2103 | + ret['reason'] = e |
| 2104 | + except KeyboardInterrupt, e: |
| 2105 | + ret['reason'] = e |
| 2106 | + except: |
| 2107 | + # Print unhandled exceptions here |
| 2108 | + import traceback |
| 2109 | + traceback.print_exc(file=sys.stdout) |
| 2110 | + |
| 2111 | + return ret |
| 2112 | + |
| 2113 | + |
2071 | 2114 | def build_tests(tests, base_source_paths, build_path, target, toolchain_name,
|
2072 | 2115 | clean=False, notify=None, verbose=False, jobs=1, macros=None,
|
2073 | 2116 | silent=False, report=None, properties=None,
|
@@ -2095,58 +2138,101 @@ def build_tests(tests, base_source_paths, build_path, target, toolchain_name,
|
2095 | 2138 |
|
2096 | 2139 | result = True
|
2097 | 2140 |
|
2098 |
| - map_outputs_total = list() |
| 2141 | + jobs_count = int(jobs if jobs else cpu_count()) |
| 2142 | + p = Pool(processes=jobs_count) |
| 2143 | + results = [] |
2099 | 2144 | for test_name, test_path in tests.iteritems():
|
2100 | 2145 | test_build_path = os.path.join(build_path, test_path)
|
2101 | 2146 | src_path = base_source_paths + [test_path]
|
2102 | 2147 | bin_file = None
|
2103 | 2148 | test_case_folder_name = os.path.basename(test_path)
|
2104 | 2149 |
|
| 2150 | + args = (src_path, test_build_path, target, toolchain_name) |
| 2151 | + kwargs = { |
| 2152 | + 'jobs': jobs, |
| 2153 | + 'clean': clean, |
| 2154 | + 'macros': macros, |
| 2155 | + 'name': test_case_folder_name, |
| 2156 | + 'project_id': test_name, |
| 2157 | + 'report': report, |
| 2158 | + 'properties': properties, |
| 2159 | + 'verbose': verbose, |
| 2160 | + 'app_config': app_config, |
| 2161 | + 'build_profile': build_profile, |
| 2162 | + 'silent': True |
| 2163 | + } |
2105 | 2164 |
|
2106 |
| - try: |
2107 |
| - bin_file = build_project(src_path, test_build_path, target, toolchain_name, |
2108 |
| - jobs=jobs, |
2109 |
| - clean=clean, |
2110 |
| - macros=macros, |
2111 |
| - name=test_case_folder_name, |
2112 |
| - project_id=test_name, |
2113 |
| - report=report, |
2114 |
| - properties=properties, |
2115 |
| - verbose=verbose, |
2116 |
| - app_config=app_config, |
2117 |
| - build_profile=build_profile) |
2118 |
| - |
2119 |
| - except NotSupportedException: |
2120 |
| - pass |
2121 |
| - except ToolException: |
2122 |
| - result = False |
2123 |
| - if continue_on_build_fail: |
2124 |
| - continue |
2125 |
| - else: |
2126 |
| - break |
| 2165 | + results.append(p.apply_async(build_test_worker, args, kwargs)) |
2127 | 2166 |
|
2128 |
| - # If a clean build was carried out last time, disable it for the next build. |
2129 |
| - # Otherwise the previously built test will be deleted. |
2130 |
| - if clean: |
2131 |
| - clean = False |
2132 |
| - |
2133 |
| - # Normalize the path |
2134 |
| - if bin_file: |
2135 |
| - bin_file = norm_relative_path(bin_file, execution_directory) |
2136 |
| - |
2137 |
| - test_build['tests'][test_name] = { |
2138 |
| - "binaries": [ |
2139 |
| - { |
2140 |
| - "path": bin_file |
2141 |
| - } |
2142 |
| - ] |
2143 |
| - } |
| 2167 | + p.close() |
| 2168 | + result = True |
| 2169 | + itr = 0 |
| 2170 | + while len(results): |
| 2171 | + itr += 1 |
| 2172 | + if itr > 360000: |
| 2173 | + p.terminate() |
| 2174 | + p.join() |
| 2175 | + raise ToolException("Compile did not finish in 10 minutes") |
| 2176 | + else: |
| 2177 | + sleep(0.01) |
| 2178 | + pending = 0 |
| 2179 | + for r in results: |
| 2180 | + if r.ready() is True: |
| 2181 | + try: |
| 2182 | + worker_result = r.get() |
| 2183 | + results.remove(r) |
| 2184 | + |
| 2185 | + # Take report from the kwargs and merge it into existing report |
| 2186 | + report_entry = worker_result['kwargs']['report'][target_name][toolchain_name] |
| 2187 | + for test_key in report_entry.keys(): |
| 2188 | + report[target_name][toolchain_name][test_key] = report_entry[test_key] |
| 2189 | + |
| 2190 | + # Set the overall result to a failure if a build failure occurred |
| 2191 | + if not worker_result['result'] and not isinstance(worker_result['reason'], NotSupportedException): |
| 2192 | + result = False |
| 2193 | + break |
| 2194 | + |
| 2195 | + # Adding binary path to test build result |
| 2196 | + if worker_result['result'] and 'bin_file' in worker_result: |
| 2197 | + bin_file = norm_relative_path(worker_result['bin_file'], execution_directory) |
| 2198 | + |
| 2199 | + test_build['tests'][worker_result['kwargs']['project_id']] = { |
| 2200 | + "binaries": [ |
| 2201 | + { |
| 2202 | + "path": bin_file |
| 2203 | + } |
| 2204 | + ] |
| 2205 | + } |
| 2206 | + |
| 2207 | + test_key = worker_result['kwargs']['project_id'].upper() |
| 2208 | + print report[target_name][toolchain_name][test_key][0][0]['output'].rstrip() |
| 2209 | + print 'Image: %s\n' % bin_file |
2144 | 2210 |
|
2145 |
| - print 'Image: %s'% bin_file |
| 2211 | + except: |
| 2212 | + if p._taskqueue.queue: |
| 2213 | + p._taskqueue.queue.clear() |
| 2214 | + sleep(0.5) |
| 2215 | + p.terminate() |
| 2216 | + p.join() |
| 2217 | + raise |
| 2218 | + else: |
| 2219 | + pending += 1 |
| 2220 | + if pending >= jobs_count: |
| 2221 | + break |
| 2222 | + |
| 2223 | + # Break as soon as possible if there is a failure and we are not |
| 2224 | + # continuing on build failures |
| 2225 | + if not result and not continue_on_build_fail: |
| 2226 | + if p._taskqueue.queue: |
| 2227 | + p._taskqueue.queue.clear() |
| 2228 | + sleep(0.5) |
| 2229 | + p.terminate() |
| 2230 | + break |
| 2231 | + |
| 2232 | + p.join() |
2146 | 2233 |
|
2147 | 2234 | test_builds = {}
|
2148 | 2235 | test_builds["%s-%s" % (target_name, toolchain_name)] = test_build
|
2149 |
| - |
2150 | 2236 |
|
2151 | 2237 | return result, test_builds
|
2152 | 2238 |
|
|
0 commit comments