1 # jhbuild - a build script for GNOME 1.x and 2.x
2 # Copyright (C) 2001-2006 James Henstridge
4 # moduleset.py: logic for running the build.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 from __future__
import generators
27 from jhbuild
.errors
import UsageError
, FatalError
, DependencyCycleError
, CommandError
30 import xml
.dom
.minidom
32 raise FatalError(_('Python xml packages are required but could not be found'))
34 from jhbuild
import modtypes
35 from jhbuild
.versioncontrol
import get_repo_type
36 from jhbuild
.utils
import httpcache
37 from jhbuild
.utils
.cmds
import get_output
39 __all__
= ['load', 'load_tests']
42 def __init__(self
, config
= None):
45 def add(self
, module
):
46 '''add a Module object to this set of modules'''
47 self
.modules
[module
.name
] = module
49 def get_module(self
, module_name
, ignore_case
= False):
50 if self
.modules
.has_key(module_name
) or not ignore_case
:
51 return self
.modules
[module_name
]
52 module_name
= module_name
.lower()
53 for module
in self
.modules
.keys():
54 if module
.lower() == module_name
:
55 if self
.config
is None or not self
.config
.quiet_mode
:
56 logging
.info(_('fixed case of module \'%(orig)s\' to \'%(new)s\'') % {
57 'orig': module_name
, 'new': module
})
58 return self
.modules
[module
]
59 raise KeyError(module_name
)
61 def get_module_list(self
, seed
, skip
=[], tags
=[], ignore_cycles
=False,
62 ignore_suggests
=False, include_optional_modules
=False,
63 ignore_missing
=False):
64 '''gets a list of module objects (in correct dependency order)
65 needed to build the modules in the seed list'''
67 if seed
== 'all': seed
= self
.modules
.keys()
69 all_modules
= [self
.get_module(mod
, ignore_case
= True) for mod
in seed
if mod
not in skip
]
71 raise UsageError(_('module "%s" not found') % e
)
73 asked_modules
= all_modules
[:]
75 # 1st: get all modules that will be needed
76 # note this is only needed to skip "after" modules that would not
79 while i
< len(all_modules
):
80 for modname
in all_modules
[i
].dependencies
:
81 depmod
= self
.modules
.get(modname
)
83 if not ignore_missing
:
85 '%(module)s has a dependency on unknown "%(invalid)s" module') % {
86 'module': all_modules
[i
].name
,
90 if not depmod
in all_modules
:
91 all_modules
.append(depmod
)
93 if not ignore_suggests
:
94 # suggests can be ignored if not in moduleset
95 for modname
in all_modules
[i
].suggests
:
96 depmod
= self
.modules
.get(modname
)
99 if not depmod
in all_modules
:
100 all_modules
.append(depmod
)
103 # 2nd: order them, raise an exception on hard dependency cycle, ignore
104 # them for soft dependencies
109 # mark skipped modules as already processed
110 self
._state
[self
.modules
.get(modname
)] = 'processed'
113 for modname
in self
.modules
:
115 if tag
in self
.modules
[modname
].tags
:
118 # no tag matched, mark module as processed
119 self
._state
[self
.modules
[modname
]] = 'processed'
121 def order(modules
, module
, mode
= 'dependencies'):
122 if self
._state
.get(module
, 'clean') == 'processed':
125 if self
._state
.get(module
, 'clean') == 'in-progress':
126 # dependency circle, abort when processing hard dependencies
127 if not ignore_cycles
:
128 raise DependencyCycleError()
130 self
._state
[module
] = 'in-progress'
132 self
._state
[module
] = 'in-progress'
133 for modname
in module
.dependencies
:
134 depmod
= self
.modules
[modname
]
135 order([self
.modules
[x
] for x
in depmod
.dependencies
], depmod
, 'dependencies')
136 if not ignore_suggests
:
137 for modname
in module
.suggests
:
138 depmod
= self
.modules
.get(modname
)
141 save_state
, save_ordered
= self
._state
.copy(), self
._ordered
[:]
143 order([self
.modules
[x
] for x
in depmod
.dependencies
], depmod
, 'suggests')
144 except DependencyCycleError
:
145 self
._state
, self
._ordered
= save_state
, save_ordered
148 for modname
in module
.after
:
149 depmod
= self
.modules
.get(modname
)
151 # this module doesn't exist, skip.
153 if not depmod
in all_modules
and not include_optional_modules
:
154 # skip modules that would not be built otherwise
155 # (build_optional_modules being the argument to force them
156 # to be included nevertheless)
158 if not depmod
.dependencies
:
159 # depmod itself has no dependencies, skip.
162 # more expensive, if depmod has dependencies, compute its
163 # full list of hard dependencies, getting it into
164 # extra_afters, so they are also evaluated.
165 # <http://bugzilla.gnome.org/show_bug.cgi?id=546640>
166 t_ms
= ModuleSet(self
.config
)
167 t_ms
.modules
= self
.modules
.copy()
168 dep_modules
= t_ms
.get_module_list(seed
=[depmod
.name
])
169 for m
in dep_modules
[:-1]:
171 extra_afters
.append(m
)
173 save_state
, save_ordered
= self
._state
.copy(), self
._ordered
[:]
175 order([self
.modules
[x
] for x
in depmod
.dependencies
], depmod
, 'after')
176 except DependencyCycleError
:
177 self
._state
, self
._ordered
= save_state
, save_ordered
178 for depmod
in extra_afters
:
179 save_state
, save_ordered
= self
._state
.copy(), self
._ordered
[:]
181 order([self
.modules
[x
] for x
in depmod
.dependencies
], depmod
, 'after')
182 except DependencyCycleError
:
183 self
._state
, self
._ordered
= save_state
, save_ordered
184 self
._state
[module
] = 'processed'
185 self
._ordered
.append(module
)
187 for i
, module
in enumerate(all_modules
):
189 if i
+1 == len(asked_modules
):
192 ordered
= self
._ordered
[:]
197 def get_full_module_list(self
, skip
=[], ignore_cycles
=False):
198 return self
.get_module_list(self
.modules
.keys(), skip
=skip
,
199 ignore_cycles
=ignore_cycles
, ignore_missing
=True)
201 def get_test_module_list (self
, seed
, skip
=[]):
205 for mod
in self
.modules
.values():
206 for test_app
in seed
:
207 if test_app
in mod
.tested_pkgs
:
208 test_modules
.append(mod
)
211 def write_dot(self
, modules
=None, fp
=sys
.stdout
, suggests
=False, clusters
=False):
212 from jhbuild
.modtypes
import MetaModule
213 from jhbuild
.modtypes
.autotools
import AutogenModule
214 from jhbuild
.versioncontrol
.tarball
import TarballBranch
217 modules
= self
.modules
.keys()
219 for module
in modules
:
220 inlist
[module
] = None
222 fp
.write('digraph "G" {\n'
228 mod
= self
.modules
[modname
]
230 logging
.warning(_('Unknown module:') + ' '+ modname
)
233 if isinstance(mod
, MetaModule
):
234 attrs
= '[color="lightcoral",style="filled",' \
235 'label="%s"]' % mod
.name
238 color
= 'lightskyblue'
239 if mod
.branch
.branchname
:
240 label
+= '\\n(%s)' % mod
.branch
.branchname
241 if isinstance(mod
.branch
, TarballBranch
):
242 color
= 'lightgoldenrod'
243 attrs
= '[color="%s",style="filled",label="%s"]' % (color
, label
)
244 fp
.write(' "%s" %s;\n' % (modname
, attrs
))
247 for dep
in self
.modules
[modname
].dependencies
:
248 fp
.write(' "%s" -> "%s";\n' % (modname
, dep
))
249 if not inlist
.has_key(dep
):
254 for dep
in self
.modules
[modname
].after
+ self
.modules
[modname
].suggests
:
255 if self
.modules
.has_key(dep
):
256 fp
.write(' "%s" -> "%s" [style=dotted];\n' % (modname
, dep
))
257 if not inlist
.has_key(dep
):
262 # create clusters for MetaModules
263 for modname
in inlist
.keys():
264 mod
= self
.modules
.get(modname
)
265 if isinstance(mod
, MetaModule
):
266 fp
.write(' subgraph "cluster_%s" {\n' % mod
.name
)
267 fp
.write(' label="%s";\n' % mod
.name
)
268 fp
.write(' style="filled";bgcolor="honeydew2";\n')
270 for dep
in mod
.dependencies
:
271 fp
.write(' "%s";\n' % dep
)
276 def load(config
, uri
=None):
279 elif type(config
.moduleset
) in (list, tuple):
280 modulesets
= config
.moduleset
282 modulesets
= [ config
.moduleset
]
283 ms
= ModuleSet(config
= config
)
284 for uri
in modulesets
:
286 if config
.modulesets_dir
and config
.nonetwork
or config
.use_local_modulesets
:
287 uri
= os
.path
.join(config
.modulesets_dir
, uri
+ '.modules')
289 uri
= 'http://git.gnome.org/cgit/jhbuild/plain/modulesets/%s.modules' % uri
291 ms
.modules
.update(_parse_module_set(config
, uri
).modules
)
292 except xml
.parsers
.expat
.ExpatError
, e
:
293 raise FatalError(_('failed to parse %s: %s') % (uri
, e
))
296 def load_tests (config
, uri
=None):
297 ms
= load (config
, uri
)
298 ms_tests
= ModuleSet(config
= config
)
299 for app
, module
in ms
.modules
.iteritems():
300 if module
.__class
__ == testmodule
.TestModule
:
301 ms_tests
.modules
[app
] = module
304 def _child_elements(parent
):
305 for node
in parent
.childNodes
:
306 if node
.nodeType
== node
.ELEMENT_NODE
:
309 def _child_elements_matching(parent
, names
):
310 for node
in parent
.childNodes
:
311 if node
.nodeType
== node
.ELEMENT_NODE
and node
.nodeName
in names
:
314 def _parse_module_set(config
, uri
):
316 filename
= httpcache
.load(uri
, nonetwork
=config
.nonetwork
, age
=0)
318 raise FatalError(_('could not download %s: %s') % (uri
, e
))
319 filename
= os
.path
.normpath(filename
)
321 document
= xml
.dom
.minidom
.parse(filename
)
323 raise FatalError(_('failed to parse %s: %s') % (filename
, e
))
325 assert document
.documentElement
.nodeName
== 'moduleset'
326 moduleset
= ModuleSet(config
= config
)
327 moduleset_name
= document
.documentElement
.getAttribute('name')
328 if not moduleset_name
and uri
.endswith('.modules'):
329 moduleset_name
= os
.path
.basename(uri
)[:-len('.modules')]
331 # load up list of repositories
334 for node
in _child_elements_matching(
335 document
.documentElement
, ['repository', 'cvsroot', 'svnroot',
337 name
= node
.getAttribute('name')
338 if node
.getAttribute('default') == 'yes':
340 if node
.nodeName
== 'repository':
341 repo_type
= node
.getAttribute('type')
342 repo_class
= get_repo_type(repo_type
)
344 for attr
in repo_class
.init_xml_attrs
:
345 if node
.hasAttribute(attr
):
346 kws
[attr
.replace('-', '_')] = node
.getAttribute(attr
)
347 repositories
[name
] = repo_class(config
, name
, **kws
)
348 repositories
[name
].moduleset_uri
= uri
350 for mirror
in _child_elements_matching(node
, ['mirror']):
351 mirror_type
= mirror
.getAttribute('type')
352 mirror_class
= get_repo_type(mirror_type
)
354 for attr
in mirror_class
.init_xml_attrs
:
355 if mirror
.hasAttribute(attr
):
356 kws
[attr
.replace('-','_')] = mirror
.getAttribute(attr
)
357 mirrors
[mirror_type
] = mirror_class(config
, name
, **kws
)
358 #mirrors[mirror_type].moduleset_uri = uri
359 setattr(repositories
[name
], "mirrors", mirrors
)
360 if node
.nodeName
== 'cvsroot':
361 cvsroot
= node
.getAttribute('root')
362 if node
.hasAttribute('password'):
363 password
= node
.getAttribute('password')
366 repo_type
= get_repo_type('cvs')
367 repositories
[name
] = repo_type(config
, name
,
368 cvsroot
=cvsroot
, password
=password
)
369 elif node
.nodeName
== 'svnroot':
370 svnroot
= node
.getAttribute('href')
371 repo_type
= get_repo_type('svn')
372 repositories
[name
] = repo_type(config
, name
, href
=svnroot
)
373 elif node
.nodeName
== 'arch-archive':
374 archive_uri
= node
.getAttribute('href')
375 repo_type
= get_repo_type('arch')
376 repositories
[name
] = repo_type(config
, name
,
377 archive
=name
, href
=archive_uri
)
379 # and now module definitions
380 for node
in _child_elements(document
.documentElement
):
381 if node
.nodeName
== 'include':
382 href
= node
.getAttribute('href')
383 inc_uri
= urlparse
.urljoin(uri
, href
)
385 inc_moduleset
= _parse_module_set(config
, inc_uri
)
386 except FatalError
, e
:
387 if inc_uri
[0] == '/':
389 # look up in local modulesets
390 inc_uri
= os
.path
.join(os
.path
.dirname(__file__
), '..', 'modulesets',
392 inc_moduleset
= _parse_module_set(config
, inc_uri
)
394 moduleset
.modules
.update(inc_moduleset
.modules
)
395 elif node
.nodeName
in ['repository', 'cvsroot', 'svnroot',
399 module
= modtypes
.parse_xml_node(node
, config
, uri
,
400 repositories
, default_repo
)
402 module
.tags
.append(moduleset_name
)
403 module
.moduleset_name
= moduleset_name
404 module
.config
= config
405 moduleset
.add(module
)
409 def warn_local_modulesets(config
):
410 if config
.use_local_modulesets
:
413 moduleset_local_path
= os
.path
.join(SRCDIR
, 'modulesets')
414 if not os
.path
.exists(moduleset_local_path
):
415 # moduleset-less checkout
418 if not os
.path
.exists(os
.path
.join(moduleset_local_path
, '..', '.git')):
419 # checkout was not done via git
422 if type(config
.moduleset
) == type([]):
423 modulesets
= config
.moduleset
425 modulesets
= [ config
.moduleset
]
427 if not [x
for x
in modulesets
if x
.find('/') == -1]:
428 # all modulesets have a slash; they are URI
432 git_diff
= get_output(['git', 'diff', 'origin/master', '--', '.'],
433 cwd
=moduleset_local_path
).strip()
439 # no locally modified moduleset
443 _('Modulesets were edited locally but JHBuild is configured '\
444 'to get them from the network, perhaps you need to add '\
445 'use_local_modulesets = True to your .jhbuildrc.'))