diff options
-rw-r--r-- | lib/spack/docs/pipelines.rst | 11 | ||||
-rw-r--r-- | lib/spack/spack/ci.py | 20 | ||||
-rw-r--r-- | lib/spack/spack/ci_needs_workaround.py | 34 | ||||
-rw-r--r-- | lib/spack/spack/ci_optimization.py | 363 | ||||
-rw-r--r-- | lib/spack/spack/cmd/ci.py | 21 | ||||
-rw-r--r-- | lib/spack/spack/test/ci.py | 162 | ||||
-rw-r--r-- | lib/spack/spack/test/cmd/ci.py | 49 | ||||
-rwxr-xr-x | share/spack/spack-completion.fish | 4 |
8 files changed, 18 insertions, 646 deletions
diff --git a/lib/spack/docs/pipelines.rst b/lib/spack/docs/pipelines.rst index 8aeefd116e..0a72793a89 100644 --- a/lib/spack/docs/pipelines.rst +++ b/lib/spack/docs/pipelines.rst @@ -253,17 +253,6 @@ can easily happen if it is not updated frequently, this behavior ensures that spack has a way to know for certain about the status of any concrete spec on the remote mirror, but can slow down pipeline generation significantly. -The ``--optimize`` argument is experimental and runs the generated pipeline -document through a series of optimization passes designed to reduce the size -of the generated file. - -The ``--dependencies`` is also experimental and disables what in Gitlab is -referred to as DAG scheduling, internally using the ``dependencies`` keyword -rather than ``needs`` to list dependency jobs. The drawback of using this option -is that before any job can begin, all jobs in previous stages must first -complete. The benefit is that Gitlab allows more dependencies to be listed -when using ``dependencies`` instead of ``needs``. - The optional ``--output-file`` argument should be an absolute path (including file name) to the generated pipeline, and if not given, the default is ``./.gitlab-ci.yml``. diff --git a/lib/spack/spack/ci.py b/lib/spack/spack/ci.py index 7237c95e69..956b19abdc 100644 --- a/lib/spack/spack/ci.py +++ b/lib/spack/spack/ci.py @@ -553,10 +553,9 @@ def generate_gitlab_ci_yaml( env, print_summary, output_file, + *, prune_dag=False, check_index_only=False, - run_optimizer=False, - use_dependencies=False, artifacts_root=None, remote_mirror_override=None, ): @@ -577,12 +576,6 @@ def generate_gitlab_ci_yaml( 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. @@ -1273,17 +1266,6 @@ def generate_gitlab_ci_yaml( with open(copy_specs_file, "w") as fd: fd.write(json.dumps(buildcache_copies)) - # TODO(opadron): remove this or refactor - if run_optimizer: - import spack.ci_optimization as ci_opt - - output_object = ci_opt.optimizer(output_object) - - # TODO(opadron): remove this or refactor - if use_dependencies: - import spack.ci_needs_workaround as cinw - - output_object = cinw.needs_to_dependencies(output_object) else: # No jobs were generated noop_job = spack_ci_ir["jobs"]["noop"]["attributes"] diff --git a/lib/spack/spack/ci_needs_workaround.py b/lib/spack/spack/ci_needs_workaround.py deleted file mode 100644 index b89de48f8f..0000000000 --- a/lib/spack/spack/ci_needs_workaround.py +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright 2013-2024 Lawrence Livermore National Security, LLC and other -# Spack Project Developers. See the top-level COPYRIGHT file for details. -# -# SPDX-License-Identifier: (Apache-2.0 OR MIT) -import collections.abc - -get_job_name = lambda needs_entry: ( - needs_entry.get("job") - if (isinstance(needs_entry, collections.abc.Mapping) and needs_entry.get("artifacts", True)) - else needs_entry if isinstance(needs_entry, str) else None -) - - -def convert_job(job_entry): - if not isinstance(job_entry, collections.abc.Mapping): - return job_entry - - needs = job_entry.get("needs") - if needs is None: - return job_entry - - new_job = {} - new_job.update(job_entry) - del new_job["needs"] - - new_job["dependencies"] = list( - filter((lambda x: x is not None), (get_job_name(needs_entry) for needs_entry in needs)) - ) - - return new_job - - -def needs_to_dependencies(yaml): - return dict((k, convert_job(v)) for k, v in yaml.items()) diff --git a/lib/spack/spack/ci_optimization.py b/lib/spack/spack/ci_optimization.py deleted file mode 100644 index 7d799fc907..0000000000 --- a/lib/spack/spack/ci_optimization.py +++ /dev/null @@ -1,363 +0,0 @@ -# Copyright 2013-2024 Lawrence Livermore National Security, LLC and other -# Spack Project Developers. See the top-level COPYRIGHT file for details. -# -# SPDX-License-Identifier: (Apache-2.0 OR MIT) -import collections -import collections.abc -import copy -import hashlib - -import spack.util.spack_yaml as syaml - - -def sort_yaml_obj(obj): - if isinstance(obj, collections.abc.Mapping): - return syaml.syaml_dict( - (k, sort_yaml_obj(v)) for k, v in sorted(obj.items(), key=(lambda item: str(item[0]))) - ) - - if isinstance(obj, collections.abc.Sequence) and not isinstance(obj, str): - return syaml.syaml_list(sort_yaml_obj(x) for x in obj) - - return obj - - -def matches(obj, proto): - """Returns True if the test object "obj" matches the prototype object - "proto". - - If obj and proto are mappings, obj matches proto if (key in obj) and - (obj[key] matches proto[key]) for every key in proto. - - If obj and proto are sequences, obj matches proto if they are of the same - length and (a matches b) for every (a,b) in zip(obj, proto). - - Otherwise, obj matches proto if obj == proto. - - Precondition: proto must not have any reference cycles - """ - if isinstance(obj, collections.abc.Mapping): - if not isinstance(proto, collections.abc.Mapping): - return False - - return all((key in obj and matches(obj[key], val)) for key, val in proto.items()) - - if isinstance(obj, collections.abc.Sequence) and not isinstance(obj, str): - if not (isinstance(proto, collections.abc.Sequence) and not isinstance(proto, str)): - return False - - if len(obj) != len(proto): - return False - - return all(matches(obj[index], val) for index, val in enumerate(proto)) - - return obj == proto - - -def subkeys(obj, proto): - """Returns the test mapping "obj" after factoring out the items it has in - common with the prototype mapping "proto". - - Consider a recursive merge operation, merge(a, b) on mappings a and b, that - returns a mapping, m, whose keys are the union of the keys of a and b, and - for every such key, "k", its corresponding value is: - - - merge(a[key], b[key]) if a[key] and b[key] are mappings, or - - b[key] if (key in b) and not matches(a[key], b[key]), - or - - a[key] otherwise - - - If obj and proto are mappings, the returned object is the smallest object, - "a", such that merge(a, proto) matches obj. - - Otherwise, obj is returned. - """ - if not ( - isinstance(obj, collections.abc.Mapping) and isinstance(proto, collections.abc.Mapping) - ): - return obj - - new_obj = {} - for key, value in obj.items(): - if key not in proto: - new_obj[key] = value - continue - - if matches(value, proto[key]) and matches(proto[key], value): - continue - - if isinstance(value, collections.abc.Mapping): - new_obj[key] = subkeys(value, proto[key]) - continue - - new_obj[key] = value - - return new_obj - - -def add_extends(yaml, key): - """Modifies the given object "yaml" so that it includes an "extends" key - whose value features "key". - - If "extends" is not in yaml, then yaml is modified such that - yaml["extends"] == key. - - If yaml["extends"] is a str, then yaml is modified such that - yaml["extends"] == [yaml["extends"], key] - - If yaml["extends"] is a list that does not include key, then key is - appended to the list. - - Otherwise, yaml is left unchanged. - """ - - has_key = "extends" in yaml - extends = yaml.get("extends") - - if has_key and not isinstance(extends, (str, collections.abc.Sequence)): - return - - if extends is None: - yaml["extends"] = key - return - - if isinstance(extends, str): - if extends != key: - yaml["extends"] = [extends, key] - return - - if key not in extends: - extends.append(key) - - -def common_subobject(yaml, sub): - """Factor prototype object "sub" out of the values of mapping "yaml". - - Consider a modified copy of yaml, "new", where for each key, "key" in yaml: - - - If yaml[key] matches sub, then new[key] = subkeys(yaml[key], sub). - - Otherwise, new[key] = yaml[key]. - - If the above match criteria is not satisfied for any such key, then (yaml, - None) is returned. The yaml object is returned unchanged. - - Otherwise, each matching value in new is modified as in - add_extends(new[key], common_key), and then new[common_key] is set to sub. - The common_key value is chosen such that it does not match any preexisting - key in new. In this case, (new, common_key) is returned. - """ - match_list = set(k for k, v in yaml.items() if matches(v, sub)) - - if not match_list: - return yaml, None - - common_prefix = ".c" - common_index = 0 - - while True: - common_key = "".join((common_prefix, str(common_index))) - if common_key not in yaml: - break - common_index += 1 - - new_yaml = {} - - for key, val in yaml.items(): - new_yaml[key] = copy.deepcopy(val) - - if not matches(val, sub): - continue - - new_yaml[key] = subkeys(new_yaml[key], sub) - add_extends(new_yaml[key], common_key) - - new_yaml[common_key] = sub - - return new_yaml, common_key - - -def print_delta(name, old, new, applied=None): - delta = new - old - reldelta = (1000 * delta) // old - reldelta = (reldelta // 10, reldelta % 10) - - if applied is None: - applied = new <= old - - print( - "\n".join( - ( - "{0} {1}:", - " before: {2: 10d}", - " after : {3: 10d}", - " delta : {4:+10d} ({5:=+3d}.{6}%)", - ) - ).format(name, ("+" if applied else "x"), old, new, delta, reldelta[0], reldelta[1]) - ) - - -def try_optimization_pass(name, yaml, optimization_pass, *args, **kwargs): - """Try applying an optimization pass and return information about the - result - - "name" is a string describing the nature of the pass. If it is a non-empty - string, summary statistics are also printed to stdout. - - "yaml" is the object to apply the pass to. - - "optimization_pass" is the function implementing the pass to be applied. - - "args" and "kwargs" are the additional arguments to pass to optimization - pass. The pass is applied as - - >>> (new_yaml, *other_results) = optimization_pass(yaml, *args, **kwargs) - - The pass's results are greedily rejected if it does not modify the original - yaml document, or if it produces a yaml document that serializes to a - larger string. - - Returns (new_yaml, yaml, applied, other_results) if applied, or - (yaml, new_yaml, applied, other_results) otherwise. - """ - result = optimization_pass(yaml, *args, **kwargs) - new_yaml, other_results = result[0], result[1:] - - if new_yaml is yaml: - # pass was not applied - return (yaml, new_yaml, False, other_results) - - pre_size = len(syaml.dump_config(sort_yaml_obj(yaml), default_flow_style=True)) - post_size = len(syaml.dump_config(sort_yaml_obj(new_yaml), default_flow_style=True)) - - # pass makes the size worse: not applying - applied = post_size <= pre_size - if applied: - yaml, new_yaml = new_yaml, yaml - - if name: - print_delta(name, pre_size, post_size, applied) - - return (yaml, new_yaml, applied, other_results) - - -def build_histogram(iterator, key): - """Builds a histogram of values given an iterable of mappings and a key. - - For each mapping "m" with key "key" in iterator, the value m[key] is - considered. - - Returns a list of tuples (hash, count, proportion, value), where - - - "hash" is a sha1sum hash of the value. - - "count" is the number of occurences of values that hash to "hash". - - "proportion" is the proportion of all values considered above that - hash to "hash". - - "value" is one of the values considered above that hash to "hash". - Which value is chosen when multiple values hash to the same "hash" is - undefined. - - The list is sorted in descending order by count, yielding the most - frequently occuring hashes first. - """ - buckets = collections.defaultdict(int) - values = {} - - num_objects = 0 - for obj in iterator: - num_objects += 1 - - try: - val = obj[key] - except (KeyError, TypeError): - continue - - value_hash = hashlib.sha1() - value_hash.update(syaml.dump_config(sort_yaml_obj(val)).encode()) - value_hash = value_hash.hexdigest() - - buckets[value_hash] += 1 - values[value_hash] = val - - return [ - (h, buckets[h], float(buckets[h]) / num_objects, values[h]) - for h in sorted(buckets.keys(), key=lambda k: -buckets[k]) - ] - - -def optimizer(yaml): - original_size = len(syaml.dump_config(sort_yaml_obj(yaml), default_flow_style=True)) - - # try factoring out commonly repeated portions - common_job = { - "variables": {"SPACK_COMPILER_ACTION": "NONE"}, - "after_script": ['rm -rf "./spack"'], - "artifacts": {"paths": ["jobs_scratch_dir", "cdash_report"], "when": "always"}, - } - - # look for a list of tags that appear frequently - _, count, proportion, tags = next(iter(build_histogram(yaml.values(), "tags")), (None,) * 4) - - # If a list of tags is found, and there are more than one job that uses it, - # *and* the jobs that do use it represent at least 70% of all jobs, then - # add the list to the prototype object. - if tags and count > 1 and proportion >= 0.70: - common_job["tags"] = tags - - # apply common object factorization - yaml, other, applied, rest = try_optimization_pass( - "general common object factorization", yaml, common_subobject, common_job - ) - - # look for a common script, and try factoring that out - _, count, proportion, script = next( - iter(build_histogram(yaml.values(), "script")), (None,) * 4 - ) - - if script and count > 1 and proportion >= 0.70: - yaml, other, applied, rest = try_optimization_pass( - "script factorization", yaml, common_subobject, {"script": script} - ) - - # look for a common before_script, and try factoring that out - _, count, proportion, script = next( - iter(build_histogram(yaml.values(), "before_script")), (None,) * 4 - ) - - if script and count > 1 and proportion >= 0.70: - yaml, other, applied, rest = try_optimization_pass( - "before_script factorization", yaml, common_subobject, {"before_script": script} - ) - - # Look specifically for the SPACK_ROOT_SPEC environment variables. - # Try to factor them out. - h = build_histogram( - (getattr(val, "get", lambda *args: {})("variables") for val in yaml.values()), - "SPACK_ROOT_SPEC", - ) - - # In this case, we try to factor out *all* instances of the SPACK_ROOT_SPEC - # environment variable; not just the one that appears with the greatest - # frequency. We only require that more than 1 job uses a given instance's - # value, because we expect the value to be very large, and so expect even - # few-to-one factorizations to yield large space savings. - counter = 0 - for _, count, proportion, spec in h: - if count <= 1: - continue - - counter += 1 - - yaml, other, applied, rest = try_optimization_pass( - "SPACK_ROOT_SPEC factorization ({count})".format(count=counter), - yaml, - common_subobject, - {"variables": {"SPACK_ROOT_SPEC": spec}}, - ) - - new_size = len(syaml.dump_config(sort_yaml_obj(yaml), default_flow_style=True)) - - print("\n") - print_delta("overall summary", original_size, new_size) - print("\n") - return yaml diff --git a/lib/spack/spack/cmd/ci.py b/lib/spack/spack/cmd/ci.py index 811da010b7..934acc0d4e 100644 --- a/lib/spack/spack/cmd/ci.py +++ b/lib/spack/spack/cmd/ci.py @@ -6,6 +6,7 @@ import json import os import shutil +import warnings from urllib.parse import urlparse, urlunparse import llnl.util.filesystem as fs @@ -73,7 +74,7 @@ def setup_parser(subparser): "--optimize", action="store_true", default=False, - help="(experimental) optimize the gitlab yaml file for size\n\n" + help="(DEPRECATED) optimize the gitlab yaml file for size\n\n" "run the generated document through a series of optimization passes " "designed to reduce the size of the generated file", ) @@ -81,7 +82,7 @@ def setup_parser(subparser): "--dependencies", action="store_true", default=False, - help="(experimental) disable DAG scheduling (use 'plain' dependencies)", + help="(DEPRECATED) disable DAG scheduling (use 'plain' dependencies)", ) generate.add_argument( "--buildcache-destination", @@ -200,6 +201,18 @@ def ci_generate(args): before invoking this command. the value must be the CDash authorization token needed to create a build group and register all generated jobs under it """ + if args.optimize: + warnings.warn( + "The --optimize option has been deprecated, and currently has no effect. " + "It will be removed in Spack v0.24." + ) + + if args.dependencies: + warnings.warn( + "The --dependencies option has been deprecated, and currently has no effect. " + "It will be removed in Spack v0.24." + ) + env = spack.cmd.require_active_env(cmd_name="ci generate") if args.copy_to: @@ -212,8 +225,6 @@ def ci_generate(args): output_file = args.output_file copy_yaml_to = args.copy_to - run_optimizer = args.optimize - use_dependencies = args.dependencies prune_dag = args.prune_dag index_only = args.index_only artifacts_root = args.artifacts_root @@ -234,8 +245,6 @@ def ci_generate(args): output_file, prune_dag=prune_dag, check_index_only=index_only, - run_optimizer=run_optimizer, - use_dependencies=use_dependencies, artifacts_root=artifacts_root, remote_mirror_override=buildcache_destination, ) diff --git a/lib/spack/spack/test/ci.py b/lib/spack/spack/test/ci.py index f3d47c45e9..cf936eb593 100644 --- a/lib/spack/spack/test/ci.py +++ b/lib/spack/spack/test/ci.py @@ -2,7 +2,6 @@ # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) -import itertools import os import subprocess @@ -11,15 +10,12 @@ import pytest import llnl.util.filesystem as fs import spack.ci as ci -import spack.ci_needs_workaround as cinw -import spack.ci_optimization as ci_opt import spack.config import spack.environment as ev import spack.error import spack.paths as spack_paths import spack.util.git import spack.util.gpg -import spack.util.spack_yaml as syaml @pytest.fixture @@ -203,164 +199,6 @@ def test_setup_spack_repro_version(tmpdir, capfd, last_two_git_commits, monkeypa assert "Unable to merge {0}".format(c1) in err -@pytest.mark.parametrize("obj, proto", [({}, [])]) -def test_ci_opt_argument_checking(obj, proto): - """Check that matches() and subkeys() return False when `proto` is not a dict.""" - assert not ci_opt.matches(obj, proto) - assert not ci_opt.subkeys(obj, proto) - - -@pytest.mark.parametrize("yaml", [{"extends": 1}]) -def test_ci_opt_add_extends_non_sequence(yaml): - """Check that add_extends() exits if 'extends' is not a sequence.""" - yaml_copy = yaml.copy() - ci_opt.add_extends(yaml, None) - assert yaml == yaml_copy - - -def test_ci_workarounds(): - fake_root_spec = "x" * 544 - fake_spack_ref = "x" * 40 - - common_variables = {"SPACK_IS_PR_PIPELINE": "False"} - - common_before_script = [ - 'git clone "https://github.com/spack/spack"', - " && ".join(("pushd ./spack", 'git checkout "{ref}"'.format(ref=fake_spack_ref), "popd")), - '. "./spack/share/spack/setup-env.sh"', - ] - - def make_build_job(name, deps, stage, use_artifact_buildcache, optimize, use_dependencies): - variables = common_variables.copy() - variables["SPACK_JOB_SPEC_PKG_NAME"] = name - - result = { - "stage": stage, - "tags": ["tag-0", "tag-1"], - "artifacts": { - "paths": ["jobs_scratch_dir", "cdash_report", name + ".spec.json", name], - "when": "always", - }, - "retry": {"max": 2, "when": ["always"]}, - "after_script": ['rm -rf "./spack"'], - "script": ["spack ci rebuild"], - "image": {"name": "spack/centos7", "entrypoint": [""]}, - } - - if optimize: - result["extends"] = [".c0", ".c1"] - else: - variables["SPACK_ROOT_SPEC"] = fake_root_spec - result["before_script"] = common_before_script - - result["variables"] = variables - - if use_dependencies: - result["dependencies"] = list(deps) if use_artifact_buildcache else [] - else: - result["needs"] = [{"job": dep, "artifacts": use_artifact_buildcache} for dep in deps] - - return {name: result} - - def make_rebuild_index_job(use_artifact_buildcache, optimize, use_dependencies): - result = { - "stage": "stage-rebuild-index", - "script": "spack buildcache update-index s3://mirror", - "tags": ["tag-0", "tag-1"], - "image": {"name": "spack/centos7", "entrypoint": [""]}, - "after_script": ['rm -rf "./spack"'], - } - - if optimize: - result["extends"] = ".c0" - else: - result["before_script"] = common_before_script - - return {"rebuild-index": result} - - def make_factored_jobs(optimize): - return ( - { - ".c0": {"before_script": common_before_script}, - ".c1": {"variables": {"SPACK_ROOT_SPEC": fake_root_spec}}, - } - if optimize - else {} - ) - - def make_stage_list(num_build_stages): - return { - "stages": ( - ["-".join(("stage", str(i))) for i in range(num_build_stages)] - + ["stage-rebuild-index"] - ) - } - - def make_yaml_obj(use_artifact_buildcache, optimize, use_dependencies): - result = {} - - result.update( - make_build_job( - "pkg-a", [], "stage-0", use_artifact_buildcache, optimize, use_dependencies - ) - ) - - result.update( - make_build_job( - "pkg-b", ["pkg-a"], "stage-1", use_artifact_buildcache, optimize, use_dependencies - ) - ) - - result.update( - make_build_job( - "pkg-c", - ["pkg-a", "pkg-b"], - "stage-2", - use_artifact_buildcache, - optimize, - use_dependencies, - ) - ) - - result.update(make_rebuild_index_job(use_artifact_buildcache, optimize, use_dependencies)) - - result.update(make_factored_jobs(optimize)) - - result.update(make_stage_list(3)) - - return result - - # test every combination of: - # use artifact buildcache: true or false - # run optimization pass: true or false - # convert needs to dependencies: true or false - for use_ab in (False, True): - original = make_yaml_obj( - use_artifact_buildcache=use_ab, optimize=False, use_dependencies=False - ) - - for opt, deps in itertools.product(*(((False, True),) * 2)): - # neither optimizing nor converting needs->dependencies - if not (opt or deps): - # therefore, nothing to test - continue - - predicted = make_yaml_obj( - use_artifact_buildcache=use_ab, optimize=opt, use_dependencies=deps - ) - - actual = original.copy() - if opt: - actual = ci_opt.optimizer(actual) - if deps: - actual = cinw.needs_to_dependencies(actual) - - predicted = syaml.dump_config(ci_opt.sort_yaml_obj(predicted), default_flow_style=True) - actual = syaml.dump_config(ci_opt.sort_yaml_obj(actual), default_flow_style=True) - - assert predicted == actual - - def test_get_spec_filter_list(mutable_mock_env_path, config, mutable_mock_repo): """Test that given an active environment and list of touched pkgs, we get the right list of possibly-changed env specs""" diff --git a/lib/spack/spack/test/cmd/ci.py b/lib/spack/spack/test/cmd/ci.py index 583046df94..edd90bbbd5 100644 --- a/lib/spack/spack/test/cmd/ci.py +++ b/lib/spack/spack/test/cmd/ci.py @@ -1432,55 +1432,6 @@ spack: assert the_elt["after_script"][0] == "post step one" -def test_ci_generate_with_workarounds( - tmpdir, mutable_mock_env_path, install_mockery, mock_packages, monkeypatch, ci_base_environment -): - """Make sure the post-processing cli workarounds do what they should""" - filename = str(tmpdir.join("spack.yaml")) - with open(filename, "w") as f: - f.write( - """\ -spack: - specs: - - callpath%gcc@=9.5 - mirrors: - some-mirror: https://my.fake.mirror - ci: - pipeline-gen: - - submapping: - - match: ['%gcc@9.5'] - build-job: - tags: - - donotcare - image: donotcare - enable-artifacts-buildcache: true -""" - ) - - with tmpdir.as_cwd(): - env_cmd("create", "test", "./spack.yaml") - outputfile = str(tmpdir.join(".gitlab-ci.yml")) - - with ev.read("test"): - ci_cmd("generate", "--output-file", outputfile, "--dependencies") - - with open(outputfile) as f: - contents = f.read() - yaml_contents = syaml.load(contents) - - found_one = False - non_rebuild_keys = ["workflow", "stages", "variables", "rebuild-index"] - - for ci_key in yaml_contents.keys(): - if ci_key not in non_rebuild_keys: - found_one = True - job_obj = yaml_contents[ci_key] - assert "needs" not in job_obj - assert "dependencies" in job_obj - - assert found_one is True - - @pytest.mark.disable_clean_stage_check def test_ci_rebuild_index( tmpdir, diff --git a/share/spack/spack-completion.fish b/share/spack/spack-completion.fish index 4239faddc8..d2d883ff93 100755 --- a/share/spack/spack-completion.fish +++ b/share/spack/spack-completion.fish @@ -962,9 +962,9 @@ complete -c spack -n '__fish_spack_using_command ci generate' -l output-file -r complete -c spack -n '__fish_spack_using_command ci generate' -l copy-to -r -f -a copy_to complete -c spack -n '__fish_spack_using_command ci generate' -l copy-to -r -d 'path to additional directory for job files' complete -c spack -n '__fish_spack_using_command ci generate' -l optimize -f -a optimize -complete -c spack -n '__fish_spack_using_command ci generate' -l optimize -d '(experimental) optimize the gitlab yaml file for size' +complete -c spack -n '__fish_spack_using_command ci generate' -l optimize -d '(DEPRECATED) optimize the gitlab yaml file for size' complete -c spack -n '__fish_spack_using_command ci generate' -l dependencies -f -a dependencies -complete -c spack -n '__fish_spack_using_command ci generate' -l dependencies -d '(experimental) disable DAG scheduling (use \'plain\' dependencies)' +complete -c spack -n '__fish_spack_using_command ci generate' -l dependencies -d '(DEPRECATED) disable DAG scheduling (use \'plain\' dependencies)' complete -c spack -n '__fish_spack_using_command ci generate' -l buildcache-destination -r -f -a buildcache_destination complete -c spack -n '__fish_spack_using_command ci generate' -l buildcache-destination -r -d 'override the mirror configured in the environment' complete -c spack -n '__fish_spack_using_command ci generate' -l prune-dag -f -a prune_dag |