1# Copyright 2015 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'''This module provides helper functions for RPM related
16functionality such as generating template RPM spec file.'''
17
18from .. import build
19from .. import compilers
20import datetime
21from .. import mlog
22from . import GirTarget, TypelibTarget
23from . import ExtensionModule
24from ..interpreterbase import noKwargs
25
26import os
27
28class RPMModule(ExtensionModule):
29    def __init__(self, interpreter):
30        super().__init__(interpreter)
31        self.methods.update({
32            'generate_spec_template': self.generate_spec_template,
33        })
34
35    @noKwargs
36    def generate_spec_template(self, state, args, kwargs):
37        required_compilers = self.__get_required_compilers(state)
38        proj = state.project_name.replace(' ', '_').replace('\t', '_')
39        so_installed = False
40        devel_subpkg = False
41        files = set()
42        files_devel = set()
43        to_delete = set()
44        for target in state.targets.values():
45            if isinstance(target, build.Executable) and target.need_install:
46                files.add('%%{_bindir}/%s' % target.get_filename())
47            elif isinstance(target, build.SharedLibrary) and target.need_install:
48                files.add('%%{_libdir}/%s' % target.get_filename())
49                for alias in target.get_aliases():
50                    if alias.endswith('.so'):
51                        files_devel.add('%%{_libdir}/%s' % alias)
52                    else:
53                        files.add('%%{_libdir}/%s' % alias)
54                so_installed = True
55            elif isinstance(target, build.StaticLibrary) and target.need_install:
56                to_delete.add('%%{buildroot}%%{_libdir}/%s' % target.get_filename())
57                mlog.warning('removing', mlog.bold(target.get_filename()),
58                             'from package because packaging static libs not recommended')
59            elif isinstance(target, GirTarget) and target.should_install():
60                files_devel.add('%%{_datadir}/gir-1.0/%s' % target.get_filename()[0])
61            elif isinstance(target, TypelibTarget) and target.should_install():
62                files.add('%%{_libdir}/girepository-1.0/%s' % target.get_filename()[0])
63        for header in state.headers:
64            if header.get_install_subdir():
65                files_devel.add('%%{_includedir}/%s/' % header.get_install_subdir())
66            else:
67                for hdr_src in header.get_sources():
68                    files_devel.add('%%{_includedir}/%s' % hdr_src)
69        for man in state.man:
70            for man_file in man.get_sources():
71                if man.locale:
72                    files.add('%%{_mandir}/%s/man%u/%s.*' % (man.locale, int(man_file.split('.')[-1]), man_file))
73                else:
74                    files.add('%%{_mandir}/man%u/%s.*' % (int(man_file.split('.')[-1]), man_file))
75        if files_devel:
76            devel_subpkg = True
77
78        filename = os.path.join(state.environment.get_build_dir(),
79                                '%s.spec' % proj)
80        with open(filename, 'w+', encoding='utf-8') as fn:
81            fn.write('Name: %s\n' % proj)
82            fn.write('Version: # FIXME\n')
83            fn.write('Release: 1%{?dist}\n')
84            fn.write('Summary: # FIXME\n')
85            fn.write('License: # FIXME\n')
86            fn.write('\n')
87            fn.write('Source0: %{name}-%{version}.tar.xz # FIXME\n')
88            fn.write('\n')
89            fn.write('BuildRequires: meson\n')
90            for compiler in required_compilers:
91                fn.write('BuildRequires: %s\n' % compiler)
92            for dep in state.environment.coredata.deps.host:
93                fn.write('BuildRequires: pkgconfig(%s)\n' % dep[0])
94#   ext_libs and ext_progs have been removed from coredata so the following code
95#   no longer works. It is kept as a reminder of the idea should anyone wish
96#   to re-implement it.
97#
98#            for lib in state.environment.coredata.ext_libs.values():
99#                name = lib.get_name()
100#                fn.write('BuildRequires: {} # FIXME\n'.format(name))
101#                mlog.warning('replace', mlog.bold(name), 'with the real package.',
102#                             'You can use following command to find package which '
103#                             'contains this lib:',
104#                             mlog.bold("dnf provides '*/lib{}.so'".format(name)))
105#            for prog in state.environment.coredata.ext_progs.values():
106#                if not prog.found():
107#                    fn.write('BuildRequires: %%{_bindir}/%s # FIXME\n' %
108#                             prog.get_name())
109#                else:
110#                    fn.write('BuildRequires: {}\n'.format(prog.get_path()))
111            fn.write('\n')
112            fn.write('%description\n')
113            fn.write('\n')
114            if devel_subpkg:
115                fn.write('%package devel\n')
116                fn.write('Summary: Development files for %{name}\n')
117                fn.write('Requires: %{name}%{?_isa} = %{?epoch:%{epoch}:}{version}-%{release}\n')
118                fn.write('\n')
119                fn.write('%description devel\n')
120                fn.write('Development files for %{name}.\n')
121                fn.write('\n')
122            fn.write('%prep\n')
123            fn.write('%autosetup\n')
124            fn.write('\n')
125            fn.write('%build\n')
126            fn.write('%meson\n')
127            fn.write('%meson_build\n')
128            fn.write('\n')
129            fn.write('%install\n')
130            fn.write('%meson_install\n')
131            if to_delete:
132                fn.write('rm -vf %s\n' % ' '.join(to_delete))
133            fn.write('\n')
134            fn.write('%check\n')
135            fn.write('%meson_test\n')
136            fn.write('\n')
137            fn.write('%files\n')
138            for f in files:
139                fn.write('%s\n' % f)
140            fn.write('\n')
141            if devel_subpkg:
142                fn.write('%files devel\n')
143                for f in files_devel:
144                    fn.write('%s\n' % f)
145                fn.write('\n')
146            if so_installed:
147                fn.write('%post -p /sbin/ldconfig\n')
148                fn.write('%postun -p /sbin/ldconfig\n')
149            fn.write('\n')
150            fn.write('%changelog\n')
151            fn.write('* %s meson <meson@example.com> - \n' %
152                     datetime.date.today().strftime('%a %b %d %Y'))
153            fn.write('- \n')
154            fn.write('\n')
155        mlog.log('RPM spec template written to %s.spec.\n' % proj)
156
157    def __get_required_compilers(self, state):
158        required_compilers = set()
159        for compiler in state.environment.coredata.compilers.host.values():
160            # Elbrus has one 'lcc' package for every compiler
161            if isinstance(compiler, compilers.GnuCCompiler):
162                required_compilers.add('gcc')
163            elif isinstance(compiler, compilers.GnuCPPCompiler):
164                required_compilers.add('gcc-c++')
165            elif isinstance(compiler, compilers.ElbrusCCompiler):
166                required_compilers.add('lcc')
167            elif isinstance(compiler, compilers.ElbrusCPPCompiler):
168                required_compilers.add('lcc')
169            elif isinstance(compiler, compilers.ElbrusFortranCompiler):
170                required_compilers.add('lcc')
171            elif isinstance(compiler, compilers.ValaCompiler):
172                required_compilers.add('vala')
173            elif isinstance(compiler, compilers.GnuFortranCompiler):
174                required_compilers.add('gcc-gfortran')
175            elif isinstance(compiler, compilers.GnuObjCCompiler):
176                required_compilers.add('gcc-objc')
177            elif compiler == compilers.GnuObjCPPCompiler:
178                required_compilers.add('gcc-objc++')
179            else:
180                mlog.log('RPM spec file not created, generation not allowed for:',
181                         mlog.bold(compiler.get_id()))
182        return required_compilers
183
184
185def initialize(*args, **kwargs):
186    return RPMModule(*args, **kwargs)
187