~ubuntu-branches/ubuntu/quantal/spectacle/quantal

« back to all changes in this revision

Viewing changes to spectacle/specify.py

  • Committer: Bazaar Package Importer
  • Author(s): Fathi Boudra
  • Date: 2010-08-08 20:01:42 UTC
  • Revision ID: james.westby@ubuntu.com-20100808200142-q09anvq02isk4o6n
Tags: upstream-0.18+git19+4768025
ImportĀ upstreamĀ versionĀ 0.18+git19+4768025

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/python -tt
 
2
# vim: ai ts=4 sts=4 et sw=4
 
3
 
 
4
#    Copyright (c) 2009 Intel Corporation
 
5
#
 
6
#    This program is free software; you can redistribute it and/or modify it
 
7
#    under the terms of the GNU General Public License as published by the Free
 
8
#    Software Foundation; version 2 of the License
 
9
#
 
10
#    This program is distributed in the hope that it will be useful, but
 
11
#    WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 
12
#    or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 
13
#    for more details.
 
14
#
 
15
#    You should have received a copy of the GNU General Public License along
 
16
#    with this program; if not, write to the Free Software Foundation, Inc., 59
 
17
#    Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 
18
 
 
19
import os, sys
 
20
import re
 
21
import tempfile
 
22
import shutil
 
23
import copy
 
24
import distutils.version as _V
 
25
import datetime
 
26
import csv
 
27
import tarfile
 
28
 
 
29
# third-party modules
 
30
import yaml
 
31
 
 
32
# internal modules
 
33
import __version__
 
34
import spec
 
35
import logger
 
36
 
 
37
SERIES_PATH = 'series.conf'
 
38
 
 
39
MAND_KEYS = ('Name',
 
40
             'Summary',
 
41
             'Version',
 
42
             'Group',
 
43
             'License',
 
44
            )
 
45
 
 
46
SUB_MAND_KEYS = ('Name',
 
47
                 'Summary',
 
48
                 'Group',
 
49
                )
 
50
 
 
51
# boolean keys with the default 'False' value
 
52
BOOLNO_KEYS = ('Check',
 
53
               'SupportOtherDistros',
 
54
               'NoAutoReq',
 
55
               'NoAutoProv',
 
56
               'NoSetup',
 
57
               'NoAutoLocale',
 
58
               'AsWholeName',
 
59
               'NoFiles',
 
60
               'NoDesktop',
 
61
               'UpdateDesktopDB',
 
62
              )
 
63
# boolean keys with the default 'True' value
 
64
BOOLYES_KEYS = ('UseAsNeeded',
 
65
                'AutoDepend',
 
66
               )
 
67
BOOL_KEYS = BOOLNO_KEYS + BOOLYES_KEYS
 
68
 
 
69
LIST_KEYS = ('Sources',
 
70
             'ExtraSources',
 
71
             'Patches',
 
72
             'ConfigOptions',
 
73
             'Requires',
 
74
             'RequiresPre',
 
75
             'RequiresPreUn',
 
76
             'RequiresPost',
 
77
             'RequiresPostUn',
 
78
             'PkgBR',
 
79
             'PkgConfigBR',
 
80
             'Provides',
 
81
             'Conflicts',
 
82
             'BuildConflicts',
 
83
             'Obsoletes',
 
84
             'AutoSubPackages',
 
85
             'Files',
 
86
             'Documents',
 
87
             'RpmLintIgnore'
 
88
             )
 
89
 
 
90
STR_KEYS =  ('Name',
 
91
             'Summary',
 
92
             'Description',
 
93
             'Version',
 
94
             'Release',
 
95
             'Epoch',
 
96
             'Group',
 
97
             'License',
 
98
             'URL',
 
99
             'SCM',
 
100
             'Archive',
 
101
             'BuildArch',
 
102
             'ExclusiveArch',
 
103
             'SourcePrefix',
 
104
             'Configure',
 
105
             'Builder',
 
106
             'SetupOptions',
 
107
             'LocaleName',
 
108
             'LocaleOptions',
 
109
             'FilesInput',
 
110
             'PostScripts',
 
111
             'RunFdupes',
 
112
             'Prefix',
 
113
            )
 
114
 
 
115
SUBONLY_KEYS = ('AsWholeName',
 
116
                'AutoDepend',
 
117
                )
 
118
 
 
119
SUBWARN_KEYS = ('PkgBR',
 
120
                'PkgConfigBR',
 
121
                'BuildConflicts',
 
122
               )
 
123
SUBAVAIL_KEYS = ('Name',
 
124
                 'Summary',
 
125
                 'Description',
 
126
                 'Group',
 
127
                 'License',
 
128
                 'Files',
 
129
                 'Prefix',
 
130
                 'Requires',
 
131
                 'RequiresPre',
 
132
                 'RequiresPreUn',
 
133
                 'RequiresPost',
 
134
                 'RequiresPostUn',
 
135
                 'Provides',
 
136
                 'Conflicts',
 
137
                 'Obsoletes',
 
138
                 'NoAutoReq',
 
139
                 'NoAutoProv',
 
140
                 'Version', 'Release', 'Epoch', 'URL', 'BuildArch' # very rare
 
141
                )
 
142
 
 
143
DROP_KEYS = ('PostScripts',
 
144
             'Documents',
 
145
            )
 
146
 
 
147
RENAMED_KEYS = {'NeedCheckSection': 'Check',
 
148
                'NoLocale': 'NoAutoLocale',
 
149
               }
 
150
 
 
151
TYPO_KEYS = {'BuildRequires': 'PkgBR or PkgConfigBR',
 
152
             'Url': 'URL',
 
153
            }
 
154
 
 
155
ARCHED_KEYS = ('Requires',
 
156
               'PkgBR',
 
157
               'PkgConfigBR',
 
158
               'Patches',
 
159
               'ConfigOptions',
 
160
              )
 
161
ARCHS = ('ix86', 'arm')
 
162
 
 
163
CONFIGURES = ('configure', 'reconfigure', 'autogen', 'cmake', 'none')
 
164
BUILDERS = ('make', 'single-make', 'python', 'perl', 'qmake', 'cmake', 'none')
 
165
 
 
166
class GitAccess():
 
167
    def __init__(self, path):
 
168
        self.path = path
 
169
 
 
170
    def _gettags(self):
 
171
        tags = {}
 
172
        try:
 
173
            fh = os.popen('git ls-remote --tags "%s" 2>/dev/null' % self.path)
 
174
 
 
175
            prefix = 'refs/tags/'
 
176
            for line in fh:
 
177
                line = line.strip()
 
178
                node, tag = line.split(None, 1)
 
179
                if not tag.startswith(prefix):
 
180
                    continue
 
181
                tagx = tag[len(prefix):len(tag)]
 
182
                tags[tagx] = node
 
183
        except KeyboardInterrupt:
 
184
            sys.exit(2)
 
185
 
 
186
        return tags
 
187
 
 
188
    def get_toptag(self):
 
189
        vers = [_V.LooseVersion(tag) for tag in self._gettags()]
 
190
        if vers:
 
191
            vers.sort()
 
192
            return str(vers[-1])
 
193
 
 
194
        return None
 
195
 
 
196
class RPMWriter():
 
197
    """
 
198
        The following keys will be generated on the fly based on values from
 
199
        YAML, and transfered to tmpl:
 
200
            MyVersion:    version of spectacle
 
201
            ExtraInstall: extra install script for 'ExtraSources'
 
202
 
 
203
    """
 
204
 
 
205
    extra_per_pkg = {
 
206
                        'Desktop': False,
 
207
                        'DesktopDB': False,
 
208
                        'Schema': False,
 
209
                        'Schemas': [],
 
210
                        'Lib': False,
 
211
                        'HasStatic': False,
 
212
                        'Icon': False,
 
213
                        'Service': False,
 
214
                        'Info': False,
 
215
                        'Infos': [],
 
216
                    }
 
