3 # Copyright The SCons Foundation
5 # Permission is hereby granted, free of charge, to any person obtaining
6 # a copy of this software and associated documentation files (the
7 # "Software"), to deal in the Software without restriction, including
8 # without limitation the rights to use, copy, modify, merge, publish,
9 # distribute, sublicense, and/or sell copies of the Software, and to
10 # permit persons to whom the Software is furnished to do so, subject to
11 # the following conditions:
13 # The above copyright notice and this permission notice shall be included
14 # in all copies or substantial portions of the Software.
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
17 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
18 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 """The rpm packager."""
27 import SCons
.Tool
.rpmutils
28 from SCons
.Environment
import OverrideEnvironment
29 from SCons
.Tool
.packaging
import stripinstallbuilder
, src_targz
30 from SCons
.Errors
import UserError
32 def package(env
, target
, source
, PACKAGEROOT
, NAME
, VERSION
,
33 PACKAGEVERSION
, DESCRIPTION
, SUMMARY
, X_RPM_GROUP
, LICENSE
,
35 # initialize the rpm tool
36 SCons
.Tool
.Tool('rpm').generate(env
)
38 bld
= env
['BUILDERS']['Rpm']
40 # Generate a UserError whenever the target name has been set explicitly,
41 # since rpm does not allow for controlling it. This is detected by
42 # checking if the target has been set to the default by the Package()
43 # Environment function.
44 if str(target
[0])!="%s-%s"%(NAME
, VERSION
):
45 raise UserError( "Setting target is not supported for rpm." )
47 # Deduce the build architecture, but allow it to be overridden
48 # by setting ARCHITECTURE in the construction env.
49 buildarchitecture
= SCons
.Tool
.rpmutils
.defaultMachine()
50 if 'ARCHITECTURE' in kw
:
51 buildarchitecture
= kw
['ARCHITECTURE']
53 fmt
= '%s-%s-%s.%s.rpm'
54 srcrpm
= fmt
% (NAME
, VERSION
, PACKAGEVERSION
, 'src')
55 binrpm
= fmt
% (NAME
, VERSION
, PACKAGEVERSION
, buildarchitecture
)
57 target
= [ srcrpm
, binrpm
]
59 # get the correct arguments into the kw hash
63 del kw
['source'], kw
['target'], kw
['env']
65 # if no "SOURCE_URL" tag is given add a default one.
66 if 'SOURCE_URL' not in kw
:
67 kw
['SOURCE_URL']=(str(target
[0])+".tar.gz").replace('.rpm', '')
69 # mangle the source and target list for the rpmbuild
70 env
= OverrideEnvironment(env
, kw
)
71 target
, source
= stripinstallbuilder(target
, source
, env
)
72 target
, source
= addspecfile(target
, source
, env
)
73 target
, source
= collectintargz(target
, source
, env
)
75 # now call the rpm builder to actually build the packet.
76 return bld(env
, target
, source
, **kw
)
78 def collectintargz(target
, source
, env
):
79 """ Puts all source files into a tar.gz file. """
80 # the rpm tool depends on a source package, until this is changed
81 # this hack needs to be here that tries to pack all sources in.
82 sources
= env
.FindSourceFiles()
84 # filter out the target we are building the source list for.
85 sources
= [s
for s
in sources
if s
not in target
]
87 # find the .spec file for rpm and add it since it is not necessarily found
88 # by the FindSourceFiles function.
89 sources
.extend( [s
for s
in source
if str(s
).rfind('.spec')!=-1] )
90 # sort to keep sources from changing order across builds
93 # as the source contains the url of the source package this rpm package
94 # is built from, we extract the target name
95 tarball
= (str(target
[0])+".tar.gz").replace('.rpm', '')
97 tarball
= env
['SOURCE_URL'].split('/')[-1]
99 raise SCons
.Errors
.UserError( "Missing PackageTag '%s' for RPM packager" % e
.args
[0] )
101 tarball
= src_targz
.package(env
, source
=sources
, target
=tarball
,
102 PACKAGEROOT
=env
['PACKAGEROOT'], )
104 return (target
, tarball
)
106 def addspecfile(target
, source
, env
):
107 specfile
= "%s-%s" % (env
['NAME'], env
['VERSION'])
109 bld
= SCons
.Builder
.Builder(action
= build_specfile
,
111 target_factory
= SCons
.Node
.FS
.File
)
113 source
.extend(bld(env
, specfile
, source
))
115 return (target
,source
)
117 def build_specfile(target
, source
, env
):
118 """ Builds a RPM specfile from a dictionary with string metadata and
119 by analyzing a tree of nodes.
121 with
open(target
[0].get_abspath(), 'w') as ofp
:
123 ofp
.write(build_specfile_header(env
))
124 ofp
.write(build_specfile_sections(env
))
125 ofp
.write(build_specfile_filesection(env
, source
))
127 # call a user specified function
128 if 'CHANGE_SPECFILE' in env
:
129 env
['CHANGE_SPECFILE'](target
, source
)
131 except KeyError as e
:
132 raise SCons
.Errors
.UserError('"%s" package field for RPM is missing.' % e
.args
[0])
136 # mandatory and optional package tag section
138 def build_specfile_sections(spec
):
139 """ Builds the sections of a rpm specfile.
143 mandatory_sections
= {
144 'DESCRIPTION' : '\n%%description\n%s\n\n', }
146 str = str + SimpleTagCompiler(mandatory_sections
).compile( spec
)
148 optional_sections
= {
149 'DESCRIPTION_' : '%%description -l %s\n%s\n\n',
150 'CHANGELOG' : '%%changelog\n%s\n\n',
151 'X_RPM_PREINSTALL' : '%%pre\n%s\n\n',
152 'X_RPM_POSTINSTALL' : '%%post\n%s\n\n',
153 'X_RPM_PREUNINSTALL' : '%%preun\n%s\n\n',
154 'X_RPM_POSTUNINSTALL' : '%%postun\n%s\n\n',
155 'X_RPM_VERIFY' : '%%verify\n%s\n\n',
157 # These are for internal use but could possibly be overridden
158 'X_RPM_PREP' : '%%prep\n%s\n\n',
159 'X_RPM_BUILD' : '%%build\n%s\n\n',
160 'X_RPM_INSTALL' : '%%install\n%s\n\n',
161 'X_RPM_CLEAN' : '%%clean\n%s\n\n',
164 # Default prep, build, install and clean rules
165 # TODO: optimize those build steps, to not compile the project a second time
166 if 'X_RPM_PREP' not in spec
:
167 spec
['X_RPM_PREP'] = '[ -n "$RPM_BUILD_ROOT" -a "$RPM_BUILD_ROOT" != / ] && rm -rf "$RPM_BUILD_ROOT"' + '\n%setup -q'
169 if 'X_RPM_BUILD' not in spec
:
170 spec
['X_RPM_BUILD'] = '[ ! -e "$RPM_BUILD_ROOT" -a "$RPM_BUILD_ROOT" != / ] && mkdir "$RPM_BUILD_ROOT"'
172 if 'X_RPM_INSTALL' not in spec
:
173 spec
['X_RPM_INSTALL'] = 'scons --install-sandbox="$RPM_BUILD_ROOT" "$RPM_BUILD_ROOT"'
175 if 'X_RPM_CLEAN' not in spec
:
176 spec
['X_RPM_CLEAN'] = '[ -n "$RPM_BUILD_ROOT" -a "$RPM_BUILD_ROOT" != / ] && rm -rf "$RPM_BUILD_ROOT"'
178 str = str + SimpleTagCompiler(optional_sections
, mandatory
=0).compile( spec
)
182 def build_specfile_header(spec
):
183 """ Builds all sections but the %file of a rpm specfile
187 # first the mandatory sections
188 mandatory_header_fields
= {
189 'NAME' : '%%define name %s\nName: %%{name}\n',
190 'VERSION' : '%%define version %s\nVersion: %%{version}\n',
191 'PACKAGEVERSION' : '%%define release %s\nRelease: %%{release}\n',
192 'X_RPM_GROUP' : 'Group: %s\n',
193 'SUMMARY' : 'Summary: %s\n',
194 'LICENSE' : 'License: %s\n',
197 str = str + SimpleTagCompiler(mandatory_header_fields
).compile( spec
)
199 # now the optional tags
200 optional_header_fields
= {
201 'VENDOR' : 'Vendor: %s\n',
202 'X_RPM_URL' : 'Url: %s\n',
203 'SOURCE_URL' : 'Source: %s\n',
204 'SUMMARY_' : 'Summary(%s): %s\n',
205 'ARCHITECTURE' : 'BuildArch: %s\n',
206 'X_RPM_DISTRIBUTION' : 'Distribution: %s\n',
207 'X_RPM_ICON' : 'Icon: %s\n',
208 'X_RPM_PACKAGER' : 'Packager: %s\n',
209 'X_RPM_GROUP_' : 'Group(%s): %s\n',
211 'X_RPM_REQUIRES' : 'Requires: %s\n',
212 'X_RPM_PROVIDES' : 'Provides: %s\n',
213 'X_RPM_CONFLICTS' : 'Conflicts: %s\n',
214 'X_RPM_BUILDREQUIRES' : 'BuildRequires: %s\n',
216 'X_RPM_SERIAL' : 'Serial: %s\n',
217 'X_RPM_EPOCH' : 'Epoch: %s\n',
218 'X_RPM_AUTOREQPROV' : 'AutoReqProv: %s\n',
219 'X_RPM_EXCLUDEARCH' : 'ExcludeArch: %s\n',
220 'X_RPM_EXCLUSIVEARCH' : 'ExclusiveArch: %s\n',
221 'X_RPM_PREFIX' : 'Prefix: %s\n',
224 'X_RPM_BUILDROOT' : 'BuildRoot: %s\n',
227 # fill in default values:
228 # Adding a BuildRequires renders the .rpm unbuildable under systems which
229 # are not managed by rpm, since the database to resolve this dependency is
230 # missing (take Gentoo as an example)
231 #if 'X_RPM_BUILDREQUIRES' not in spec:
232 # spec['X_RPM_BUILDREQUIRES'] = 'scons'
234 if 'X_RPM_BUILDROOT' not in spec
:
235 spec
['X_RPM_BUILDROOT'] = '%{_tmppath}/%{name}-%{version}-%{release}'
237 str = str + SimpleTagCompiler(optional_header_fields
, mandatory
=0).compile( spec
)
239 # Add any extra specfile definitions the user may have supplied.
240 # These flags get no processing, they are just added.
241 # github #3164: if we don't turn off debug package generation
242 # the tests which build packages all fail. If there are no
243 # extra flags, default to adding this one. If the user wants
244 # to turn this back on, supply the flag set to None.
246 if 'X_RPM_EXTRADEFS' not in spec
:
247 spec
['X_RPM_EXTRADEFS'] = ['%global debug_package %{nil}']
248 for extra
in spec
['X_RPM_EXTRADEFS']:
254 # mandatory and optional file tags
256 def build_specfile_filesection(spec
, files
):
257 """ builds the %file section of the specfile
261 if 'X_RPM_DEFATTR' not in spec
:
262 spec
['X_RPM_DEFATTR'] = '(-,root,root)'
264 str = str + '%%defattr %s\n' % spec
['X_RPM_DEFATTR']
267 'PACKAGING_CONFIG' : '%%config %s',
268 'PACKAGING_CONFIG_NOREPLACE' : '%%config(noreplace) %s',
269 'PACKAGING_DOC' : '%%doc %s',
270 'PACKAGING_UNIX_ATTR' : '%%attr %s',
271 'PACKAGING_LANG_' : '%%lang(%s) %s',
272 'PACKAGING_X_RPM_VERIFY' : '%%verify %s',
273 'PACKAGING_X_RPM_DIR' : '%%dir %s',
274 'PACKAGING_X_RPM_DOCDIR' : '%%docdir %s',
275 'PACKAGING_X_RPM_GHOST' : '%%ghost %s', }
280 for k
in supported_tags
.keys():
285 except AttributeError:
289 str = str + SimpleTagCompiler(supported_tags
, mandatory
=0).compile( tags
)
292 str = str + file.GetTag('PACKAGING_INSTALL_LOCATION')
297 class SimpleTagCompiler
:
298 """ Compile RPM tags by doing simple string substitution.
300 The replacement specfication is stored in the *tagset* dictionary,
303 {"abc" : "cdef %s ", "abc_": "cdef %s %s"}
305 The :func:`compile` function gets a value dictionary, which may look like::
307 {"abc": "ghij", "abc_gh": "ij"}
309 The resulting string will be::
311 "cdef ghij cdef gh ij"
314 def __init__(self
, tagset
, mandatory
: int=1) -> None:
316 self
.mandatory
= mandatory
318 def compile(self
, values
):
319 """ Compiles the tagset and returns a str containing the result
321 def is_international(tag
) -> bool:
322 return tag
.endswith('_')
324 def get_country_code(tag
):
327 def strip_country_code(tag
):
330 replacements
= list(self
.tagset
.items())
333 domestic
= [t
for t
in replacements
if not is_international(t
[0])]
334 for key
, replacement
in domestic
:
336 str = str + replacement
% values
[key
]
337 except KeyError as e
:
341 international
= [t
for t
in replacements
if is_international(t
[0])]
342 for key
, replacement
in international
:
344 x
= [t
for t
in values
.items() if strip_country_code(t
[0]) == key
]
345 int_values_for_key
= [(get_country_code(t
[0]),t
[1]) for t
in x
]
346 for v
in int_values_for_key
:
347 str = str + replacement
% v
348 except KeyError as e
:
356 # indent-tabs-mode:nil
358 # vim: set expandtab tabstop=4 shiftwidth=4: