hpccm.recipe

[docs] module hpccm.recipe

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# Copyright (c) 2018, NVIDIA CORPORATION.  All rights reserved.
#
# 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.

# pylint: disable=invalid-name, unused-import

"""Container recipe"""

from packaging.version import Version
import logging
import os
import sys
import traceback

import hpccm

import hpccm.config

from hpccm.common import container_type

from hpccm.Stage import Stage

from hpccm.building_blocks import *
from hpccm.primitives import *

def include(recipe_file, _globals=None, _locals=None, prepend_path=True,
            raise_exceptions=False):
    """Include a recipe file

    Args:
        recipe_file: path to a recipe file (required)
        _globals: a dictionary representing the global symbol table
        _locals: a dictionary representing the local symbol table
        prepend_path: If True, prepend the path of the main recipe to the
            recipe_file. If the recipe_file is an absolute path, then the path
            is not prepended regardless of the value of this parameter.
        raise_exceptions: If False, do not print stack traces when an
            exception is raised.  The default value is False.
    """

    if _locals is None:
        # caller's locals
        _locals = sys._getframe(1).f_locals
    if _globals is None:
        # caller's globals
        _globals = sys._getframe(1).f_globals

    # If a recipe file is included from another recipe file, some way
    # is needed to find the included recipe if it specified using a
    # relative path (relative to the including recipe file). Since
    # recipe files are exec'ed, the value of __file__ is this file,
    # not the recipe file. In order to make including recipes in other
    # recipes using relative paths more intuitive, prepend the path of
    # the base recipe file.
    if (prepend_path and hasattr(include, 'prepend_path')
        and not os.path.isabs(recipe_file)):
        recipe_file = os.path.join(include.prepend_path, recipe_file)

    try:
        with open(recipe_file) as f:
            # pylint: disable=exec-used
            exec(compile(f.read(), recipe_file, 'exec'), _globals, _locals)
    except Exception as e:
        if raise_exceptions:
            raise e
        else:
            traceback.print_exc()
            exit(1)

def recipe(recipe_file, cpu_target=None, ctype=container_type.DOCKER,
           raise_exceptions=False, single_stage=False,
           singularity_version='2.6', userarg=None,
           working_directory='/var/tmp',
           singularity_tmp_fallback=True):
    """Recipe builder

    Args:
        recipe_file: path to a recipe file (required).
        cpu_target: A CPU microarchitecture string recognized by archspec.
        ctype: Enum representing the container specification format.  The
            default is `container_type.DOCKER`.
        raise_exceptions: If False, do not print stack traces when an
            exception is raised.  The default value is False.
        single_stage: If True, only print the first stage of a multi-stage
            recipe.  The default is False.
        singularity_version: Version of the Singularity definition file
            format to use.  Multi-stage support was added in version 3.2, but
            the changes are incompatible with earlier versions of Singularity.
            The default is '2.6'.
        userarg: A dictionary of key / value pairs provided to the recipe
            as the `USERARG` dictionary.
        working_directory: path to use as the working directory in the
            container specification
        singularity_tmp_fallback: If True (default), automatically handle
            copy destinations under /tmp or /var/tmp using a %setup block when
            targeting Singularity >= 3.6. If False, such copy operations are
            rejected and will raise an error, requiring the user to modify the
            recipe.
    """

    # Make user arguments available
    USERARG = {} # pylint: disable=unused-variable
    if userarg:
        USERARG = userarg # alias

    # Consider just 2 stages for the time being
    stages = [Stage(), Stage()]
    Stage0 = stages[0] # alias # pylint: disable=unused-variable
    Stage1 = stages[1] # alias # pylint: disable=unused-variable

    # Set the CPU target
    hpccm.config.set_cpu_target(cpu_target)

    # Set the global container type
    hpccm.config.g_ctype = ctype

    # Set the global Singularity version
    hpccm.config.g_singularity_version = Version(singularity_version)

    # Set the global working directory
    hpccm.config.g_wd = working_directory

    # Set Singularity /tmp fallback behavior
    hpccm.config.g_singularity_tmp_fallback = singularity_tmp_fallback

    # Any included recipes that are specified using relative paths will
    # need to prepend the path to the main recipe in order to be found.
    # Save the path to the main recipe.
    include.prepend_path = os.path.dirname(recipe_file)

    # Load in the recipe file
    include(recipe_file, _locals=locals(), _globals=globals(),
            prepend_path=False, raise_exceptions=raise_exceptions)

    # Only process the first stage of a recipe
    if single_stage:
        del stages[1:]
    elif len(Stage1) > 0:
        if (ctype == container_type.SINGULARITY and
            hpccm.config.g_singularity_version < Version('3.2')):
            # Singularity prior to version 3.2 did not support
            # multi-stage builds.  If the Singularity version is not
            # sufficient to support multi-stage, provide advice to
            # specify a sufficient Singularity version or disable
            # multi-stage.
            logging.warning('This looks like a multi-stage recipe. '
                            'Singularity 3.2 or later is required for '
                            'multi-stage builds.  Use '
                            '--singularity-version=3.2 to enable this '
                            'feature or --single-stage to get rid of this '
                            'warning.  Only processing the first stage...')
            del stages[1:]
        elif ctype == container_type.BASH:
            logging.warning('This looks like a multi-stage recipe, but '
                            'bash does not support multi-stage builds. '
                            'Use --single-stage to get rid of this warning. '
                            'Only processing the first stage...')
            del stages[1:]

    r = []
    for index, stage in enumerate(stages):
        if index >= 1:
            r.append('')
        r.append(str(stage))

    return '\n'.join(r)