217
 
 
218
    def __init__(self, yaml_fpath, spec_fpath=None, clean_old=False, download_new=True, skip_scm=False):
 
219
        self.yaml_fpath = yaml_fpath
 
220
        now = datetime.datetime.now()
 
221
        self.metadata = {'MyVersion': __version__.VERSION, 'Date': now.strftime("%Y-%m-%d")}
 
222
        self.pkg = None
 
223
        self.version = None
 
224
        self.release = None
 
225
        self.specfile = spec_fpath
 
226
        self.packages = {}
 
227
 
 
228
        self.clean_old = clean_old
 
229
        self.download_new = download_new
 
230
        self.skip_scm = skip_scm
 
231
 
 
232
        # initialize extra info for spec
 
233
        self.extra = { 'subpkgs': {}, 'content': {} }
 
234
 
 
235
        # update extra info for main package
 
236
        self.extra.update(copy.deepcopy(self.extra_per_pkg))
 
237
 
 
238
        # record filelist from 'ExtraSources' directive
 
239
        self.extras_filelist = []
 
240
 
 
241
        try:
 
242
            self.stream = file(yaml_fpath, 'r')
 
243
        except IOError:
 
244
            logger.error('Cannot read file: %s' % yaml_fpath)
 
245
 
 
246
    def dump(self):
 
247
        print yaml.dump(yaml.load(self.stream))
 
248
 
 
249
    def _check_dup_files(self, files):
 
250
        # try to remove duplicate '%defattr' in files list
 
251
        dup1 = '%defattr(-,root,root,-)'
 
252
        dup2 = '%defattr(-,root,root)'
 
253
        found_dup = dup1 if dup1 in files else dup2 if dup2 in files else None
 
254
        if found_dup:
 
255
            logger.warning('found duplicate "%s" in file list, removed!' % found_dup)
 
256
            files.remove(found_dup)
 
257
 
 
258
    def _check_dup_ldconfig(self, pkgname = None):
 
259
        if not pkgname:
 
260
            pkgname = 'main'
 
261
            if not self.extra['Lib']:
 
262
                return
 
263
        else:
 
264
            if not self.extra['subpkgs'][pkgname]['Lib']:
 
265
                return
 
266
 
 
267
        dup1 = '/sbin/ldconfig'
 
268
        dup2 = 'ldconfig'
 
269
 
 
270
        for sec in ('post', 'postun'):
 
271
            try:
 
272
                extra = self.extra['content'][sec][pkgname]
 
273
            except KeyError:
 
274
                continue
 
275
            found_dup = dup1 if dup1 in extra else dup2 if dup2 in extra else None
 
276
            if found_dup:
 
277
                extra.remove(found_dup)
 
278
                logger.warning('Found duplicate "%s" calling in "%%%s" of %s package, removed!' % (found_dup, sec, pkgname))
 
279
 
 
280
    def _check_dup_scriptlets(self, pkgname = None):
 
281
        if not pkgname:
 
282
            pkgname = 'main'
 
283
            extra = self.extra
 
284
        else:
 
285
            extra = self.extra['subpkgs'][pkgname]
 
286
 
 
287
        if extra['Desktop']:
 
288
            re_idstr = re.compile('^desktop-file-install\s+')
 
289
            try:
 
290
                lines = extra['content']['install']['pre'] + \
 
291
                        extra['content']['install']['post']
 
292
            except KeyError:
 
293
                pass
 
294
            else:
 
295
                for line in lines:
 
296
                    if re_idstr.match(line):
 
297
                        logger.warning('Found possible duplicate "desktop-file-install" script in post install')
 
298
                        break
 
299
 
 
300
            if extra['DesktopDB']:
 
301
                re_idstr = re.compile('^update-desktop-database\s+')
 
302
                for sec in ('post', 'postun'):
 
303
                    try:
 
304
                        lines = self.extra['content'][sec][pkgname]
 
305
                    except KeyError:
 
306
                        continue
 
307
 
 
308
                    for line in lines:
 
309
                        if re_idstr.match(line):
 
310
                            logger.warning('Found possible duplicate "update-desktop-database" script in %%%s of %s package'%(sec, pkgname))
 
311
                            break
 
312
 
 
313
        if extra['Info']:
 
314
            re_idstr = re.compile('^%install_info')
 
315
            for sec in ('post', 'postun'):
 
316
                try:
 
317
                    lines = self.extra['content'][sec][pkgname]
 
318
                except KeyError:
 
319
                    continue
 
320
 
 
321
                for line in lines:
 
322
                    if re_idstr.match(line):
 
323
                        logger.warning('Found possible duplicate "%%install_info..." script in %%%s of %s package'%(sec, pkgname))
 
324
                        break
 
325
 
 
326
        if extra['Icon']:
 
327
            re_idstr = re.compile('^/bin/touch\s+.*%{_datadir}/icons/hicolor.*')
 
328
            re_idstr2 = re.compile('gtk-update-icon-cache\s+')
 
329
            for sec in ('post', 'postun'):
 
330
                try:
 
331
                    lines = self.extra['content'][sec][pkgname]
 
332
                except KeyError:
 
333
                    continue
 
334
 
 
335
                for line in lines:
 
336
                    if re_idstr.match(line):
 
337
                        logger.warning('Found possible duplicate script to touch icons in %%%s of %s package'%(sec, pkgname))
 
338
                    elif re_idstr2.search(line):
 
339
                        logger.warning('Found possible duplicate "gtk-update-icon-cache" script in %%%s of %s package'%(sec, pkgname))
 
340
 
 
341
        if extra['Schema']:
 
342
            re_idstr = re.compile('gconftool-2\s+')
 
343
            for sec in ('post', 'pre', 'preun'):
 
344
                try:
 
345
                    lines = self.extra['content'][sec][pkgname]
 
346
                except KeyError:
 
347
                    continue
 
348
 
 
349
                for line in lines:
 
350
                    if re_idstr.search(line):
 
351
                        logger.warning('Found possible duplicate "gconftool-2" script in %%%s of %s package'%(sec, pkgname))
 
352
                        break
 
353
 
 
354
    def sanity_check(self):
 
355
 
 
356
        def _check_empty_keys(metadata):
 
357
            """ return the empty keys """
 
358
            keys = []
 
359
            for key in metadata.keys():
 
360
                if metadata[key] is None:
 
361
                    keys.append(key)
 
362
                    del metadata[key]
 
363
 
 
364
            return keys
 
365
 
 
366
        def _check_mandatory_keys(metadata, subpkg = None):
 
367
            """ return [] if all mandatory keys found, otherwise return the lost keys """
 
368
            if subpkg:
 
369
                mkeys = list(SUB_MAND_KEYS)
 
370
            else:
 
371
                mkeys = list(MAND_KEYS)
 
372
 
 
373
            for key in metadata:
 
374
                if key in mkeys:
 
375
                    mkeys.remove(key)
 
376
                    if not mkeys: break
 
377
 
 
378
            return mkeys
 
379
 
 
380
        def _check_invalid_keys(metadata, subpkg = None):
 
381
            """ return list of invalid keys """
 
382
            if not subpkg:
 
383
                # main package
 
384
                all_keys = list(LIST_KEYS + STR_KEYS + BOOL_KEYS + ('Date', 'MyVersion'))
 
385
                all_keys += RENAMED_KEYS.keys()
 
386
                all_keys.append('SubPackages')
 
387
                for key in SUBONLY_KEYS:
 
388
                    all_keys.remove(key)
 
389
            else:
 
