# HG changeset patch # User Sylvain Thénault # Date 1269536124 -3600 # Node ID 58e89f3dfbae65138e7df1dd0e14af81f0f4b401 # Parent b2cc2a51706f562442a2cd7d38ffea789d33db49 handle nicely typical installation other than debian package / mercurial forest * new _find_prefix method to detect installation prefix (maybe specified using CW_INSTALL_PREFIX environment variable) * set various resource accessors according to this, cleanup on the way * remove no more necessary APYCOT_ROOT hacks diff -r b2cc2a51706f -r 58e89f3dfbae cwconfig.py --- a/cwconfig.py Thu Mar 25 14:33:50 2010 +0100 +++ b/cwconfig.py Thu Mar 25 17:55:24 2010 +0100 @@ -15,8 +15,7 @@ * cubicweb migration files are by default searched in `/misc/migration` instead of - `/usr/share/cubicweb/migration/`(unless another emplacement is specified - using `CW_MIGRATION_DIR`. + `/share/cubicweb/migration/` * Cubicweb will start in 'user' mode (see below) @@ -66,9 +65,6 @@ .. envvar:: CW_RUNTIME_DIR Directory where pid files will be written -.. envvar:: CW_MIGRATION_DIR - Directory where cubicweb migration files will be found - :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses """ @@ -78,12 +74,11 @@ import sys import os import logging -import tempfile from smtplib import SMTP from threading import Lock -from os.path import exists, join, expanduser, abspath, normpath, basename, isdir +from os.path import (exists, join, expanduser, abspath, normpath, + basename, isdir, dirname) from warnings import warn - from logilab.common.decorators import cached, classproperty from logilab.common.deprecation import deprecated from logilab.common.logging_ext import set_log_methods, init_log @@ -131,6 +126,23 @@ % (directory, modes)) return modes[0] +def _find_prefix(start_path=CW_SOFTWARE_ROOT): + """Runs along the parent directories of *start_path* (default to cubicweb source directory) + looking for one containing a 'share/cubicweb' directory. + The first matching directory is assumed as the prefix installation of cubicweb + + Returns the matching prefix or None. + """ + prefix = start_path + old_prefix = None + if not isdir(start_path): + prefix = dirname(start_path) + while not isdir(join(prefix, 'share', 'cubicweb')) and prefix != old_prefix: + old_prefix = prefix + prefix = dirname(prefix) + if isdir(join(prefix, 'share', 'cubicweb')): + return prefix + return sys.prefix # persistent options definition PERSISTENT_OPTIONS = ( @@ -203,6 +215,11 @@ CWDEV = exists(join(CW_SOFTWARE_ROOT, '.hg')) +try: + _INSTALL_PREFIX = os.environ['CW_INSTALL_PREFIX'] +except KeyError: + _INSTALL_PREFIX = _find_prefix() + class CubicWebNoAppConfiguration(ConfigurationMixIn): """base class for cubicweb configuration without a specific instance directory """ @@ -216,25 +233,16 @@ # debug mode debugmode = False - if os.environ.get('APYCOT_ROOT'): - mode = 'test' - # allow to test cubes within apycot using cubicweb not installed by - # apycot - if __file__.startswith(os.environ['APYCOT_ROOT']): - CUBES_DIR = '%(APYCOT_ROOT)s/local/share/cubicweb/cubes/' % os.environ - # create __init__ file - file(join(CUBES_DIR, '__init__.py'), 'w').close() - else: - CUBES_DIR = '/usr/share/cubicweb/cubes/' - elif (CWDEV and _forced_mode != 'system'): + + if (CWDEV and _forced_mode != 'system'): mode = 'user' - CUBES_DIR = abspath(normpath(join(CW_SOFTWARE_ROOT, '../cubes'))) + _CUBES_DIR = join(CW_SOFTWARE_ROOT, '../cubes') else: - if _forced_mode == 'user': - mode = 'user' - else: - mode = 'system' - CUBES_DIR = '/usr/share/cubicweb/cubes/' + mode = _forced_mode or 'system' + _CUBES_DIR = join(_INSTALL_PREFIX, 'cubes') + + CUBES_DIR = env_path('CW_CUBES_DIR', _CUBES_DIR, 'cubes', checkexists=False) + CUBES_PATH = os.environ.get('CW_CUBES_PATH', '').split(os.pathsep) options = ( ('log-threshold', @@ -296,7 +304,6 @@ }), ) # static and class methods used to get instance independant resources ## - @staticmethod def cubicweb_version(): """return installed cubicweb version""" @@ -343,13 +350,10 @@ def cubes_search_path(cls): """return the path of directories where cubes should be searched""" path = [] - try: - for directory in os.environ['CW_CUBES_PATH'].split(os.pathsep): - directory = abspath(normpath(directory)) - if exists(directory) and not directory in path: - path.append(directory) - except KeyError: - pass + for directory in cls.CUBES_PATH: + directory = abspath(normpath(directory)) + if exists(directory) and not directory in path: + path.append(directory) if not cls.CUBES_DIR in path and exists(cls.CUBES_DIR): path.append(cls.CUBES_DIR) return path @@ -365,7 +369,7 @@ @classmethod def cube_dir(cls, cube): """return the cube directory for the given cube id, - raise ConfigurationError if it doesn't exists + raise `ConfigurationError` if it doesn't exists """ for directory in cls.cubes_search_path(): cubedir = join(directory, cube) @@ -383,10 +387,12 @@ """return the information module for the given cube""" cube = CW_MIGRATION_MAP.get(cube, cube) try: - return getattr(__import__('cubes.%s.__pkginfo__' % cube), cube).__pkginfo__ + parent = __import__('cubes.%s.__pkginfo__' % cube) + return getattr(parent, cube).__pkginfo__ except Exception, ex: - raise ConfigurationError('unable to find packaging information for ' - 'cube %s (%s: %s)' % (cube, ex.__class__.__name__, ex)) + raise ConfigurationError( + 'unable to find packaging information for cube %s (%s: %s)' + % (cube, ex.__class__.__name__, ex)) @classmethod def cube_version(cls, cube): @@ -588,6 +594,7 @@ cw_rest_init() def adjust_sys_path(self): + # overriden in CubicWebConfiguration self.cls_adjust_sys_path() def init_log(self, logthreshold=None, debug=False, @@ -637,35 +644,24 @@ """ return None + class CubicWebConfiguration(CubicWebNoAppConfiguration): """base class for cubicweb server and web configurations""" - INSTANCES_DATA_DIR = None - if os.environ.get('APYCOT_ROOT'): - root = os.environ['APYCOT_ROOT'] - REGISTRY_DIR = '%s/etc/cubicweb.d/' % root - if not exists(REGISTRY_DIR): - os.makedirs(REGISTRY_DIR) - RUNTIME_DIR = tempfile.gettempdir() - # allow to test cubes within apycot using cubicweb not installed by - # apycot - if __file__.startswith(os.environ['APYCOT_ROOT']): - MIGRATION_DIR = '%s/local/share/cubicweb/migration/' % root + if CubicWebNoAppConfiguration.mode == 'user': + _INSTANCES_DIR = expanduser('~/etc/cubicweb.d/') + else: #mode = 'system' + if _INSTALL_PREFIX == '/usr': + _INSTANCES_DIR = '/etc/cubicweb.d/' else: - MIGRATION_DIR = '/usr/share/cubicweb/migration/' - else: - if CubicWebNoAppConfiguration.mode == 'user': - REGISTRY_DIR = expanduser('~/etc/cubicweb.d/') - RUNTIME_DIR = tempfile.gettempdir() - INSTANCES_DATA_DIR = REGISTRY_DIR - else: #mode = 'system' - REGISTRY_DIR = '/etc/cubicweb.d/' - RUNTIME_DIR = '/var/run/cubicweb/' - INSTANCES_DATA_DIR = '/var/lib/cubicweb/instances/' - if CWDEV: - MIGRATION_DIR = join(CW_SOFTWARE_ROOT, 'misc', 'migration') - else: - MIGRATION_DIR = '/usr/share/cubicweb/migration/' + _INSTANCES_DIR = join(_INSTALL_PREFIX, 'etc', 'cubicweb.d') + + if os.environ.get('APYCOT_ROOT'): + _cubes_init = join(CubicWebNoAppConfiguration.CUBES_DIR, '__init__.py') + if not exists(_cubes_init): + file(join(_cubes_init), 'w').close() + if not exists(_INSTANCES_DIR): + os.makedirs(_INSTANCES_DIR) # for some commands (creation...) we don't want to initialize gettext set_language = True @@ -711,25 +707,19 @@ ) @classmethod - def runtime_dir(cls): - """run time directory for pid file...""" - return env_path('CW_RUNTIME_DIR', cls.RUNTIME_DIR, 'run time') - - @classmethod - def registry_dir(cls): + def instances_dir(cls): """return the control directory""" - return env_path('CW_INSTANCES_DIR', cls.REGISTRY_DIR, 'registry') - - @classmethod - def instance_data_dir(cls): - """return the instance data directory""" - return env_path('CW_INSTANCES_DATA_DIR', cls.INSTANCES_DATA_DIR, - 'additional data') + return env_path('CW_INSTANCES_DIR', cls._INSTANCES_DIR, 'registry') @classmethod def migration_scripts_dir(cls): """cubicweb migration scripts directory""" - return env_path('CW_MIGRATION_DIR', cls.MIGRATION_DIR, 'migration') + if CWDEV: + return join(CW_SOFTWARE_ROOT, 'misc', 'migration') + mdir = join(_INSTALL_PREFIX, 'share', 'cubicweb', 'migration') + if not exists(path): + raise ConfigurationError('migration path %s doesn\'t exist' % mdir) + return mdir @classmethod def config_for(cls, appid, config=None): @@ -752,9 +742,10 @@ """return the home directory of the instance with the given instance id """ - home = join(cls.registry_dir(), appid) + home = join(cls.instances_dir(), appid) if not exists(home): - raise ConfigurationError('no such instance %s (check it exists with "cubicweb-ctl list")' % appid) + raise ConfigurationError('no such instance %s (check it exists with' + ' "cubicweb-ctl list")' % appid) return home MODES = ('common', 'repository', 'Any', 'web') @@ -777,7 +768,9 @@ def default_log_file(self): """return default path to the log file of the instance'server""" if self.mode == 'user': - basepath = join(tempfile.gettempdir(), '%s-%s' % (basename(self.appid), self.name)) + import tempfile + basepath = join(tempfile.gettempdir(), '%s-%s' % ( + basename(self.appid), self.name)) path = basepath + '.log' i = 1 while exists(path) and i < 100: # arbitrary limit to avoid infinite loop @@ -792,7 +785,13 @@ def default_pid_file(self): """return default path to the pid file of the instance'server""" - return join(self.runtime_dir(), '%s-%s.pid' % (self.appid, self.name)) + if self.mode == 'system': + # XXX not under _INSTALL_PREFIX, right? + rtdir = env_path('CW_RUNTIME_DIR', '/var/run/cubicweb/', 'run time') + else: + import tempfile + rtdir = env_path('CW_RUNTIME_DIR', tempfile.gettempdir(), 'run time') + return join(rtdir, '%s-%s.pid' % (self.appid, self.name)) # instance methods used to get instance specific resources ############# @@ -812,11 +811,17 @@ @property def apphome(self): - return join(self.registry_dir(), self.appid) + return join(self.instances_dir(), self.appid) @property def appdatahome(self): - return join(self.instance_data_dir(), self.appid) + if self.mode == 'system': + # XXX not under _INSTALL_PREFIX, right? + iddir = '/var/lib/cubicweb/instances/' + else: + iddir = self.instances_dir() + iddir = env_path('CW_INSTANCES_DATA_DIR', iddir, 'additional data') + return join(iddir, self.appid) def init_cubes(self, cubes): assert self._cubes is None, self._cubes diff -r b2cc2a51706f -r 58e89f3dfbae cwctl.py --- a/cwctl.py Thu Mar 25 14:33:50 2010 +0100 +++ b/cwctl.py Thu Mar 25 17:55:24 2010 +0100 @@ -85,7 +85,7 @@ Instance used by another one should appears first in the file (one instance per line) """ - regdir = cwcfg.registry_dir() + regdir = cwcfg.instances_dir() _allinstances = list_instances(regdir) if isfile(join(regdir, 'startorder')): allinstances = [] @@ -303,7 +303,7 @@ print ' available modes: %s' % ', '.join(modes) print try: - regdir = cwcfg.registry_dir() + regdir = cwcfg.instances_dir() except ConfigurationError, ex: print 'No instance available:', ex print @@ -612,7 +612,7 @@ actionverb = 'restarted' def run_args(self, args, askconfirm): - regdir = cwcfg.registry_dir() + regdir = cwcfg.instances_dir() if not isfile(join(regdir, 'startorder')) or len(args) <= 1: # no specific startorder super(RestartInstanceCommand, self).run_args(args, askconfirm) @@ -958,7 +958,7 @@ def run(self, args): """run the command with its specific arguments""" - regdir = cwcfg.registry_dir() + regdir = cwcfg.instances_dir() for appid in sorted(listdir(regdir)): print appid diff -r b2cc2a51706f -r 58e89f3dfbae devtools/__init__.py --- a/devtools/__init__.py Thu Mar 25 14:33:50 2010 +0100 +++ b/devtools/__init__.py Thu Mar 25 17:55:24 2010 +0100 @@ -98,9 +98,6 @@ }), )) - if not os.environ.get('APYCOT_ROOT'): - REGISTRY_DIR = normpath(join(CW_SOFTWARE_ROOT, '../cubes')) - def __init__(self, appid, log_threshold=logging.CRITICAL+10): ServerConfiguration.__init__(self, appid) self.init_log(log_threshold, force=True) diff -r b2cc2a51706f -r 58e89f3dfbae test/unittest_cwconfig.py --- a/test/unittest_cwconfig.py Thu Mar 25 14:33:50 2010 +0100 +++ b/test/unittest_cwconfig.py Thu Mar 25 17:55:24 2010 +0100 @@ -7,13 +7,16 @@ """ import sys import os +import tempfile from os.path import dirname, join, abspath from logilab.common.modutils import cleanup_sys_modules -from logilab.common.testlib import TestCase, unittest_main +from logilab.common.testlib import (TestCase, unittest_main, + with_tempdir) from logilab.common.changelog import Version from cubicweb.devtools import ApptestConfiguration +from cubicweb.cwconfig import _find_prefix def unabsolutize(path): parts = path.split(os.sep) @@ -32,7 +35,7 @@ self.config._cubes = ('email', 'file') def tearDown(self): - os.environ.pop('CW_CUBES_PATH', None) + ApptestConfiguration.CUBES_PATH = [] def test_reorder_cubes(self): # jpl depends on email and file and comment @@ -91,11 +94,11 @@ # make sure we don't import the email cube, but the stdlib email package import email self.assertNotEquals(dirname(email.__file__), self.config.CUBES_DIR) - os.environ['CW_CUBES_PATH'] = CUSTOM_CUBES_DIR + self.config.__class__.CUBES_PATH = [CUSTOM_CUBES_DIR] self.assertEquals(self.config.cubes_search_path(), [CUSTOM_CUBES_DIR, self.config.CUBES_DIR]) - os.environ['CW_CUBES_PATH'] = os.pathsep.join([ - CUSTOM_CUBES_DIR, self.config.CUBES_DIR, 'unexistant']) + self.config.__class__.CUBES_PATH = [CUSTOM_CUBES_DIR, + self.config.CUBES_DIR, 'unexistant'] # filter out unexistant and duplicates self.assertEquals(self.config.cubes_search_path(), [CUSTOM_CUBES_DIR, @@ -114,6 +117,91 @@ from cubes import file self.assertEquals(file.__path__, [join(CUSTOM_CUBES_DIR, 'file')]) +class FindPrefixTC(TestCase): + def make_dirs(self, *args): + path = join(tempfile.tempdir, *args) + if not os.path.exists(path): + os.makedirs(path) + return path + + def make_file(self, *args): + self.make_dirs(*args[: -1]) + file_path = join(tempfile.tempdir, *args) + file_obj = open(file_path, 'w') + file_obj.write('""" None """') + file_obj.close() + return file_path + + @with_tempdir + def test_samedir(self): + prefix = tempfile.tempdir + self.make_dirs('share', 'cubicweb') + self.assertEquals(_find_prefix(prefix), prefix) + + @with_tempdir + def test_samedir_filepath(self): + prefix = tempfile.tempdir + self.make_dirs('share', 'cubicweb') + file_path = self.make_file('bob.py') + self.assertEquals(_find_prefix(file_path), prefix) + + @with_tempdir + def test_dir_inside_prefix(self): + prefix = tempfile.tempdir + self.make_dirs('share', 'cubicweb') + dir_path = self.make_dirs('bob') + self.assertEquals(_find_prefix(dir_path), prefix) + + @with_tempdir + def test_file_in_dir_inside_prefix(self): + prefix = tempfile.tempdir + self.make_dirs('share', 'cubicweb') + file_path = self.make_file('bob', 'toto.py') + self.assertEquals(_find_prefix(file_path), prefix) + + @with_tempdir + def test_file_in_deeper_dir_inside_prefix(self): + prefix = tempfile.tempdir + self.make_dirs('share', 'cubicweb') + file_path = self.make_file('bob', 'pyves', 'alain', 'adim', 'syt', 'toto.py') + self.assertEquals(_find_prefix(file_path), prefix) + + @with_tempdir + def test_multiple_candidate_prefix(self): + self.make_dirs('share', 'cubicweb') + prefix = self.make_dirs('bob') + self.make_dirs('bob', 'share', 'cubicweb') + file_path = self.make_file('bob', 'pyves', 'alain', 'adim', 'syt', 'toto.py') + self.assertEquals(_find_prefix(file_path), prefix) + + @with_tempdir + def test_sister_candidate_prefix(self): + prefix = tempfile.tempdir + self.make_dirs('share', 'cubicweb') + self.make_dirs('bob', 'share', 'cubicweb') + file_path = self.make_file('bell', 'toto.py') + self.assertEquals(_find_prefix(file_path), prefix) + + @with_tempdir + def test_multiple_parent_candidate_prefix(self): + self.make_dirs('share', 'cubicweb') + prefix = self.make_dirs('share', 'cubicweb', 'bob') + self.make_dirs('share', 'cubicweb', 'bob', 'share', 'cubicweb') + file_path = self.make_file('share', 'cubicweb', 'bob', 'pyves', 'alain', 'adim', 'syt', 'toto.py') + self.assertEquals(_find_prefix(file_path), prefix) + + @with_tempdir + def test_upper_candidate_prefix(self): + prefix = tempfile.tempdir + self.make_dirs('share', 'cubicweb') + self.make_dirs('bell','bob', 'share', 'cubicweb') + file_path = self.make_file('bell', 'toto.py') + self.assertEquals(_find_prefix(file_path), prefix) + + @with_tempdir + def test_no_prefix(self): + prefix = tempfile.tempdir + self.assertEquals(_find_prefix(prefix), sys.prefix) if __name__ == '__main__': unittest_main() diff -r b2cc2a51706f -r 58e89f3dfbae test/unittest_cwctl.py --- a/test/unittest_cwctl.py Thu Mar 25 14:33:50 2010 +0100 +++ b/test/unittest_cwctl.py Thu Mar 25 17:55:24 2010 +0100 @@ -10,15 +10,8 @@ from cStringIO import StringIO from logilab.common.testlib import TestCase, unittest_main -if os.environ.get('APYCOT_ROOT'): - root = os.environ['APYCOT_ROOT'] - CUBES_DIR = '%s/local/share/cubicweb/cubes/' % root - os.environ['CW_CUBES_PATH'] = CUBES_DIR - REGISTRY_DIR = '%s/etc/cubicweb.d/' % root - os.environ['CW_INSTANCES_DIR'] = REGISTRY_DIR - from cubicweb.cwconfig import CubicWebConfiguration -CubicWebConfiguration.load_cwctl_plugins() +CubicWebConfiguration.load_cwctl_plugins() # XXX necessary? class CubicWebCtlTC(TestCase): def setUp(self): diff -r b2cc2a51706f -r 58e89f3dfbae toolsutils.py --- a/toolsutils.py Thu Mar 25 14:33:50 2010 +0100 +++ b/toolsutils.py Thu Mar 25 17:55:24 2010 +0100 @@ -184,7 +184,7 @@ config_file, ex) return config -def env_path(env_var, default, name): +def env_path(env_var, default, name, checkexists=True): """get a path specified in a variable or using the default value and return it. @@ -203,8 +203,8 @@ :raise `ConfigurationError`: if the returned path does not exist """ path = environ.get(env_var, default) - if not exists(path): - raise ConfigurationError('%s path %s doesn\'t exist' % (name, path)) + if checkexists and not exists(path): + raise ConfigurationError('%s directory %s doesn\'t exist' % (name, path)) return abspath(path)