Added --select-version option to say which implementations you want to change
[0publish.git] / 0publish
blobf607d841599875378de15bb029ee5636fb916a15
1 #!/usr/bin/env python
2 from zeroinstall import SafeException
3 from xml.dom import minidom
4 from optparse import OptionParser
5 import os, sys
6 import signing
7 from logging import info, debug
8 import edit, validator, create
10 version = '0.16'
12 parser = OptionParser(usage="usage: %prog [options] interface")
13 parser.add_option("--add-version", help="add a new implementation", action='store', metavar='VERSION')
14 parser.add_option("--archive-url", help="add archive at this URL", action='store', metavar='URL')
15 parser.add_option("--archive-file", help="local copy of archive-url", action='store', metavar='FILE')
16 parser.add_option("--archive-extract", help="subdirectory of archive to extract", action='store', metavar='DIR')
17 parser.add_option("-c", "--create", help="create file if nonexistant", action='store_true')
18 parser.add_option("-d", "--add-digest", help="add extra digests", action='store', metavar='ALG')
19 parser.add_option("-e", "--edit", help="edit with $EDITOR", action='store_true')
20 parser.add_option("-g", "--gpgsign", help="add a GPG signature block", action='store_true')
21 parser.add_option("-k", "--key", help="key to use for signing")
22 parser.add_option("-l", "--local", help="create feed from local interface")
23 parser.add_option("--manifest-algorithm", help="select algorithm for manifests", action='append', metavar='ALG')
24 parser.add_option("--set-interface-uri", help="set interface URI", action='store', metavar='URI')
25 parser.add_option("--set-id", help="set implementation ID", action='store', metavar='DIGEST')
26 parser.add_option("--set-main", help="set main executable", action='store', metavar='EXEC')
27 parser.add_option("--set-arch", help="set architecture", action='store', metavar='ARCH')
28 parser.add_option("--set-released", help="set release date", action='store', metavar='DATE')
29 parser.add_option("--set-stability", help="set stability", action='store', metavar='STABILITY')
30 parser.add_option("--set-version", help="set version number", action='store', metavar='VERSION')
31 parser.add_option("-s", "--stable", help="mark testing version stable", action='store_true')
32 parser.add_option("", "--select-version", help="select version to use in --set-* commands", action='store', metavar='VERSION')
33 parser.add_option("-x", "--xmlsign", help="add an XML signature block", action='store_true')
34 parser.add_option("-u", "--unsign", help="remove any signature", action='store_true')
35 parser.add_option("-v", "--verbose", help="more verbose output", action='count')
36 parser.add_option("-V", "--version", help="display version information", action='store_true')
38 (options, args) = parser.parse_args()
40 force_save = options.create
42 if options.version:
43 print "0publish (zero-install) " + version
44 print "Copyright (C) 2005-2010 Thomas Leonard"
45 print "This program comes with ABSOLUTELY NO WARRANTY,"
46 print "to the extent permitted by law."
47 print "You may redistribute copies of this program"
48 print "under the terms of the GNU General Public License."
49 print "For more information about these matters, see the file named COPYING."
50 sys.exit(0)
52 if options.verbose:
53 import logging
54 logger = logging.getLogger()
55 if options.verbose == 1:
56 logger.setLevel(logging.INFO)
57 else:
58 logger.setLevel(logging.DEBUG)
60 if len(args) != 1:
61 parser.print_help()
62 sys.exit(1)
63 interface = args[0]
65 def confirm(q):
66 while True:
67 ans = raw_input(q + " [Y/N] ").lower()
68 if ans in ('y', 'yes'): return True
69 if ans in ('n', 'no'): return False
71 try:
72 # Load or create the starting data...
74 if os.path.exists(interface):
75 contents = file(interface).read()
76 data, sign_fn, key = signing.check_signature(interface)
77 elif options.local:
78 if os.path.exists(options.local):
79 data = create.create_from_local(options.local)
80 sign_fn = signing.sign_unsigned
81 key = None
82 force_save = True
83 options.local = False
84 else:
85 raise Exception("File '%s' does not exist." % options.local)
86 else:
87 if options.create or confirm("Interface file '%s' does not exist. Create it?" % interface):
88 data = create.create(interface)
89 sign_fn = signing.sign_unsigned
90 key = None
91 options.edit = not options.create
92 else:
93 sys.exit(1)
95 debug("Original data: %s", data)
96 info("Original signing method: %s", sign_fn.__name__)
97 info("Original key: %s", key)
99 old_data = data
100 old_sign_fn = sign_fn
101 old_key = key
103 if sign_fn is signing.sign_unsigned and options.key:
104 sign_fn = signing.sign_xml
106 while True:
107 # Validate the input...
108 try:
109 validator.check(data, warnings = False) # Don't warn on load AND save!
110 break
111 except validator.InvalidInterface, ex:
112 print "Invalid interface: " + str(ex)
114 while True:
115 ans = raw_input("Interface is invalid. (E)dit or (A)bort?").lower()
116 if ans in ('e', 'edit'):
117 data = edit.edit(data)
118 options.edit = False # Don't edit twice
119 break
120 if ans in ('a', 'abort'): sys.exit(1)
122 # Process it...
123 if options.xmlsign:
124 sign_fn = signing.sign_xml
125 if options.unsign:
126 sign_fn = signing.sign_unsigned
127 if options.gpgsign:
128 sign_fn = signing.sign_plain
129 if options.key:
130 print "Changing key from '%s' to '%s'" % (key, options.key)
131 key = options.key
132 if options.set_interface_uri:
133 import release
134 data = release.set_interface_uri(data, options.set_interface_uri)
135 if options.add_version:
136 import release
137 data = release.add_version(data, options.add_version)
138 if options.set_id or options.set_version or options.set_released or \
139 options.set_stability or options.set_arch or options.set_main:
140 import release
141 data = release.set_attributes(data, options.select_version,
142 id = options.set_id,
143 version = options.set_version,
144 released = options.set_released,
145 stability = options.set_stability,
146 main = options.set_main,
147 arch = options.set_arch)
148 if options.stable:
149 assert not options.select_version, "Use --set-stability=stable --select-version=... instead"
150 import stable
151 data = stable.mark_stable(data)
152 if options.archive_url:
153 import archive
154 algs = options.manifest_algorithm
155 if algs is None:
156 algs = ['sha1new']
157 import hashlib
158 if hasattr(hashlib, 'sha256'):
159 algs.append('sha256')
160 data = archive.add_archive(data, options.archive_url, options.archive_file, options.archive_extract, algs)
161 elif options.archive_file or options.archive_extract:
162 raise Exception('Must use --archive-uri option')
163 if options.local:
164 import merge
165 data = merge.merge(data, options.local)
166 if options.add_digest:
167 import digest
168 data = digest.add_digests(data, alg = options.add_digest)
169 if options.edit:
170 data = edit.edit(data)
172 while True:
173 # Validate the result...
174 try:
175 validator.check(data)
176 break
177 except validator.InvalidInterface, ex:
178 print "Invalid interface: " + str(ex)
180 while True:
181 ans = raw_input("Interface is invalid. (E)dit or (A)bort?").lower()
182 if ans in ('e', 'edit'):
183 data = edit.edit(data)
184 break
185 if ans in ('a', 'abort'): sys.exit(1)
187 if (old_data == data and sign_fn == old_sign_fn and key == old_key) and not force_save:
188 print "Interface unchanged. Not writing."
189 sys.exit(1)
191 # Tidy up the XML
192 doc = minidom.parseString(data)
193 data = create.xml_header + doc.documentElement.toxml()
195 # Write it back out
196 if not data.endswith('\n'): data += '\n'
197 sign_fn(interface, data, key)
199 info("Wrote '%s'", interface)
201 if sign_fn != signing.sign_unsigned:
202 # Read it back in to find out what key we signed it with
203 # and ensure that the key has been exported
204 contents = file(interface).read()
205 saved_data, saved_sign_fn, saved_key = signing.check_signature(interface)
206 assert saved_data == data
207 assert saved_sign_fn == sign_fn
208 signing.export_key(os.path.dirname(interface), saved_key)
209 except KeyboardInterrupt, ex:
210 print >>sys.stderr, "Aborted at user's request"
211 sys.exit(1)
212 except SafeException, ex:
213 if options.verbose: raise
214 print >>sys.stderr, ex
215 sys.exit(1)