390
                # sub package
 
391
                all_keys = list(SUBAVAIL_KEYS + SUBWARN_KEYS + SUBONLY_KEYS)
 
392
 
 
393
            keys = []
 
394
            for key in metadata:
 
395
                if key not in all_keys:
 
396
                    keys.append(key)
 
397
 
 
398
            # whether the invalid keys are common typo
 
399
            for key in keys[:]:
 
400
                if key in TYPO_KEYS:
 
401
                    logger.warning('"%s" might be a typo of %s, please correct it' %(key,TYPO_KEYS[key]))
 
402
                    keys.remove(key)
 
403
 
 
404
            return keys
 
405
 
 
406
        def _check_subwarn_keys(metadata, subpkg):
 
407
            for key in SUBWARN_KEYS:
 
408
                if key in metadata:
 
409
                    logger.warning('"%s" found in sub-pkg: %s, please consider to move it to main package' %(key, subpkg))
 
410
 
 
411
        def _check_key_group(metadata):
 
412
            if metadata.has_key("Group"):
 
413
                warn = True
 
414
                for line in open("/usr/share/spectacle/GROUPS"):
 
415
                    if metadata['Group'] in line:
 
416
                        warn = False
 
417
                        break
 
418
                if warn:
 
419
                    logger.warning('Group \'%s\' is not in the list of approved groups. See /usr/share/spectacle/GROUPS for the complete list.' % (metadata['Group']))
 
420
 
 
421
        def _check_key_epoch(metadata):
 
422
            if 'Epoch' in metadata:
 
423
                logger.warning('Please consider to remove "Epoch"')
 
424
 
 
425
        def _check_pkgconfig():
 
426
            pkgcfg = csv.reader(open('/usr/share/spectacle/pkgconfig-provides.csv'), delimiter=',')
 
427
            for row in pkgcfg:
 
428
                pc = re.search('pkgconfig\(([^)]+)\)', row[1])
 
429
                m = pc.group(1)
 
430
                if self.packages.has_key(row[0]):
 
431
                    ll = self.packages[row[0]]
 
432
                    ll.append(m)
 
433
                    self.packages[row[0]] = ll
 
434
                else:
 
435
                    self.packages[row[0]] = [m]
 
436
 
 
437
        def _check_key_desc(metadata):
 
438
            """ sub-routine for 'description' checking """
 
439
            if 'Description' not in metadata or \
 
440
                metadata['Description'] == '%{summary}':
 
441
                return False
 
442
            return True
 
443
 
 
444
        def _check_listkey(metadata, key):
 
445
            """ sub-routine for LIST typed keys checking """
 
446
            if key in metadata and not isinstance(metadata[key], list):
 
447
                return False
 
448
            return True
 
449
 
 
450
        def _check_strkey(metadata, key):
 
451
            """ sub-routine for STR typed keys checking """
 
452
            if key in metadata and not isinstance(metadata[key], str) and not isinstance(metadata[key], unicode):
 
453
                return False
 
454
            return True
 
455
 
 
456
        def _check_boolkey(metadata, key):
 
457
            """ sub-routine for boolean typed keys checking """
 
458
            if key in metadata and not isinstance(metadata[key], bool):
 
459
                return False
 
460
            return True
 
461
 
 
462
        def _check_arched_keys(metadata):
 
463
            """ sub-routine for ARCH namespace available keys """
 
464
            def __check_arch(key, item):
 
465
                if re.match('^\w+:[^:]+', item):
 
466
                    arch = item.split(':')[0].strip()
 
467
                    if arch not in ARCHS:
 
468
                        logger.warning('unsupport arch namespace: %s in key %s' % (arch, key))
 
469
 
 
470
            for key in ARCHED_KEYS:
 
471
                if key in metadata:
 
472
                    if key in STR_KEYS:
 
473
                        __check_arch(key, metadata[key])
 
474
                    elif key in LIST_KEYS:
 
475
                        for item in metadata[key]:
 
476
                            __check_arch(key, item)
 
477
 
 
478
        def _check_key_localename(metadata):
 
479
            """ sub-routine for 'LocaleName' checking """
 
480
            if 'LocaleOptions' in metadata and 'LocaleName' not in metadata:
 
481
                return False
 
482
            return True
 
483
 
 
484
        def _check_dropped_keys(metadata):
 
485
            for key in DROP_KEYS:
 
486
                if key in metadata:
 
487
                    logger.warning('Deprecated key: %s found, please use other valid keys' % key)
 
488
 
 
489
        def _check_renamed_keys(metadata):
 
490
            for key in RENAMED_KEYS:
 
491
                if key in metadata:
 
492
                    metadata[RENAMED_KEYS[key]] = metadata[key]
 
493
                    del metadata[key]
 
494
                    logger.warning('Renamed key: %s found, please use %s instead' % (key, RENAMED_KEYS[key]))
 
495
 
 
496
        def _check_key_setups(metadata):
 
497
            if 'NoSetup' in metadata:
 
498
                if 'SetupOptions' in metadata:
 
499
                    logger.warning('"SetupOptions" will have NO effect when "NoSetup" specified in YAML')
 
500
                if 'SourcePrefix' in metadata:
 
501
                    logger.warning('"SourcePrefix" will have NO effect when "NoSetup" specified in YAML')
 
502
            else:
 
503
                if 'SetupOptions' in metadata and 'SourcePrefix' in metadata:
 
504
                    logger.warning('"SourcePrefix" will have NO effect when "SetupOptions" specified in YAML')
 
505
 
 
506
        def _check_key_nofiles(metadata):
 
507
            if 'Files' in metadata:
 
508
                logger.error('both "NoFiles" and "Files" exists in YAML, please correct it')
 
509
            for req in ('Requires',
 
510
                        'RequiresPre',
 
511
                        'RequiresPreUn',
 
512
                        'RequiresPost',
 
513
                        'RequiresPostUn',
 
514
                        'Provides',
 
515
                        'Conflicts',
 
516
                        'BuildConflicts',
 
517
                        'Obsoletes'):
 
518
                if req in metadata and metadata[req]:
 
519
                    logger.warning('"NoFiles" exists, key %s has no effect any more' % req)
 
520
 
 
521
        def _check_key_configure(metadata):
 
522
            cfg = metadata['Configure']
 
523
            if cfg not in CONFIGURES:
 
524
                logger.warning('"%s" is not a valid choice of Configure(%s)' % (cfg, '/'.join(CONFIGURES)))
 
525
 
 
526
        def _check_key_builder(metadata):
 
527
            builder = metadata['Builder']
 
528
            if builder not in BUILDERS:
 
529
                logger.warning('"%s" is not a valid choice of Builder(%s)' % (builder, '/'.join(BUILDERS)))
 
530
            # checking invalid 'Configure' for special builder
 
531
            if builder in ('python', 'perl', 'qmake', 'cmake') and \
 
532
               'Configure' in metadata:
 
533
                logger.warning('"%s" need no "Configure" setting which will be ignored' % builder)
 
534
 
 
535
        # checking for empty keys
 
536
        keys = _check_empty_keys(self.metadata)
 
537
        if keys:
 
538
            logger.warning('Please remove empty keys in main package: %s' % ', '.join(keys))
 
539
        if "SubPackages" in self.metadata:
 
540
            for sp in self.metadata["SubPackages"]:
 
541
                keys = _check_empty_keys(sp)
 
542
                if keys:
 
543
                    logger.warning('Please remove empty keys in %s subpackage: %s' % (sp['Name'], ', '.join(keys)))
 
544
 
 
545
        # checking for mandatory keys
 
546
        keys = _check_mandatory_keys(self.metadata)
 
547
        if keys:
 
548
            logger.error('Missing mandatory keys for main package: %s' % ', '.join(keys))
 
