1#     Copyright 2021, Kay Hayen, mailto:kay.hayen@gmail.com
2#
3#     Part of "Nuitka", an optimizing Python compiler that is compatible and
4#     integrates with CPython, but also works on its own.
5#
6#     Licensed under the Apache License, Version 2.0 (the "License");
7#     you may not use this file except in compliance with the License.
8#     You may obtain a copy of the License at
9#
10#        http://www.apache.org/licenses/LICENSE-2.0
11#
12#     Unless required by applicable law or agreed to in writing, software
13#     distributed under the License is distributed on an "AS IS" BASIS,
14#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15#     See the License for the specific language governing permissions and
16#     limitations under the License.
17#
18""" Code to generate and interact with module loaders.
19
20This is for generating the look-up table for the modules included in a binary
21or distribution folder.
22
23Also this prepares tables for the freezer for bytecode compiled modules. Not
24real C compiled modules.
25
26This is including modules as bytecode and mostly intended for modules, where
27we know compiling it useless or does not make much sense, or for standalone
28mode to access modules during CPython library init that cannot be avoided.
29
30The level of compatibility for C compiled stuff is so high that this is not
31needed except for technical reasons.
32"""
33
34from nuitka import Options
35from nuitka.ModuleRegistry import (
36    getDoneModules,
37    getUncompiledModules,
38    getUncompiledTechnicalModules,
39)
40from nuitka.plugins.Plugins import Plugins
41from nuitka.Tracing import inclusion_logger
42from nuitka.utils.CStrings import encodePythonStringToC
43
44from .Indentation import indented
45from .templates.CodeTemplatesLoader import (
46    template_metapath_loader_body,
47    template_metapath_loader_bytecode_module_entry,
48    template_metapath_loader_compiled_module_entry,
49    template_metapath_loader_shlib_module_entry,
50)
51
52
53def getModuleMetapathLoaderEntryCode(module, bytecode_accessor):
54    module_c_name = encodePythonStringToC(
55        Plugins.encodeDataComposerName(module.getFullName().asString())
56    )
57
58    flags = ["NUITKA_TRANSLATED_FLAG"]
59
60    if module.isUncompiledPythonModule():
61        code_data = module.getByteCode()
62        is_package = module.isUncompiledPythonPackage()
63
64        flags.append("NUITKA_BYTECODE_FLAG")
65        if is_package:
66            flags.append("NUITKA_PACKAGE_FLAG")
67
68        accessor_code = bytecode_accessor.getBlobDataCode(code_data)
69
70        return template_metapath_loader_bytecode_module_entry % {
71            "module_name": module_c_name,
72            "bytecode": accessor_code[accessor_code.find("[") + 1 : -1],
73            "size": len(code_data),
74            "flags": " | ".join(flags) or "0",
75        }
76    elif module.isPythonShlibModule():
77        flags.append("NUITKA_SHLIB_FLAG")
78
79        return template_metapath_loader_shlib_module_entry % {
80            "module_name": module_c_name,
81            "flags": " | ".join(flags) or "0",
82        }
83    else:
84        if module.isCompiledPythonPackage():
85            flags.append("NUITKA_PACKAGE_FLAG")
86
87        return template_metapath_loader_compiled_module_entry % {
88            "module_name": module_c_name,
89            "module_identifier": module.getCodeName(),
90            "flags": " | ".join(flags),
91        }
92
93
94def getMetapathLoaderBodyCode(bytecode_accessor):
95    metapath_loader_inittab = []
96    metapath_module_decls = []
97
98    uncompiled_modules = getUncompiledModules()
99
100    for other_module in getDoneModules():
101        # Put those at the end.
102        if other_module in uncompiled_modules:
103            continue
104
105        metapath_loader_inittab.append(
106            getModuleMetapathLoaderEntryCode(
107                module=other_module, bytecode_accessor=bytecode_accessor
108            )
109        )
110
111        if other_module.isCompiledPythonModule():
112            metapath_module_decls.append(
113                """\
114extern PyObject *modulecode_%(module_identifier)s(PyObject *, struct Nuitka_MetaPathBasedLoaderEntry const *);"""
115                % {"module_identifier": other_module.getCodeName()}
116            )
117
118    for uncompiled_module in uncompiled_modules:
119        metapath_loader_inittab.append(
120            getModuleMetapathLoaderEntryCode(
121                module=uncompiled_module, bytecode_accessor=bytecode_accessor
122            )
123        )
124
125    frozen_defs = []
126
127    for uncompiled_module in getUncompiledTechnicalModules():
128        module_name = uncompiled_module.getFullName()
129        code_data = uncompiled_module.getByteCode()
130        is_package = uncompiled_module.isUncompiledPythonPackage()
131
132        size = len(code_data)
133
134        # Packages are indicated with negative size.
135        if is_package:
136            size = -size
137
138        accessor_code = bytecode_accessor.getBlobDataCode(code_data)
139
140        frozen_defs.append(
141            """\
142{{"{module_name}", {start}, {size}}},""".format(
143                module_name=module_name,
144                start=accessor_code[accessor_code.find("[") + 1 : -1],
145                size=size,
146            )
147        )
148
149        if Options.isShowInclusion():
150            inclusion_logger.info("Embedded as frozen module '%s'." % module_name)
151
152    return template_metapath_loader_body % {
153        "metapath_module_decls": indented(metapath_module_decls, 0),
154        "metapath_loader_inittab": indented(metapath_loader_inittab),
155        "bytecode_count": bytecode_accessor.getConstantsCount(),
156        "frozen_modules": indented(frozen_defs),
157    }
158