27 from os.path import exists, join, basename, splitext |
27 from os.path import exists, join, basename, splitext |
28 |
28 |
29 from logilab.common.decorators import cached |
29 from logilab.common.decorators import cached |
30 from logilab.common.configuration import REQUIRED, read_old_config |
30 from logilab.common.configuration import REQUIRED, read_old_config |
31 from logilab.common.shellutils import ASK |
31 from logilab.common.shellutils import ASK |
|
32 from logilab.common.changelog import Version |
32 |
33 |
33 from cubicweb import ConfigurationError |
34 from cubicweb import ConfigurationError |
34 |
35 |
35 |
36 |
36 def filter_scripts(config, directory, fromversion, toversion, quiet=True): |
37 def filter_scripts(config, directory, fromversion, toversion, quiet=True): |
385 |
386 |
386 |
387 |
387 from logging import getLogger |
388 from logging import getLogger |
388 from cubicweb import set_log_methods |
389 from cubicweb import set_log_methods |
389 set_log_methods(MigrationHelper, getLogger('cubicweb.migration')) |
390 set_log_methods(MigrationHelper, getLogger('cubicweb.migration')) |
|
391 |
|
392 |
|
393 def version_strictly_lower(a, b): |
|
394 if a: |
|
395 a = Version(a) |
|
396 if b: |
|
397 b = Version(b) |
|
398 return a < b |
|
399 |
|
400 def max_version(a, b): |
|
401 return str(max(Version(a), Version(b))) |
|
402 |
|
403 class ConfigurationProblem(object): |
|
404 """Each cube has its own list of dependencies on other cubes/versions. |
|
405 |
|
406 The ConfigurationProblem is used to record the loaded cubes, then to detect |
|
407 inconsistencies in their dependencies. |
|
408 |
|
409 See configuration management on wikipedia for litterature. |
|
410 """ |
|
411 |
|
412 def __init__(self, config): |
|
413 self.cubes = {} |
|
414 self.config = config |
|
415 |
|
416 def add_cube(self, name, version): |
|
417 self.cubes[name] = version |
|
418 |
|
419 def solve(self): |
|
420 self.warnings = [] |
|
421 self.errors = [] |
|
422 self.read_constraints() |
|
423 for cube, versions in sorted(self.constraints.items()): |
|
424 oper, version = None, None |
|
425 # simplify constraints |
|
426 if versions: |
|
427 for constraint in versions: |
|
428 op, ver = constraint |
|
429 if oper is None: |
|
430 oper = op |
|
431 version = ver |
|
432 elif op == '>=' and oper == '>=': |
|
433 version = max_version(ver, version) |
|
434 else: |
|
435 print 'unable to handle this case', oper, version, op, ver |
|
436 # "solve" constraint satisfaction problem |
|
437 if cube not in self.cubes: |
|
438 self.errors.append( ('add', cube, version) ) |
|
439 elif versions: |
|
440 lower_strict = version_strictly_lower(self.cubes[cube], version) |
|
441 if oper in ('>=','='): |
|
442 if lower_strict: |
|
443 self.errors.append( ('update', cube, version) ) |
|
444 else: |
|
445 print 'unknown operator', oper |
|
446 |
|
447 def read_constraints(self): |
|
448 self.constraints = {} |
|
449 self.reverse_constraints = {} |
|
450 for cube in self.cubes: |
|
451 use = self.config.cube_dependencies(cube) |
|
452 for name, constraint in use.iteritems(): |
|
453 self.constraints.setdefault(name,set()) |
|
454 if constraint: |
|
455 try: |
|
456 oper, version = constraint.split() |
|
457 self.constraints[name].add( (oper, version) ) |
|
458 except: |
|
459 self.warnings.append( |
|
460 'cube %s depends on %s but constraint badly ' |
|
461 'formatted: %s' % (cube, name, constraint)) |
|
462 self.reverse_constraints.setdefault(name, set()).add(cube) |