549
        if "SubPackages" in self.metadata:
 
550
            for sp in self.metadata["SubPackages"]:
 
551
                keys = _check_mandatory_keys(sp, sp['Name'])
 
552
                if keys:
 
553
                    logger.error('Missing mandatory keys for sub-pkg %s: %s' % (sp['Name'], ', '.join(keys)))
 
554
 
 
555
        # checking for unexpected keys
 
556
        keys = _check_invalid_keys(self.metadata)
 
557
        if keys:
 
558
            logger.warning('Unexpected keys found: %s' % ', '.join(keys))
 
559
        if "SubPackages" in self.metadata:
 
560
            for sp in self.metadata["SubPackages"]:
 
561
                keys = _check_invalid_keys(sp, sp['Name'])
 
562
                if keys:
 
563
                    logger.warning('Unexpected keys for sub-pkg %s found: %s' % (sp['Name'], ', '.join(keys)))
 
564
 
 
565
        # checking for questionable sub-package keys
 
566
        if "SubPackages" in self.metadata:
 
567
            for sp in self.metadata["SubPackages"]:
 
568
                keys = _check_subwarn_keys(sp, sp['Name'])
 
569
 
 
570
        # checking for deprecated keys
 
571
        _check_dropped_keys(self.metadata)
 
572
 
 
573
        # checking for renamed keys
 
574
        _check_renamed_keys(self.metadata)
 
575
 
 
576
 
 
577
        ######### Type checkings ##########
 
578
        # checking for LIST expected keys
 
579
        for key in LIST_KEYS:
 
580
            if not _check_listkey(self.metadata, key):
 
581
                logger.warning('the value of "%s" in main package is expected as list typed' % key)
 
582
                self.metadata[key] = [self.metadata[key]]
 
583
            if "SubPackages" in self.metadata:
 
584
                for sp in self.metadata["SubPackages"]:
 
585
                    if not _check_listkey(sp, key):
 
586
                        logger.warning('the value of "%s" in %s sub-package is expected as list typed' % (key, sp['Name']))
 
587
                        sp[key] = [sp[key]]
 
588
 
 
589
        # checking for STR expected keys
 
590
        for key in STR_KEYS:
 
591
            if not _check_strkey(self.metadata, key):
 
592
                logger.warning('the value of "%s" in main package is expected as string typed' % key)
 
593
                self.metadata[key] = ' '.join(self.metadata[key])
 
594
            if "SubPackages" in self.metadata:
 
595
                for sp in self.metadata["SubPackages"]:
 
596
                    if not _check_strkey(sp, key):
 
597
                        logger.warning('the value of "%s" in %s sub-package is expected as string typed' % (key, sp['Name']))
 
598
                        sp[key] = ' '.join(sp[key])
 
599
 
 
600
        # checking for BOOL expected keys
 
601
        for key in BOOL_KEYS:
 
602
            if not _check_boolkey(self.metadata, key):
 
603
                logger.warning('the value of "%s" in main package is expected as bool typed, dropped!' % key)
 
604
                # just drop it
 
605
                del self.metadata[key]
 
606
            if "SubPackages" in self.metadata:
 
607
                for sp in self.metadata["SubPackages"]:
 
608
                    if not _check_boolkey(sp, key):
 
609
                        logger.warning('the value of "%s" in %s sub-package is expected as bool typed, dropped!' % (key, sp['Name']))
 
610
                        del sp[key]
 
611
 
 
612
        ######### checkings for special keys ##########
 
613
        # checking for arch namespace enabled keys
 
614
        _check_arched_keys(self.metadata)
 
615
        if "SubPackages" in self.metadata:
 
616
            for sp in self.metadata["SubPackages"]:
 
617
                _check_arched_keys(sp)
 
618
 
 
619
        # checking for proposal pkgconfig requires
 
620
        if self.metadata.has_key("PkgBR"):
 
621
            _check_pkgconfig()
 
622
            pcbr = []
 
623
            br = []
 
624
            for p in self.metadata['PkgBR']:
 
625
                px = p.split()[0]
 
626
                for arch in ARCHS:
 
627
                    prefix = arch + ':'
 
628
                    if px.startswith(prefix):
 
629
                        px = px[len(prefix):]
 
630
 
 
631
                pl = self.packages
 
632
                if pl.has_key(px):
 
633
                    if len(pl[px]) == 1:
 
634
                        pcbr.append(pl[px][0])
 
635
                    else:
 
636
                        br.append(p)
 
637
                    logger.warning("""Please use one of the followings:
 
638
           - %s
 
639
         in PkgConfigBR instead of %s in PkgBR""" %('\n           - '.join(pl[px]), px))
 
640
                else:
 
641
                    br.append(p)
 
642
 
 
643
            if len(pcbr) > 0:
 
644
                if self.metadata.has_key('PkgConfigBR'):
 
645
                    pcbr.extend(self.metadata['PkgConfigBR'])
 
646
                print """
 
647
Proposal (multiple values skipped, please insert them manually):
 
648
PkgConfigBR:
 
649
    - %s
 
650
PkgBR:
 
651
    - %s
 
652
                    """ %('\n    - '.join(pcbr), '\n    - '.join(br))
 
653
 
 
654
        _check_key_epoch(self.metadata)
 
655
 
 
656
        # checking for meego valid groups
 
657
        _check_key_group(self.metadata)
 
658
        if "SubPackages" in self.metadata:
 
659
            for sp in self.metadata["SubPackages"]:
 
660
                _check_key_group(sp)
 
661
 
 
662
        # checking for validation of 'Description'
 
663
        if not _check_key_desc(self.metadata):
 
664
            logger.warning('main package has no qualified "Description" tag')
 
665
        if "SubPackages" in self.metadata:
 
666
            for sp in self.metadata["SubPackages"]:
 
667
                if not _check_key_desc(sp):
 
668
                    logger.warning('sub-pkg: %s has no qualified "Description" tag' % sp['Name'])
 
669
 
 
670
        # checking for validation of 'LocaleName' and 'LocaleOptions'
 
671
        if not _check_key_localename(self.metadata):
 
672
            self.metadata['LocaleName'] = "%{name}"
 
673
            logger.warning('lost "LocaleName" keyword, use "%{name}" as default')
 
674
 
 
675
        # checking for validation of 'NoSetup', 'SetupOptions' and 'SourcePrefix'
 
676
        _check_key_setups(self.metadata)
 
677
 
 
678
        # checking for validation of 'NoFiles'
 
679
        if 'NoFiles' in self.metadata:
 
680
            _check_key_nofiles(self.metadata)
 
681
 
 
682
        # checking duplicate 'Files' items
 
683
        if 'Files' in self.metadata:
 
684
            self._check_dup_files(self.metadata['Files'])
 
685
        if "SubPackages" in self.metadata:
 
686
            for sp in self.metadata["SubPackages"]:
 
687
                if 'Files' in sp:
 
688
                    self._check_dup_files(sp['Files'])
 
689
 
 
690
        # checking for validation of 'Configure and Builder'
 
691
        if 'Configure' in self.metadata:
 
692
            _check_key_configure(self.metadata)
 
693
        if 'Builder' in self.metadata:
 
694
            _check_key_builder(self.metadata)
 
695
 
 
696
    def __get_scm_latest_release(self):
 
697
 
 
698
        if "Archive" in self.metadata:
 
699
            archive = self.metadata['Archive']
 
700
            if archive not in ('bzip2', 'gzip'):
 
701
                archive = 'bzip2'
 
702
        else:
 
703
            archive = 'bzip2'
 
704
 
 
705
        if archive == 'bzip2':
 
706
            appendix = 'bz2'
 
707
        else:
 
