summaryrefslogtreecommitdiff
path: root/recipes/librsvg.recipe
blob: bedb9279d911f9931ce1063e5ce0cb59010d75c1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# -*- Mode: Python -*- vi:si:et:sw=4:sts=4:ts=4:syntax=python

from cerbero.tools.libtool import LibtoolLibrary
from cerbero.errors import FatalError
from cerbero.utils import messages as m
from cerbero.utils import shell, to_winpath
from pathlib import Path
import re
import shlex
import shutil
import tempfile


class Recipe(recipe.Recipe):
    name = 'librsvg'
    version = '2.58.94'
    licenses = [License.LGPLv2Plus]
    deps = ['gdk-pixbuf', 'pango', 'cairo', 'libxml2']
    btype = BuildType.MESON
    stype = SourceType.TARBALL
    url = 'gnome://'
    tarball_checksum = '05adf6dc58b3cfb319c2efb02b2bbdff5c75ca47cc941d48098839f20496abed'

    files_libs = ['librsvg-2']
    files_bins = ['rsvg-convert']
    licenses_bins = [License.GPLv2Plus]
    files_loader = ['%(libdir)s/gdk-pixbuf-2.0/2.10.0/loaders/libpixbufloader-svg%(mext)s']
    files_devel = ['include/librsvg-2.0/librsvg/*.h',
                   '%(libdir)s/pkgconfig/librsvg-2.0.pc']
    files_typelibs = ['Rsvg-2.0']

    meson_options = {
        'introspection': 'disabled',
        'docs': 'disabled',
        'vala': 'disabled',
        'tests': 'false',
    }

    patches = [
        f"{name}/0001-makedef-rework-flags-to-work-with-Ubuntu-and-Fedora.patch",
        f"{name}/0002-meson-Consider-native-dependencies-also-on-Linux.patch",
        f"{name}/0003-meson-Use-a-temporary-directory-instead-of-a-tempora.patch",
    ]

    def append_config_toml(self, s):
        dot_cargo = Path(self.config_src_dir) / '.cargo'
        dot_cargo.mkdir(exist_ok=True)
        # Append so we don't overwrite cargo vendor settings
        with (dot_cargo / 'config.toml').open('a') as f:
            f.write(s)

    def get_llvm_tool(self, tool: str) -> Path:
        '''
        Gets one of the LLVM tools matching the current Rust toolchain.
        '''
        root_dir = shell.check_output(
            ["rustc", "--print", "sysroot"], env=self.env
        ).strip()

        tools = list(Path(root_dir).glob(f"**/{tool}"))

        if len(tools) == 0:
            raise FatalError(
                f"Rust {tool} tool not found at {root_dir}, try re-running bootstrap"
            )
        return tools[0]

    def prepare(self):
        if self.config.target_platform == Platform.WINDOWS:
            # Cargo tries to look up an import lib when LibraryType.BOTH
            # librsvg only generates the shared library
            self.library_type = LibraryType.SHARED
            # GCC willfully obeys LIBRARY_PATH
            if self.config.platform != self.config.target_platform:
                self.set_env('LDFLAGS')
                self.set_env('LIBRARY_PATH')
        self.target_triple = self.config.rust_triple(self.config.target_arch,
            self.config.target_platform, self.using_msvc())
        self.meson_options['triplet'] = self.target_triple

    async def configure(self):
        # AppleClang's nm is too old
        # This should be injected as in android.config
        if self.config.target_platform in [Platform.DARWIN, Platform.IOS]:
            self.env['NM'] = str(self.get_llvm_tool("llvm-nm"))
        if self.config.target_platform == Platform.DARWIN or self.using_msvc():
            s = '\n[profile.release]\nstrip = "debuginfo"\n'
            self.append_config_toml(s)
        elif self.config.target_platform != Platform.ANDROID:
            s = '\n[profile.release]\nsplit-debuginfo = "packed"\n'
            self.append_config_toml(s)
        s = '[build]\n' \
            f'target = "{self.target_triple}"\n'
        self.append_config_toml(s)

        if self.config.target_platform == Platform.ANDROID:
            # Use the compiler's forwarding
            # See https://android.googlesource.com/platform/ndk/+/master/docs/BuildSystemMaintainers.md#linkers
            linker = self.get_env('RUSTC_LINKER')
            link_args = []
            # We need to extract necessary linker flags from LDFLAGS which is
            # passed to the compiler
            for arg in shlex.split(self.get_env('RUSTC_LDFLAGS', self.get_env('LDFLAGS'))):
                link_args += ['-C', f"link-arg={arg}"]
            s = f'[target.{self.target_triple}]\n' \
                f'linker = "{linker}"\n' \
                f'rustflags = {link_args!r}\n'
            self.append_config_toml(s)

        if self.using_msvc():
            # Making MSYS2's `env` available mucks up Meson
            # https://github.com/mesonbuild/meson/blob/30c38e2bd69b2bab74b6e76da1c626f3c9853613/mesonbuild/backend/backends.py#L638
            # Code should be kept in sync with shell.get_path_minus_msys()
            path = self.env['PATH'].split(os.pathsep)
            msys2_prefix = to_winpath('/')
            for p in path:
                if msys2_prefix in p:
                    self.remove_env('PATH', p, sep=os.pathsep)

        await super().configure()

    async def compile(self):
        # This recipe mixes Meson with Rust!
        # Pass through the number of Ninja jobs as an upper limit
        self.set_env('CARGO_BUILD_JOBS', f'{self.num_of_cpus()}')
        await super().compile()

    def post_install(self):
        LibtoolLibrary('librsvg', None, None, None, self.config.libdir,
                self.config.target_platform).save()
        # It's not actually needed for restricting the export list, but for
        # removing the bitcode from the static library
        libraries = [f for f in self.devel_files_list()
                                if f.endswith('.a')]
        for f in libraries:
            if self.config.target_platform in (Platform.IOS, Platform.DARWIN):
                # Apple wants you to do Single-Object Prelink
                source = Path(self.config.prefix) / f

                # Only global symbols
                # Only symbol names
                # Use portable output format
                # Skip undefined symbols
                # Write pathname of the object file
                manifest = shell.check_output(
                    [self.get_llvm_tool("llvm-nm"), "-gjPUA", "--quiet", source.absolute()],
                    env=self.env,
                )

                # Now we need to match the symbols to the pattern

                # Here's the catch: Apple strip is silly enough to be unable to
                # -undefined suppress a .o because of the -two_level_namespace being
                # the default post-10.1. So we need to determine which objects have
                # matching symbols. The rest can be safely stripped.

                # The symbol listing format is as follows:
                #  ./libgstrswebrtc.a[gstrswebrtc-3a8116aacab254c2.2u9b7sba8k2fvc9v.rcgu.o]: _gst_plugin_rswebrtc_get_desc T 500 0
                # Field 1 has the object name between brackets.
                # Field 2 is the symbol name.
                symbol_pattern = re.compile('_rsvg*')

                with tempfile.TemporaryDirectory(prefix='cerbero', dir=self.config.home_dir) as tmp:
                    # List those symbols that will be kept
                    symbols_to_keep = set()

                    for line in manifest.splitlines():
                        data = line.split(sep=" ")
                        symbol = data[1]

                        if symbol_pattern.match(symbol):
                            symbols_to_keep.add(symbol)

                    module = (
                        Path(tmp) / source.name).with_suffix('.symbols')

                    with module.open('w', encoding='utf-8') as f:
                        f.write('# Stripped by Cerbero\n')

                        for symbol in symbols_to_keep:
                            f.write(f'{symbol}\n')

                    m.log(
                        f"Symbols to preserve in {source.absolute()}:", self.logfile
                    )
                    for symbol in symbols_to_keep:
                        m.log(f"\t{symbol}", self.logfile)

                    # Unpack archive
                    m.log(f"Unpacking {source.absolute()} with ar", self.logfile)
                    shell.new_call(
                        [shutil.which('ar'), 'xv', source.absolute()], cmd_dir=tmp, logfile=self.logfile)

                    # Now everything is flat in the pwd
                    m.log("Performing Single-Object Prelinking", self.logfile)
                    prelinked_obj = (
                        Path(tmp) / source.name).with_suffix('.prelinked.o')

                    ld = shutil.which("ld")

                    if ld is None:
                        raise FatalError(f'ld not found')

                    # DO NOT split this into an array unless
                    # you wrap this into a 'sh -c' call.
                    # It needs the glob to be parsed by the shell!
                    shell.new_call(
                        ' '.join([
                            ld,
                            "-r",
                            "-exported_symbols_list",
                            str(module.absolute()),
                            "-o",
                            str(prelinked_obj.absolute()),
                            "*.o",
                        ]),
                        cmd_dir=tmp,
                        logfile=self.logfile,
                    )

                    # With the stripping done, all files now need to be rearchived
                    dest = Path(tmp) / source.name
                    m.log(f"Repacking library to {dest.absolute()}", self.logfile)

                    libtool = shutil.which("libtool")

                    if libtool is None:
                        raise FatalError(f'libtool not found')

                    shell.new_call([
                            libtool,
                            "-static",
                            prelinked_obj.absolute(),
                            "-o",
                            dest.absolute(),
                        ],
                        cmd_dir=tmp,
                        logfile=self.logfile,
                    )

                    # And now we paper over
                    os.replace(dest.absolute(), source.absolute())
        super().post_install()