gitlab: Prune untouched specs less aggressively (#33669)
Untouched spec pruning was added to reduce the number of specs developers see getting rebuilt in their PR pipelines that they don't understand. Because the state of the develop mirror lags quite far behind the tip of the develop branch, PRs often find they need to rebuild things untouched by their PR. Untouched spec pruning was previously implemented by finding all specs in the environment with names of packages touched by the PR, traversing in both directions the DAGS of those specs, and adding all dependencies as well as dependents to a list of concrete specs that should not be considered for pruning. We found that this heuristic results in too many pruned specs, and that dependents of touched specs must have all their dependencies added to the list of specs that should not be considered for pruning.
This commit is contained in:
parent
08bee718a2
commit
b55509ffa8
@ -515,38 +515,36 @@ def compute_affected_packages(rev1="HEAD^", rev2="HEAD"):
|
|||||||
return spack.repo.get_all_package_diffs("ARC", rev1=rev1, rev2=rev2)
|
return spack.repo.get_all_package_diffs("ARC", rev1=rev1, rev2=rev2)
|
||||||
|
|
||||||
|
|
||||||
def get_spec_filter_list(env, affected_pkgs, dependencies=True, dependents=True):
|
def get_spec_filter_list(env, affected_pkgs):
|
||||||
"""Given a list of package names, and assuming an active and
|
"""Given a list of package names and an active/concretized
|
||||||
concretized environment, return a set of concrete specs from
|
environment, return the set of all concrete specs from the
|
||||||
the environment corresponding to any of the affected pkgs (or
|
environment that could have been affected by changing the
|
||||||
optionally to any of their dependencies/dependents).
|
list of packages.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
|
|
||||||
env (spack.environment.Environment): Active concrete environment
|
env (spack.environment.Environment): Active concrete environment
|
||||||
affected_pkgs (List[str]): Affected package names
|
affected_pkgs (List[str]): Affected package names
|
||||||
dependencies (bool): Include dependencies of affected packages
|
|
||||||
dependents (bool): Include dependents of affected pacakges
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
A list of concrete specs from the active environment including
|
A set of concrete specs from the active environment including
|
||||||
those associated with affected packages, and possible their
|
those associated with affected packages, their dependencies and
|
||||||
dependencies and dependents as well.
|
dependents, as well as their dependents dependencies.
|
||||||
"""
|
"""
|
||||||
affected_specs = set()
|
affected_specs = set()
|
||||||
all_concrete_specs = env.all_specs()
|
all_concrete_specs = env.all_specs()
|
||||||
tty.debug("All concrete environment specs:")
|
tty.debug("All concrete environment specs:")
|
||||||
for s in all_concrete_specs:
|
for s in all_concrete_specs:
|
||||||
tty.debug(" {0}/{1}".format(s.name, s.dag_hash()[:7]))
|
tty.debug(" {0}/{1}".format(s.name, s.dag_hash()[:7]))
|
||||||
for pkg in affected_pkgs:
|
env_matches = [s for s in all_concrete_specs if s.name in frozenset(affected_pkgs)]
|
||||||
env_matches = [s for s in all_concrete_specs if s.name == pkg]
|
visited = set()
|
||||||
|
dag_hash = lambda s: s.dag_hash()
|
||||||
for match in env_matches:
|
for match in env_matches:
|
||||||
affected_specs.add(match)
|
for parent in match.traverse(direction="parents", key=dag_hash):
|
||||||
if dependencies:
|
affected_specs.update(
|
||||||
affected_specs.update(match.traverse(direction="children", root=False))
|
parent.traverse(direction="children", visited=visited, key=dag_hash)
|
||||||
if dependents:
|
)
|
||||||
affected_specs.update(match.traverse(direction="parents", root=False))
|
|
||||||
return affected_specs
|
return affected_specs
|
||||||
|
|
||||||
|
|
||||||
@ -625,7 +623,7 @@ def generate_gitlab_ci_yaml(
|
|||||||
affected_specs = get_spec_filter_list(env, affected_pkgs)
|
affected_specs = get_spec_filter_list(env, affected_pkgs)
|
||||||
tty.debug("all affected specs:")
|
tty.debug("all affected specs:")
|
||||||
for s in affected_specs:
|
for s in affected_specs:
|
||||||
tty.debug(" {0}".format(s.name))
|
tty.debug(" {0}/{1}".format(s.name, s.dag_hash()[:7]))
|
||||||
|
|
||||||
# Allow overriding --prune-dag cli opt with environment variable
|
# Allow overriding --prune-dag cli opt with environment variable
|
||||||
prune_dag_override = os.environ.get("SPACK_PRUNE_UP_TO_DATE", None)
|
prune_dag_override = os.environ.get("SPACK_PRUNE_UP_TO_DATE", None)
|
||||||
@ -848,7 +846,11 @@ def generate_gitlab_ci_yaml(
|
|||||||
|
|
||||||
if prune_untouched_packages:
|
if prune_untouched_packages:
|
||||||
if release_spec not in affected_specs:
|
if release_spec not in affected_specs:
|
||||||
tty.debug("Pruning {0}, untouched by change.".format(release_spec.name))
|
tty.debug(
|
||||||
|
"Pruning {0}/{1}, untouched by change.".format(
|
||||||
|
release_spec.name, release_spec.dag_hash()[:7]
|
||||||
|
)
|
||||||
|
)
|
||||||
spec_record["needs_rebuild"] = False
|
spec_record["needs_rebuild"] = False
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -442,13 +442,16 @@ def test_get_spec_filter_list(mutable_mock_env_path, config, mutable_mock_repo):
|
|||||||
touched = ["libdwarf"]
|
touched = ["libdwarf"]
|
||||||
|
|
||||||
# traversing both directions from libdwarf in the graphs depicted
|
# traversing both directions from libdwarf in the graphs depicted
|
||||||
# above results in the following possibly affected env specs:
|
# above (and additionally including dependencies of dependents of
|
||||||
# mpileaks, callpath, dyninst, libdwarf, and libelf. Unaffected
|
# libdwarf) results in the following possibly affected env specs:
|
||||||
# specs are mpich, plus hypre and it's dependencies.
|
# mpileaks, callpath, dyninst, libdwarf, libelf, and mpich.
|
||||||
|
# Unaffected specs are hypre and it's dependencies.
|
||||||
|
|
||||||
affected_specs = ci.get_spec_filter_list(e1, touched)
|
affected_specs = ci.get_spec_filter_list(e1, touched)
|
||||||
affected_pkg_names = set([s.name for s in affected_specs])
|
affected_pkg_names = set([s.name for s in affected_specs])
|
||||||
expected_affected_pkg_names = set(["mpileaks", "callpath", "dyninst", "libdwarf", "libelf"])
|
expected_affected_pkg_names = set(
|
||||||
|
["mpileaks", "mpich", "callpath", "dyninst", "libdwarf", "libelf"]
|
||||||
|
)
|
||||||
|
|
||||||
assert affected_pkg_names == expected_affected_pkg_names
|
assert affected_pkg_names == expected_affected_pkg_names
|
||||||
|
|
||||||
|
@ -1837,8 +1837,8 @@ def fake_stack_changed(env_path, rev1="HEAD^", rev2="HEAD"):
|
|||||||
yaml_contents = syaml.load(contents)
|
yaml_contents = syaml.load(contents)
|
||||||
|
|
||||||
for ci_key in yaml_contents.keys():
|
for ci_key in yaml_contents.keys():
|
||||||
if "archive-files" in ci_key or "mpich" in ci_key:
|
if "archive-files" in ci_key:
|
||||||
print("Error: archive-files and mpich should have been pruned")
|
print("Error: archive-files should have been pruned")
|
||||||
assert False
|
assert False
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user