708
            appendix = 'gz'
 
709
 
 
710
        scm_url = self.metadata['SCM']
 
711
 
 
712
        scm = GitAccess(scm_url)
 
713
        logger.info("Getting tags from SCM...")
 
714
        top = scm.get_toptag()
 
715
        if not top or top == self.version:
 
716
            # no need to fetch from SCM
 
717
            return
 
718
 
 
719
        logger.warning('Version in YAML shoud be updated according SCM tags')
 
720
        self.version = top
 
721
        self.metadata['Version'] = self.version
 
722
 
 
723
        pwd = os.getcwd()
 
724
        if os.path.exists("%s/%s-%s.tar.%s" %(pwd, self.pkg, self.version, appendix )):
 
725
            logger.info("Archive already exists, will not creating a new one")
 
726
        else:
 
727
            logger.info("Creating archive %s/%s-%s.tar.%s ..." % (pwd, self.pkg, self.version, appendix))
 
728
            tmp = tempfile.mkdtemp()
 
729
            os.chdir(tmp)
 
730
            os.system('git clone %s' % scm_url)
 
731
            os.chdir( "%s/%s" %(tmp, self.pkg))
 
732
            os.system(' git archive --format=tar --prefix=%s-%s/ %s | %s  > %s/%s-%s.tar.%s' \
 
733
                    % (self.pkg,
 
734
                       self.version,
 
735
                       self.version,
 
736
                       archive,
 
737
                       pwd,
 
738
                       self.pkg,
 
739
                       self.version,
 
740
                       appendix ))
 
741
            shutil.rmtree(tmp)
 
742
 
 
743
        os.chdir(pwd)
 
744
 
 
745
    def __download_sources(self):
 
746
        pkg = self.pkg
 
747
        rev = self.version
 
748
        sources = self.metadata['Sources']
 
749
 
 
750
        for s in sources:
 
751
            if s.startswith('http://') or s.startswith('https://') or s.startswith('ftp://'):
 
752
                target = s.replace('%{name}', pkg)
 
753
                target = target.replace('%{version}', rev)
 
754
                f_name = os.path.basename(target)
 
755
                if not os.path.isfile(f_name):
 
756
                    repl = logger.ask('Need to download source package: %s ?(Y/n) ' % f_name)
 
757
                    if repl == 'n': break
 
758
 
 
759
                    logger.info('Downloading latest source package from: %s' % target)
 
760
                    import urlgrabber
 
761
                    from urlgrabber.progress import text_progress_meter
 
762
                    try:
 
763
                        urlgrabber.urlgrab(target, f_name, progress_obj = text_progress_meter())
 
764
                    except urlgrabber.grabber.URLGrabError, e:
 
765
                        if e.errno == 14: # HTTPError
 
766
                            logger.warning('Invalid source URL')
 
767
                        else:
 
768
                            raise e
 
769
                    except KeyboardInterrupt:
 
770
                        logger.info('Downloading is interrupted by ^C')
 
771
                    """
 
772
                    for ext in ('.md5', '.gpg', '.sig', '.sha1sum'):
 
773
                        urllib.urlretrieve(target + ext, f_name + ext)
 
774
                    """
 
775
 
 
776
    def analyze_source(self):
 
777
        def pc_files(members):
 
778
            for tarinfo in members:
 
779
                f = os.path.split(tarinfo.name)[1]
 
780
                xx = f.split(".pc.")
 
781
                if len(xx) > 1 and xx[1] == "in":
 
782
                    extractfile
 
783
                    buf = tarinfo.tobuf()
 
784
                    #print buf
 
785
 
 
786
        tarball = None
 
787
        for uri in self.metadata['Sources']:
 
788
            fpath = os.path.basename(uri)
 
789
            fpath = fpath.replace('%{name}', self.pkg)
 
790
            fpath = fpath.replace('%{version}', self.version)
 
791
            if os.path.exists(fpath):
 
792
                try:
 
793
                    if tarfile.is_tarfile(fpath):
 
794
                        tarball = fpath
 
795
                        break
 
796
                except:
 
797
                    logger.warning('Corrupt tarball %s found!' % fpath)
 
798
                    pass
 
799
 
 
800
        prefix = None
 
801
        if tarball:
 
802
            tf = tarfile.open(tarball, 'r:*')
 
803
            for member in tf.getmembers():
 
804
                if member.type == tarfile.DIRTYPE:
 
805
                    prefix = member.name.rstrip('/')
 
806
                    break
 
807
 
 
808
            #analyze_path = tempfile.mkdtemp(dir=os.getcwd(), prefix=".spectacle_")
 
809
            #tf.extractll(path=analyze_path, members=pc_files(tf))
 
810
            for member in tf.getmembers():
 
811
                f = os.path.split(member.name)[1]
 
812
                xx = f.split(".pc.")
 
813
                if len(xx) > 1 and xx[1] == "in":
 
814
                    pc = tf.extractfile(member)
 
815
                    # TODO
 
816
            tf.close()
 
817
 
 
818
 
 
819
        # confirm 'SourcePrefix' is valid
 
820
        if 'SourcePrefix' not in self.metadata and 'NoSetup' not in self.metadata:
 
821
            # setting the default value firstly
 
822
            self.metadata['SourcePrefix'] = '%{name}-%{version}'
 
823
            if not prefix or prefix == '.':
 
824
                if tarball:
 
825
                    prefix, ignore = os.path.basename(tarball).split('.tar.')
 
826
 
 
827
            if prefix and prefix != '%s-%s' % (self.pkg, self.version):
 
828
                prefix = prefix.replace(self.pkg, '%{name}')
 
829
                prefix = prefix.replace(self.version, '%{version}')
 
830
                self.metadata['SourcePrefix'] = prefix
 
831
 
 
832
    def __parse_series(self, patches, comments):
 
833
        comment = ""
 
834
        for line in file(SERIES_PATH):
 
835
            if not line.strip():
 
836
                continue
 
837
            if line.startswith('#'):
 
838
                comment += line
 
839
            else:
 
840
                line = line.strip()
 
841
                patches.append(line)
 
842
                comments.append(comment + '# ' + line)
 
843
                comment = ''
 
844
 
 
845
    def __cleanup_boolkeys(self, items):
 
846
        """ clean up all boolean type keys,
 
847
            use the exists status to present bool value
 
848
        """
 
849
        #   for keys with default value FALSE
 
850
        for bopt in BOOLNO_KEYS:
 
851
            if bopt in items and not items[bopt]:
 
852
                del items[bopt]
 
853
        #   for keys with default value TRUE
 
854
        for bopt in BOOLYES_KEYS:
 
855
            if bopt in items and not items[bopt]:
 
856
                del items[bopt]
 
857
            else:
 
858
                items[bopt] = True
 
859
 
 
860
    def _gen_auto_requires(self, metadata, extra, pkg_name = 'main'):
 
861
        auto_requires = {
 
862
                'Lib': {'RequiresPost': ['/sbin/ldconfig'],
 
863
                        'RequiresPostUn': ['/sbin/ldconfig'],
 
864
                       },
 
865
                'Icon': {'RequiresPost': ['/bin/touch', 'gtk2'],
 
866
                        },
 
867
                'Desktop': {'PkgBR': ['desktop-file-utils'],
 
868
                           },
 
869
                'DesktopDB': {'RequiresPost': ['desktop-file-utils'],
 
870
                              'RequiresPostUn': ['desktop-file-utils'],
 
871
                             },
 
872
                'Info': {'RequiresPost': ['/sbin/install-info'],
 
873
                         'RequiresPostUn': ['/sbin/install-info'],
 
874
                        },
 
875
                'Service': {'RequiresPost': ['/sbin/service', '/sbin/chkconfig'],
 
876
                            'RequiresPostUn': ['/sbin/service', '/sbin/chkconfig'],
 
877
                           },
 
878
                'Schema': {'RequiresPost': ['GConf2'],
 
879
                           'RequiresPreUn': ['GConf2'],
 
880
                           'RequiresPre': ['GConf2'],
 
881
                          },
 
882
        }
 
