1# Copyright 2019 The meson development team
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""Provides a mixin for shared code between C and C++ Emscripten compilers."""
16
17import os.path
18import typing as T
19
20from ... import coredata
21from ... import mesonlib
22from ...mesonlib import OptionKey
23from ...mesonlib import LibType
24
25if T.TYPE_CHECKING:
26    from ...environment import Environment
27    from ...compilers.compilers import Compiler
28    from ...dependencies import Dependency
29else:
30    # This is a bit clever, for mypy we pretend that these mixins descend from
31    # Compiler, so we get all of the methods and attributes defined for us, but
32    # for runtime we make them descend from object (which all classes normally
33    # do). This gives up DRYer type checking, with no runtime impact
34    Compiler = object
35
36
37def wrap_js_includes(args: T.List[str]) -> T.List[str]:
38    final_args = []
39    for i in args:
40        if i.endswith('.js') and not i.startswith('-'):
41            final_args += ['--js-library', i]
42        else:
43            final_args += [i]
44    return final_args
45
46class EmscriptenMixin(Compiler):
47
48    def _get_compile_output(self, dirname: str, mode: str) -> str:
49        # In pre-processor mode, the output is sent to stdout and discarded
50        if mode == 'preprocess':
51            return None
52        # Unlike sane toolchains, emcc infers the kind of output from its name.
53        # This is the only reason why this method is overridden; compiler tests
54        # do not work well with the default exe/obj suffices.
55        if mode == 'link':
56            suffix = 'js'
57        else:
58            suffix = 'o'
59        return os.path.join(dirname, 'output.' + suffix)
60
61    def thread_flags(self, env: 'Environment') -> T.List[str]:
62        return ['-s', 'USE_PTHREADS=1']
63
64    def thread_link_flags(self, env: 'Environment') -> T.List[str]:
65        args = ['-s', 'USE_PTHREADS=1']
66        count: int = env.coredata.options[OptionKey('thread_count', lang=self.language, machine=self.for_machine)].value
67        if count:
68            args.extend(['-s', f'PTHREAD_POOL_SIZE={count}'])
69        return args
70
71    def get_options(self) -> 'coredata.KeyedOptionDictType':
72        opts = super().get_options()
73        key = OptionKey('thread_count', machine=self.for_machine, lang=self.language)
74        opts.update({
75            key: coredata.UserIntegerOption(
76                'Number of threads to use in web assembly, set to 0 to disable',
77                (0, None, 4),  # Default was picked at random
78            ),
79        })
80
81        return opts
82
83    @classmethod
84    def native_args_to_unix(cls, args: T.List[str]) -> T.List[str]:
85        return wrap_js_includes(super().native_args_to_unix(args))
86
87    def get_dependency_link_args(self, dep: 'Dependency') -> T.List[str]:
88        return wrap_js_includes(super().get_dependency_link_args(dep))
89
90    def find_library(self, libname: str, env: 'Environment', extra_dirs: T.List[str],
91                     libtype: LibType = LibType.PREFER_SHARED) -> T.Optional[T.List[str]]:
92        if not libname.endswith('.js'):
93            return super().find_library(libname, env, extra_dirs, libtype)
94        if os.path.isabs(libname):
95            if os.path.exists(libname):
96                return [libname]
97        if len(extra_dirs) == 0:
98            raise mesonlib.EnvironmentException('Looking up Emscripten JS libraries requires either an absolute path or specifying extra_dirs.')
99        for d in extra_dirs:
100            abs_path = os.path.join(d, libname)
101            if os.path.exists(abs_path):
102                return [abs_path]
103        return None
104