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
):
27 iface_cache
.IfaceCache
.__init
__(self
)
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
:
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')]
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
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
]
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)
71 msg
= 'Use existing binary %s (%s)' % (impl
.get_version(), impl
.arch
)
73 msg
= 'Use existing architecture-independent package %s' % impl
.get_version()
74 print "%s%s: %s" % (indent
, iface
.get_name(), msg
)
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')
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-')
107 # Write configuration for build...
111 sel_file
= open('selections.xml', 'w')
114 doc
.writexml(sel_file
)
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()
128 for line
in alg
.generate_manifest(buildenv
.distdir
):
132 actual_digest
= alg
.getID(digest
)
134 local_feed_file
= file(local_feed
, 'w')
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')
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
)
153 print "WARNING: feed %s already registered!" % local_feed
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
)
162 print "\nBuild failed: leaving build directory %s for inspection...\n" % tmpdir
165 shutil
.rmtree(tmpdir
)
167 def do_autocompile(args
):
168 """autocompile URI"""
170 raise __main__
.UsageError()
171 iface_uri
= model
.canonical_iface_uri(args
[0])
173 h
= handler
.Handler()
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
181 p
.solver
.extra_restrictions
[iface
] = [model
.VersionRestriction(model
.parse_version(version
))]
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, '=')
189 print "\nSelecting versions for %s..." % iface
.get_name()
190 solved
= p
.solve_with_downloads()
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."
203 pretty_print_plan(p
.solver
, p
.root
)
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())
211 break # Try again with that dependency built...
213 print "No dependencies need compiling... compile %s itself..." % iface
.get_name()
214 compile_and_register(p
)
217 h
.wait_for_blocker(recursive_build(iface_uri
))
219 __main__
.commands
+= [do_autocompile
]