883
 
 
884
        for key,reqs in auto_requires.iteritems():
 
885
            if extra[key]:
 
886
                for req,items in reqs.iteritems():
 
887
                    if req in metadata:
 
888
                        for i in items:
 
889
                            # e.g. GConf2 >= 0.14
 
890
                            yaml_reqs = map(lambda s: s.split()[0], metadata[req])
 
891
                            if i in yaml_reqs:
 
892
                                if i in metadata[req]:
 
893
                                    logger.warning('duplicate item: %s for %s in package %s' % (i,req,pkg_name))
 
894
                                # else do nothing
 
895
                            else:
 
896
                                metadata[req].append(i)
 
897
                    else:
 
898
                        metadata[req] = items
 
899
 
 
900
    def parse(self):
 
901
 
 
902
        # customized int/float constructor for Loader of in PyYAML
 
903
        # to regard all numbers as plain string
 
904
        def _no_number(self, node):
 
905
            return str(self.construct_scalar(node))
 
906
        yaml.Loader.add_constructor(u'tag:yaml.org,2002:int', _no_number)
 
907
        yaml.Loader.add_constructor(u'tag:yaml.org,2002:float', _no_number)
 
908
 
 
909
        # loading data from YAML
 
910
        try:
 
911
            self.metadata.update(yaml.load(self.stream))
 
912
        except yaml.scanner.ScannerError, e:
 
913
            logger.error('syntax error found in yaml: %s' % str(e))
 
914
 
 
915
        # verifying the sanity
 
916
        self.sanity_check()
 
917
 
 
918
        # for convenience
 
919
        self.pkg = self.metadata['Name']
 
920
        self.version = self.metadata['Version']
 
921
        try:
 
922
            self.release = self.metadata['Release']
 
923
        except KeyError:
 
924
            logger.warning('"Release" not specified, use "1" as the default value')
 
925
            self.release = self.metadata['Release'] = '1'
 
926
 
 
927
        if not self.specfile:
 
928
            self.specfile = "%s.spec" % self.pkg
 
929
        self.newspec = True
 
930
 
 
931
 
 
932
        if "RpmLintIgnore" in self.metadata:
 
933
            rpmlintrc = "%s-rpmlintrc" %self.metadata['Name']
 
934
            rpmlint = "from Config import *\n"
 
935
            for lint in self.metadata['RpmLintIgnore']:
 
936
                rpmlint = rpmlint + "addFilter(\"%s\")\n" %lint
 
937
 
 
938
            file = open(rpmlintrc, "w")
 
939
            file.write(rpmlint)
 
940
            file.close()
 
941
 
 
942
        # handling 'ExtraSources', extra separated files which need to be install
 
943
        # specific paths
 
944
        if "ExtraSources" in self.metadata:
 
945
 
 
946
            # confirm 'Sources' valid
 
947
            if 'Sources' not in self.metadata:
 
948
                self.metadata['Sources'] = []
 
949
 
 
950
            extra_srcs = []
 
951
            extra_install = ''
 
952
            count = len(self.metadata['Sources'])
 
953
            for extra_src in self.metadata['ExtraSources']:
 
954
                try:
 
955
                    file, path = map(str.strip, extra_src.split(';'))
 
956
                except:
 
957
                    file = extra_src.strip()
 
958
                    path = ''
 
959
                self.extras_filelist.append(os.path.join(path, file))
 
960
 
 
961
                extra_srcs.append(file)
 
962
                if path:
 
963
                    extra_install += "mkdir -p %%{buildroot}%s\n" % (path)
 
964
                extra_install += "cp -a %%{SOURCE%s} %%{buildroot}%s\n" % (count, path)
 
965
                count = count + 1
 
966
 
 
967
            self.metadata['Sources'].extend(extra_srcs)
 
968
            self.metadata['ExtraInstall'] = extra_install
 
969
 
 
970
        if self.download_new:
 
971
            if not self.skip_scm:
 
972
                # update to SCM latest release
 
973
                if "SCM" in self.metadata:
 
974
                    self.__get_scm_latest_release()
 
975
 
 
976
            # if no srcpkg with yaml.version exists in cwd, trying to download
 
977
            if 'Sources' in self.metadata:
 
978
                self.__download_sources()
 
979
 
 
980
        # handle patches with extra options
 
981
        if "Patches" in self.metadata:
 
982
            patches = self.metadata['Patches']
 
983
 
 
984
            self.metadata['Patches']   = []
 
985
            self.metadata['PatchOpts'] = []
 
986
            self.metadata['PatchCmts'] = []
 
987
            for patch in patches:
 
988
                self.metadata['PatchCmts'].append('# ' + patch)
 
989
 
 
990
                if isinstance(patch, str):
 
991
                    self.metadata['Patches'].append(patch)
 
992
                    self.metadata['PatchOpts'].append('-p1')
 
993
                elif isinstance(patch, dict):
 
994
                    self.metadata['Patches'].append(patch.keys()[0])
 
995
                    self.metadata['PatchOpts'].append(patch.values()[0])
 
996
                elif isinstance(patch, list):
 
997
                    self.metadata['Patches'].append(patch[0])
 
998
                    self.metadata['PatchOpts'].append(' '.join(patch[1:]))
 
999
 
 
1000
        # detect 'series.conf' in current dir
 
1001
        if os.path.exists(SERIES_PATH):
 
1002
            if "Patches" in self.metadata:
 
1003
                logger.warning('Both "Patches" tag in yaml and series.conf exists, please use only one.')
 
1004
            else:
 
1005
                self.metadata['Patches'] = []
 
1006
                self.metadata['PatchCmts'] = []
 
1007
                self.__parse_series(self.metadata['Patches'],
 
1008
                                    self.metadata['PatchCmts'])
 
1009
 
 
1010
        if 'Sources' in self.metadata:
 
1011
            self.analyze_source()
 
1012
 
 
1013
        # clean up all boolean type keys, use the exists status to present bool value
 
1014
        self.__cleanup_boolkeys(self.metadata)
 
1015
        if "SubPackages" in self.metadata:
 
1016
            for sp in self.metadata["SubPackages"]:
 
1017
                self.__cleanup_boolkeys(sp)
 
1018
 
 
1019
        # check duplicate default configopts
 
1020
        dup = '--disable-static'
 
1021
        if 'ConfigOptions' in self.metadata and dup in self.metadata['ConfigOptions']:
 
1022
            logger.warning('found duplicate configure options: "%s", please remove it' % dup)
 
1023
            self.metadata['ConfigOptions'].remove(dup)
 
1024
            if not self.metadata['ConfigOptions']:
 
1025
                del self.metadata['ConfigOptions']
 
1026
 
 
1027
        # check duplicate requires for base package
 
1028
        if "SubPackages" in self.metadata:
 
1029
            if 'Epoch' in self.metadata:
 
1030
                autodep = "%{name} = %{epoch}:%{version}-%{release}"
 
1031
            else:
 
1032
                autodep = "%{name} = %{version}-%{release}"
 
1033
 
 
1034
            for sp in self.metadata["SubPackages"]:
 
1035
                if 'Requires' in sp and autodep in sp['Requires'] and 'AutoDepend' in sp:
 
1036
                    logger.warning('found duplicate Requires for %s in sub-pkg:%s, please remove it' %(autodep, sp['Name']))
 
1037
                    sp['Requires'].remove(autodep)
 
