Use VersionRestriction from 0launch 0.40 instead of bundling a copy
[0compile.git] / autocompile.py
blob89cba2ef51a5b1bd1df3c5923c839c926259d3ca
1 # Copyright (C) 2009, Thomas Leonard
2 # See http://0install.net/0compile.html
4 import sys, os, __main__, tempfile, shutil, subprocess
5 from xml.dom import minidom
7 from zeroinstall import SafeException
8 from zeroinstall.injector import arch, handler, policy, model, iface_cache, selections, namespaces, writer, reader
9 from zeroinstall.zerostore import manifest
10 from zeroinstall.support import tasks, basedir
12 from support import BuildEnv
14 # This is a bit hacky...
16 # We invent a new CPU type which is compatible with the host but worse than
17 # every existing type, and we use * for the OS type so that we don't beat 'Any'
18 # binaries either. This means that we always prefer an existing binary of the
19 # desired version to compiling a new one, but we'll compile a new version from source
20 # rather than use an older binary.
21 arch.machine_groups['newbuild'] = arch.machine_groups.get(arch._uname[-1], 0)
22 arch.machine_ranks['newbuild'] = max(arch.machine_ranks.values()) + 1
23 host_arch = '*-newbuild'
25 class AutocompileCache(iface_cache.IfaceCache):
26 def __init__(self):
27 iface_cache.IfaceCache.__init__(self)
28 self.done = set()
30 def get_interface(self, uri):
31 iface = iface_cache.IfaceCache.get_interface(self, uri)
32 if not iface: return None
33 feed = iface._main_feed
35 # Note: when a feed is updated, a new ZeroInstallFeed object is created,
36 # so record whether we've seen the feed, not the interface.
38 if feed not in self.done:
39 self.done.add(feed)
41 # For each source impl, add a corresponding binary
42 # (the binary has no dependencies as we can't predict them here,
43 # but they're not the same as the source's dependencies)
45 srcs = [x for x in feed.implementations.itervalues() if x.arch and x.arch.endswith('-src')]
46 for x in srcs:
47 new_id = '0compile=' + x.id
48 if not new_id in feed.implementations:
49 new = feed._get_impl(new_id)
50 new.set_arch(host_arch)
51 new.version = x.version
53 return iface
55 policy.iface_cache = AutocompileCache()
57 def pretty_print_plan(solver, root, indent = '- '):
58 """Display a tree showing the selected implementations."""
59 iface = solver.iface_cache.get_interface(root)
60 impl = solver.selections[iface]
61 if impl is None:
62 msg = 'Failed to select any suitable version (source or binary)'
63 elif impl.id.startswith('0compile='):
64 real_impl_id = impl.id.split('=', 1)[1]
65 real_impl = impl.feed.implementations[real_impl_id]
66 msg = 'Compile %s (%s)' % (real_impl.get_version(), real_impl.id)
67 elif impl.arch and impl.arch.endswith('-src'):
68 msg = 'Compile %s (%s)' % (impl.get_version(), impl.id)
69 else:
70 if impl.arch:
71 msg = 'Use existing binary %s (%s)' % (impl.get_version(), impl.arch)
72 else:
73 msg = 'Use existing architecture-independent package %s' % impl.get_version()
74 print "%s%s: %s" % (indent, iface.get_name(), msg)
76 if impl:
77 indent = ' ' + indent
78 for x in impl.requires:
79 pretty_print_plan(solver, x.interface, indent)
81 def print_details(solver):
82 """Dump debugging details."""
83 print "\nDetails of all components and versions considered:"
84 for iface in solver.details:
85 print '\n%s\n' % iface.get_name()
86 for impl, note in solver.details[iface]:
87 print '%s (%s) : %s' % (impl.get_version(), impl.arch or '*-*', note or 'OK')
88 print "\nEnd details"
90 def compile_and_register(policy):
91 local_feed_dir = basedir.save_config_path('0install.net', '0compile', 'builds', model._pretty_escape(policy.root))
92 s = selections.Selections(policy)
94 buildenv = BuildEnv(need_config = False)
95 buildenv.config.set('compile', 'interface', policy.root)
96 buildenv.config.set('compile', 'selections', 'selections.xml')
98 version = s.selections[policy.root].version
99 local_feed = os.path.join(local_feed_dir, '%s-%s-%s.xml' % (buildenv.iface_name, version, arch._uname[-1]))
100 if os.path.exists(local_feed):
101 raise SafeException("Build metadata file '%s' already exists!" % local_feed)
103 tmpdir = tempfile.mkdtemp(prefix = '0compile-')
104 try:
105 os.chdir(tmpdir)
107 # Write configuration for build...
109 buildenv.save()
111 sel_file = open('selections.xml', 'w')
112 try:
113 doc = s.toDOM()
114 doc.writexml(sel_file)
115 sel_file.write('\n')
116 finally:
117 sel_file.close()
119 # Do the build...
121 subprocess.check_call([sys.executable, sys.argv[0], 'build'])
123 # Register the result...
125 alg = manifest.get_algorithm('sha1new')
126 digest = alg.new_digest()
127 lines = []
128 for line in alg.generate_manifest(buildenv.distdir):
129 line += '\n'
130 digest.update(line)
131 lines.append(line)
132 actual_digest = alg.getID(digest)
134 local_feed_file = file(local_feed, 'w')
135 try:
136 dom = minidom.parse(buildenv.local_iface_file)
137 impl, = dom.getElementsByTagNameNS(namespaces.XMLNS_IFACE, 'implementation')
138 impl.setAttribute('id', actual_digest)
139 dom.writexml(local_feed_file)
140 local_feed_file.write('\n')
141 finally:
142 local_feed_file.close()
144 print "Implementation metadata written to %s" % local_feed
146 print "Storing build in cache..."
147 policy.solver.iface_cache.stores.add_dir_to_cache(actual_digest, buildenv.distdir)
149 print "Registering feed..."
150 iface = policy.solver.iface_cache.get_interface(policy.root)
151 feed = iface.get_feed(local_feed)
152 if feed:
153 print "WARNING: feed %s already registered!" % local_feed
154 else:
155 iface.extra_feeds.append(model.Feed(local_feed, impl.getAttribute('arch'), user_override = True))
156 writer.save_interface(iface)
158 # We might have cached an old version
159 new_feed = policy.solver.iface_cache.get_interface(local_feed)
160 reader.update_from_cache(new_feed)
161 except:
162 print "\nBuild failed: leaving build directory %s for inspection...\n" % tmpdir
163 raise
164 else:
165 shutil.rmtree(tmpdir)
167 def do_autocompile(args):
168 """autocompile URI"""
169 if len(args) != 1:
170 raise __main__.UsageError()
171 iface_uri = model.canonical_iface_uri(args[0])
173 h = handler.Handler()
175 @tasks.async
176 def recursive_build(iface_uri, version = None):
177 p = policy.Policy(iface_uri, handler = h, src = True)
178 iface = p.solver.iface_cache.get_interface(iface_uri)
179 p.solver.record_details = True
180 if version:
181 p.solver.extra_restrictions[iface] = [model.VersionRestriction(model.parse_version(version))]
183 # For testing...
184 #p.target_arch = arch.Architecture(os_ranks = {'FreeBSD': 0, None: 1}, machine_ranks = {'i386': 0, None: 1, 'newbuild': 2})
186 print (' %s ' % iface_uri).center(76, '=')
188 while True:
189 print "\nSelecting versions for %s..." % iface.get_name()
190 solved = p.solve_with_downloads()
191 if solved:
192 yield solved
193 tasks.check(solved)
195 if not p.solver.ready:
196 print_details(p.solver)
197 raise SafeException("Can't find all required implementations (source or binary):\n" +
198 '\n'.join(["- %s -> %s" % (iface, p.solver.selections[iface])
199 for iface in p.solver.selections]))
200 print "Selection done."
202 print "\nPlan:\n"
203 pretty_print_plan(p.solver, p.root)
204 print
206 for dep_iface, dep_impl in p.solver.selections.iteritems():
207 if dep_impl.id.startswith('0compile='):
208 build = recursive_build(dep_iface.uri, dep_impl.get_version())
209 yield build
210 tasks.check(build)
211 break # Try again with that dependency built...
212 else:
213 print "No dependencies need compiling... compile %s itself..." % iface.get_name()
214 compile_and_register(p)
215 return
217 h.wait_for_blocker(recursive_build(iface_uri))
219 __main__.commands += [do_autocompile]