1import os
2
3from conans.client.build.compiler_flags import rpath_flags, format_frameworks, format_framework_paths
4from conans.client.tools.oss import get_build_os_arch
5from conans.model import Generator
6from conans.model.conan_generator import GeneratorComponentsMixin
7
8"""
9PC FILE EXAMPLE:
10
11prefix=/usr
12exec_prefix=${prefix}
13libdir=${exec_prefix}/lib
14includedir=${prefix}/include
15
16Name: my-project
17Description: Some brief but informative description
18Version: 1.2.3
19Libs: -L${libdir} -lmy-project-1 -linkerflag -Wl,-rpath=${libdir}
20Cflags: -I${includedir}/my-project-1
21Requires: glib-2.0 >= 2.40 gio-2.0 >= 2.42 nice >= 0.1.6
22Requires.private: gthread-2.0 >= 2.40
23"""
24
25
26class PkgConfigGenerator(GeneratorComponentsMixin, Generator):
27    name = "pkg_config"
28
29    @property
30    def filename(self):
31        return None
32
33    @property
34    def compiler(self):
35        return self.conanfile.settings.get_safe("compiler")
36
37    def _get_components(self, pkg_name, cpp_info):
38        generator_components = super(PkgConfigGenerator, self)._get_components(pkg_name, cpp_info)
39        ret = []
40        for comp_genname, comp, comp_requires_gennames in generator_components:
41            ret.append((comp_genname, comp, [it[1] for it in comp_requires_gennames]))
42        return ret
43
44    @property
45    def content(self):
46        ret = {}
47        for depname, cpp_info in self.deps_build_info.dependencies:
48            pkg_genname = cpp_info.get_name(PkgConfigGenerator.name)
49            self._validate_components(cpp_info)
50            if not cpp_info.components:
51                public_deps = self.get_public_deps(cpp_info)
52                deps_names = [self._get_require_name(*it)[1] for it in public_deps]
53                ret["%s.pc" % pkg_genname] = self._pc_file_content(pkg_genname, cpp_info, deps_names)
54            else:
55                components = self._get_components(depname, cpp_info)
56                for comp_genname, comp, comp_requires_gennames in components:
57                    ret["%s.pc" % comp_genname] = self._pc_file_content(
58                        "%s-%s" % (pkg_genname, comp_genname),
59                        comp,
60                        comp_requires_gennames)
61                comp_gennames = [comp_genname for comp_genname, _, _ in components]
62                if pkg_genname not in comp_gennames:
63                    ret["%s.pc" % pkg_genname] = self.global_pc_file_contents(pkg_genname, cpp_info,
64                                                                              comp_gennames)
65        return ret
66
67    def _pc_file_content(self, name, cpp_info, requires_gennames):
68        prefix_path = cpp_info.rootpath.replace("\\", "/")
69        lines = ['prefix=%s' % prefix_path]
70
71        libdir_vars = []
72        dir_lines, varnames = self._generate_dir_lines(prefix_path, "libdir", cpp_info.lib_paths)
73        if dir_lines:
74            libdir_vars = varnames
75            lines.extend(dir_lines)
76
77        includedir_vars = []
78        dir_lines, varnames = self._generate_dir_lines(prefix_path, "includedir",
79                                                       cpp_info.include_paths)
80        if dir_lines:
81            includedir_vars = varnames
82            lines.extend(dir_lines)
83
84        pkg_config_custom_content = cpp_info.get_property("pkg_config_custom_content")
85        if pkg_config_custom_content:
86            lines.append(pkg_config_custom_content)
87
88        lines.append("")
89        lines.append("Name: %s" % name)
90        description = cpp_info.description or "Conan package: %s" % name
91        lines.append("Description: %s" % description)
92        lines.append("Version: %s" % cpp_info.version)
93        libdirs_flags = ['-L"${%s}"' % name for name in libdir_vars]
94        libnames_flags = ["-l%s " % name for name in (cpp_info.libs + cpp_info.system_libs)]
95        shared_flags = cpp_info.sharedlinkflags + cpp_info.exelinkflags
96
97        os_build, _ = get_build_os_arch(self.conanfile)
98        if not hasattr(self.conanfile, 'settings_build'):
99            os_build = os_build or self.conanfile.settings.get_safe("os")
100
101        rpaths = rpath_flags(self.conanfile.settings, os_build,
102                             ["${%s}" % libdir for libdir in libdir_vars])
103        frameworks = format_frameworks(cpp_info.frameworks, self.conanfile.settings)
104        framework_paths = format_framework_paths(cpp_info.framework_paths, self.conanfile.settings)
105
106        lines.append("Libs: %s" % _concat_if_not_empty([libdirs_flags,
107                                                        libnames_flags,
108                                                        shared_flags,
109                                                        rpaths,
110                                                        frameworks,
111                                                        framework_paths]))
112        include_dirs_flags = ['-I"${%s}"' % name for name in includedir_vars]
113
114        lines.append("Cflags: %s" % _concat_if_not_empty(
115            [include_dirs_flags,
116             cpp_info.cxxflags,
117             cpp_info.cflags,
118             ["-D%s" % d for d in cpp_info.defines]]))
119
120        if requires_gennames:
121            public_deps = " ".join(requires_gennames)
122            lines.append("Requires: %s" % public_deps)
123        return "\n".join(lines) + "\n"
124
125    @staticmethod
126    def global_pc_file_contents(name, cpp_info, comp_gennames):
127        lines = ["Name: %s" % name]
128        description = cpp_info.description or "Conan package: %s" % name
129        lines.append("Description: %s" % description)
130        lines.append("Version: %s" % cpp_info.version)
131
132        if comp_gennames:
133            public_deps = " ".join(comp_gennames)
134            lines.append("Requires: %s" % public_deps)
135        return "\n".join(lines) + "\n"
136
137    @staticmethod
138    def _generate_dir_lines(prefix_path, varname, dirs):
139        lines = []
140        varnames = []
141        for i, directory in enumerate(dirs):
142            directory = os.path.normpath(directory).replace("\\", "/")
143            name = varname if i == 0 else "%s%d" % (varname, (i + 1))
144            prefix = ""
145            if not os.path.isabs(directory):
146                prefix = "${prefix}/"
147            elif directory.startswith(prefix_path):
148                prefix = "${prefix}/"
149                directory = os.path.relpath(directory, prefix_path).replace("\\", "/")
150            lines.append("%s=%s%s" % (name, prefix, directory))
151            varnames.append(name)
152        return lines, varnames
153
154
155def _concat_if_not_empty(groups):
156    return " ".join([param for group in groups for param in group if param and param.strip()])
157