1038
                    if not sp['Requires']:
 
1039
                        del sp['Requires']
 
1040
 
 
1041
        # initialize extra flags for subpkgs
 
1042
        if "SubPackages" in self.metadata:
 
1043
            for sp in self.metadata["SubPackages"]:
 
1044
                self.extra['subpkgs'][sp['Name']] = copy.deepcopy(self.extra_per_pkg)
 
1045
 
 
1046
 
 
1047
        """ NOTE
 
1048
        we need NOT to do the following checking:
 
1049
         * whether auto-added Requires(include pre/post/preun/postun) duplicated
 
1050
 
 
1051
        They should be checked by users manually.
 
1052
        """
 
1053
 
 
1054
    def parse_files(self, files = {}, docs = {}):
 
1055
 
 
1056
        py_path_check = False
 
1057
        if 'Builder' in self.metadata and self.metadata['Builder'] == 'python':
 
1058
            py_path_check = True
 
1059
            if 'BuildArch' in self.metadata and self.metadata['BuildArch'] == 'noarch':
 
1060
                py_path = '%{python_sitelib}'
 
1061
            else:
 
1062
                py_path = '%{python_sitearch}'
 
1063
 
 
1064
        for pkg_name,v in files.iteritems():
 
1065
            if pkg_name == 'main':
 
1066
                pkg_extra = self.extra
 
1067
            else:
 
1068
                pkg_extra = self.extra['subpkgs'][pkg_name]
 
1069
 
 
1070
            for l in v:
 
1071
                if re.match('.*\.info\..*', l) or re.match('.*(usr/share/info|%{_infodir}).*info\..*$', l):
 
1072
                    p1 = re.compile('^%doc\s+(.*)')
 
1073
                    l1 = p1.sub(r'\1', l)
 
1074
                    pkg_extra['Infos'].append(l1)
 
1075
                    pkg_extra['Info'] = True
 
1076
                elif re.match('.*(usr/share|%{_datadir})/applications/.*\.desktop$', l):
 
1077
                    if 'NoDesktop' not in self.metadata:
 
1078
                        pkg_extra['Desktop'] = True
 
1079
                elif re.match('.*etc/rc.d/init.d.*', l) or re.match('.*etc/init.d.*', l):
 
1080
                    pkg_extra['Service'] = True
 
1081
                elif re.match('.*(%{_libdir}|%{_lib}|/lib|/usr/lib)/[^/]*[.*?]+so([.*?]+.*$|$)', l) or \
 
1082
                   re.match('.*(/ld.so.conf.d/).*', l):
 
1083
                    if pkg_name != 'devel' and not pkg_name.endswith('-devel'):
 
1084
                        # 'devel' sub pkgs should not set Lib flags
 
1085
                        pkg_extra['Lib'] = True
 
1086
                elif re.match('.*(%{_libdir}|%{_lib}).*', l) and re.match('.*\.a$', l):
 
1087
                    # if *.a found, set 'HasStatic' flag for MAIN pkg
 
1088
                    self.extra['HasStatic'] = True
 
1089
                elif re.match('.*\.schema.*', l):
 
1090
                    comp = l.split()
 
1091
                    if len(comp) > 1:
 
1092
                        l = comp[1]
 
1093
                    pkg_extra['Schema'] = True
 
1094
                    pkg_extra['Schemas'].append(l)
 
1095
                elif re.match('.*\/icons\/.*', l):
 
1096
                    pkg_extra['Icon'] = True
 
1097
 
 
1098
                # special checking for python packages
 
1099
                if py_path_check:
 
1100
                    if '%{python_sitelib}' in l or '%{python_sitearch}' in l:
 
1101
                        if py_path not in l:
 
1102
                            logger.error('please use %s in %%files to specify module installation path' % py_path)
 
1103
 
 
1104
        # check whether need to update desktop database
 
1105
        if self.extra['Desktop'] == True and 'UpdateDesktopDB' in self.metadata:
 
1106
            self.extra['DesktopDB'] = True
 
1107
 
 
1108
        # files listed in '%doc' need handling
 
1109
        # TODO to be cleanup
 
1110
        for pkg_name,v in docs.iteritems():
 
1111
            if pkg_name == 'main':
 
1112
                pkg_extra = self.extra
 
1113
            else:
 
1114
                pkg_extra = self.extra['subpkgs'][pkg_name]
 
1115
 
 
1116
            for l in v:
 
1117
                for item in l.split():
 
1118
                    if re.match('.*\.info.*', item) or \
 
1119
                       re.match('.*(usr/share/info|%{_infodir}).*', item):
 
1120
                        pkg_extra['Info'] = True
 
1121
                        pkg_extra['Infos'].append(item)
 
1122
 
 
1123
    def parse_existing(self, spec_fpath):
 
1124
        sin = re.compile("^# >> ([^\s]+)\s*(.*)")
 
1125
        sout = re.compile("^# << ([^\s]+)\s*(.*)")
 
1126
 
 
1127
        version = None
 
1128
 
 
1129
        # temp vars
 
1130
        recording = []
 
1131
        record = False
 
1132
 
 
1133
        files = {}
 
1134
        install = {}
 
1135
        build = {}
 
1136
        macros = {}         # global macros
 
1137
        setup = {}
 
1138
        pre = {}
 
1139
        preun = {}
 
1140
        post = {}
 
1141
        postun = {}
 
1142
        check = {} # extra headers
 
1143
 
 
1144
        line_num = 0
 
1145
        for i in file(spec_fpath):
 
1146
            i = i.strip()
 
1147
            if line_num < 3:
 
1148
                if line_num == 2:
 
1149
                    m = re.match("^#.*spectacle version ([\d.]+)$", i)
 
1150
                    if m:
 
1151
                        version = m.group(1)
 
1152
                        spec_ver = _V.LooseVersion(version)
 
1153
                        cur_ver = _V.LooseVersion(__version__.VERSION)
 
1154
                        if cur_ver < spec_ver:
 
1155
                            logger.warning('!!! Current spectacle version is lower than the one used for this package last time')
 
1156
                            repl = logger.ask('Please upgrade your spectacle firstly, continue?(y/N) ')
 
1157
                            if repl != 'y':
 
1158
                                sys.exit(1)
 
1159
                    else:
 
1160
                        repl = logger.ask('The exist spec file might be not a spectacle generated one, continue?(y/N) ')
 
1161
                        if repl != 'y':
 
1162
                            sys.exit(1)
 
1163
                line_num += 1
 
1164
 
 
1165
            matchin = sin.match(i)
 
1166
            matchout = sout.match(i)
 
1167
 
 
1168
            if matchin and not record:
 
1169
                record = True
 
1170
                recording = []
 
1171
                continue
 
1172
 
 
1173
            if matchout:
 
1174
                record = False
 
1175
                if not recording: continue # empty
 
1176
 
 
1177
                if matchout.group(1) == "files":
 
1178
                    if matchout.group(2):
 
1179
                        files[matchout.group(2)] = recording
 
1180
                    else:
 
1181
                        files['main'] = recording
 
1182
                elif matchout.group(1) == "post":
 
1183
                    if matchout.group(2):
 
1184
                        post[matchout.group(2)] = recording
 
1185
                    else:
 
1186
                        post['main'] = recording
 
1187
                elif matchout.group(1) == "postun":
 
1188
                    if matchout.group(2):
 
1189
                        postun[matchout.group(2)] = recording
 
1190
                    else:
 
1191
                        postun['main'] = recording
 
1192
                elif matchout.group(1) == "pre":
 
1193
                    if matchout.group(2):
 
1194
                        pre[matchout.group(2)] = recording
 
1195
                    else:
 
1196
                        pre['main'] = recording
 
