13 if sys.version_info[0] == 2:
14 sys.exit("FATAL: Python 2.x is not supported")
16 from pathlib import Path
18 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
20 class Fail(Exception):
21 def __init__(self, text=None):
24 return "ERROR" if self.t is None else self.t
26 def execute(cmd, cwd=None, shell=False):
28 log.debug("Executing: %s" % cmd)
29 log.info('Executing: ' + ' '.join(cmd))
31 log.info(" in: %s" % cwd)
32 retcode = subprocess.call(cmd, shell=shell, cwd=str(cwd) if cwd else None)
34 raise Fail("Child was terminated by signal: %s" % -retcode)
36 raise Fail("Child returned: %s" % retcode)
38 raise Fail("Execution failed: %d / %s" % (e.errno, e.strerror))
40 def check_executable(cmd):
42 log.debug("Executing: %s" % cmd)
43 result = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
44 if not isinstance(result, str):
45 result = result.decode("utf-8")
46 log.debug("Result: %s" % (result + '\n').split('\n')[0])
49 log.debug('Failed: %s' % e)
54 d = str(d) # Python 3.5 may not handle Path
55 d = os.path.abspath(d)
58 log.info("Removing dir: %s", d)
60 elif os.path.isfile(d):
61 log.info("Removing file: %s", d)
65 def prepare_dir(d, clean=False):
66 d = str(d) # Python 3.5 may not handle Path
67 d = os.path.abspath(d)
68 log.info("Preparing directory: '%s' (clean: %r)", d, clean)
70 if not os.path.isdir(d):
71 raise Fail("Not a directory: %s" % d)
73 for item in os.listdir(d):
74 rm_one(os.path.join(d, item))
81 d = str(d) # Python 3.5 may not handle Path
82 d = os.path.abspath(d)
83 log.info("Check directory: '%s'", d)
85 if not os.path.isdir(d):
86 raise Fail("Not a directory: %s" % d)
88 raise Fail("The directory is missing: %s" % d)
92 # shutil.copytree fails if dst exists
93 def copytree(src, dst, exclude=None):
94 log.debug('copytree(%s, %s)', src, dst)
95 src = str(src) # Python 3.5 may not handle Path
96 dst = str(dst) # Python 3.5 may not handle Path
97 if os.path.isfile(src):
98 shutil.copy2(src, dst)
100 def copy_recurse(subdir):
101 if exclude and subdir in exclude:
102 log.debug(' skip: %s', subdir)
104 s = os.path.join(src, subdir)
105 d = os.path.join(dst, subdir)
106 if os.path.exists(d) or exclude:
107 if os.path.isfile(s):
109 elif os.path.isdir(s):
110 if not os.path.isdir(d):
112 for item in os.listdir(s):
113 copy_recurse(os.path.join(subdir, item))
115 assert False, s + " => " + d
117 if os.path.isfile(s):
119 elif os.path.isdir(s):
120 shutil.copytree(s, d)
122 assert False, s + " => " + d
126 def git_checkout(dst, url, branch, revision, clone_extra_args, noFetch=False):
127 assert isinstance(dst, Path)
128 log.info("Git checkout: '%s' (%s @ %s)", dst, url, revision)
131 elif not os.path.exists(str(dst / '.git')):
132 execute(cmd=['git', 'clone'] +
133 (['-b', branch] if branch else []) +
134 clone_extra_args + [url, '.'], cwd=dst)
136 execute(cmd=['git', 'fetch', 'origin'] + ([branch + ':' + branch] if branch else []), cwd=dst)
137 execute(cmd=['git', 'reset', '--hard'], cwd=dst)
138 execute(cmd=['git', 'clean', '-f', '-d'], cwd=dst)
139 execute(cmd=['git', 'checkout', '--force', '-B', 'winpack_dldt', revision], cwd=dst)
140 execute(cmd=['git', 'clean', '-f', '-d'], cwd=dst)
141 execute(cmd=['git', 'submodule', 'init'], cwd=dst)
142 execute(cmd=['git', 'submodule', 'update', '--force', '--depth=1000'], cwd=dst)
143 log.info("Git checkout: DONE")
144 execute(cmd=['git', 'status'], cwd=dst)
145 execute(cmd=['git', 'log', '--max-count=1', 'HEAD'], cwd=dst)
148 def git_apply_patch(src_dir, patch_file):
149 src_dir = str(src_dir) # Python 3.5 may not handle Path
150 patch_file = str(patch_file) # Python 3.5 may not handle Path
151 assert os.path.exists(patch_file), patch_file
152 execute(cmd=['git', 'apply', '--3way', '-v', '--ignore-space-change', str(patch_file)], cwd=src_dir)
153 execute(cmd=['git', '--no-pager', 'diff', 'HEAD'], cwd=src_dir)
154 os.environ['GIT_AUTHOR_NAME'] = os.environ['GIT_COMMITTER_NAME']='build'
155 os.environ['GIT_AUTHOR_EMAIL'] = os.environ['GIT_COMMITTER_EMAIL']='build@opencv.org'
156 execute(cmd=['git', 'commit', '-am', 'apply opencv patch'], cwd=src_dir)
159 #===================================================================================================
162 def __init__(self, config):
165 cpath = self.config.dldt_config
166 log.info('DLDT build configuration: %s', cpath)
167 if not os.path.exists(cpath):
168 cpath = os.path.join(SCRIPT_DIR, cpath)
169 if not os.path.exists(cpath):
170 raise Fail('Config "%s" is missing' % cpath)
171 self.cpath = Path(cpath)
173 clean_src_dir = self.config.clean_dldt
174 if self.config.dldt_src_dir:
175 assert os.path.exists(self.config.dldt_src_dir), self.config.dldt_src_dir
176 dldt_dir_name = 'dldt-custom'
177 self.srcdir = self.config.dldt_src_dir
178 clean_src_dir = False
180 assert not self.config.dldt_src_dir
182 dldt_dir_name = 'dldt-' + self.config.dldt_src_commit + \
183 ('/patch-' + self.patch_hashsum if self.patch_hashsum else '')
184 if self.config.build_debug:
185 dldt_dir_name += '-debug'
186 self.srcdir = None # updated below
187 log.info('DLDT directory: %s', dldt_dir_name)
188 self.outdir = prepare_dir(os.path.join(self.config.build_cache_dir, dldt_dir_name))
189 if self.srcdir is None:
190 self.srcdir = prepare_dir(self.outdir / 'sources', clean=clean_src_dir)
191 self.build_dir = prepare_dir(self.outdir / 'build', clean=self.config.clean_dldt)
192 self.sysrootdir = prepare_dir(self.outdir / 'sysroot', clean=self.config.clean_dldt or self.config.clean_dldt_sysroot)
193 if not (self.config.clean_dldt or self.config.clean_dldt_sysroot):
194 _ = prepare_dir(self.sysrootdir / 'bin', clean=True) # always clean sysroot/bin (package files)
195 _ = prepare_dir(self.sysrootdir / 'etc', clean=True) # always clean sysroot/etc (package files)
197 if self.config.build_subst_drive:
198 if os.path.exists(self.config.build_subst_drive + ':\\'):
199 execute(['subst', self.config.build_subst_drive + ':', '/D'])
200 execute(['subst', self.config.build_subst_drive + ':', str(self.outdir)])
202 return str(p).replace(str(self.outdir), self.config.build_subst_drive + ':')
203 self.srcdir = Path(fix_path(self.srcdir))
204 self.build_dir = Path(fix_path(self.build_dir))
205 self.sysrootdir = Path(fix_path(self.sysrootdir))
208 def init_patchset(self):
210 self.patch_file = str(cpath / 'patch.config.py') # Python 3.5 may not handle Path
211 with open(self.patch_file, 'r') as f:
212 self.patch_file_contents = f.read()
217 patch_hashsum = hashlib.md5(self.patch_file_contents.encode('utf-8')).hexdigest()
219 log.warn("Can't compute hashsum of patches: %s", self.patch_file)
220 self.patch_hashsum = self.config.override_patch_hashsum if self.config.override_patch_hashsum else patch_hashsum
223 def prepare_sources(self):
224 if self.config.dldt_src_dir:
225 log.info('Using DLDT custom repository: %s', self.srcdir)
228 def do_clone(srcdir, noFetch):
229 git_checkout(srcdir, self.config.dldt_src_url, self.config.dldt_src_branch, self.config.dldt_src_commit,
230 ['-n', '--depth=100', '--no-single-branch', '--recurse-submodules'] +
231 (self.config.dldt_src_git_clone_extra or []),
235 if not os.path.exists(str(self.srcdir / '.git')):
236 log.info('DLDT git checkout through "reference" copy.')
237 reference_dir = self.config.dldt_reference_dir
238 if reference_dir is None:
239 reference_dir = prepare_dir(os.path.join(self.config.build_cache_dir, 'dldt-git-reference-repository'))
240 do_clone(reference_dir, False)
241 log.info('DLDT reference git checkout completed. Copying...')
243 log.info('Using DLDT reference repository. Copying...')
244 copytree(reference_dir, self.srcdir)
245 do_clone(self.srcdir, True)
247 do_clone(self.srcdir, False)
249 log.info('DLDT git checkout completed. Patching...')
251 def applyPatch(patch_file, subdir = None):
253 log.info('Patching "%s": %s' % (subdir, patch_file))
255 log.info('Patching: %s' % (patch_file))
256 git_apply_patch(self.srcdir / subdir if subdir else self.srcdir, self.cpath / patch_file)
258 exec(compile(self.patch_file_contents, self.patch_file, 'exec'))
260 log.info('DLDT patches applied')
264 self.cmake_path = 'cmake'
265 build_config = 'Release' if not self.config.build_debug else 'Debug'
267 cmd = [self.cmake_path, '-G', 'Visual Studio 16 2019', '-A', 'x64']
270 CMAKE_BUILD_TYPE=build_config,
271 TREAT_WARNING_AS_ERROR='OFF',
272 ENABLE_SAMPLES='OFF',
277 ENABLE_SPEECH_DEMO='OFF', # 2020.4+
278 NGRAPH_DOC_BUILD_ENABLE='OFF',
279 NGRAPH_UNIT_TEST_ENABLE='OFF',
280 NGRAPH_UNIT_TEST_OPENVINO_ENABLE='OFF',
281 NGRAPH_TEST_UTIL_ENABLE='OFF',
282 NGRAPH_ONNX_IMPORT_ENABLE='OFF',
283 CMAKE_INSTALL_PREFIX=str(self.build_dir / 'install'),
284 OUTPUT_ROOT=str(self.build_dir), # 2020.4+
287 self.build_config_file = str(self.cpath / 'build.config.py') # Python 3.5 may not handle Path
288 if os.path.exists(str(self.build_config_file)):
289 with open(self.build_config_file, 'r') as f:
291 exec(compile(cfg, str(self.build_config_file), 'exec'))
292 log.info('DLDT processed build configuration script')
294 cmd += [ '-D%s=%s' % (k, v) for (k, v) in cmake_vars.items() if v is not None]
295 if self.config.cmake_option_dldt:
296 cmd += self.config.cmake_option_dldt
298 cmd.append(str(self.srcdir))
300 build_dir = self.build_dir
302 execute(cmd, cwd=build_dir)
305 cmd = [self.cmake_path, '--build', '.', '--config', build_config, # '--target', 'install',
307 # '/m:2' is removed, not properly supported by 2021.3
308 '/v:n', '/consoleloggerparameters:NoSummary',
310 execute(cmd, cwd=build_dir)
312 # install ngraph only
313 cmd = [self.cmake_path, '-DBUILD_TYPE=' + build_config, '-P', 'cmake_install.cmake']
314 execute(cmd, cwd=build_dir / 'ngraph')
318 log.info('DLDT build completed')
321 def make_sysroot(self):
322 cfg_file = str(self.cpath / 'sysroot.config.py') # Python 3.5 may not handle Path
323 with open(cfg_file, 'r') as f:
325 exec(compile(cfg, cfg_file, 'exec'))
327 log.info('DLDT sysroot preparation completed')
331 if self.config.build_subst_drive:
332 execute(['subst', self.config.build_subst_drive + ':', '/D'])
335 #===================================================================================================
338 def __init__(self, config):
340 build_dir_name = 'opencv_build' if not self.config.build_debug else 'opencv_build_debug'
341 self.build_dir = prepare_dir(Path(self.config.output_dir) / build_dir_name, clean=self.config.clean_opencv)
342 self.package_dir = prepare_dir(Path(self.config.output_dir) / 'package/opencv', clean=True)
343 self.install_dir = prepare_dir(self.package_dir / 'build')
344 self.src_dir = check_dir(self.config.opencv_dir)
347 def build(self, builderDLDT):
348 self.cmake_path = 'cmake'
349 build_config = 'Release' if not self.config.build_debug else 'Debug'
351 cmd = [self.cmake_path, '-G', 'Visual Studio 16 2019', '-A', 'x64']
354 CMAKE_BUILD_TYPE=build_config,
355 INSTALL_CREATE_DISTRIB='ON',
356 BUILD_opencv_world='OFF',
358 BUILD_PERF_TESTS='OFF',
360 WITH_INF_ENGINE='ON',
363 CMAKE_INSTALL_PREFIX=str(self.install_dir),
365 INSTALL_PDB_COMPONENT_EXCLUDE_FROM_ALL='OFF',
367 VIDEOIO_PLUGIN_LIST='all',
369 OPENCV_SKIP_CMAKE_ROOT_CONFIG='ON',
370 OPENCV_BIN_INSTALL_PATH='bin',
371 OPENCV_INCLUDE_INSTALL_PATH='include',
372 OPENCV_LIB_INSTALL_PATH='lib',
373 OPENCV_CONFIG_INSTALL_PATH='cmake',
374 OPENCV_3P_LIB_INSTALL_PATH='3rdparty',
375 OPENCV_SAMPLES_SRC_INSTALL_PATH='samples',
376 OPENCV_DOC_INSTALL_PATH='doc',
377 OPENCV_OTHER_INSTALL_PATH='etc',
378 OPENCV_LICENSES_INSTALL_PATH='etc/licenses',
380 OPENCV_INSTALL_DATA_DIR_RELATIVE='../../src/opencv',
382 BUILD_opencv_python2='OFF',
383 BUILD_opencv_python3='ON',
384 PYTHON3_LIMITED_API='ON',
385 OPENCV_PYTHON_INSTALL_PATH='python',
388 if self.config.dldt_release:
389 cmake_vars['INF_ENGINE_RELEASE'] = str(self.config.dldt_release)
391 InferenceEngine_DIR = str(builderDLDT.sysrootdir / 'deployment_tools' / 'inference_engine' / 'cmake')
392 assert os.path.exists(InferenceEngine_DIR), InferenceEngine_DIR
393 cmake_vars['InferenceEngine_DIR:PATH'] = InferenceEngine_DIR
395 ngraph_DIR = str(builderDLDT.sysrootdir / 'ngraph/cmake')
396 if not os.path.exists(ngraph_DIR):
397 ngraph_DIR = str(builderDLDT.sysrootdir / 'ngraph/deployment_tools/ngraph/cmake')
398 assert os.path.exists(ngraph_DIR), ngraph_DIR
399 cmake_vars['ngraph_DIR:PATH'] = ngraph_DIR
401 cmake_vars['TBB_DIR:PATH'] = str(builderDLDT.sysrootdir / 'tbb/cmake')
402 assert os.path.exists(cmake_vars['TBB_DIR:PATH']), cmake_vars['TBB_DIR:PATH']
404 if self.config.build_debug:
405 cmake_vars['CMAKE_BUILD_TYPE'] = 'Debug'
406 cmake_vars['BUILD_opencv_python3'] ='OFF' # python3x_d.lib is missing
407 cmake_vars['OPENCV_INSTALL_APPS_LIST'] = 'all'
409 if self.config.build_tests:
410 cmake_vars['BUILD_TESTS'] = 'ON'
411 cmake_vars['BUILD_PERF_TESTS'] = 'ON'
412 cmake_vars['BUILD_opencv_ts'] = 'ON'
413 cmake_vars['INSTALL_TESTS']='ON'
415 if self.config.build_tests_dnn:
416 cmake_vars['BUILD_TESTS'] = 'ON'
417 cmake_vars['BUILD_PERF_TESTS'] = 'ON'
418 cmake_vars['BUILD_opencv_ts'] = 'ON'
419 cmake_vars['OPENCV_BUILD_TEST_MODULES_LIST'] = 'dnn'
420 cmake_vars['OPENCV_BUILD_PERF_TEST_MODULES_LIST'] = 'dnn'
421 cmake_vars['INSTALL_TESTS']='ON'
423 cmd += [ "-D%s=%s" % (k, v) for (k, v) in cmake_vars.items() if v is not None]
424 if self.config.cmake_option:
425 cmd += self.config.cmake_option
427 cmd.append(str(self.src_dir))
429 log.info('Configuring OpenCV...')
431 execute(cmd, cwd=self.build_dir)
433 log.info('Building OpenCV...')
436 cmd = [self.cmake_path, '--build', '.', '--config', build_config, '--target', 'install',
437 '--', '/v:n', '/m:2', '/consoleloggerparameters:NoSummary'
439 execute(cmd, cwd=self.build_dir)
441 log.info('OpenCV build/install completed')
444 def copy_sysroot(self, builderDLDT):
445 log.info('Copy sysroot files')
447 copytree(builderDLDT.sysrootdir / 'bin', self.install_dir / 'bin')
448 copytree(builderDLDT.sysrootdir / 'etc', self.install_dir / 'etc')
450 log.info('Copy sysroot files - DONE')
453 def package_sources(self):
454 package_opencv = prepare_dir(self.package_dir / 'src/opencv', clean=True)
455 package_opencv = str(package_opencv) # Python 3.5 may not handle Path
456 execute(cmd=['git', 'clone', '-s', str(self.src_dir), '.'], cwd=str(package_opencv))
457 for item in os.listdir(package_opencv):
458 if str(item).startswith('.git'):
459 rm_one(os.path.join(package_opencv, item))
461 with open(str(self.package_dir / 'README.md'), 'w') as f:
462 f.write('See licensing/copying statements in "build/etc/licenses"\n')
463 f.write('Wiki page: https://github.com/opencv/opencv/wiki/Intel%27s-Deep-Learning-Inference-Engine-backend\n')
465 log.info('Package OpenCV sources - DONE')
468 #===================================================================================================
472 dldt_src_url = 'https://github.com/openvinotoolkit/openvino'
473 dldt_src_commit = '2021.4.2'
477 build_cache_dir_default = os.environ.get('BUILD_CACHE_DIR', '.build_cache')
478 build_subst_drive = os.environ.get('BUILD_SUBST_DRIVE', None)
480 parser = argparse.ArgumentParser(
481 description='Build OpenCV Windows package with Inference Engine (DLDT)',
483 parser.add_argument('output_dir', nargs='?', default='.', help='Output directory')
484 parser.add_argument('opencv_dir', nargs='?', default=os.path.join(SCRIPT_DIR, '../..'), help='Path to OpenCV source dir')
485 parser.add_argument('--build_cache_dir', default=build_cache_dir_default, help='Build cache directory (sources and binaries cache of build dependencies, default = "%s")' % build_cache_dir_default)
486 parser.add_argument('--build_subst_drive', default=build_subst_drive, help='Drive letter to workaround Windows limit for 260 symbols in path (error MSB3491)')
488 parser.add_argument('--cmake_option', action='append', help='Append OpenCV CMake option')
489 parser.add_argument('--cmake_option_dldt', action='append', help='Append CMake option for DLDT project')
491 parser.add_argument('--clean_dldt', action='store_true', help='Clean DLDT build and sysroot directories')
492 parser.add_argument('--clean_dldt_sysroot', action='store_true', help='Clean DLDT sysroot directories')
493 parser.add_argument('--clean_opencv', action='store_true', help='Clean OpenCV build directory')
495 parser.add_argument('--build_debug', action='store_true', help='Build debug binaries')
496 parser.add_argument('--build_tests', action='store_true', help='Build OpenCV tests')
497 parser.add_argument('--build_tests_dnn', action='store_true', help='Build OpenCV DNN accuracy and performance tests only')
499 parser.add_argument('--dldt_src_url', default=dldt_src_url, help='DLDT source URL (tag / commit, default: %s)' % dldt_src_url)
500 parser.add_argument('--dldt_src_branch', help='DLDT checkout branch')
501 parser.add_argument('--dldt_src_commit', default=dldt_src_commit, help='DLDT source commit / tag (default: %s)' % dldt_src_commit)
502 parser.add_argument('--dldt_src_git_clone_extra', action='append', help='DLDT git clone extra args')
503 parser.add_argument('--dldt_release', default=dldt_release, help='DLDT release code for INF_ENGINE_RELEASE, e.g 2021030000 (default: %s)' % dldt_release)
505 parser.add_argument('--dldt_reference_dir', help='DLDT reference git repository (optional)')
506 parser.add_argument('--dldt_src_dir', help='DLDT custom source repository (skip git checkout and patching, use for TESTING only)')
508 parser.add_argument('--dldt_config', default=dldt_config, help='Specify DLDT build configuration (defaults to evaluate from DLDT commit/branch)')
510 parser.add_argument('--override_patch_hashsum', default='', help='(script debug mode)')
512 args = parser.parse_args()
515 format='%(asctime)s %(levelname)-8s %(message)s',
516 level=os.environ.get('LOGLEVEL', 'INFO'),
517 datefmt='%Y-%m-%d %H:%M:%S'
519 log.debug('Args: %s', args)
521 if not check_executable(['git', '--version']):
522 sys.exit("FATAL: 'git' is not available")
523 if not check_executable(['cmake', '--version']):
524 sys.exit("FATAL: 'cmake' is not available")
526 if os.path.realpath(args.output_dir) == os.path.realpath(SCRIPT_DIR):
527 raise Fail("Specify output_dir (building from script directory is not supported)")
528 if os.path.realpath(args.output_dir) == os.path.realpath(args.opencv_dir):
529 raise Fail("Specify output_dir (building from OpenCV source directory is not supported)")
531 # Relative paths become invalid in sub-directories
532 if args.opencv_dir is not None and not os.path.isabs(args.opencv_dir):
533 args.opencv_dir = os.path.abspath(args.opencv_dir)
535 if not args.dldt_config:
536 if str(args.dldt_src_commit).startswith('releases/20'): # releases/2020/4
537 args.dldt_config = str(args.dldt_src_commit)[len('releases/'):].replace('/', '.')
538 if not args.dldt_src_branch:
539 args.dldt_src_branch = args.dldt_src_commit
540 elif str(args.dldt_src_branch).startswith('releases/20'): # releases/2020/4
541 args.dldt_config = str(args.dldt_src_branch)[len('releases/'):].replace('/', '.')
543 args.dldt_config = args.dldt_src_commit
545 _opencv_dir = check_dir(args.opencv_dir)
546 _outdir = prepare_dir(args.output_dir)
547 _cachedir = prepare_dir(args.build_cache_dir)
549 ocv_hooks_dir = os.environ.get('OPENCV_CMAKE_HOOKS_DIR', None)
550 hooks_dir = os.path.join(SCRIPT_DIR, 'cmake-opencv-checks')
551 os.environ['OPENCV_CMAKE_HOOKS_DIR'] = hooks_dir if ocv_hooks_dir is None else (hooks_dir + ';' + ocv_hooks_dir)
553 builder_dldt = BuilderDLDT(args)
556 builder_dldt.prepare_sources()
558 builder_dldt.make_sysroot()
560 builder_opencv = Builder(args)
561 builder_opencv.build(builder_dldt)
562 builder_opencv.copy_sysroot(builder_dldt)
563 builder_opencv.package_sources()
565 builder_dldt.cleanup()
569 log.info("===== Build finished")
573 if __name__ == "__main__":
577 log.info('FATAL: Error occurred. To investigate problem try to change logging level using LOGLEVEL=DEBUG environment variable.')