gitlab ci: Docstring methods or make them private
This commit is contained in:
parent
be0e3f4458
commit
c202953528
@ -90,7 +90,7 @@ def _create_buildgroup(opener, headers, url, project, group_name, group_type):
|
|||||||
return build_group_id
|
return build_group_id
|
||||||
|
|
||||||
|
|
||||||
def populate_buildgroup(job_names, group_name, project, site,
|
def _populate_buildgroup(job_names, group_name, project, site,
|
||||||
credentials, cdash_url):
|
credentials, cdash_url):
|
||||||
url = "{0}/api/v1/buildgroup.php".format(cdash_url)
|
url = "{0}/api/v1/buildgroup.php".format(cdash_url)
|
||||||
|
|
||||||
@ -132,16 +132,30 @@ def populate_buildgroup(job_names, group_name, project, site,
|
|||||||
response_code = response.getcode()
|
response_code = response.getcode()
|
||||||
|
|
||||||
if response_code != 200:
|
if response_code != 200:
|
||||||
msg = 'Error response code ({0}) in populate_buildgroup'.format(
|
msg = 'Error response code ({0}) in _populate_buildgroup'.format(
|
||||||
response_code)
|
response_code)
|
||||||
tty.warn(msg)
|
tty.warn(msg)
|
||||||
|
|
||||||
|
|
||||||
def is_main_phase(phase_name):
|
def _is_main_phase(phase_name):
|
||||||
return True if phase_name == 'specs' else False
|
return True if phase_name == 'specs' else False
|
||||||
|
|
||||||
|
|
||||||
def get_job_name(phase, strip_compiler, spec, osarch, build_group):
|
def get_job_name(phase, strip_compiler, spec, osarch, build_group):
|
||||||
|
""" Given the necessary parts, format the gitlab job name
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
phase (str): Either 'specs' for the main phase, or the name of a
|
||||||
|
bootstrapping phase
|
||||||
|
strip_compiler (bool): Should compiler be stripped from job name
|
||||||
|
spec (spack.spec.Spec): Spec job will build
|
||||||
|
osarch: Architecture TODO: (this is a spack.spec.ArchSpec,
|
||||||
|
but sphinx doesn't recognize the type and fails).
|
||||||
|
build_group (str): Name of build group this job belongs to (a CDash
|
||||||
|
notion)
|
||||||
|
|
||||||
|
Returns: The job name
|
||||||
|
"""
|
||||||
item_idx = 0
|
item_idx = 0
|
||||||
format_str = ''
|
format_str = ''
|
||||||
format_args = []
|
format_args = []
|
||||||
@ -163,7 +177,7 @@ def get_job_name(phase, strip_compiler, spec, osarch, build_group):
|
|||||||
format_args.append(spec.version)
|
format_args.append(spec.version)
|
||||||
item_idx += 1
|
item_idx += 1
|
||||||
|
|
||||||
if is_main_phase(phase) is True or strip_compiler is False:
|
if _is_main_phase(phase) is True or strip_compiler is False:
|
||||||
format_str += ' {{{0}}}'.format(item_idx)
|
format_str += ' {{{0}}}'.format(item_idx)
|
||||||
format_args.append(spec.compiler)
|
format_args.append(spec.compiler)
|
||||||
item_idx += 1
|
item_idx += 1
|
||||||
@ -180,12 +194,12 @@ def get_job_name(phase, strip_compiler, spec, osarch, build_group):
|
|||||||
return format_str.format(*format_args)
|
return format_str.format(*format_args)
|
||||||
|
|
||||||
|
|
||||||
def get_cdash_build_name(spec, build_group):
|
def _get_cdash_build_name(spec, build_group):
|
||||||
return '{0}@{1}%{2} arch={3} ({4})'.format(
|
return '{0}@{1}%{2} arch={3} ({4})'.format(
|
||||||
spec.name, spec.version, spec.compiler, spec.architecture, build_group)
|
spec.name, spec.version, spec.compiler, spec.architecture, build_group)
|
||||||
|
|
||||||
|
|
||||||
def get_spec_string(spec):
|
def _get_spec_string(spec):
|
||||||
format_elements = [
|
format_elements = [
|
||||||
'{name}{@version}',
|
'{name}{@version}',
|
||||||
'{%compiler}',
|
'{%compiler}',
|
||||||
@ -197,7 +211,7 @@ def get_spec_string(spec):
|
|||||||
return spec.format(''.join(format_elements))
|
return spec.format(''.join(format_elements))
|
||||||
|
|
||||||
|
|
||||||
def format_root_spec(spec, main_phase, strip_compiler):
|
def _format_root_spec(spec, main_phase, strip_compiler):
|
||||||
if main_phase is False and strip_compiler is True:
|
if main_phase is False and strip_compiler is True:
|
||||||
return '{0}@{1} arch={2}'.format(
|
return '{0}@{1} arch={2}'.format(
|
||||||
spec.name, spec.version, spec.architecture)
|
spec.name, spec.version, spec.architecture)
|
||||||
@ -205,7 +219,7 @@ def format_root_spec(spec, main_phase, strip_compiler):
|
|||||||
return spec.dag_hash()
|
return spec.dag_hash()
|
||||||
|
|
||||||
|
|
||||||
def spec_deps_key(s):
|
def _spec_deps_key(s):
|
||||||
return '{0}/{1}'.format(s.name, s.dag_hash(7))
|
return '{0}/{1}'.format(s.name, s.dag_hash(7))
|
||||||
|
|
||||||
|
|
||||||
@ -217,8 +231,8 @@ def _add_dependency(spec_label, dep_label, deps):
|
|||||||
deps[spec_label].add(dep_label)
|
deps[spec_label].add(dep_label)
|
||||||
|
|
||||||
|
|
||||||
def get_spec_dependencies(specs, deps, spec_labels, check_index_only=False):
|
def _get_spec_dependencies(specs, deps, spec_labels, check_index_only=False):
|
||||||
spec_deps_obj = compute_spec_deps(specs, check_index_only=check_index_only)
|
spec_deps_obj = _compute_spec_deps(specs, check_index_only=check_index_only)
|
||||||
|
|
||||||
if spec_deps_obj:
|
if spec_deps_obj:
|
||||||
dependencies = spec_deps_obj['dependencies']
|
dependencies = spec_deps_obj['dependencies']
|
||||||
@ -266,11 +280,11 @@ def stage_spec_jobs(specs, check_index_only=False):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# The convenience method below, "remove_satisfied_deps()", does not modify
|
# The convenience method below, "_remove_satisfied_deps()", does not modify
|
||||||
# the "deps" parameter. Instead, it returns a new dictionary where only
|
# the "deps" parameter. Instead, it returns a new dictionary where only
|
||||||
# dependencies which have not yet been satisfied are included in the
|
# dependencies which have not yet been satisfied are included in the
|
||||||
# return value.
|
# return value.
|
||||||
def remove_satisfied_deps(deps, satisfied_list):
|
def _remove_satisfied_deps(deps, satisfied_list):
|
||||||
new_deps = {}
|
new_deps = {}
|
||||||
|
|
||||||
for key, value in iteritems(deps):
|
for key, value in iteritems(deps):
|
||||||
@ -283,7 +297,7 @@ def remove_satisfied_deps(deps, satisfied_list):
|
|||||||
deps = {}
|
deps = {}
|
||||||
spec_labels = {}
|
spec_labels = {}
|
||||||
|
|
||||||
get_spec_dependencies(
|
_get_spec_dependencies(
|
||||||
specs, deps, spec_labels, check_index_only=check_index_only)
|
specs, deps, spec_labels, check_index_only=check_index_only)
|
||||||
|
|
||||||
# Save the original deps, as we need to return them at the end of the
|
# Save the original deps, as we need to return them at the end of the
|
||||||
@ -302,7 +316,7 @@ def remove_satisfied_deps(deps, satisfied_list):
|
|||||||
# Note that "dependencies" is a dictionary mapping each dependent
|
# Note that "dependencies" is a dictionary mapping each dependent
|
||||||
# package to the set of not-yet-handled dependencies. The final step
|
# package to the set of not-yet-handled dependencies. The final step
|
||||||
# below removes all the dependencies that are handled by this stage.
|
# below removes all the dependencies that are handled by this stage.
|
||||||
dependencies = remove_satisfied_deps(dependencies, next_stage)
|
dependencies = _remove_satisfied_deps(dependencies, next_stage)
|
||||||
|
|
||||||
if unstaged:
|
if unstaged:
|
||||||
stages.append(unstaged.copy())
|
stages.append(unstaged.copy())
|
||||||
@ -310,7 +324,7 @@ def remove_satisfied_deps(deps, satisfied_list):
|
|||||||
return spec_labels, deps, stages
|
return spec_labels, deps, stages
|
||||||
|
|
||||||
|
|
||||||
def print_staging_summary(spec_labels, dependencies, stages):
|
def _print_staging_summary(spec_labels, dependencies, stages):
|
||||||
if not stages:
|
if not stages:
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -323,10 +337,10 @@ def print_staging_summary(spec_labels, dependencies, stages):
|
|||||||
tty.msg(' [{1}] {0} -> {2}'.format(
|
tty.msg(' [{1}] {0} -> {2}'.format(
|
||||||
job,
|
job,
|
||||||
'x' if spec_labels[job]['needs_rebuild'] else ' ',
|
'x' if spec_labels[job]['needs_rebuild'] else ' ',
|
||||||
get_spec_string(s)))
|
_get_spec_string(s)))
|
||||||
|
|
||||||
|
|
||||||
def compute_spec_deps(spec_list, check_index_only=False):
|
def _compute_spec_deps(spec_list, check_index_only=False):
|
||||||
"""
|
"""
|
||||||
Computes all the dependencies for the spec(s) and generates a JSON
|
Computes all the dependencies for the spec(s) and generates a JSON
|
||||||
object which provides both a list of unique spec names as well as a
|
object which provides both a list of unique spec names as well as a
|
||||||
@ -401,15 +415,15 @@ def append_dep(s, d):
|
|||||||
up_to_date_mirrors = bindist.get_mirrors_for_spec(
|
up_to_date_mirrors = bindist.get_mirrors_for_spec(
|
||||||
spec=s, index_only=check_index_only)
|
spec=s, index_only=check_index_only)
|
||||||
|
|
||||||
skey = spec_deps_key(s)
|
skey = _spec_deps_key(s)
|
||||||
spec_labels[skey] = {
|
spec_labels[skey] = {
|
||||||
'spec': get_spec_string(s),
|
'spec': _get_spec_string(s),
|
||||||
'root': root_spec,
|
'root': root_spec,
|
||||||
'needs_rebuild': not up_to_date_mirrors,
|
'needs_rebuild': not up_to_date_mirrors,
|
||||||
}
|
}
|
||||||
|
|
||||||
for d in s.dependencies(deptype=all):
|
for d in s.dependencies(deptype=all):
|
||||||
dkey = spec_deps_key(d)
|
dkey = _spec_deps_key(d)
|
||||||
if d.external:
|
if d.external:
|
||||||
tty.msg('Will not stage external dep: {0}'.format(d))
|
tty.msg('Will not stage external dep: {0}'.format(d))
|
||||||
continue
|
continue
|
||||||
@ -432,11 +446,11 @@ def append_dep(s, d):
|
|||||||
return deps_json_obj
|
return deps_json_obj
|
||||||
|
|
||||||
|
|
||||||
def spec_matches(spec, match_string):
|
def _spec_matches(spec, match_string):
|
||||||
return spec.satisfies(match_string)
|
return spec.satisfies(match_string)
|
||||||
|
|
||||||
|
|
||||||
def copy_attributes(attrs_list, src_dict, dest_dict):
|
def _copy_attributes(attrs_list, src_dict, dest_dict):
|
||||||
for runner_attr in attrs_list:
|
for runner_attr in attrs_list:
|
||||||
if runner_attr in src_dict:
|
if runner_attr in src_dict:
|
||||||
if runner_attr in dest_dict and runner_attr == 'tags':
|
if runner_attr in dest_dict and runner_attr == 'tags':
|
||||||
@ -457,7 +471,7 @@ def copy_attributes(attrs_list, src_dict, dest_dict):
|
|||||||
dest_dict[runner_attr] = copy.deepcopy(src_dict[runner_attr])
|
dest_dict[runner_attr] = copy.deepcopy(src_dict[runner_attr])
|
||||||
|
|
||||||
|
|
||||||
def find_matching_config(spec, gitlab_ci):
|
def _find_matching_config(spec, gitlab_ci):
|
||||||
runner_attributes = {}
|
runner_attributes = {}
|
||||||
overridable_attrs = [
|
overridable_attrs = [
|
||||||
'image',
|
'image',
|
||||||
@ -468,14 +482,14 @@ def find_matching_config(spec, gitlab_ci):
|
|||||||
'after_script',
|
'after_script',
|
||||||
]
|
]
|
||||||
|
|
||||||
copy_attributes(overridable_attrs, gitlab_ci, runner_attributes)
|
_copy_attributes(overridable_attrs, gitlab_ci, runner_attributes)
|
||||||
|
|
||||||
ci_mappings = gitlab_ci['mappings']
|
ci_mappings = gitlab_ci['mappings']
|
||||||
for ci_mapping in ci_mappings:
|
for ci_mapping in ci_mappings:
|
||||||
for match_string in ci_mapping['match']:
|
for match_string in ci_mapping['match']:
|
||||||
if spec_matches(spec, match_string):
|
if _spec_matches(spec, match_string):
|
||||||
if 'runner-attributes' in ci_mapping:
|
if 'runner-attributes' in ci_mapping:
|
||||||
copy_attributes(overridable_attrs,
|
_copy_attributes(overridable_attrs,
|
||||||
ci_mapping['runner-attributes'],
|
ci_mapping['runner-attributes'],
|
||||||
runner_attributes)
|
runner_attributes)
|
||||||
return runner_attributes
|
return runner_attributes
|
||||||
@ -485,16 +499,16 @@ def find_matching_config(spec, gitlab_ci):
|
|||||||
return runner_attributes
|
return runner_attributes
|
||||||
|
|
||||||
|
|
||||||
def pkg_name_from_spec_label(spec_label):
|
def _pkg_name_from_spec_label(spec_label):
|
||||||
return spec_label[:spec_label.index('/')]
|
return spec_label[:spec_label.index('/')]
|
||||||
|
|
||||||
|
|
||||||
def format_job_needs(phase_name, strip_compilers, dep_jobs,
|
def _format_job_needs(phase_name, strip_compilers, dep_jobs,
|
||||||
osname, build_group, prune_dag, stage_spec_dict,
|
osname, build_group, prune_dag, stage_spec_dict,
|
||||||
enable_artifacts_buildcache):
|
enable_artifacts_buildcache):
|
||||||
needs_list = []
|
needs_list = []
|
||||||
for dep_job in dep_jobs:
|
for dep_job in dep_jobs:
|
||||||
dep_spec_key = spec_deps_key(dep_job)
|
dep_spec_key = _spec_deps_key(dep_job)
|
||||||
dep_spec_info = stage_spec_dict[dep_spec_key]
|
dep_spec_info = stage_spec_dict[dep_spec_key]
|
||||||
|
|
||||||
if not prune_dag or dep_spec_info['needs_rebuild']:
|
if not prune_dag or dep_spec_info['needs_rebuild']:
|
||||||
@ -589,6 +603,33 @@ def generate_gitlab_ci_yaml(env, print_summary, output_file,
|
|||||||
prune_dag=False, check_index_only=False,
|
prune_dag=False, check_index_only=False,
|
||||||
run_optimizer=False, use_dependencies=False,
|
run_optimizer=False, use_dependencies=False,
|
||||||
artifacts_root=None):
|
artifacts_root=None):
|
||||||
|
""" Generate a gitlab yaml file to run a dynamic chile pipeline from
|
||||||
|
the spec matrix in the active environment.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
env (spack.environment.Environment): Activated environment object
|
||||||
|
which must contain a gitlab-ci section describing how to map
|
||||||
|
specs to runners
|
||||||
|
print_summary (bool): Should we print a summary of all the jobs in
|
||||||
|
the stages in which they were placed.
|
||||||
|
output_file (str): File path where generated file should be written
|
||||||
|
prune_dag (bool): If True, do not generate jobs for specs already
|
||||||
|
exist built on the mirror.
|
||||||
|
check_index_only (bool): If True, attempt to fetch the mirror index
|
||||||
|
and only use that to determine whether built specs on the mirror
|
||||||
|
this mode results in faster yaml generation time). Otherwise, also
|
||||||
|
check each spec directly by url (useful if there is no index or it
|
||||||
|
might be out of date).
|
||||||
|
run_optimizer (bool): If True, post-process the generated yaml to try
|
||||||
|
try to reduce the size (attempts to collect repeated configuration
|
||||||
|
and replace with definitions).)
|
||||||
|
use_dependencies (bool): If true, use "dependencies" rather than "needs"
|
||||||
|
("needs" allows DAG scheduling). Useful if gitlab instance cannot
|
||||||
|
be configured to handle more than a few "needs" per job.
|
||||||
|
artifacts_root (str): Path where artifacts like logs, environment
|
||||||
|
files (spack.yaml, spack.lock), etc should be written. GitLab
|
||||||
|
requires this to be within the project directory.
|
||||||
|
"""
|
||||||
with spack.concretize.disable_compiler_existence_check():
|
with spack.concretize.disable_compiler_existence_check():
|
||||||
with env.write_transaction():
|
with env.write_transaction():
|
||||||
env.concretize()
|
env.concretize()
|
||||||
@ -816,7 +857,7 @@ def generate_gitlab_ci_yaml(env, print_summary, output_file,
|
|||||||
phase_name = phase['name']
|
phase_name = phase['name']
|
||||||
strip_compilers = phase['strip-compilers']
|
strip_compilers = phase['strip-compilers']
|
||||||
|
|
||||||
main_phase = is_main_phase(phase_name)
|
main_phase = _is_main_phase(phase_name)
|
||||||
spec_labels, dependencies, stages = staged_phases[phase_name]
|
spec_labels, dependencies, stages = staged_phases[phase_name]
|
||||||
|
|
||||||
for stage_jobs in stages:
|
for stage_jobs in stages:
|
||||||
@ -827,7 +868,7 @@ def generate_gitlab_ci_yaml(env, print_summary, output_file,
|
|||||||
for spec_label in stage_jobs:
|
for spec_label in stage_jobs:
|
||||||
spec_record = spec_labels[spec_label]
|
spec_record = spec_labels[spec_label]
|
||||||
root_spec = spec_record['rootSpec']
|
root_spec = spec_record['rootSpec']
|
||||||
pkg_name = pkg_name_from_spec_label(spec_label)
|
pkg_name = _pkg_name_from_spec_label(spec_label)
|
||||||
release_spec = root_spec[pkg_name]
|
release_spec = root_spec[pkg_name]
|
||||||
release_spec_dag_hash = release_spec.dag_hash()
|
release_spec_dag_hash = release_spec.dag_hash()
|
||||||
release_spec_runtime_hash = release_spec.runtime_hash()
|
release_spec_runtime_hash = release_spec.runtime_hash()
|
||||||
@ -839,7 +880,7 @@ def generate_gitlab_ci_yaml(env, print_summary, output_file,
|
|||||||
spec_record['needs_rebuild'] = False
|
spec_record['needs_rebuild'] = False
|
||||||
continue
|
continue
|
||||||
|
|
||||||
runner_attribs = find_matching_config(
|
runner_attribs = _find_matching_config(
|
||||||
release_spec, gitlab_ci)
|
release_spec, gitlab_ci)
|
||||||
|
|
||||||
if not runner_attribs:
|
if not runner_attribs:
|
||||||
@ -893,11 +934,11 @@ def generate_gitlab_ci_yaml(env, print_summary, output_file,
|
|||||||
compiler_action = 'NONE'
|
compiler_action = 'NONE'
|
||||||
if len(phases) > 1:
|
if len(phases) > 1:
|
||||||
compiler_action = 'FIND_ANY'
|
compiler_action = 'FIND_ANY'
|
||||||
if is_main_phase(phase_name):
|
if _is_main_phase(phase_name):
|
||||||
compiler_action = 'INSTALL_MISSING'
|
compiler_action = 'INSTALL_MISSING'
|
||||||
|
|
||||||
job_vars = {
|
job_vars = {
|
||||||
'SPACK_ROOT_SPEC': format_root_spec(
|
'SPACK_ROOT_SPEC': _format_root_spec(
|
||||||
root_spec, main_phase, strip_compilers),
|
root_spec, main_phase, strip_compilers),
|
||||||
'SPACK_JOB_SPEC_DAG_HASH': release_spec_dag_hash,
|
'SPACK_JOB_SPEC_DAG_HASH': release_spec_dag_hash,
|
||||||
'SPACK_JOB_SPEC_RUNTIME_HASH': release_spec_runtime_hash,
|
'SPACK_JOB_SPEC_RUNTIME_HASH': release_spec_runtime_hash,
|
||||||
@ -919,12 +960,12 @@ def generate_gitlab_ci_yaml(env, print_summary, output_file,
|
|||||||
# purposes, so we only get the direct dependencies.
|
# purposes, so we only get the direct dependencies.
|
||||||
dep_jobs = []
|
dep_jobs = []
|
||||||
for dep_label in dependencies[spec_label]:
|
for dep_label in dependencies[spec_label]:
|
||||||
dep_pkg = pkg_name_from_spec_label(dep_label)
|
dep_pkg = _pkg_name_from_spec_label(dep_label)
|
||||||
dep_root = spec_labels[dep_label]['rootSpec']
|
dep_root = spec_labels[dep_label]['rootSpec']
|
||||||
dep_jobs.append(dep_root[dep_pkg])
|
dep_jobs.append(dep_root[dep_pkg])
|
||||||
|
|
||||||
job_dependencies.extend(
|
job_dependencies.extend(
|
||||||
format_job_needs(phase_name, strip_compilers,
|
_format_job_needs(phase_name, strip_compilers,
|
||||||
dep_jobs, osname, build_group,
|
dep_jobs, osname, build_group,
|
||||||
prune_dag, spec_labels,
|
prune_dag, spec_labels,
|
||||||
enable_artifacts_buildcache))
|
enable_artifacts_buildcache))
|
||||||
@ -938,7 +979,7 @@ def generate_gitlab_ci_yaml(env, print_summary, output_file,
|
|||||||
# compiler we are supposed to use is listed in any of the
|
# compiler we are supposed to use is listed in any of the
|
||||||
# bootstrap spec lists, then we will add more dependencies to
|
# bootstrap spec lists, then we will add more dependencies to
|
||||||
# the job (that compiler and maybe it's dependencies as well).
|
# the job (that compiler and maybe it's dependencies as well).
|
||||||
if is_main_phase(phase_name):
|
if _is_main_phase(phase_name):
|
||||||
spec_arch_family = (release_spec.architecture
|
spec_arch_family = (release_spec.architecture
|
||||||
.target
|
.target
|
||||||
.microarchitecture
|
.microarchitecture
|
||||||
@ -966,7 +1007,7 @@ def generate_gitlab_ci_yaml(env, print_summary, output_file,
|
|||||||
# be rebuilt if the compiler targeted to build it
|
# be rebuilt if the compiler targeted to build it
|
||||||
# needs to be rebuilt.
|
# needs to be rebuilt.
|
||||||
bs_specs, _, _ = staged_phases[bs['phase-name']]
|
bs_specs, _, _ = staged_phases[bs['phase-name']]
|
||||||
c_spec_key = spec_deps_key(c_spec)
|
c_spec_key = _spec_deps_key(c_spec)
|
||||||
rbld_comp = bs_specs[c_spec_key]['needs_rebuild']
|
rbld_comp = bs_specs[c_spec_key]['needs_rebuild']
|
||||||
rebuild_spec = rebuild_spec or rbld_comp
|
rebuild_spec = rebuild_spec or rbld_comp
|
||||||
# Also update record so dependents do not fail to
|
# Also update record so dependents do not fail to
|
||||||
@ -980,7 +1021,7 @@ def generate_gitlab_ci_yaml(env, print_summary, output_file,
|
|||||||
]
|
]
|
||||||
|
|
||||||
job_dependencies.extend(
|
job_dependencies.extend(
|
||||||
format_job_needs(bs['phase-name'],
|
_format_job_needs(bs['phase-name'],
|
||||||
bs['strip-compilers'],
|
bs['strip-compilers'],
|
||||||
dep_jobs,
|
dep_jobs,
|
||||||
str(bs_arch),
|
str(bs_arch),
|
||||||
@ -1017,7 +1058,7 @@ def generate_gitlab_ci_yaml(env, print_summary, output_file,
|
|||||||
job_vars['SPACK_SPEC_NEEDS_REBUILD'] = str(rebuild_spec)
|
job_vars['SPACK_SPEC_NEEDS_REBUILD'] = str(rebuild_spec)
|
||||||
|
|
||||||
if enable_cdash_reporting:
|
if enable_cdash_reporting:
|
||||||
cdash_build_name = get_cdash_build_name(
|
cdash_build_name = _get_cdash_build_name(
|
||||||
release_spec, build_group)
|
release_spec, build_group)
|
||||||
all_job_names.append(cdash_build_name)
|
all_job_names.append(cdash_build_name)
|
||||||
job_vars['SPACK_CDASH_BUILD_NAME'] = cdash_build_name
|
job_vars['SPACK_CDASH_BUILD_NAME'] = cdash_build_name
|
||||||
@ -1082,7 +1123,7 @@ def generate_gitlab_ci_yaml(env, print_summary, output_file,
|
|||||||
phase_name = phase['name']
|
phase_name = phase['name']
|
||||||
tty.msg('Stages for phase "{0}"'.format(phase_name))
|
tty.msg('Stages for phase "{0}"'.format(phase_name))
|
||||||
phase_stages = staged_phases[phase_name]
|
phase_stages = staged_phases[phase_name]
|
||||||
print_staging_summary(*phase_stages)
|
_print_staging_summary(*phase_stages)
|
||||||
|
|
||||||
tty.debug('{0} build jobs generated in {1} stages'.format(
|
tty.debug('{0} build jobs generated in {1} stages'.format(
|
||||||
job_id, stage_id))
|
job_id, stage_id))
|
||||||
@ -1094,7 +1135,7 @@ def generate_gitlab_ci_yaml(env, print_summary, output_file,
|
|||||||
# Use "all_job_names" to populate the build group for this set
|
# Use "all_job_names" to populate the build group for this set
|
||||||
if enable_cdash_reporting and cdash_auth_token:
|
if enable_cdash_reporting and cdash_auth_token:
|
||||||
try:
|
try:
|
||||||
populate_buildgroup(all_job_names, build_group, cdash_project,
|
_populate_buildgroup(all_job_names, build_group, cdash_project,
|
||||||
cdash_site, cdash_auth_token, cdash_url)
|
cdash_site, cdash_auth_token, cdash_url)
|
||||||
except (SpackError, HTTPError, URLError) as err:
|
except (SpackError, HTTPError, URLError) as err:
|
||||||
tty.warn('Problem populating buildgroup: {0}'.format(err))
|
tty.warn('Problem populating buildgroup: {0}'.format(err))
|
||||||
@ -1131,7 +1172,7 @@ def generate_gitlab_ci_yaml(env, print_summary, output_file,
|
|||||||
cleanup_job = {}
|
cleanup_job = {}
|
||||||
|
|
||||||
if service_job_config:
|
if service_job_config:
|
||||||
copy_attributes(default_attrs,
|
_copy_attributes(default_attrs,
|
||||||
service_job_config,
|
service_job_config,
|
||||||
cleanup_job)
|
cleanup_job)
|
||||||
|
|
||||||
@ -1151,7 +1192,7 @@ def generate_gitlab_ci_yaml(env, print_summary, output_file,
|
|||||||
final_job = {}
|
final_job = {}
|
||||||
|
|
||||||
if service_job_config:
|
if service_job_config:
|
||||||
copy_attributes(default_attrs,
|
_copy_attributes(default_attrs,
|
||||||
service_job_config,
|
service_job_config,
|
||||||
final_job)
|
final_job)
|
||||||
|
|
||||||
@ -1224,7 +1265,7 @@ def generate_gitlab_ci_yaml(env, print_summary, output_file,
|
|||||||
noop_job = {}
|
noop_job = {}
|
||||||
|
|
||||||
if service_job_config:
|
if service_job_config:
|
||||||
copy_attributes(default_attrs,
|
_copy_attributes(default_attrs,
|
||||||
service_job_config,
|
service_job_config,
|
||||||
noop_job)
|
noop_job)
|
||||||
|
|
||||||
@ -1249,7 +1290,7 @@ def generate_gitlab_ci_yaml(env, print_summary, output_file,
|
|||||||
outf.write(syaml.dump_config(sorted_output, default_flow_style=True))
|
outf.write(syaml.dump_config(sorted_output, default_flow_style=True))
|
||||||
|
|
||||||
|
|
||||||
def url_encode_string(input_string):
|
def _url_encode_string(input_string):
|
||||||
encoded_keyval = urlencode({'donotcare': input_string})
|
encoded_keyval = urlencode({'donotcare': input_string})
|
||||||
eq_idx = encoded_keyval.find('=') + 1
|
eq_idx = encoded_keyval.find('=') + 1
|
||||||
encoded_value = encoded_keyval[eq_idx:]
|
encoded_value = encoded_keyval[eq_idx:]
|
||||||
@ -1257,6 +1298,17 @@ def url_encode_string(input_string):
|
|||||||
|
|
||||||
|
|
||||||
def import_signing_key(base64_signing_key):
|
def import_signing_key(base64_signing_key):
|
||||||
|
""" Given Base64-encoded gpg key, decode and import it to use for
|
||||||
|
signing packages.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
base64_signing_key (str): A gpg key including the secret key,
|
||||||
|
armor-exported and base64 encoded, so it can be stored in a
|
||||||
|
gitlab CI variable. For an example of how to generate such
|
||||||
|
a key, see:
|
||||||
|
|
||||||
|
https://github.com/spack/spack-infrastructure/blob/main/gitlab-docker/files/gen-key
|
||||||
|
"""
|
||||||
if not base64_signing_key:
|
if not base64_signing_key:
|
||||||
tty.warn('No key found for signing/verifying packages')
|
tty.warn('No key found for signing/verifying packages')
|
||||||
return
|
return
|
||||||
@ -1294,14 +1346,34 @@ def import_signing_key(base64_signing_key):
|
|||||||
|
|
||||||
|
|
||||||
def can_sign_binaries():
|
def can_sign_binaries():
|
||||||
|
""" Utility method to determine if this spack instance is capable of
|
||||||
|
signing binary packages. This is currently only possible if the
|
||||||
|
spack gpg keystore contains exactly one secret key."""
|
||||||
return len(gpg_util.signing_keys()) == 1
|
return len(gpg_util.signing_keys()) == 1
|
||||||
|
|
||||||
|
|
||||||
def can_verify_binaries():
|
def can_verify_binaries():
|
||||||
|
""" Utility method to determin if this spack instance is capable (at
|
||||||
|
least in theory) of verifying signed binaries."""
|
||||||
return len(gpg_util.public_keys()) >= 1
|
return len(gpg_util.public_keys()) >= 1
|
||||||
|
|
||||||
|
|
||||||
def configure_compilers(compiler_action, scope=None):
|
def configure_compilers(compiler_action, scope=None):
|
||||||
|
""" Depending on the compiler_action parameter, either turn on the
|
||||||
|
install_missing_compilers config option, or find spack compilers,
|
||||||
|
or do nothing. This is used from rebuild jobs in bootstrapping
|
||||||
|
pipelines, where in the bootsrapping phase we would pass
|
||||||
|
FIND_ANY in case of compiler-agnostic bootstrapping, while in the
|
||||||
|
spec building phase we would pass INSTALL_MISSING in order to get
|
||||||
|
spack to use the compiler which was built in the previous phase and
|
||||||
|
is now sitting in the binary mirror.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
compiler_action (str): 'FIND_ANY', 'INSTALL_MISSING' have meanings
|
||||||
|
described above. Any other value essentially results in a no-op.
|
||||||
|
scope (spack.config.ConfigScope): Optional. The scope in which to look for
|
||||||
|
compilers, in case 'FIND_ANY' was provided.
|
||||||
|
"""
|
||||||
if compiler_action == 'INSTALL_MISSING':
|
if compiler_action == 'INSTALL_MISSING':
|
||||||
tty.debug('Make sure bootstrapped compiler will be installed')
|
tty.debug('Make sure bootstrapped compiler will be installed')
|
||||||
config = cfg.get('config')
|
config = cfg.get('config')
|
||||||
@ -1325,6 +1397,35 @@ def configure_compilers(compiler_action, scope=None):
|
|||||||
|
|
||||||
|
|
||||||
def get_concrete_specs(env, root_spec, job_name, compiler_action):
|
def get_concrete_specs(env, root_spec, job_name, compiler_action):
|
||||||
|
""" Build a dictionary of concrete specs relevant to a particular
|
||||||
|
rebuild job. This includes the root spec and the spec to be
|
||||||
|
rebuilt (which could be the same).
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
|
||||||
|
env (spack.environment.Environment): Activated spack environment
|
||||||
|
used to get concrete root spec by hash in case compiler_action
|
||||||
|
is anthing other than FIND_ANY.
|
||||||
|
root_spec (str): If compiler_action is FIND_ANY root_spec is
|
||||||
|
a string representation which can be turned directly into
|
||||||
|
a spec, otherwise, it's a hash used to index the activated
|
||||||
|
spack environment.
|
||||||
|
job_name (str): Name of package to be built, used to index the
|
||||||
|
concrete root spec and produce the concrete spec to be
|
||||||
|
built.
|
||||||
|
compiler_action (str): Determines how to interpret the root_spec
|
||||||
|
parameter, either as a string representation as a hash.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
.. code-block:: JSON
|
||||||
|
|
||||||
|
{
|
||||||
|
"root": "<spec>",
|
||||||
|
"<job-pkg-name>": "<spec>",
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
spec_map = {
|
spec_map = {
|
||||||
'root': None,
|
'root': None,
|
||||||
}
|
}
|
||||||
@ -1371,6 +1472,19 @@ def _push_mirror_contents(env, specfile_path, sign_binaries, mirror_url):
|
|||||||
|
|
||||||
|
|
||||||
def push_mirror_contents(env, specfile_path, mirror_url, sign_binaries):
|
def push_mirror_contents(env, specfile_path, mirror_url, sign_binaries):
|
||||||
|
""" Push one or more binary packages to the mirror.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
|
||||||
|
env (spack.environment.Environment): Optional environment. If
|
||||||
|
provided, it is used to make sure binary package to push
|
||||||
|
exists in the environment.
|
||||||
|
specfile_path (str): Path to spec.json corresponding to built pkg
|
||||||
|
to push.
|
||||||
|
mirror_url (str): Base url of target mirror
|
||||||
|
sign_binaries (bool): If True, spack will attempt to sign binary
|
||||||
|
package before pushing.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
_push_mirror_contents(env, specfile_path, sign_binaries, mirror_url)
|
_push_mirror_contents(env, specfile_path, sign_binaries, mirror_url)
|
||||||
except Exception as inst:
|
except Exception as inst:
|
||||||
@ -1395,6 +1509,15 @@ def push_mirror_contents(env, specfile_path, mirror_url, sign_binaries):
|
|||||||
|
|
||||||
|
|
||||||
def copy_stage_logs_to_artifacts(job_spec, job_log_dir):
|
def copy_stage_logs_to_artifacts(job_spec, job_log_dir):
|
||||||
|
""" Looks for spack-build-out.txt in the stage directory of the given
|
||||||
|
job_spec, and attempts to copy the file into the directory given
|
||||||
|
by job_log_dir.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
|
||||||
|
job_spec (spack.spec.Spec): Spec associated with spack install log
|
||||||
|
job_log_dir (str): Path into which build log should be copied
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
job_pkg = spack.repo.get(job_spec)
|
job_pkg = spack.repo.get(job_spec)
|
||||||
tty.debug('job package: {0}'.format(job_pkg))
|
tty.debug('job package: {0}'.format(job_pkg))
|
||||||
@ -1413,6 +1536,14 @@ def copy_stage_logs_to_artifacts(job_spec, job_log_dir):
|
|||||||
|
|
||||||
|
|
||||||
def download_and_extract_artifacts(url, work_dir):
|
def download_and_extract_artifacts(url, work_dir):
|
||||||
|
""" Look for gitlab artifacts.zip at the given url, and attempt to download
|
||||||
|
and extract the contents into the given work_dir
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
|
||||||
|
url (str): Complete url to artifacts.zip file
|
||||||
|
work_dir (str): Path to destination where artifacts should be extracted
|
||||||
|
"""
|
||||||
tty.msg('Fetching artifacts from: {0}\n'.format(url))
|
tty.msg('Fetching artifacts from: {0}\n'.format(url))
|
||||||
|
|
||||||
headers = {
|
headers = {
|
||||||
@ -1452,6 +1583,8 @@ def download_and_extract_artifacts(url, work_dir):
|
|||||||
|
|
||||||
|
|
||||||
def get_spack_info():
|
def get_spack_info():
|
||||||
|
""" If spack is running from a git repo, return the most recent git log
|
||||||
|
entry, otherwise, return a string containing the spack version. """
|
||||||
git_path = os.path.join(spack.paths.prefix, ".git")
|
git_path = os.path.join(spack.paths.prefix, ".git")
|
||||||
if os.path.exists(git_path):
|
if os.path.exists(git_path):
|
||||||
git = exe.which("git")
|
git = exe.which("git")
|
||||||
@ -1467,6 +1600,23 @@ def get_spack_info():
|
|||||||
|
|
||||||
|
|
||||||
def setup_spack_repro_version(repro_dir, checkout_commit, merge_commit=None):
|
def setup_spack_repro_version(repro_dir, checkout_commit, merge_commit=None):
|
||||||
|
""" Look in the local spack clone to find the checkout_commit, and if
|
||||||
|
provided, the merge_commit given as arguments. If those commits can
|
||||||
|
be found locally, then clone spack and attempt to recreate a merge
|
||||||
|
commit with the same parent commits as tested in gitlab. This looks
|
||||||
|
something like 1) git clone repo && cd repo 2) git checkout
|
||||||
|
<checkout_commit> 3) git merge <merge_commit>. If there is no
|
||||||
|
merge_commit provided, then skip step (3).
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
|
||||||
|
repro_dir (str): Location where spack should be cloned
|
||||||
|
checkout_commit (str): SHA of PR branch commit
|
||||||
|
merge_commit (str): SHA of target branch parent
|
||||||
|
|
||||||
|
Returns: True if git repo state was successfully recreated, or False
|
||||||
|
otherwise.
|
||||||
|
"""
|
||||||
# figure out the path to the spack git version being used for the
|
# figure out the path to the spack git version being used for the
|
||||||
# reproduction
|
# reproduction
|
||||||
print('checkout_commit: {0}'.format(checkout_commit))
|
print('checkout_commit: {0}'.format(checkout_commit))
|
||||||
@ -1541,6 +1691,18 @@ def setup_spack_repro_version(repro_dir, checkout_commit, merge_commit=None):
|
|||||||
|
|
||||||
|
|
||||||
def reproduce_ci_job(url, work_dir):
|
def reproduce_ci_job(url, work_dir):
|
||||||
|
""" Given a url to gitlab artifacts.zip from a failed 'spack ci rebuild' job,
|
||||||
|
attempt to setup an environment in which the failure can be reproduced
|
||||||
|
locally. This entails the following:
|
||||||
|
|
||||||
|
First download and extract artifacts. Then look through those artifacts
|
||||||
|
to glean some information needed for the reproduer (e.g. one of the
|
||||||
|
artifacts contains information about the version of spack tested by
|
||||||
|
gitlab, another is the generated pipeline yaml containing details
|
||||||
|
of the job like the docker image used to run it). The output of this
|
||||||
|
function is a set of printed instructions for running docker and then
|
||||||
|
commands to run to reproduce the build once inside the container.
|
||||||
|
"""
|
||||||
download_and_extract_artifacts(url, work_dir)
|
download_and_extract_artifacts(url, work_dir)
|
||||||
|
|
||||||
lock_file = fs.find(work_dir, 'spack.lock')[0]
|
lock_file = fs.find(work_dir, 'spack.lock')[0]
|
||||||
|
@ -44,7 +44,7 @@ def tmp_scope():
|
|||||||
def test_urlencode_string():
|
def test_urlencode_string():
|
||||||
s = 'Spack Test Project'
|
s = 'Spack Test Project'
|
||||||
|
|
||||||
s_enc = ci.url_encode_string(s)
|
s_enc = ci._url_encode_string(s)
|
||||||
|
|
||||||
assert(s_enc == 'Spack+Test+Project')
|
assert(s_enc == 'Spack+Test+Project')
|
||||||
|
|
||||||
|
@ -122,13 +122,13 @@ def test_specs_staging(config):
|
|||||||
spec_a = Spec('a')
|
spec_a = Spec('a')
|
||||||
spec_a.concretize()
|
spec_a.concretize()
|
||||||
|
|
||||||
spec_a_label = ci.spec_deps_key(spec_a)
|
spec_a_label = ci._spec_deps_key(spec_a)
|
||||||
spec_b_label = ci.spec_deps_key(spec_a['b'])
|
spec_b_label = ci._spec_deps_key(spec_a['b'])
|
||||||
spec_c_label = ci.spec_deps_key(spec_a['c'])
|
spec_c_label = ci._spec_deps_key(spec_a['c'])
|
||||||
spec_d_label = ci.spec_deps_key(spec_a['d'])
|
spec_d_label = ci._spec_deps_key(spec_a['d'])
|
||||||
spec_e_label = ci.spec_deps_key(spec_a['e'])
|
spec_e_label = ci._spec_deps_key(spec_a['e'])
|
||||||
spec_f_label = ci.spec_deps_key(spec_a['f'])
|
spec_f_label = ci._spec_deps_key(spec_a['f'])
|
||||||
spec_g_label = ci.spec_deps_key(spec_a['g'])
|
spec_g_label = ci._spec_deps_key(spec_a['g'])
|
||||||
|
|
||||||
spec_labels, dependencies, stages = ci.stage_spec_jobs([spec_a])
|
spec_labels, dependencies, stages = ci.stage_spec_jobs([spec_a])
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user