1import textwrap
2
3from jinja2 import Template
4
5from conan.tools._check_build_profile import check_using_build_profile
6from conans.util.files import save
7
8
9class BazelDeps(object):
10    def __init__(self, conanfile):
11        self._conanfile = conanfile
12        check_using_build_profile(self._conanfile)
13
14    def generate(self):
15        local_repositories = []
16
17        for build_dependency in self._conanfile.dependencies.direct_build.values():
18            content = self._get_build_dependency_buildfile_content(build_dependency)
19            filename = self._save_dependendy_buildfile(build_dependency, content)
20
21            local_repository = self._create_new_local_repository(build_dependency, filename)
22            local_repositories.append(local_repository)
23
24        for dependency in self._conanfile.dependencies.host.values():
25            content = self._get_dependency_buildfile_content(dependency)
26            filename = self._save_dependendy_buildfile(dependency, content)
27
28            local_repository = self._create_new_local_repository(dependency, filename)
29            local_repositories.append(local_repository)
30
31        content = self._get_main_buildfile_content(local_repositories)
32        self._save_main_buildfiles(content)
33
34    def _save_dependendy_buildfile(self, dependency, buildfile_content):
35        filename = 'conandeps/{}/BUILD'.format(dependency.ref.name)
36        save(filename, buildfile_content)
37        return filename
38
39    def _get_build_dependency_buildfile_content(self, dependency):
40        filegroup = textwrap.dedent("""
41            filegroup(
42                name = "{}_binaries",
43                data = glob(["**"]),
44                visibility = ["//visibility:public"],
45            )
46
47        """).format(dependency.ref.name)
48
49        return filegroup
50
51    def _get_dependency_buildfile_content(self, dependency):
52        template = textwrap.dedent("""
53            load("@rules_cc//cc:defs.bzl", "cc_import", "cc_library")
54
55            {% for lib in libs %}
56            cc_import(
57                name = "{{ lib }}_precompiled",
58                static_library = "{{ libdir }}/lib{{ lib }}.a"
59            )
60            {% endfor %}
61
62            cc_library(
63                name = "{{ name }}",
64                {% if headers %}
65                hdrs = glob([{{ headers }}]),
66                {% endif %}
67                {% if includes %}
68                includes = [{{ includes }}],
69                {% endif %}
70                {% if defines %}
71                defines = [{{ defines }}],
72                {% endif %}
73                {% if linkopts %}
74                linkopts = [{{ linkopts }}],
75                {% endif %}
76                visibility = ["//visibility:public"],
77                {% if libs %}
78                deps = [
79                {% for lib in libs %}
80                ":{{ lib }}_precompiled",
81                {% endfor %}
82                ],
83                {% endif %}
84            )
85
86        """)
87
88        cpp_info = dependency.cpp_info.aggregated_components()
89
90        if not cpp_info.libs and not cpp_info.includedirs:
91            return None
92
93        headers = []
94        includes = []
95
96        for path in cpp_info.includedirs:
97            headers.append('"{}/**"'.format(path))
98            includes.append('"{}"'.format(path))
99
100        headers = ', '.join(headers)
101        includes = ', '.join(includes)
102
103        defines = ('"{}"'.format(define.replace('"', "'"))
104                   for define in cpp_info.defines)
105        defines = ', '.join(defines)
106
107        linkopts = []
108        for linkopt in cpp_info.system_libs:
109            linkopts.append('"-l{}"'.format(linkopt))
110        linkopts = ', '.join(linkopts)
111
112        context = {
113            "name": dependency.ref.name,
114            "libs": cpp_info.libs,
115            "libdir": cpp_info.libdirs[0],
116            "headers": headers,
117            "includes": includes,
118            "defines": defines,
119            "linkopts": linkopts
120        }
121
122        content = Template(template).render(**context)
123        return content
124
125    def _create_new_local_repository(self, dependency, dependency_buildfile_name):
126        snippet = textwrap.dedent("""
127            native.new_local_repository(
128                name="{}",
129                path="{}",
130                build_file="{}",
131            )
132        """).format(
133            dependency.ref.name,
134            dependency.package_folder,
135            dependency_buildfile_name
136        )
137
138        return snippet
139
140    def _get_main_buildfile_content(self, local_repositories):
141        template = textwrap.dedent("""
142            def load_conan_dependencies():
143                {}
144        """)
145
146        if local_repositories:
147            function_content = "\n".join(local_repositories)
148            function_content = '    '.join(line for line in function_content.splitlines(True))
149        else:
150            function_content = '    pass'
151
152        content = template.format(function_content)
153
154        return content
155
156    def _save_main_buildfiles(self, content):
157        # A BUILD file must exist, even if it's empty, in order for bazel
158        # to detect it as a bazel package and allow to load the .bzl files
159        save("conandeps/BUILD", "")
160
161        save("conandeps/dependencies.bzl", content)
162