diff --git a/lib/ramble/ramble/application.py b/lib/ramble/ramble/application.py index d5cc036e0..057318dd4 100644 --- a/lib/ramble/ramble/application.py +++ b/lib/ramble/ramble/application.py @@ -58,7 +58,7 @@ class ApplicationBase(object, metaclass=ApplicationMeta): _workload_exec_key = 'executables' _inventory_file_name = 'ramble_inventory.json' _status_file_name = 'ramble_status.json' - _pipelines = ['analyze', 'archive', 'mirror', 'setup'] + _pipelines = ['analyze', 'archive', 'mirror', 'setup', 'pushtocache'] #: Lists of strings which contains GitHub usernames of attributes. #: Do not include @ here in order not to unnecessarily ping the users. @@ -155,6 +155,9 @@ def build_phase_order(self): for pipeline in self._pipelines: pipeline_phases = [] + if pipeline not in self.phase_definitions: + self.phase_definitions[pipeline] = {} + # Detect cycles for phase in self.phase_definitions[pipeline].keys(): diff --git a/lib/ramble/ramble/application_types/spack.py b/lib/ramble/ramble/application_types/spack.py index d57c217b2..eff5d68c0 100644 --- a/lib/ramble/ramble/application_types/spack.py +++ b/lib/ramble/ramble/application_types/spack.py @@ -365,6 +365,30 @@ def _mirror_software(self, workspace): with self.logger.force_echo(): tty.die(e) + register_phase('push_to_spack_cache', pipeline='pushtocache', depends_on=[]) + + def _push_to_spack_cache(self, workspace): + + env_path = self.expander.env_path + cache_tupl = ('push-to-cache', env_path) + if workspace.check_cache(cache_tupl): + tty.debug('{} already pushed, skipping'.format(cache_tupl)) + return + else: + workspace.add_to_cache(cache_tupl) + + try: + self.spack_runner.set_dry_run(workspace.dry_run) + self.spack_runner.set_env(env_path) + self.spack_runner.activate() + + self.spack_runner.push_to_spack_cache(workspace.spack_cache_path) + + self.spack_runner.deactivate() + except ramble.spack_runner.RunnerError as e: + with self.logger.force_echo(): + tty.die(e) + def populate_inventory(self, workspace, force_compute=False, require_exist=False): """Add software environment information to hash inventory""" diff --git a/lib/ramble/ramble/cmd/workspace.py b/lib/ramble/ramble/cmd/workspace.py index 9e075c81d..2bfe7a7d1 100644 --- a/lib/ramble/ramble/cmd/workspace.py +++ b/lib/ramble/ramble/cmd/workspace.py @@ -51,6 +51,7 @@ 'concretize', 'setup', 'analyze', + 'push-to-cache', 'info', 'edit', 'mirror', @@ -311,7 +312,8 @@ def workspace_concretize(args): def workspace_run_pipeline(args, pipeline): - if args.include_phase_dependencies: + include_phase_dependencies = getattr(args, 'include_phase_dependencies', None) + if include_phase_dependencies: with ramble.config.override('config:include_phase_dependencies', True): pipeline.run() else: @@ -404,6 +406,35 @@ def workspace_analyze(args): workspace_run_pipeline(args, pipeline) +def workspace_push_to_cache(args): + current_pipeline = ramble.pipeline.pipelines.pushtocache + ws = ramble.cmd.require_active_workspace(cmd_name='workspace pushtocache') + + filters = ramble.filters.Filters( + phase_filters='*', + include_where_filters=args.where, + exclude_where_filters=args.exclude_where + ) + pipeline_cls = ramble.pipeline.pipeline_class(current_pipeline) + + pipeline = pipeline_cls(ws, filters, spack_cache_path=args.cache_path) + + workspace_run_pipeline(args, pipeline) + pipeline.run() + + +def workspace_push_to_cache_setup_parser(subparser): + """push workspace envs to a given spack buildcache""" + + subparser.add_argument( + '-d', dest='cache_path', + default=None, + required=True, + help='Path to spack cache.') + + arguments.add_common_arguments(subparser, ['where', 'exclude_where']) + + def workspace_info_setup_parser(subparser): """Information about a workspace""" subparser.add_argument('-v', '--verbose', action='count', default=0, diff --git a/lib/ramble/ramble/pipeline.py b/lib/ramble/ramble/pipeline.py index 925decc7b..af5970cc0 100644 --- a/lib/ramble/ramble/pipeline.py +++ b/lib/ramble/ramble/pipeline.py @@ -337,16 +337,35 @@ def _complete(self): | stat.S_IROTH | stat.S_IXOTH) +class PushToCachePipeline(Pipeline): + """Class for the pushtocache pipeline""" + + name = 'pushtocache' + + def __init__(self, workspace, filters, spack_cache_path=None): + super().__init__(workspace, filters) + self.action_string = 'Pushing to Spack Cache' + self.spack_cache_path = spack_cache_path + + def _prepare(self): + super()._prepare() + self.workspace.spack_cache_path = self.spack_cache_path + + def _complete(self): + tty.msg('Pushed envs to spack cache %s' % self.spack_cache_path) + + pipelines = Enum('pipelines', [AnalyzePipeline.name, ArchivePipeline.name, MirrorPipeline.name, - SetupPipeline.name] + SetupPipeline.name, PushToCachePipeline.name] ) _pipeline_map = { pipelines.analyze: AnalyzePipeline, pipelines.archive: ArchivePipeline, pipelines.mirror: MirrorPipeline, - pipelines.setup: SetupPipeline + pipelines.setup: SetupPipeline, + pipelines.pushtocache: PushToCachePipeline } diff --git a/lib/ramble/ramble/schema/config.py b/lib/ramble/ramble/schema/config.py index 1d1139f66..c0eb0661f 100644 --- a/lib/ramble/ramble/schema/config.py +++ b/lib/ramble/ramble/schema/config.py @@ -86,6 +86,24 @@ }, 'additionalProperties': False }, + 'buildcache': { + 'type': 'object', + 'default': { + 'flags': '', + 'prefix': '', + }, + 'properties': { + 'flags': { + 'type': 'string', + 'default': '', + }, + 'prefix': { + 'type': 'string', + 'default': '' + } + }, + 'additionalProperties': False + }, }, 'additionalProperties': False, } diff --git a/lib/ramble/ramble/spack_runner.py b/lib/ramble/ramble/spack_runner.py index 3ab4b2d48..33cb6b2a0 100644 --- a/lib/ramble/ramble/spack_runner.py +++ b/lib/ramble/ramble/spack_runner.py @@ -49,6 +49,7 @@ class SpackRunner(object): global_config_name = 'config:spack:global' install_config_name = 'config:spack:install' + buildcache_config_name = 'config:spack:buildcache' concretize_config_name = 'config:spack:concretize' env_create_args = [ @@ -668,6 +669,41 @@ def mirror_environment(self, mirror_path): %-4d added %-4d failed to fetch.""" % (0, 0, 0) + def get_env_hash_list(self): + self._check_active() + args = [ + 'find', + '--format', + '/{hash}' + ] + output = self.spack(*args, output=str).strip().replace('\n', ' ') + return output + + def push_to_spack_cache(self, spack_cache_path): + """Push packages for a given env to the spack cache""" + self._check_active() + + hash_list = self.get_env_hash_list() + + args = [ + "buildcache", + "push" + ] + user_flags = ramble.config.get(f'{self.buildcache_config_name}:flags') + + tty.debug("Running with user flags: {}".format(user_flags)) + + if user_flags is not None: + args.extend(shlex.split(user_flags)) + + args.extend([spack_cache_path, hash_list]) + + if not self.dry_run: + return self.spack(*args, output=str).strip() + else: + self._dry_run_print(self.spack, args) + return + def _dry_run_print(self, executable, args): tty.msg('DRY-RUN: would run %s' % executable) tty.msg(' with args: %s' % args) diff --git a/share/ramble/ramble-completion.bash b/share/ramble/ramble-completion.bash index 6591eb8d8..85b25581c 100755 --- a/share/ramble/ramble-completion.bash +++ b/share/ramble/ramble-completion.bash @@ -631,7 +631,7 @@ _ramble_workspace() { then RAMBLE_COMPREPLY="-h --help" else - RAMBLE_COMPREPLY="activate archive deactivate create concretize setup analyze info edit mirror list ls remove rm" + RAMBLE_COMPREPLY="activate archive deactivate create concretize setup analyze push-to-cache info edit mirror list ls remove rm" fi } @@ -673,6 +673,10 @@ _ramble_workspace_analyze() { RAMBLE_COMPREPLY="-h --help -f --formats -u --upload --always-print-foms --phases --include-phase-dependencies --where --exclude-where" } +_ramble_workspace_push_to_cache() { + RAMBLE_COMPREPLY="-h --help -d --where --exclude-where" +} + _ramble_workspace_info() { RAMBLE_COMPREPLY="-h --help -v --verbose" }