1197
                elif matchout.group(1) == "preun":
 
1198
                    if matchout.group(2):
 
1199
                        preun[matchout.group(2)] = recording
 
1200
                    else:
 
1201
                        preun['main'] = recording
 
1202
                elif matchout.group(1) == "install":
 
1203
                    install[matchout.group(2)] = recording
 
1204
                elif matchout.group(1) == "build":
 
1205
                    build[matchout.group(2)] = recording
 
1206
                elif matchout.group(1) == "macros":
 
1207
                    macros['main'] = recording
 
1208
                elif matchout.group(1) == "setup":
 
1209
                    setup['main'] = recording
 
1210
                elif matchout.group(1) == "check" or \
 
1211
                     matchout.group(1) == "check_scriptlets": #TODO, remove it whenever cleanup
 
1212
                    check['main'] = recording
 
1213
 
 
1214
            if record:
 
1215
                recording.append(i)
 
1216
 
 
1217
        content= { "files" : files,
 
1218
                   "install": install,
 
1219
                   "build" : build,
 
1220
                 }
 
1221
 
 
1222
        if macros:
 
1223
            content["macros"] = macros
 
1224
        if setup:
 
1225
            content["setup"] = setup
 
1226
        if post:
 
1227
            content["post"] = post
 
1228
        if postun:
 
1229
            content["postun"] = postun
 
1230
        if pre:
 
1231
            content["pre"] = pre
 
1232
        if preun:
 
1233
            content["preun"] = preun
 
1234
 
 
1235
        if check and 'Check' in self.metadata:
 
1236
            content["check"] = check
 
1237
 
 
1238
        # checking whether both 'Files' key and inline files exists
 
1239
        if files:
 
1240
            files_yaml = False
 
1241
            if 'Files' in self.metadata and self.metadata['Files']:
 
1242
                files_yaml = True
 
1243
            elif 'SubPackages' in self.metadata:
 
1244
                for spkg in self.metadata['SubPackages']:
 
1245
                    if 'Files' in spkg:
 
1246
                        files_yaml = True
 
1247
                        break
 
1248
 
 
1249
            if files_yaml:
 
1250
                logger.warning('both "Files" YAML keyword and inline %file content in spec present')
 
1251
 
 
1252
        # try to remove duplicate '%defattr' in files list
 
1253
        for key in content['files']:
 
1254
            self._check_dup_files(content['files'][key])
 
1255
 
 
1256
        return content
 
1257
 
 
1258
    def process(self, extra_content):
 
1259
        """ Read in old spec and record all customized stuff,
 
1260
            And auto-detect extra infos from %files list
 
1261
        """
 
1262
 
 
1263
        # backup old spec file if needed
 
1264
        if os.path.exists(self.specfile):
 
1265
            if self.clean_old:
 
1266
                # backup original file
 
1267
                backdir = 'spec.backup'
 
1268
                try:
 
1269
                    os.mkdir(backdir)
 
1270
                except:
 
1271
                    pass
 
1272
                bak_spec_fpath = os.path.join(backdir, self.specfile)
 
1273
                if os.path.exists(bak_spec_fpath):
 
1274
                    repl = logger.ask('%s will be overwritten by the backup, continue?(Y/n) ' % bak_spec_fpath)
 
1275
                    if repl == 'n':
 
1276
                        sys.exit(1)
 
1277
 
 
1278
                logger.info('Old spec file is saved as "%s"' % bak_spec_fpath)
 
1279
                os.rename(self.specfile, bak_spec_fpath)
 
1280
            else:
 
1281
                self.newspec = False
 
1282
 
 
1283
        specfile = self.specfile
 
1284
        if not self.newspec:
 
1285
            self.extra['content'] = self.parse_existing(specfile)
 
1286
 
 
1287
        if extra_content:
 
1288
            self.extra['content'].update(extra_content)
 
1289
 
 
1290
        """
 
1291
        TODO: should not regard them as the content of MAIN pkg
 
1292
        if self.extras_filelist:
 
1293
            try:
 
1294
                self.extra['content']['files']['main'].extend(self.extras_filelist)
 
1295
            except KeyError:
 
1296
                self.extra['content'].update({'files': {'main': self.extras_filelist}})
 
1297
        """
 
1298
 
 
1299
        try:
 
1300
            # TODO, cleanup docs handling when all pkgs need not, include spec.tmpl
 
1301
            docs = {}
 
1302
            if 'Documents' in self.metadata:
 
1303
                docs['main'] = self.metadata['Documents']
 
1304
            if "SubPackages" in self.metadata:
 
1305
                for sp in self.metadata["SubPackages"]:
 
1306
                    if 'Documents' in sp:
 
1307
                        docs[sp['Name']] = sp['Documents']
 
1308
 
 
1309
            if 'files' in self.extra['content']:
 
1310
                files = copy.deepcopy(self.extra['content']['files'])
 
1311
            else:
 
1312
                files = {}
 
1313
 
 
1314
            if 'Files' in self.metadata:
 
1315
                if 'main' in files:
 
1316
                    files['main'] += self.metadata['Files']
 
1317
                else:
 
1318
                    files['main'] = self.metadata['Files']
 
1319
            if "SubPackages" in self.metadata:
 
1320
                for sp in self.metadata["SubPackages"]:
 
1321
                    if 'Files' in sp:
 
1322
                        if sp['Name'] in files:
 
1323
                            files[sp['Name']] += sp['Files']
 
1324
                        else:
 
1325
                            files[sp['Name']] = sp['Files']
 
1326
 
 
1327
            self.parse_files(files, docs)
 
1328
        except KeyError:
 
1329
            pass
 
1330
 
 
1331
        # adding automatic requires according %files
 
1332
        self._gen_auto_requires(self.metadata, self.extra)
 
1333
        if "SubPackages" in self.metadata:
 
1334
            for sp in self.metadata["SubPackages"]:
 
1335
                self._gen_auto_requires(sp, self.extra['subpkgs'][sp['Name']], sp['Name'])
 
1336
 
 
1337
        # check duplicate 'ldconfig' in %post/%postun
 
1338
        #   actually, all auto generated scripts in %post like sections should be checked
 
1339
        #   for duplicate issue, but it's not nice to do that according current design.
 
1340
        #   But for 'ldconfig', there're too many issues with it, the following code
 
1341
        #   is just a workaround to fix them.
 
1342
        self._check_dup_ldconfig()
 
1343
        if "SubPackages" in self.metadata:
 
1344
            for sp in self.metadata["SubPackages"]:
 
1345
                self._check_dup_ldconfig(sp['Name'])
 
1346
 
 
1347
        # check duplicate other auto-scriptlets in %post/%postun
 
1348
        self._check_dup_scriptlets()
 
1349
        if "SubPackages" in self.metadata:
 
1350
            for sp in self.metadata["SubPackages"]:
 
1351
                self._check_dup_scriptlets(sp['Name'])
 
1352
 
 
1353
        spec_content = spec.spec(searchList=[{
 
1354
                                        'metadata': self.metadata,
 
1355
                                        'extra': self.extra
 
1356
                                      }]).respond()
 
1357
 
 
1358
        file = open(specfile, "w")
 
1359
        file.write(spec_content.encode('utf-8'))
 
1360
        file.close()
 
1361
 
 
1362
def generate_rpm(yaml_fpath, clean_old = False, extra_content = None, spec_fpath=None, download_new=True, skip_scm=False):
 
1363
    rpm_writer = RPMWriter(yaml_fpath, spec_fpath, clean_old, download_new, skip_scm)
 
1364
    rpm_writer.parse()
 
1365
    rpm_writer.process(extra_content)
 
1366
 
 
1367
    return rpm_writer.specfile, rpm_writer.newspec