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