build_js.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. #!/usr/bin/env python
  2. import os, sys, subprocess, argparse, shutil, glob, re, multiprocessing
  3. import logging as log
  4. SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
  5. class Fail(Exception):
  6. def __init__(self, text=None):
  7. self.t = text
  8. def __str__(self):
  9. return "ERROR" if self.t is None else self.t
  10. def execute(cmd, shell=False):
  11. try:
  12. log.info("Executing: %s" % cmd)
  13. env = os.environ.copy()
  14. env['VERBOSE'] = '1'
  15. retcode = subprocess.call(cmd, shell=shell, env=env)
  16. if retcode < 0:
  17. raise Fail("Child was terminated by signal: %s" % -retcode)
  18. elif retcode > 0:
  19. raise Fail("Child returned: %s" % retcode)
  20. except OSError as e:
  21. raise Fail("Execution failed: %d / %s" % (e.errno, e.strerror))
  22. def rm_one(d):
  23. d = os.path.abspath(d)
  24. if os.path.exists(d):
  25. if os.path.isdir(d):
  26. log.info("Removing dir: %s", d)
  27. shutil.rmtree(d)
  28. elif os.path.isfile(d):
  29. log.info("Removing file: %s", d)
  30. os.remove(d)
  31. def check_dir(d, create=False, clean=False):
  32. d = os.path.abspath(d)
  33. log.info("Check dir %s (create: %s, clean: %s)", d, create, clean)
  34. if os.path.exists(d):
  35. if not os.path.isdir(d):
  36. raise Fail("Not a directory: %s" % d)
  37. if clean:
  38. for x in glob.glob(os.path.join(d, "*")):
  39. rm_one(x)
  40. else:
  41. if create:
  42. os.makedirs(d)
  43. return d
  44. def check_file(d):
  45. d = os.path.abspath(d)
  46. if os.path.exists(d):
  47. if os.path.isfile(d):
  48. return True
  49. else:
  50. return False
  51. return False
  52. def find_file(name, path):
  53. for root, dirs, files in os.walk(path):
  54. if name in files:
  55. return os.path.join(root, name)
  56. class Builder:
  57. def __init__(self, options):
  58. self.options = options
  59. self.build_dir = check_dir(options.build_dir, create=True)
  60. self.opencv_dir = check_dir(options.opencv_dir)
  61. print('-----------------------------------------------------------')
  62. print('options.opencv_dir:', options.opencv_dir)
  63. self.emscripten_dir = check_dir(options.emscripten_dir)
  64. def get_toolchain_file(self):
  65. return os.path.join(self.emscripten_dir, "cmake", "Modules", "Platform", "Emscripten.cmake")
  66. def clean_build_dir(self):
  67. for d in ["CMakeCache.txt", "CMakeFiles/", "bin/", "libs/", "lib/", "modules"]:
  68. rm_one(d)
  69. def get_cmake_cmd(self):
  70. cmd = [
  71. "cmake",
  72. "-DPYTHON_DEFAULT_EXECUTABLE=%s" % sys.executable,
  73. "-DENABLE_PIC=FALSE", # To workaround emscripten upstream backend issue https://github.com/emscripten-core/emscripten/issues/8761
  74. "-DCMAKE_BUILD_TYPE=Release",
  75. "-DCPU_BASELINE=''",
  76. "-DCMAKE_INSTALL_PREFIX=/usr/local",
  77. "-DCPU_DISPATCH=''",
  78. "-DCV_TRACE=OFF",
  79. "-DBUILD_SHARED_LIBS=OFF",
  80. "-DWITH_1394=OFF",
  81. "-DWITH_ADE=OFF",
  82. "-DWITH_VTK=OFF",
  83. "-DWITH_EIGEN=OFF",
  84. "-DWITH_FFMPEG=OFF",
  85. "-DWITH_GSTREAMER=OFF",
  86. "-DWITH_GTK=OFF",
  87. "-DWITH_GTK_2_X=OFF",
  88. "-DWITH_IPP=OFF",
  89. "-DWITH_AVIF=OFF",
  90. "-DWITH_JASPER=OFF",
  91. "-DWITH_JPEG=OFF",
  92. "-DWITH_WEBP=OFF",
  93. "-DWITH_OPENEXR=OFF",
  94. "-DWITH_OPENJPEG=OFF",
  95. "-DWITH_OPENGL=OFF",
  96. "-DWITH_OPENVX=OFF",
  97. "-DWITH_OPENNI=OFF",
  98. "-DWITH_OPENNI2=OFF",
  99. "-DWITH_PNG=OFF",
  100. "-DWITH_TBB=OFF",
  101. "-DWITH_TIFF=OFF",
  102. "-DWITH_V4L=OFF",
  103. "-DWITH_OPENCL=OFF",
  104. "-DWITH_OPENCL_SVM=OFF",
  105. "-DWITH_OPENCLAMDFFT=OFF",
  106. "-DWITH_OPENCLAMDBLAS=OFF",
  107. "-DWITH_GPHOTO2=OFF",
  108. "-DWITH_LAPACK=OFF",
  109. "-DWITH_ITT=OFF",
  110. "-DWITH_QUIRC=OFF",
  111. "-DBUILD_ZLIB=ON",
  112. "-DBUILD_opencv_apps=OFF",
  113. "-DBUILD_opencv_calib3d=ON",
  114. "-DBUILD_opencv_dnn=ON",
  115. "-DBUILD_opencv_features2d=ON",
  116. "-DBUILD_opencv_flann=ON", # No bindings provided. This module is used as a dependency for other modules.
  117. "-DBUILD_opencv_gapi=OFF",
  118. "-DBUILD_opencv_ml=OFF",
  119. "-DBUILD_opencv_photo=ON",
  120. "-DBUILD_opencv_imgcodecs=OFF",
  121. "-DBUILD_opencv_shape=OFF",
  122. "-DBUILD_opencv_videoio=OFF",
  123. "-DBUILD_opencv_videostab=OFF",
  124. "-DBUILD_opencv_highgui=OFF",
  125. "-DBUILD_opencv_superres=OFF",
  126. "-DBUILD_opencv_stitching=OFF",
  127. "-DBUILD_opencv_java=OFF",
  128. "-DBUILD_opencv_js=ON",
  129. "-DBUILD_opencv_python2=OFF",
  130. "-DBUILD_opencv_python3=OFF",
  131. "-DBUILD_EXAMPLES=ON",
  132. "-DBUILD_PACKAGE=OFF",
  133. "-DBUILD_TESTS=ON",
  134. "-DBUILD_PERF_TESTS=ON"]
  135. if self.options.cmake_option:
  136. cmd += self.options.cmake_option
  137. if not self.options.cmake_option or all(["-DCMAKE_TOOLCHAIN_FILE" not in opt for opt in self.options.cmake_option]):
  138. cmd.append("-DCMAKE_TOOLCHAIN_FILE='%s'" % self.get_toolchain_file())
  139. if self.options.build_doc:
  140. cmd.append("-DBUILD_DOCS=ON")
  141. else:
  142. cmd.append("-DBUILD_DOCS=OFF")
  143. if self.options.threads:
  144. cmd.append("-DWITH_PTHREADS_PF=ON")
  145. else:
  146. cmd.append("-DWITH_PTHREADS_PF=OFF")
  147. if self.options.simd:
  148. cmd.append("-DCV_ENABLE_INTRINSICS=ON")
  149. else:
  150. cmd.append("-DCV_ENABLE_INTRINSICS=OFF")
  151. if self.options.build_wasm_intrin_test:
  152. cmd.append("-DBUILD_WASM_INTRIN_TESTS=ON")
  153. else:
  154. cmd.append("-DBUILD_WASM_INTRIN_TESTS=OFF")
  155. if self.options.webnn:
  156. cmd.append("-DWITH_WEBNN=ON")
  157. flags = self.get_build_flags()
  158. if flags:
  159. cmd += ["-DCMAKE_C_FLAGS='%s'" % flags,
  160. "-DCMAKE_CXX_FLAGS='%s'" % flags]
  161. if self.options.extra_modules:
  162. cmd.append("-DOPENCV_EXTRA_MODULES_PATH='%s'" % self.options.extra_modules)
  163. return cmd
  164. def get_build_flags(self):
  165. flags = ""
  166. if self.options.build_wasm:
  167. flags += "-s WASM=1 "
  168. elif self.options.disable_wasm:
  169. flags += "-s WASM=0 "
  170. if not self.options.disable_single_file:
  171. flags += "-s SINGLE_FILE=1 "
  172. if self.options.threads:
  173. flags += "-s USE_PTHREADS=1 -s PTHREAD_POOL_SIZE=4 "
  174. else:
  175. flags += "-s USE_PTHREADS=0 "
  176. if self.options.enable_exception:
  177. flags += "-s DISABLE_EXCEPTION_CATCHING=0 "
  178. if self.options.simd:
  179. flags += "-msimd128 "
  180. if self.options.build_flags:
  181. flags += self.options.build_flags + " "
  182. if self.options.webnn:
  183. flags += "-s USE_WEBNN=1 "
  184. flags += "-s EXPORTED_FUNCTIONS=\"['_malloc', '_free']\""
  185. return flags
  186. def config(self):
  187. cmd = self.get_cmake_cmd()
  188. cmd.append(self.opencv_dir)
  189. execute(cmd)
  190. def build_opencvjs(self):
  191. execute(["make", "-j", str(multiprocessing.cpu_count()), "opencv.js"])
  192. def build_test(self):
  193. execute(["make", "-j", str(multiprocessing.cpu_count()), "opencv_js_test"])
  194. def build_perf(self):
  195. execute(["make", "-j", str(multiprocessing.cpu_count()), "opencv_js_perf"])
  196. def build_doc(self):
  197. execute(["make", "-j", str(multiprocessing.cpu_count()), "doxygen"])
  198. def build_loader(self):
  199. execute(["make", "-j", str(multiprocessing.cpu_count()), "opencv_js_loader"])
  200. #===================================================================================================
  201. if __name__ == "__main__":
  202. log.basicConfig(format='%(message)s', level=log.DEBUG)
  203. opencv_dir = os.path.abspath(os.path.join(SCRIPT_DIR, '../..'))
  204. emscripten_dir = None
  205. if "EMSDK" in os.environ:
  206. emscripten_dir = os.path.join(os.environ["EMSDK"], "upstream", "emscripten")
  207. elif "EMSCRIPTEN" in os.environ:
  208. emscripten_dir = os.environ["EMSCRIPTEN"]
  209. else:
  210. log.warning("EMSCRIPTEN/EMSDK environment variable is not available. Please properly activate Emscripten SDK and consider using 'emcmake' launcher")
  211. parser = argparse.ArgumentParser(description='Build OpenCV.js by Emscripten')
  212. parser.add_argument("build_dir", help="Building directory (and output)")
  213. parser.add_argument('--opencv_dir', default=opencv_dir, help='Opencv source directory (default is "../.." relative to script location)')
  214. parser.add_argument('--emscripten_dir', default=emscripten_dir, help="Path to Emscripten to use for build (deprecated in favor of 'emcmake' launcher)")
  215. parser.add_argument('--build_wasm', action="store_true", help="Build OpenCV.js in WebAssembly format")
  216. parser.add_argument('--disable_wasm', action="store_true", help="Build OpenCV.js in Asm.js format")
  217. parser.add_argument('--disable_single_file', action="store_true", help="Do not merge JavaScript and WebAssembly into one single file")
  218. parser.add_argument('--threads', action="store_true", help="Build OpenCV.js with threads optimization")
  219. parser.add_argument('--simd', action="store_true", help="Build OpenCV.js with SIMD optimization")
  220. parser.add_argument('--build_test', action="store_true", help="Build tests")
  221. parser.add_argument('--build_perf', action="store_true", help="Build performance tests")
  222. parser.add_argument('--build_doc', action="store_true", help="Build tutorials")
  223. parser.add_argument('--build_loader', action="store_true", help="Build OpenCV.js loader")
  224. parser.add_argument('--clean_build_dir', action="store_true", help="Clean build dir")
  225. parser.add_argument('--skip_config', action="store_true", help="Skip cmake config")
  226. parser.add_argument('--config_only', action="store_true", help="Only do cmake config")
  227. parser.add_argument('--enable_exception', action="store_true", help="Enable exception handling")
  228. # Use flag --cmake option="-D...=ON" only for one argument, if you would add more changes write new cmake_option flags
  229. parser.add_argument('--cmake_option', action='append', help="Append CMake options")
  230. # Use flag --build_flags="-s USE_PTHREADS=0 -Os" for one and more arguments as in the example
  231. parser.add_argument('--build_flags', help="Append Emscripten build options")
  232. parser.add_argument('--build_wasm_intrin_test', action="store_true", help="Build WASM intrin tests")
  233. # Write a path to modify file like argument of this flag
  234. parser.add_argument('--config', help="Specify configuration file with own list of exported into JS functions")
  235. parser.add_argument('--webnn', action="store_true", help="Enable WebNN Backend")
  236. parser.add_argument("--extra_modules", required=False, help="Path extra modules location (OPENCV_EXTRA_MODULES_PATH)")
  237. transformed_args = ["--cmake_option={}".format(arg) if arg[:2] == "-D" else arg for arg in sys.argv[1:]]
  238. args = parser.parse_args(transformed_args)
  239. log.debug("Args: %s", args)
  240. if args.config is not None:
  241. os.environ["OPENCV_JS_WHITELIST"] = os.path.abspath(args.config)
  242. if 'EMMAKEN_JUST_CONFIGURE' in os.environ:
  243. del os.environ['EMMAKEN_JUST_CONFIGURE'] # avoid linker errors with NODERAWFS message then using 'emcmake' launcher
  244. if args.emscripten_dir is None:
  245. log.error("Cannot get Emscripten path, please use 'emcmake' launcher or specify it either by EMSCRIPTEN/EMSDK environment variable or --emscripten_dir option.")
  246. sys.exit(-1)
  247. builder = Builder(args)
  248. os.chdir(builder.build_dir)
  249. if args.clean_build_dir:
  250. log.info("=====")
  251. log.info("===== Clean build dir %s", builder.build_dir)
  252. log.info("=====")
  253. builder.clean_build_dir()
  254. if not args.skip_config:
  255. target = "default target"
  256. if args.build_wasm:
  257. target = "wasm"
  258. elif args.disable_wasm:
  259. target = "asm.js"
  260. log.info("=====")
  261. log.info("===== Config OpenCV.js build for %s" % target)
  262. log.info("=====")
  263. builder.config()
  264. if args.config_only:
  265. sys.exit(0)
  266. log.info("=====")
  267. log.info("===== Building OpenCV.js")
  268. log.info("=====")
  269. builder.build_opencvjs()
  270. if args.build_test:
  271. log.info("=====")
  272. log.info("===== Building OpenCV.js tests")
  273. log.info("=====")
  274. builder.build_test()
  275. if args.build_perf:
  276. log.info("=====")
  277. log.info("===== Building OpenCV.js performance tests")
  278. log.info("=====")
  279. builder.build_perf()
  280. if args.build_doc:
  281. log.info("=====")
  282. log.info("===== Building OpenCV.js tutorials")
  283. log.info("=====")
  284. builder.build_doc()
  285. if args.build_loader:
  286. log.info("=====")
  287. log.info("===== Building OpenCV.js loader")
  288. log.info("=====")
  289. builder.build_loader()
  290. log.info("=====")
  291. log.info("===== Build finished")
  292. log.info("=====")
  293. opencvjs_path = os.path.join(builder.build_dir, "bin", "opencv.js")
  294. if check_file(opencvjs_path):
  295. log.info("OpenCV.js location: %s", opencvjs_path)
  296. if args.build_test:
  297. opencvjs_test_path = os.path.join(builder.build_dir, "bin", "tests.html")
  298. if check_file(opencvjs_test_path):
  299. log.info("OpenCV.js tests location: %s", opencvjs_test_path)
  300. if args.build_perf:
  301. opencvjs_perf_path = os.path.join(builder.build_dir, "bin", "perf")
  302. opencvjs_perf_base_path = os.path.join(builder.build_dir, "bin", "perf", "base.js")
  303. if check_file(opencvjs_perf_base_path):
  304. log.info("OpenCV.js performance tests location: %s", opencvjs_perf_path)
  305. if args.build_doc:
  306. opencvjs_tutorial_path = find_file("tutorial_js_root.html", os.path.join(builder.build_dir, "doc", "doxygen", "html"))
  307. if check_file(opencvjs_tutorial_path):
  308. log.info("OpenCV.js tutorials location: %s", opencvjs_tutorial_path)
  309. if args.build_loader:
  310. opencvjs_loader_path = os.path.join(builder.build_dir, "bin", "loader.js")
  311. if check_file(opencvjs_loader_path):
  312. log.info("OpenCV.js loader location: %s", opencvjs_loader_path)