From 50017fc614ee84a552ba317e8d726a6bbf583c05 Mon Sep 17 00:00:00 2001 From: Mike Purvis Date: Fri, 26 May 2017 11:50:44 -0400 Subject: [PATCH] Basic implementation of pluggable spaces. --- catkin_tools/argument_parsing.py | 4 +- catkin_tools/context.py | 174 +++++++++--------------- catkin_tools/spaces/__init__.py | 0 catkin_tools/spaces/build.py | 20 +++ catkin_tools/spaces/devel.py | 21 +++ catkin_tools/spaces/install.py | 20 +++ catkin_tools/spaces/log.py | 20 +++ catkin_tools/spaces/source.py | 20 +++ catkin_tools/verbs/catkin_config/cli.py | 40 ++---- setup.py | 7 + 10 files changed, 185 insertions(+), 141 deletions(-) create mode 100644 catkin_tools/spaces/__init__.py create mode 100644 catkin_tools/spaces/build.py create mode 100644 catkin_tools/spaces/devel.py create mode 100644 catkin_tools/spaces/install.py create mode 100644 catkin_tools/spaces/log.py create mode 100644 catkin_tools/spaces/source.py diff --git a/catkin_tools/argument_parsing.py b/catkin_tools/argument_parsing.py index 189ab351..7e3edb6d 100644 --- a/catkin_tools/argument_parsing.py +++ b/catkin_tools/argument_parsing.py @@ -89,7 +89,7 @@ def add_cmake_and_make_and_catkin_make_args(parser): add = parser.add_mutually_exclusive_group().add_argument add('--make-args', metavar='ARG', dest='make_args', nargs='+', required=False, type=str, default=None, - help='Arbitrary arguments which are passes to make.' + help='Arbitrary arguments which are passes to make. ' 'It collects all of following arguments until a "--" is read.') add('--no-make-args', dest='make_args', action='store_const', const=[], default=None, help='Pass no additional arguments to make (does not affect --catkin-make-args).') @@ -97,7 +97,7 @@ def add_cmake_and_make_and_catkin_make_args(parser): add = parser.add_mutually_exclusive_group().add_argument add('--catkin-make-args', metavar='ARG', dest='catkin_make_args', nargs='+', required=False, type=str, default=None, - help='Arbitrary arguments which are passes to make but only for catkin packages.' + help='Arbitrary arguments which are passes to make but only for catkin packages. ' 'It collects all of following arguments until a "--" is read.') add('--no-catkin-make-args', dest='catkin_make_args', action='store_const', const=[], default=None, help='Pass no additional arguments to make for catkin packages (does not affect --make-args).') diff --git a/catkin_tools/context.py b/catkin_tools/context.py index e5d1aa67..172e6a9e 100644 --- a/catkin_tools/context.py +++ b/catkin_tools/context.py @@ -49,19 +49,12 @@ class Context(object): This context can be locked, so that changing the members is prevented. """ - DEFAULT_LOG_SPACE = 'logs' - DEFAULT_SOURCE_SPACE = 'src' - DEFAULT_BUILD_SPACE = 'build' - DEFAULT_DEVEL_SPACE = 'devel' - DEFAULT_INSTALL_SPACE = 'install' + CATKIN_SPACES_GROUP = 'catkin_tools.spaces' + + SPACES = {} STORED_KEYS = [ 'extend_path', - 'source_space', - 'log_space', - 'build_space', - 'devel_space', - 'install_space', 'devel_layout', 'install', 'isolate_install', @@ -75,12 +68,57 @@ class Context(object): 'blacklist', ] - KEYS = STORED_KEYS + [ + EXTRA_KEYS = [ 'workspace', 'profile', 'space_suffix', ] + KEYS = [] + + @classmethod + def _create_space_methods(cls, space): + def space_abs_getter(self): + return getattr(self, '__%s_space_abs' % space) + + def space_getter(self): + return getattr(self, '__%s_space' % space) + + def space_setter(self, value): + if self.__locked: + raise RuntimeError("Setting of context members is not allowed while locked.") + setattr(self, '__%s_space' % space, value) + setattr(self, '__%s_space_abs' % space, os.path.join(self.__workspace, value)) + + def space_exists(self): + "Returns true if the space exists" + space_abs = getattr(self, '__%s_space_abs' % space) + return os.path.exists(space_abs) and os.path.isdir(space_abs) + + setattr(cls, '%s_space' % space, property(space_getter, space_setter)) + setattr(cls, '%s_space_abs' % space, property(space_abs_getter)) + setattr(cls, '%s_space_exists' % space, space_exists) + + @classmethod + def setup_space_keys(cls): + ''' + To be called one time on initial use. Initializes the SPACE_KEYS + class members and associated member functions based on available + space plugins. + ''' + if cls.KEYS: + return + + from pkg_resources import iter_entry_points + + for entry_point in iter_entry_points(group=cls.CATKIN_SPACES_GROUP): + ep_dict = entry_point.load() + cls.STORED_KEYS.append(entry_point.name + '_space') + cls.SPACES[entry_point.name] = ep_dict + cls._create_space_methods(entry_point.name) + + cls.KEYS = cls.STORED_KEYS + cls.EXTRA_KEYS + @classmethod def load( cls, @@ -125,6 +163,7 @@ def load( :returns: A potentially valid Context object constructed from the given arguments :rtype: Context """ + Context.setup_space_keys() # Initialize dictionary version of opts namespace opts_vars = vars(opts) if opts else {} @@ -194,11 +233,6 @@ def __init__( workspace=None, profile=None, extend_path=None, - source_space=None, - log_space=None, - build_space=None, - devel_space=None, - install_space=None, devel_layout=None, install=False, isolate_install=False, @@ -264,19 +298,20 @@ def __init__( print('Warning: Unhandled config context options: {}'.format(kwargs), file=sys.stderr) # Validation is done on assignment - # Handle *space assignment and defaults self.workspace = workspace self.extend_path = extend_path if extend_path else None - ss = '' if space_suffix is None else space_suffix self.profile = profile - self.source_space = Context.DEFAULT_SOURCE_SPACE if source_space is None else source_space - self.log_space = Context.DEFAULT_LOG_SPACE + ss if ss or log_space is None else log_space - self.build_space = Context.DEFAULT_BUILD_SPACE + ss if ss or build_space is None else build_space - self.devel_space = Context.DEFAULT_DEVEL_SPACE + ss if ss or devel_space is None else devel_space - self.install_space = Context.DEFAULT_INSTALL_SPACE + ss if ss or install_space is None else install_space + # Handle *space assignment and defaults + for space, space_dict in Context.SPACES.items(): + key_name = space + '_space' + default = space_dict['default'] + if space_suffix and space != 'source': + default += space_suffix + setattr(self, key_name, kwargs.get(key_name, default)) + self.destdir = os.environ['DESTDIR'] if 'DESTDIR' in os.environ else None # Handle package whitelist/blacklist @@ -411,20 +446,20 @@ def summary(self, notes=[]): "--init`.")] if not self.source_space_exists(): summary_warnings += [clr( - "Source space `@{yf}{_Context__source_space_abs}@|` does not yet exist.")] + "Source space `@{yf}{__source_space_abs}@|` does not yet exist.")] + spaces_summary = [] + for space, space_dict in sorted(Context.SPACES.items()): + spaces_summary.append( + clr('@{cf}' + space_dict['space'] + ':@|' + ' ' * (18 - len(space_dict['space'])) + + '{' + space + '_missing} @{yf}{__' + space + '_space_abs}@|')) summary = [ [ clr("@{cf}Profile:@| @{yf}{profile}@|"), clr("@{cf}Extending:@| {extend_mode} @{yf}{extend}@|"), clr("@{cf}Workspace:@| @{yf}{_Context__workspace}@|"), ], - [ - clr("@{cf}Source Space:@| {source_missing} @{yf}{_Context__source_space_abs}@|"), - clr("@{cf}Log Space:@| {log_missing} @{yf}{_Context__log_space_abs}@|"), - clr("@{cf}Build Space:@| {build_missing} @{yf}{_Context__build_space_abs}@|"), - clr("@{cf}Devel Space:@| {devel_missing} @{yf}{_Context__devel_space_abs}@|"), - clr("@{cf}Install Space:@| {install_missing} @{yf}{_Context__install_space_abs}@|"), + spaces_summary + [ clr("@{cf}DESTDIR:@| {destdir_missing} @{yf}{_Context__destdir}@|") ], [ @@ -547,89 +582,10 @@ def extend_path(self, value): raise ValueError("Resultspace path '{0}' does not exist.".format(value)) self.__extend_path = value - @property - def source_space_abs(self): - return self.__source_space_abs - - @property - def source_space(self): - return self.__source_space - - @source_space.setter - def source_space(self, value): - if self.__locked: - raise RuntimeError("Setting of context members is not allowed while locked.") - self.__source_space = value - self.__source_space_abs = os.path.join(self.__workspace, value) - - def source_space_exists(self): - "Returns true if the source space exists" - return os.path.exists(self.source_space_abs) and os.path.isdir(self.source_space_abs) - def initialized(self): """Check if this context is initialized.""" return self.workspace == find_enclosing_workspace(self.workspace) - @property - def log_space_abs(self): - return self.__log_space_abs - - @property - def log_space(self): - return self.__log_space - - @log_space.setter - def log_space(self, value): - if self.__locked: - raise RuntimeError("Setting of context members is not allowed while locked.") - self.__log_space = value - self.__log_space_abs = os.path.join(self.__workspace, value) - - @property - def build_space_abs(self): - return self.__build_space_abs - - @property - def build_space(self): - return self.__build_space - - @build_space.setter - def build_space(self, value): - if self.__locked: - raise RuntimeError("Setting of context members is not allowed while locked.") - self.__build_space = value - self.__build_space_abs = os.path.join(self.__workspace, value) - - @property - def devel_space_abs(self): - return self.__devel_space_abs - - @property - def devel_space(self): - return self.__devel_space - - @devel_space.setter - def devel_space(self, value): - if self.__locked: - raise RuntimeError("Setting of context members is not allowed while locked.") - self.__devel_space = value - self.__devel_space_abs = os.path.join(self.__workspace, value) - - @property - def install_space_abs(self): - return self.__install_space_abs - - @property - def install_space(self): - return self.__install_space - - @install_space.setter - def install_space(self, value): - if self.__locked: - raise RuntimeError("Setting of context members is not allowed while locked.") - self.__install_space = value - self.__install_space_abs = os.path.join(self.__workspace, value) - @property def destdir(self): return self.__destdir diff --git a/catkin_tools/spaces/__init__.py b/catkin_tools/spaces/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/catkin_tools/spaces/build.py b/catkin_tools/spaces/build.py new file mode 100644 index 00000000..322cd4a8 --- /dev/null +++ b/catkin_tools/spaces/build.py @@ -0,0 +1,20 @@ +# Copyright 2017 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +description = dict( + default='build', + short_flag='-b', + space='Build Space', + description='Intermediate generated files are placed in this location.' +) diff --git a/catkin_tools/spaces/devel.py b/catkin_tools/spaces/devel.py new file mode 100644 index 00000000..60c5d287 --- /dev/null +++ b/catkin_tools/spaces/devel.py @@ -0,0 +1,21 @@ +# Copyright 2017 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +description = dict( + default='devel', + short_flag='-d', + space='Devel Space', + description='This result space contains compiled products but ' + + 'references the source space for static assets and scripts.' +) diff --git a/catkin_tools/spaces/install.py b/catkin_tools/spaces/install.py new file mode 100644 index 00000000..f2b7fdf6 --- /dev/null +++ b/catkin_tools/spaces/install.py @@ -0,0 +1,20 @@ +# Copyright 2017 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +description = dict( + default='install', + short_flag='-i', + space='Install Space', + description='Self-contained installation result space.' +) diff --git a/catkin_tools/spaces/log.py b/catkin_tools/spaces/log.py new file mode 100644 index 00000000..169aa05c --- /dev/null +++ b/catkin_tools/spaces/log.py @@ -0,0 +1,20 @@ +# Copyright 2017 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +description = dict( + default='logs', + short_flag='-l', + space='Log Space', + description='Output generated during the build stages.' +) diff --git a/catkin_tools/spaces/source.py b/catkin_tools/spaces/source.py new file mode 100644 index 00000000..e0275979 --- /dev/null +++ b/catkin_tools/spaces/source.py @@ -0,0 +1,20 @@ +# Copyright 2017 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +description = dict( + default='src', + short_flag='-s', + space='Source Space', + description='Source files, should not be modified by build system.' +) diff --git a/catkin_tools/verbs/catkin_config/cli.py b/catkin_tools/verbs/catkin_config/cli.py index 05dda9be..95567f3f 100644 --- a/catkin_tools/verbs/catkin_config/cli.py +++ b/catkin_tools/verbs/catkin_config/cli.py @@ -75,36 +75,16 @@ def prepare_arguments(parser): help='Clear all packages from the blacklist.') spaces_group = parser.add_argument_group('Spaces', 'Location of parts of the catkin workspace.') - add = spaces_group.add_mutually_exclusive_group().add_argument - add('-s', '--source-space', default=None, - help='The path to the source space.') - add('--default-source-space', - action='store_const', dest='source_space', default=None, const=Context.DEFAULT_SOURCE_SPACE, - help='Use the default path to the source space ("src")') - add = spaces_group.add_mutually_exclusive_group().add_argument - add('-l', '--log-space', default=None, - help='The path to the log space.') - add('--default-log-space', - action='store_const', dest='log_space', default=None, const=Context.DEFAULT_LOG_SPACE, - help='Use the default path to the log space ("logs")') - add = spaces_group.add_mutually_exclusive_group().add_argument - add('-b', '--build-space', default=None, - help='The path to the build space.') - add('--default-build-space', - action='store_const', dest='build_space', default=None, const=Context.DEFAULT_BUILD_SPACE, - help='Use the default path to the build space ("build")') - add = spaces_group.add_mutually_exclusive_group().add_argument - add('-d', '--devel-space', default=None, - help='Sets the target devel space') - add('--default-devel-space', - action='store_const', dest='devel_space', default=None, const=Context.DEFAULT_DEVEL_SPACE, - help='Sets the default target devel space ("devel")') - add = spaces_group.add_mutually_exclusive_group().add_argument - add('-i', '--install-space', default=None, - help='Sets the target install space') - add('--default-install-space', - action='store_const', dest='install_space', default=None, const=Context.DEFAULT_INSTALL_SPACE, - help='Sets the default target install space ("install")') + Context.setup_space_keys() + for space, space_dict in Context.SPACES.items(): + add = spaces_group.add_mutually_exclusive_group().add_argument + flags = ['--{}-space'.format(space)] + flags.extend([space_dict['short_flag']] if 'short_flag' in space_dict else []) + add(*flags, default=None, + help='The path to the {} space.'.format(space)) + add('--default-{}-space'.format(space), + action='store_const', dest='{}_space'.format(space), default=None, const=space_dict['default'], + help='Use the default path to the {} space ("{}")'.format(space, space_dict['default'])) add = spaces_group.add_argument add('-x', '--space-suffix', help='Suffix for build, devel, and install space if they are not otherwise explicitly set.') diff --git a/setup.py b/setup.py index cd8fed02..17c4033f 100644 --- a/setup.py +++ b/setup.py @@ -155,6 +155,13 @@ def run(self): 'catkin = catkin_tools.jobs.catkin:description', 'cmake = catkin_tools.jobs.cmake:description', ], + 'catkin_tools.spaces': [ + 'build = catkin_tools.spaces.build:description', + 'devel = catkin_tools.spaces.devel:description', + 'install = catkin_tools.spaces.install:description', + 'log = catkin_tools.spaces.log:description', + 'source = catkin_tools.spaces.source:description', + ], }, cmdclass={'install': PermissiveInstall}, )