2
# vim: ai ts=4 sts=4 et sw=4
4
# Copyright (c) 2009 Intel Corporation
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
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
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.
24
import distutils.version as _V
37
SERIES_PATH = 'series.conf'
46
SUB_MAND_KEYS = ('Name',
51
# boolean keys with the default 'False' value
52
BOOLNO_KEYS = ('Check',
53
'SupportOtherDistros',
63
# boolean keys with the default 'True' value
64
BOOLYES_KEYS = ('UseAsNeeded',
67
BOOL_KEYS = BOOLNO_KEYS + BOOLYES_KEYS
69
LIST_KEYS = ('Sources',
115
SUBONLY_KEYS = ('AsWholeName',
119
SUBWARN_KEYS = ('PkgBR',
123
SUBAVAIL_KEYS = ('Name',
140
'Version', 'Release', 'Epoch', 'URL', 'BuildArch' # very rare
143
DROP_KEYS = ('PostScripts',
147
RENAMED_KEYS = {'NeedCheckSection': 'Check',
148
'NoLocale': 'NoAutoLocale',
151
TYPO_KEYS = {'BuildRequires': 'PkgBR or PkgConfigBR',
155
ARCHED_KEYS = ('Requires',
161
ARCHS = ('ix86', 'arm')
163
CONFIGURES = ('configure', 'reconfigure', 'autogen', 'cmake', 'none')
164
BUILDERS = ('make', 'single-make', 'python', 'perl', 'qmake', 'cmake', 'none')
167
def __init__(self, path):
173
fh = os.popen('git ls-remote --tags "%s" 2>/dev/null' % self.path)
175
prefix = 'refs/tags/'
178
node, tag = line.split(None, 1)
179
if not tag.startswith(prefix):
181
tagx = tag[len(prefix):len(tag)]
183
except KeyboardInterrupt:
188
def get_toptag(self):
189
vers = [_V.LooseVersion(tag) for tag in self._gettags()]
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'
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")}
225
self.specfile = spec_fpath
228
self.clean_old = clean_old
229
self.download_new = download_new
230
self.skip_scm = skip_scm
232
# initialize extra info for spec
233
self.extra = { 'subpkgs': {}, 'content': {} }
235
# update extra info for main package
236
self.extra.update(copy.deepcopy(self.extra_per_pkg))
238
# record filelist from 'ExtraSources' directive
239
self.extras_filelist = []
242
self.stream = file(yaml_fpath, 'r')
244
logger.error('Cannot read file: %s' % yaml_fpath)
247
print yaml.dump(yaml.load(self.stream))
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
255
logger.warning('found duplicate "%s" in file list, removed!' % found_dup)
256
files.remove(found_dup)
258
def _check_dup_ldconfig(self, pkgname = None):
261
if not self.extra['Lib']:
264
if not self.extra['subpkgs'][pkgname]['Lib']:
267
dup1 = '/sbin/ldconfig'
270
for sec in ('post', 'postun'):
272
extra = self.extra['content'][sec][pkgname]
275
found_dup = dup1 if dup1 in extra else dup2 if dup2 in extra else None
277
extra.remove(found_dup)
278
logger.warning('Found duplicate "%s" calling in "%%%s" of %s package, removed!' % (found_dup, sec, pkgname))
280
def _check_dup_scriptlets(self, pkgname = None):
285
extra = self.extra['subpkgs'][pkgname]
288
re_idstr = re.compile('^desktop-file-install\s+')
290
lines = extra['content']['install']['pre'] + \
291
extra['content']['install']['post']
296
if re_idstr.match(line):
297
logger.warning('Found possible duplicate "desktop-file-install" script in post install')
300
if extra['DesktopDB']:
301
re_idstr = re.compile('^update-desktop-database\s+')
302
for sec in ('post', 'postun'):
304
lines = self.extra['content'][sec][pkgname]
309
if re_idstr.match(line):
310
logger.warning('Found possible duplicate "update-desktop-database" script in %%%s of %s package'%(sec, pkgname))
314
re_idstr = re.compile('^%install_info')
315
for sec in ('post', 'postun'):
317
lines = self.extra['content'][sec][pkgname]
322
if re_idstr.match(line):
323
logger.warning('Found possible duplicate "%%install_info..." script in %%%s of %s package'%(sec, pkgname))
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'):
331
lines = self.extra['content'][sec][pkgname]
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))
342
re_idstr = re.compile('gconftool-2\s+')
343
for sec in ('post', 'pre', 'preun'):
345
lines = self.extra['content'][sec][pkgname]
350
if re_idstr.search(line):
351
logger.warning('Found possible duplicate "gconftool-2" script in %%%s of %s package'%(sec, pkgname))
354
def sanity_check(self):
356
def _check_empty_keys(metadata):
357
""" return the empty keys """
359
for key in metadata.keys():
360
if metadata[key] is None:
366
def _check_mandatory_keys(metadata, subpkg = None):
367
""" return [] if all mandatory keys found, otherwise return the lost keys """
369
mkeys = list(SUB_MAND_KEYS)
371
mkeys = list(MAND_KEYS)
380
def _check_invalid_keys(metadata, subpkg = None):
381
""" return list of invalid keys """
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:
391
all_keys = list(SUBAVAIL_KEYS + SUBWARN_KEYS + SUBONLY_KEYS)
395
if key not in all_keys:
398
# whether the invalid keys are common typo
401
logger.warning('"%s" might be a typo of %s, please correct it' %(key,TYPO_KEYS[key]))
406
def _check_subwarn_keys(metadata, subpkg):
407
for key in SUBWARN_KEYS:
409
logger.warning('"%s" found in sub-pkg: %s, please consider to move it to main package' %(key, subpkg))
411
def _check_key_group(metadata):
412
if metadata.has_key("Group"):
414
for line in open("/usr/share/spectacle/GROUPS"):
415
if metadata['Group'] in line:
419
logger.warning('Group \'%s\' is not in the list of approved groups. See /usr/share/spectacle/GROUPS for the complete list.' % (metadata['Group']))
421
def _check_key_epoch(metadata):
422
if 'Epoch' in metadata:
423
logger.warning('Please consider to remove "Epoch"')
425
def _check_pkgconfig():
426
pkgcfg = csv.reader(open('/usr/share/spectacle/pkgconfig-provides.csv'), delimiter=',')
428
pc = re.search('pkgconfig\(([^)]+)\)', row[1])
430
if self.packages.has_key(row[0]):
431
ll = self.packages[row[0]]
433
self.packages[row[0]] = ll
435
self.packages[row[0]] = [m]
437
def _check_key_desc(metadata):
438
""" sub-routine for 'description' checking """
439
if 'Description' not in metadata or \
440
metadata['Description'] == '%{summary}':
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):
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):
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):
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))
470
for key in ARCHED_KEYS:
473
__check_arch(key, metadata[key])
474
elif key in LIST_KEYS:
475
for item in metadata[key]:
476
__check_arch(key, item)
478
def _check_key_localename(metadata):
479
""" sub-routine for 'LocaleName' checking """
480
if 'LocaleOptions' in metadata and 'LocaleName' not in metadata:
484
def _check_dropped_keys(metadata):
485
for key in DROP_KEYS:
487
logger.warning('Deprecated key: %s found, please use other valid keys' % key)
489
def _check_renamed_keys(metadata):
490
for key in RENAMED_KEYS:
492
metadata[RENAMED_KEYS[key]] = metadata[key]
494
logger.warning('Renamed key: %s found, please use %s instead' % (key, RENAMED_KEYS[key]))
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')
503
if 'SetupOptions' in metadata and 'SourcePrefix' in metadata:
504
logger.warning('"SourcePrefix" will have NO effect when "SetupOptions" specified in YAML')
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',
518
if req in metadata and metadata[req]:
519
logger.warning('"NoFiles" exists, key %s has no effect any more' % req)
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)))
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)
535
# checking for empty keys
536
keys = _check_empty_keys(self.metadata)
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)
543
logger.warning('Please remove empty keys in %s subpackage: %s' % (sp['Name'], ', '.join(keys)))
545
# checking for mandatory keys
546
keys = _check_mandatory_keys(self.metadata)
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'])
553
logger.error('Missing mandatory keys for sub-pkg %s: %s' % (sp['Name'], ', '.join(keys)))
555
# checking for unexpected keys
556
keys = _check_invalid_keys(self.metadata)
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'])
563
logger.warning('Unexpected keys for sub-pkg %s found: %s' % (sp['Name'], ', '.join(keys)))
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'])
570
# checking for deprecated keys
571
_check_dropped_keys(self.metadata)
573
# checking for renamed keys
574
_check_renamed_keys(self.metadata)
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']))
589
# checking for STR expected 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])
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)
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']))
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)
619
# checking for proposal pkgconfig requires
620
if self.metadata.has_key("PkgBR"):
624
for p in self.metadata['PkgBR']:
628
if px.startswith(prefix):
629
px = px[len(prefix):]
634
pcbr.append(pl[px][0])
637
logger.warning("""Please use one of the followings:
639
in PkgConfigBR instead of %s in PkgBR""" %('\n - '.join(pl[px]), px))
644
if self.metadata.has_key('PkgConfigBR'):
645
pcbr.extend(self.metadata['PkgConfigBR'])
647
Proposal (multiple values skipped, please insert them manually):
652
""" %('\n - '.join(pcbr), '\n - '.join(br))
654
_check_key_epoch(self.metadata)
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"]:
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'])
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')
675
# checking for validation of 'NoSetup', 'SetupOptions' and 'SourcePrefix'
676
_check_key_setups(self.metadata)
678
# checking for validation of 'NoFiles'
679
if 'NoFiles' in self.metadata:
680
_check_key_nofiles(self.metadata)
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"]:
688
self._check_dup_files(sp['Files'])
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)
696
def __get_scm_latest_release(self):
698
if "Archive" in self.metadata:
699
archive = self.metadata['Archive']
700
if archive not in ('bzip2', 'gzip'):
705
if archive == 'bzip2':
710
scm_url = self.metadata['SCM']
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
719
logger.warning('Version in YAML shoud be updated according SCM tags')
721
self.metadata['Version'] = self.version
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")
727
logger.info("Creating archive %s/%s-%s.tar.%s ..." % (pwd, self.pkg, self.version, appendix))
728
tmp = tempfile.mkdtemp()
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' \
745
def __download_sources(self):
748
sources = self.metadata['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
759
logger.info('Downloading latest source package from: %s' % target)
761
from urlgrabber.progress import text_progress_meter
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')
769
except KeyboardInterrupt:
770
logger.info('Downloading is interrupted by ^C')
772
for ext in ('.md5', '.gpg', '.sig', '.sha1sum'):
773
urllib.urlretrieve(target + ext, f_name + ext)
776
def analyze_source(self):
777
def pc_files(members):
778
for tarinfo in members:
779
f = os.path.split(tarinfo.name)[1]
781
if len(xx) > 1 and xx[1] == "in":
783
buf = tarinfo.tobuf()
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):
793
if tarfile.is_tarfile(fpath):
797
logger.warning('Corrupt tarball %s found!' % fpath)
802
tf = tarfile.open(tarball, 'r:*')
803
for member in tf.getmembers():
804
if member.type == tarfile.DIRTYPE:
805
prefix = member.name.rstrip('/')
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]
813
if len(xx) > 1 and xx[1] == "in":
814
pc = tf.extractfile(member)
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 == '.':
825
prefix, ignore = os.path.basename(tarball).split('.tar.')
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
832
def __parse_series(self, patches, comments):
834
for line in file(SERIES_PATH):
837
if line.startswith('#'):
842
comments.append(comment + '# ' + line)
845
def __cleanup_boolkeys(self, items):
846
""" clean up all boolean type keys,
847
use the exists status to present bool value
849
# for keys with default value FALSE
850
for bopt in BOOLNO_KEYS:
851
if bopt in items and not items[bopt]:
853
# for keys with default value TRUE
854
for bopt in BOOLYES_KEYS:
855
if bopt in items and not items[bopt]:
860
def _gen_auto_requires(self, metadata, extra, pkg_name = 'main'):
862
'Lib': {'RequiresPost': ['/sbin/ldconfig'],
863
'RequiresPostUn': ['/sbin/ldconfig'],
865
'Icon': {'RequiresPost': ['/bin/touch', 'gtk2'],
867
'Desktop': {'PkgBR': ['desktop-file-utils'],
869
'DesktopDB': {'RequiresPost': ['desktop-file-utils'],
870
'RequiresPostUn': ['desktop-file-utils'],
872
'Info': {'RequiresPost': ['/sbin/install-info'],
873
'RequiresPostUn': ['/sbin/install-info'],
875
'Service': {'RequiresPost': ['/sbin/service', '/sbin/chkconfig'],
876
'RequiresPostUn': ['/sbin/service', '/sbin/chkconfig'],
878
'Schema': {'RequiresPost': ['GConf2'],
879
'RequiresPreUn': ['GConf2'],
880
'RequiresPre': ['GConf2'],
884
for key,reqs in auto_requires.iteritems():
886
for req,items in reqs.iteritems():
889
# e.g. GConf2 >= 0.14
890
yaml_reqs = map(lambda s: s.split()[0], metadata[req])
892
if i in metadata[req]:
893
logger.warning('duplicate item: %s for %s in package %s' % (i,req,pkg_name))
896
metadata[req].append(i)
898
metadata[req] = items
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)
909
# loading data from YAML
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))
915
# verifying the sanity
919
self.pkg = self.metadata['Name']
920
self.version = self.metadata['Version']
922
self.release = self.metadata['Release']
924
logger.warning('"Release" not specified, use "1" as the default value')
925
self.release = self.metadata['Release'] = '1'
927
if not self.specfile:
928
self.specfile = "%s.spec" % self.pkg
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
938
file = open(rpmlintrc, "w")
942
# handling 'ExtraSources', extra separated files which need to be install
944
if "ExtraSources" in self.metadata:
946
# confirm 'Sources' valid
947
if 'Sources' not in self.metadata:
948
self.metadata['Sources'] = []
952
count = len(self.metadata['Sources'])
953
for extra_src in self.metadata['ExtraSources']:
955
file, path = map(str.strip, extra_src.split(';'))
957
file = extra_src.strip()
959
self.extras_filelist.append(os.path.join(path, file))
961
extra_srcs.append(file)
963
extra_install += "mkdir -p %%{buildroot}%s\n" % (path)
964
extra_install += "cp -a %%{SOURCE%s} %%{buildroot}%s\n" % (count, path)
967
self.metadata['Sources'].extend(extra_srcs)
968
self.metadata['ExtraInstall'] = extra_install
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()
976
# if no srcpkg with yaml.version exists in cwd, trying to download
977
if 'Sources' in self.metadata:
978
self.__download_sources()
980
# handle patches with extra options
981
if "Patches" in self.metadata:
982
patches = self.metadata['Patches']
984
self.metadata['Patches'] = []
985
self.metadata['PatchOpts'] = []
986
self.metadata['PatchCmts'] = []
987
for patch in patches:
988
self.metadata['PatchCmts'].append('# ' + patch)
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:]))
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.')
1005
self.metadata['Patches'] = []
1006
self.metadata['PatchCmts'] = []
1007
self.__parse_series(self.metadata['Patches'],
1008
self.metadata['PatchCmts'])
1010
if 'Sources' in self.metadata:
1011
self.analyze_source()
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)
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']
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}"
1032
autodep = "%{name} = %{version}-%{release}"
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']:
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)
1048
we need NOT to do the following checking:
1049
* whether auto-added Requires(include pre/post/preun/postun) duplicated
1051
They should be checked by users manually.
1054
def parse_files(self, files = {}, docs = {}):
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}'
1062
py_path = '%{python_sitearch}'
1064
for pkg_name,v in files.iteritems():
1065
if pkg_name == 'main':
1066
pkg_extra = self.extra
1068
pkg_extra = self.extra['subpkgs'][pkg_name]
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):
1093
pkg_extra['Schema'] = True
1094
pkg_extra['Schemas'].append(l)
1095
elif re.match('.*\/icons\/.*', l):
1096
pkg_extra['Icon'] = True
1098
# special checking for python packages
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)
1104
# check whether need to update desktop database
1105
if self.extra['Desktop'] == True and 'UpdateDesktopDB' in self.metadata:
1106
self.extra['DesktopDB'] = True
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
1114
pkg_extra = self.extra['subpkgs'][pkg_name]
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)
1123
def parse_existing(self, spec_fpath):
1124
sin = re.compile("^# >> ([^\s]+)\s*(.*)")
1125
sout = re.compile("^# << ([^\s]+)\s*(.*)")
1136
macros = {} # global macros
1142
check = {} # extra headers
1145
for i in file(spec_fpath):
1149
m = re.match("^#.*spectacle version ([\d.]+)$", i)
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) ')
1160
repl = logger.ask('The exist spec file might be not a spectacle generated one, continue?(y/N) ')
1165
matchin = sin.match(i)
1166
matchout = sout.match(i)
1168
if matchin and not record:
1175
if not recording: continue # empty
1177
if matchout.group(1) == "files":
1178
if matchout.group(2):
1179
files[matchout.group(2)] = recording
1181
files['main'] = recording
1182
elif matchout.group(1) == "post":
1183
if matchout.group(2):
1184
post[matchout.group(2)] = recording
1186
post['main'] = recording
1187
elif matchout.group(1) == "postun":
1188
if matchout.group(2):
1189
postun[matchout.group(2)] = recording
1191
postun['main'] = recording
1192
elif matchout.group(1) == "pre":
1193
if matchout.group(2):
1194
pre[matchout.group(2)] = recording
1196
pre['main'] = recording
1197
elif matchout.group(1) == "preun":
1198
if matchout.group(2):
1199
preun[matchout.group(2)] = recording
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
1217
content= { "files" : files,
1223
content["macros"] = macros
1225
content["setup"] = setup
1227
content["post"] = post
1229
content["postun"] = postun
1231
content["pre"] = pre
1233
content["preun"] = preun
1235
if check and 'Check' in self.metadata:
1236
content["check"] = check
1238
# checking whether both 'Files' key and inline files exists
1241
if 'Files' in self.metadata and self.metadata['Files']:
1243
elif 'SubPackages' in self.metadata:
1244
for spkg in self.metadata['SubPackages']:
1250
logger.warning('both "Files" YAML keyword and inline %file content in spec present')
1252
# try to remove duplicate '%defattr' in files list
1253
for key in content['files']:
1254
self._check_dup_files(content['files'][key])
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
1263
# backup old spec file if needed
1264
if os.path.exists(self.specfile):
1266
# backup original file
1267
backdir = 'spec.backup'
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)
1278
logger.info('Old spec file is saved as "%s"' % bak_spec_fpath)
1279
os.rename(self.specfile, bak_spec_fpath)
1281
self.newspec = False
1283
specfile = self.specfile
1284
if not self.newspec:
1285
self.extra['content'] = self.parse_existing(specfile)
1288
self.extra['content'].update(extra_content)
1291
TODO: should not regard them as the content of MAIN pkg
1292
if self.extras_filelist:
1294
self.extra['content']['files']['main'].extend(self.extras_filelist)
1296
self.extra['content'].update({'files': {'main': self.extras_filelist}})
1300
# TODO, cleanup docs handling when all pkgs need not, include spec.tmpl
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']
1309
if 'files' in self.extra['content']:
1310
files = copy.deepcopy(self.extra['content']['files'])
1314
if 'Files' in self.metadata:
1316
files['main'] += self.metadata['Files']
1318
files['main'] = self.metadata['Files']
1319
if "SubPackages" in self.metadata:
1320
for sp in self.metadata["SubPackages"]:
1322
if sp['Name'] in files:
1323
files[sp['Name']] += sp['Files']
1325
files[sp['Name']] = sp['Files']
1327
self.parse_files(files, docs)
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'])
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'])
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'])
1353
spec_content = spec.spec(searchList=[{
1354
'metadata': self.metadata,
1358
file = open(specfile, "w")
1359
file.write(spec_content.encode('utf-8'))
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)
1365
rpm_writer.process(extra_content)
1367
return rpm_writer.specfile, rpm_writer.newspec