-
Notifications
You must be signed in to change notification settings - Fork 1
/
shipper
executable file
·867 lines (799 loc) · 33.7 KB
/
shipper
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
#!/usr/bin/env python
#
# shipper -- a tool for shipping software
#
# Requires Python 2.72 or later.
import sys, os, re, commands, time, tempfile, shutil
import glob, optparse, stat, email.utils
shipper_version = "0.18"
DEFAULT_HTML_TEMPLATE = """\
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN'
'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>
<html>
<head>
<meta name='description' content='Resource page for %(project)s' />
<meta name='generator' content='shipper' />
<meta name='description' content="%(summary)s"/>
<title>Resource page for %(project)s %(version)s</title>
</head>
<body>
<table width='100%%' cellpadding='0' summary='Canned page header' bgcolor='#ddd'>
<tr>
<td><h2>Resource page for %(project)s %(version)s</h2></td>
<td align="center">%(centertext)s</td>
<td align='right'><h2>%(date)s</h2></td>
</tr>
</table>
<h1>Resource page for %(project)s %(version)s</td></h1>
%(description)s
<br />
%(resourcetable)s
<br />
%(extralines)s
<p>Last modified %(date)s.</p>
</div>
</body>
</html>
"""
DEFAULT_EMAIL_TEMPLATE = """\
Subject: Announcing release %(version)s of %(project)s
Release %(version)s of %(project)s is now available at:
%(website)s
Here are the most recent changes:
%(lastchange)s
--
shipper, acting for %(whoami)s
"""
class Shipper:
"Hold shipper state variables so they can be enumerated."
multiline = {'description', 'lastchange',
'extralines', 'mail_template', 'html_template'}
def __init__(self):
self.destinations = [] # List of remote directories to update
self.date = time.strftime("%Y-%m-%d", time.gmtime())
self.whoami = None # Who am I? (Used for logins and mail signatures)
self.gittip_id = None # My name on gittip.com
self.savannah_id = None # User ID to use for CVS on Savannah
self.sourceforge_id = None # User ID on SourceForge
self.berlios_id = None # User ID on Berlios
# Per-project
self.project = os.path.basename(os.getcwd()) # Nor a project name
self.version = None # Project release version
self.website = None # Project home page
self.irc_channel = None # Project IRC channels
self.project_tags = None # Keywords for tagging
self.freecode_name = None # Name of the project on Freecode
self.berlios_name = None # Name of the project on Berlios
self.sourceforge_name = None # Name of the project on SourceForge
self.savannah_name = None # Name of the project on Savannah
self.gitorious_url = None # URL of the project on Gitorious
self.github_url = None # URL of the project on Github
self.sourceforge_folder = "" # Subfolder for file releases
self.ohloh_url = "" # Where the Ohloh stats live
self.summary = None # One-line summary of the project
self.webdir = None # Web directory to be mirrored to a project site
self.web_extras = None # Extra web deliverables
self.logo = "" # Project logo
self.tag_template = None # How to tag releases
self.tag_message = "Tagged for external release %(version)s"
self.irc_message = "%(project)s-%(version)s has just shipped."
self.html_target = None # Name to template a page to
# Stuff after this point is multiline
self.description = None # Multiline description
self.lastchange = None # Last entry in changelog
self.extralines = "" # Extra lines for HTML template
self.html_template = DEFAULT_HTML_TEMPLATE
self.mail_template = DEFAULT_EMAIL_TEMPLATE
self.lastchange = None # Contents of last changelog entry
def incorporate(self, profile):
"Read in a profile in Python syntax."
before = dir()
exec(open(profile))
after = dir()
after.remove('before')
for name in set(after) - set(before):
setattr(self, name, eval(name))
def merge_with_negations(self, additional):
"Merge in additional destinations, using ~ for exclusion."
negations = [s for s in additional if s.startswith("~")]
additional = [s for s in additional if not s.startswith("~")]
if "~" in negations:
self.destinations = additional
else:
self.destinations += additional
for removable in [s[1:] for s in negations]:
if removable in self.destinations:
self.destinations.remove(removable)
def dump(self):
"Dump the shipper state variables."
for variable in sorted(shipper.__dict__.keys()):
if variable in Shipper.multiline:
if not getattr(self, variable):
print("%s = None" % variable)
else:
print("%s = <<EOF\n%sEOF" % (variable, getattr(self, variable)))
else:
print("%s = %s" % (variable, repr(getattr(self, variable))))
def croak(msg):
sys.stderr.write("shipper: " + msg + "\n")
sys.exit(1)
#
# Shipping methods
#
def upload(dest, files, subdir=None):
"Generate upload command for a file via ftp or scp."
if subdir == None:
subdir = shipper.project
files = [x for x in files if os.path.exists(x) or x == shipper.html_target]
if dest.startswith("ftp://"):
dest = dest[6:].split("/")
host = dest.pop(0)
directory = "/".join(dest)
upcmds = ["lftp\n", "open -u anonymous," + myaddress + " " + host + "<<'INAGADADAVIDA'\n"]
if directory:
upcmds.append("cd " + directory + "\n")
upcmds.append("mput " + " ".join(files) + "\n")
upcmds.append("close\n")
upcmds.append("INAGADADAVIDA\n")
print("".join(upcmds))
elif dest.find(":") > -1:
(host, directory) = dest.split(":")
for fn in files:
# Requires your files to be writeable by you - maybe not so if they
# were RCSed but not locked! If they are, the -p option to scp
# will set that permission on the remote host and allow you to
# update the files with scp later.
remote = os.path.join(directory, subdir, os.path.basename(fn))
print("scp -p " + fn + " " + host + ":" + remote)
else:
sys.stderr.write("Don't know what to do with destination %s!\n" % dest)
def make_templated_page(deliverables):
"Make templated page, listing specified deliverables."
substitutions = shipper.__dict__.copy()
resourcetable = '<table border="1" align="center" summary="Downloadable resources">\n'
for (ifile, explanation, __tag, big) in deliverables:
if big:
indurl = download
else:
indurl = shipper.website
indurl = os.path.join(indurl, ifile)
resourcetable += "<tr><td><a href='%s'>%s</a></td><td>%s</td></tr>\n" % (indurl, ifile, explanation)
resourcetable += "</table>"
if shipper.gitorious_url:
shipper.extralines += "<p>The project repository is on <a href='%s'>gitorious</a>.</p>\n" % \
shipper.gitorious_url
if shipper.github_url:
shipper.extralines += "<p>The project repository is on <a href='%s'>github</a>.</p>\n" % \
shipper.github_url
if shipper.ohloh_url:
shipper.extralines += "<p>Project statistics are available at <a href='%s'>Ohloh</a>.</p>\n" % \
shipper.ohloh_url
if shipper.irc_channel:
if ',' in shipper.irc_channel:
shipper.extralines += '<p>There are project IRC channels:'
shipper.extralines += shipper.irc_channel
shipper.extralines += '</p>\n'
else:
shipper.extralines += '<p>There is a project <a href="%s">IRC channel</a>.</p>\n' % shipper.irc_channel
# Handle the old name of freecode.com for backwards compatibility.
if 'freecode' in shipper.destinations or 'freshmeat' in shipper.destinations:
shipper.extralines += "<p>There is a Freecode <a href='http://freecode.com/projects/%s'>%s page</a>.</p>\n" % \
(shipper.freecode_name, shipper.freecode_name)
if shipper.gittip_id:
shipper.extralines += "<p>If you appreciate this code (and especially if you "
shipper.extralines += "make money by using it) please "
shipper.extralines += "<a href='http://gittip.com/%s'>" % shipper.gittip_id
shipper.extralines += "leave me a tip on Gittip</a>.</p>"
centertext = ""
if "logo" in substitutions and substitutions["logo"]:
centertext = '<img src="%s"/>' % substitutions["logo"]
substitutions["description"] = "<p>" + substitutions["description"].replace("\n\n", "</p>\n\n<p>") + "</p>"
substitutions.update(locals())
# OK, now build the templated page itself
print("cat >%s <<'INAGADADAVIDA'" % shipper.html_target)
print(shipper.html_template % substitutions)
print "INAGADADAVIDA\n"
#
# Metadata extraction
#
class Specfile:
def __init__(self, filename):
self.filename = filename
self.type = None
if filename.endswith(".spec"):
self.type = "RPM"
self.project = self.extract("Name")
self.version = self.extract("Version")
self.website = self.extract("URL")
self.summary = self.extract("Summary")
self.description = self.rpm_get_multiline("description")
last = ""
state = 0
for line in open(self.filename):
if state == 0:
if line.startswith("%changelog"):
state = 1
continue
elif state == 1:
# Skip first line of entry
state = 2
continue
elif state == 2:
if not line.strip():
break
else:
if line.strip().startswith("-"):
line = line.lstrip()[1:]
last += line
if last:
self.lastchange = last
else:
self.lastchange = None
elif filename == "control":
self.type = "deb"
self.project = self.extract("Package")
self.version = self.extract("Version")
if self.version and "-" in self.version:
self.version = self.version.split("-")[0]
self.website = self.extract("Homepage")
self.summary = self.extract("Description")
fp = open(self.filename)
desc = ""
gather = False
while True:
line = fp.readline()
if not line:
break
if line.startswith("Description:"):
gather = True
desc = line[12:]
continue
elif not line.strip():
gather = False
if gather:
if line == " .\n":
line = "\n"
else:
line = line[1:]
desc += line
fp.close()
if desc:
self.description = desc.strip() + "\n"
else:
self.description = None
self.lastchange = None
# shipper-only headers
self.destinations = self.extract("Destinations")
self.tag_template = self.extract("VC-Tag-Template")
self.tag_message = self.extract("VC-Tag-Message")
self.irc_message = self.extract("IRC-Message")
self.web_extras = self.extract("Web-Extras")
self.html_target = self.extract("HTML-Target")
self.sourceforge_folder = self.extract("SourceForge-Folder")
self.gitorious_url = self.extract("Gitorious-URL")
self.github_url = self.extract("Github-URL")
self.ohloh_url = self.extract("Ohloh-URL")
self.irc_channel = self.extract("IRC-Channel")
self.logo = self.extract("Logo")
self.webdir = self.extract("Web-Directory")
self.project_tags = self.extract("Project-Tag-List")
self.freecode_name = self.extract("Freecode-Name")
self.freecode_name = self.extract("Freshmeat-Name")
self.savannah_name = self.extract("Savannah-Name")
self.berlios_name = self.extract("Berlios-Name")
self.sourceforge_name = self.extract("SourceForge-Name")
def extract(self, fld):
"Extract a one-line field, possibly embedded as a magic comment."
if self.type == "RPM":
return Specfile.grep("^#?"+fld+r":\s*(.*)", self.filename)
elif self.type == "deb":
fld = fld.replace('-', '[-_]')
return Specfile.grep("^(?:XBS-)?"+fld+": (.*)", self.filename)
def rpm_get_multiline(self, fieldname):
"Grab everything from leader line to just before the next leader line."
fp = open(self.filename)
data = ""
gather = False
while True:
line = fp.readline()
if not line:
break
# Pick up fieldnames *without* translation options.
if line.strip() == "%" + fieldname:
gather = True
continue
elif line[0] == "%":
gather = False
if gather:
data += line
fp.close()
if data:
return data.strip() + "\n"
else:
return None
@staticmethod
def grep(regexp, fp):
"Mine for a specified regexp in a file."
# Note: this blank-strips its output!
fp = open(fp)
try:
while True:
line = fp.readline()
if not line:
return None
m = re.search(regexp, line)
if m:
return m.group(1).strip()
finally:
fp.close()
return None
#
# Who am I?
#
def whoami_really():
"Ask various programs that keep track of who you are who you are."
# Bazaar version-control system
(bzrerr, bzrout) = commands.getstatusoutput("bzr config email")
if bzrerr == 0 and bzrout:
return bzrout
# Git version-control system
(nameerr, nameout) = commands.getstatusoutput("git config user.name")
(emailerr, emailout) = commands.getstatusoutput("git config user.email")
if nameerr == 0 and nameout and emailerr == 0 and emailout:
return "%s <%s>" % (nameout, emailout)
# Various random configs
for (fn, mine) in (
("~/.hgrc", r"username\s*=\s*(.*)"), # Mercurial
("~/.lynxrc", r"personal_mail_address\s*=\s*(.*)") # Lynx
):
fn = os.path.expanduser(fn)
if os.path.exists(fn):
for line in file(fn):
m = re.search(mine, line)
if m:
return m.group(1)
# Out of alternatives
return None
#
# Shipping methods for public destinations.
#
def savannah():
if not options.webonly:
upload("dl.sv.nongnu.org:/releases/%s/" % shipper.savannah_name,
download_deliverables)
def berlios():
upload("ftp://ftp.berlios.de/incoming", download_deliverables)
berlios_webdir = \
"shell.berlios.de:/home/groups/" \
+ (shipper.berlios_name or shipper.project) \
+ "/htdocs"
upload(berlios_webdir, web_deliverables, subdir="")
def sourceforge():
if not options.webonly:
# See https://sourceforge.net/apps/trac/sourceforge/wiki/Release%20files%20for%20download
destdir = "/home/frs/project/%s" % shipper.sourceforge_name
if shipper.sourceforge_folder:
destdir += "/" + shipper.sourceforge_folder
print("rsync -avP -e ssh %s '%s,%s@frs.sourceforge.net:%s'" % (
" ".join(download_deliverables),
shipper.sourceforge_id,
shipper.sourceforge_name,
destdir))
if shipper.webdir:
websources = shipper.webdir + '/'
else:
websources = " ".join(web_deliverables),
# https://sourceforge.net/apps/trac/sourceforge/wiki/Rsync%20over%20SSH
print("rsync -aiv %s %s,%s@web.sourceforge.net:/home/project-web/%s/htdocs/" % (
websources,
shipper.sourceforge_id,
shipper.sourceforge_name,
shipper.sourceforge_name))
#
# Main sequence
#
try:
#
# Process options
#
parser = optparse.OptionParser(usage="%prog: [-u] [-f] [-v]")
parser.add_option("-v", "--verbose",
action="store_true", dest="verbose", default=False,
help="print progress messages to stdout")
parser.add_option("-d", "--dump",
action="store_true", dest="dump", default=False,
help="dump configuration only, no builds or uploads")
parser.add_option("-x", "--exclude",
dest="excluded", default="",
help="exclude some shipping targets")
parser.add_option("-w", "--web-only",
action="store_true", dest="webonly", default=False,
help="do webspace update only")
(options, args) = parser.parse_args()
shipper = Shipper()
#
# Extract metadata and compute control information
#
# Security check, don't let an attacker elevate privileges
def securecheck(fn):
if stat.S_IMODE(os.stat(fn).st_mode) & 0o0002:
croak("%s must not be world-writeable!" % fn)
# Read in user's profiles
home_profile = os.path.join(os.getenv('HOME'), ".shipper")
if os.path.exists(home_profile):
securecheck(home_profile)
shipper.incorporate(home_profile)
home_profile = os.path.join(os.getenv('HOME'), ".config/shipper")
if os.path.exists(home_profile):
securecheck(home_profile)
shipper.incorporate(home_profile)
# User's identity
if not shipper.whoami:
shipper.whoami = whoami_really()
if not shipper.whoami:
croak("please set whoami in your ~/.config.shipper or ~/.shipper file.")
(myrealname, myaddress) = email.utils.parseaddr(shipper.whoami)
(myuserid, myhost) = myaddress.split("@")
# Read in per-project metadata
if os.path.exists("control"):
metadata = Specfile("control")
else:
specfiles = glob.glob("*.spec")
if len(specfiles) == 1:
metadata = Specfile(specfiles[0])
else:
croak("must be exactly one RPM specfile in the directory!")
# Merge in information from the project metadata files
metadata_keys = metadata.__dict__.keys()
metadata_keys.remove("destinations")
metadata_keys.remove("filename")
metadata_keys.remove("type")
for name in metadata_keys:
if not getattr(shipper, name):
setattr(shipper, name, getattr(metadata, name))
# Specfiles may set their own destinations
project_destinations = metadata.extract("Destinations").split(",")
if project_destinations:
shipper.merge_with_negations([x.strip() for x in project_destinations])
# Some defaults
if not shipper.freecode_name:
shipper.freecode_name = shipper.project
if not shipper.savannah_name:
shipper.savannah_name = shipper.project
if not shipper.berlios_name:
shipper.berlios_name = shipper.project
if shipper.sourceforge_name:
if "@" in shipper.sourceforge_name:
(shipper.sourceforge_id, shipper.sourceforge_name) = shipper.sourceforge_name.split("@")
else:
shipper.sourceforge_name = shipper.project
shipper.sourceforge_id = myuserid
# Shipper-specific project profile
securecheck(".")
here_profile = ".shipper"
if os.path.exists(here_profile):
securecheck(here_profile)
shipper_incorporate(here_profile)
# Arguments can be variable settings
for arg in args:
if arg.count("=") != 1:
print("shipper: '%s' is not in name=value form." % arg)
raise SystemExit(1)
else:
(name, val) = arg.split("=")
try:
setattr(shipper, name, val)
except (SyntaxError, NameError, ValueError):
print("shipper: ill-formed variable setting at %s" % arg)
raise SystemExit, 1
# Apply command-line destination exclusions
if options.excluded:
for excludee in options.excluded.split(","):
if excludee in shipper.destinations:
shipper.destinations.remove(excludee)
else:
sys.stderr.write("shipper: %s isn't in the destinations!\n" % excludee)
raise SystemExit(1)
if not options.dump:
if shipper.version == None:
croak("can't get project version")
elif shipper.version[0] not in "0123456789":
croak("project version %s appears garbled" % shipper.version)
if options.verbose:
print "shipper: variable extraction finished"
# Finally, derive the lastchange entry; we'll need it for
# freecode.com
shipper.lastchange = None
for filename in ("NEWS", "HISTORY", "ChangeLog"):
if not shipper.lastchange and os.path.exists(filename):
if options.verbose:
print("shipper: I see a %s file" % filename)
state = 0
for line in open(filename, "r"):
if state == 0: # Skipping header
if line.startswith(" ") or line.startswith("\t"):
continue
elif not line.strip():
continue
else:
# Skip first line in the log entry.
shipper.lastchange = ""
state = 1
elif state == 1: # Past header
if not line.strip():
break
else:
shipper.lastchange += line
if not shipper.lastchange and metadata.lastchange:
shipper.lastchange = metadata.lastchange
if 'freecode' in shipper.destinations and not shipper.lastchange:
croak("Freecode notification requires a NEWS, HISTORY or ChangeLog file.")
# Some destinations imply website locations
if not shipper.website:
if "sourceforge" in shipper.destinations:
shipper.website = "http://%s.sourceforge.net/" % shipper.sourceforge_name
# This doesn't work. Savannah's webspace access is too painful.
#if "savannah" in shipper.destinations:
# shipper.website = "http://www.nongnu.org/%s/" % shipper.savannah_name
if "berlios" in shipper.destinations:
shipper.website = "http://%s.berlios.de/" % shipper.berlios_name
# Download directory has to be computed differently at
# special destinations.
if shipper.website:
if "savannah" in shipper.website:
download = "http://download.savannah.nongnu.org/releases/%s/"+(shipper.savannah_name or shipper.project)
if "berlios"in shipper.website:
download = "http://download.berlios.de/"+(shipper.berlios_name or shipper.project)
elif "sourceforge" in shipper.website:
download = "http://sourceforge.net/projects/%s/files/" % (
shipper.sourceforge_name)
if sourceforge_folder:
download += shipper.sourceforge_folder + '/'
else:
download = shipper.website
#
# Now compute the names of deliverables
#
def versioned(fn):
"Does the specified filename contain a version number?"
return re.search("[0-9]", fn)
if options.verbose:
print("shipper: starting deliverables computation")
deliverable_types = (
(re.compile("^README$"),
"roadmap file",
None,
False),
(re.compile("^READ.ME$"),
"roadmap file",
None,
False),
(re.compile("^ChangeLog$"),
"change log",
"ChangeLog",
False),
(re.compile("^NEWS$"),
"project news",
None,
False),
(re.compile("^HISTORY$"),
"project history",
None,
False),
(re.compile("^BUGS$"),
"known bugs",
None,
False),
(re.compile("^TODO$"),
"to-do file",
None,
False),
(re.compile("^COPYING$"),
"project license",
None,
False),
(re.compile(".*-" + str(shipper.version) + r".(tar.gz|tgz)$"),
"gzipped source tarball",
"Tar/GZ",
True),
(re.compile(shipper.project + ".*-" + str(shipper.version) + r".tar.bz2$"),
"bzipped source tarball",
"Tar/BZ",
True),
(re.compile(shipper.project + ".*-" + str(shipper.version) + r".md5$"),
"source tarball MD5 checksum",
"Checksum",
True),
(re.compile(shipper.project + ".*-" + str(shipper.version) + r".sha224$"),
"source tarball SHA224 checksum",
"Checksum",
True),
(re.compile(shipper.project + ".*-" + str(shipper.version) + r".sha256$"),
"source tarball SHA256 checksum",
"Checksum",
True),
(re.compile(shipper.project + ".*-" + str(shipper.version) + r".sha384$"),
"source tarball SHA384 checksum",
"Checksum",
True),
(re.compile(shipper.project + ".*-" + str(shipper.version) + r".sha512$"),
"source tarball SHA512 checksum",
"Checksum",
True),
(re.compile(shipper.project + ".*-" + str(shipper.version) + r".zip$"),
"zip archive",
"Zip",
True),
(re.compile(shipper.project + ".*-" + str(shipper.version) + r"-.*\.src.rpm$"),
"source RPM",
"SRPM-Package",
True),
(re.compile(shipper.project + ".*-" + str(shipper.version) + r"-.*\.rpm$"),
"binary RPM",
"RPM-Package",
True),
(re.compile(shipper.project + ".*-" + str(shipper.version) + r"-.*\.deb$"),
"Debian package",
"Debian-Package",
True),
)
deliverables = []
for filename in os.listdir("."):
for (regexp, explanation, tag, bulky) in deliverable_types:
if regexp.search(filename):
if not bulky:
with open(filename) as fp:
if re.search("^shipper: ignore this", fp.read()):
continue
deliverables.append((filename, explanation, tag, bulky))
if options.verbose:
print("shipper: deliverables: " + " ".join([x[0] for x in deliverables]))
#
# Might be time to dump
#
if options.dump:
shipper.dump()
raise SystemExit(0)
# Sanity checks
if not shipper.destinations:
croak("the Destinations list is empty; nothing to do.")
if not options.webonly and not [f_e_t_b[0] for f_e_t_b in deliverables if versioned(f_e_t_b[0])]:
sys.stderr.write("shipper: warning, no deliverables with versions.")
if options.verbose:
print("shipper: destinations: " + ", ".join(shipper.destinations))
print("shipper: sanity checks passed")
if not shipper.webdir:
# Compute web-related deliverables, we need this even if not rebuilding
# the templated page. Includes anything with an HTML, Javascript, or CSS
# extension.
for filename in glob.glob('*.html')+glob.glob('*.xhtml')+glob.glob('*.js')+glob.glob('*.css'):
if filename == shipper.html_target:
continue
stem = filename[:-4]
for ext in ("man", "1", "2", "3", "4", "5", "6", "7", "8", "9", "xml"):
if os.path.exists(stem + ext):
explanation = "HTML rendering of " + stem + ext
break
else:
# If the HTML has a <title> element, use it.
m = re.search("<title>([^<]*)</title>", open(filename).read())
if m:
explanation = m.group(1)
else:
explanation = "HTML page."
deliverables.append((filename, explanation, None, False))
if shipper.web_extras is not None:
for fn in shipper.web_extras.split():
firstline = open(fn).readline()
if firstline.startswith("#"):
explanation = firstline[1:].strip()
else:
explanation = "Custom web deliverable"
deliverables.append((fn, explanation, None, False))
# Template a resource page?
if shipper.html_template and shipper.html_target:
make_templated_page(deliverables)
deliverables.append((shipper.html_target,
"templated web page", None, False))
# We'll want the logo if it exists, too
if shipper.logo:
deliverables.append((shipper.logo, "project logo", None, False))
# Compute final deliverables. This computation needs to coincide
# with the way web deliverables are distinguished from download
# deliverables in make_templated_page(), otherwise havoc will ensue.
all_deliverables = [x[0] for x in deliverables]
download_deliverables = [x[0] for x in [f_e_s_b for f_e_s_b in deliverables if f_e_s_b[3]]]
web_deliverables = [x[0] for x in [f_e_s_b for f_e_s_b in deliverables if not f_e_s_b[3]]]
#
# OK, commands for everything. First, the uploads
#
for destination in shipper.destinations:
if destination in ("berlios", "savannah", "sourceforge"):
eval(destination + "()")
elif destination.startswith("ftp:"):
if not options.webonly:
upload(destination, download_deliverables)
elif destination.startswith("mailto:"):
pass # defer this until a later phase
elif destination.startswith("irc:"):
pass # defer this until a later phase
elif destination == 'freecode':
pass # defer this until a later phase
else:
if options.webonly:
upload(destination, web_deliverables)
else:
upload(destination, all_deliverables)
if not options.webonly:
# Tag the release
if shipper.tag_template and shipper.tag_message:
tag_name = shipper.tag_template % shipper.__dict__
tag_message = shipper.tag_message % shipper.__dict__
context = shipper.__dict__.copy()
context.update(locals())
# If we're in the trunk of an SVN repository, we want to tag
# what just got shipped as an external release.
if os.path.basename(os.getcwd())=='trunk' and os.path.exists(".svn"):
print("# This is a Subversion trunk directory...")
if os.path.exists("../tags"):
print("# I see an svn peer tags directory...")
if os.path.exists("../tags/" + tag_name):
print("# This release has aleady been tagged.")
else:
print("# I will copy and tag this release as %s." % tag_name)
print("cd .. && svn copy trunk tags/%s && svn -m '%s' commit" % (tag_name, tag_message))
for (idir, what, tagger, pusher) in (
(".git", "git", "git tag -a %(tag_name)s -m '%(tag_message)s'", "git push; git push --tags"),
(".hg", "hg", "hg tag %(tag_name)s -m '%(tag_message)s'", "hg push"),
(".bzr", "bzr", "bzr tag %(tag_name)s", "bzr push"),
):
if os.path.exists(idir):
print(tagger % context)
print(pusher % context)
# Freecode notification, after uploads and tagging
if 'freecode' in shipper.destinations:
if not shipper.website:
print("# Can't announce to freecode without a primary website!")
elif not shipper.lastchange:
print("# Can't announce to freecode without a changes field!")
else:
print("freecode-submit <<'INAGADAVIDA'")
print("Project: %s" % (shipper.freecode_name or shipper.project))
print("Version: %s" % shipper.version)
print("Description: %s" % shipper.description.replace("\n", "\n ").rstrip())
if shipper.project_tags:
print("Project-Tag-List: %s" % shipper.project_tags)
print("Website-URL: %s" % shipper.website)
for (f, e, s, b) in deliverables:
if s:
if b:
url = download
else:
url = shipper.website
url = os.path.join(url, f)
print("%s-URL: %s" % (s, url))
# freecode.com doesn't like bulleted entries.
freecodelog = [s.lstrip() for s in shipper.lastchange.split("\n")]
sys.stdout.write("\n" + "\n".join(freecodelog))
print("INAGADADAVIDA\n")
# Email notifications
maildests = [x[7:] for x in shipper.destinations if x.startswith("mailto:")]
if maildests:
print("sendmail %s <<'INAGADADAVIDA'" % " ".join(maildests))
print(shipper.mail_template % shipper.__dict__)
print("INAGADADAVIDA\n")
# Ship to IRC channels
if shipper.irc_channel:
irc_destinations = [chan for chan in destination \
if chan.startswith("irc:")]
for url in irc_destinations + shipper.irc_channel.split(","):
msg = shipper.irc_message % shipper.__dict__
print("irk %s '%s'" % (url, msg))
print("# That's all, folks!")
except KeyboardInterrupt:
print("# Bye!")
# The following sets edit modes for GNU EMACS
# Local Variables:
# mode:python
# End: