Spack develop: fix bug with delayed install of root#35442
Spack develop: fix bug with delayed install of root#35442scheibelp wants to merge 42 commits intospack:developfrom
Conversation
|
@spackbot fix style |
|
Let me see if I can fix that for you! |
|
I was able to run spack style --fix==> Running style checks on spack
selected: isort, black, flake8, mypy
==> Modified files
lib/spack/spack/environment/environment.py
lib/spack/spack/test/cmd/dev_build.py
==> Running isort checks
isort checks were clean
==> Running black checks
reformatted lib/spack/spack/test/cmd/dev_build.py
All done! ✨ 🍰 ✨
1 file reformatted, 1 file left unchanged.
black checks were clean
==> Running flake8 checks
flake8 checks were clean
==> Running mypy checks
Success: no issues found in 576 source files
mypy checks were clean
==> spack style checks were clean
I've updated the branch with style fixes. |
…h void; pending commits will need to reimplement
ab4811e to
8ed50a5
Compare
|
Can you implement it along the lines of def _get_overwrite_specs(
concrete_specs: List[spack.spec.Spec], database: Optional[spack.database.Database] = None
) -> List[str]:
"""If a develop spec has been modified, we need to overwrite it and its parents.
Also ensure that the parents of installed develop specs are overwritten if they
are older than the installation time of the develop spec.
Args:
database: The database to use. If None, the default database is used.
Returns:
List of dag hashes of specs that need to be overwritten."""
db = database or spack.store.STORE.db
# Get all specs in topological order.
specs: Iterable[spack.spec.Spec] = reversed(
list(traverse.traverse_nodes(concrete_specs, order="topo", key=traverse.by_dag_hash))
)
# max_time[spec.dag_hash()] is the maximum installation time dev deps; or inf
# if any of its deps needs to be overwritten.
max_time: Dict[str, float] = {}
hashes_to_overwrite = []
for spec in specs:
key = spec.dag_hash()
upstream, record = db.query_by_spec_hash(key)
dep_time = max_time.pop(key, 0.0)
if not upstream and record:
# Case 1: spec is installed locally, and a newer dev dep is in the subdag.
if record.installation_time < dep_time:
hashes_to_overwrite.append(key)
dep_time = math.inf
else:
path = spec.variants.get("dev_path", None)
if path:
mtime = fs.last_modification_time_recursive(path.value)
# Case 2: this spec is a develop spec, and it has been modified.
if mtime > record.installation_time:
# Have to reinstall this develop spec.
hashes_to_overwrite.append(key)
dep_time = math.inf
else:
# Case 3: the develop spec is already installed, dependents
# need to be overwritten if they are older than the installation
dep_time = max(dep_time, record.installation_time)
# Propagate max dep install time to dependents
for dependent in spec.dependents():
dep_key = dependent.dag_hash()
if max_time.get(dep_key, 0.0) < dep_time:
max_time[dep_key] = dep_time
return hashes_to_overwriteI think it's more clear if you topo sort and then propagate (a) install times of up-to-date develop dependencies or (b) infinity for out of date develop deps. Maybe split it into two functions to avoid the deeply nested control flow. That way we also don't check O(develop spec) modification times if say one common leaf node has changed, and checking one would be enough. Also, with the above structure ^ could you refactor the test so that it takes a few us/ms instead of seconds? Construct a dev spec, mark it concrete, create a db, add it with particular install time. That way you don't need to concretize, hit the filesystem as much, or use Btw, I'm also thinking that we should maybe reverse the default order of topo sort, cause if you look at the implementation it already does a reverse, and the call site commonly re-reverses it, like ^ |
… dev-buid command now
I have now done so: the method will now avoid checking timestamps on dependents if a dependency has already met the criteria for overwriting. The test is updated now as well (and moved, since it isn't exercising the |
|
@haampie can you review again? |
| _, record = db.query_by_spec_hash(spec.dag_hash()) | ||
| when_this_spec_was_installed = record.installation_time | ||
|
|
||
| transitive_dev_install_times[spec] = max( |
There was a problem hiding this comment.
Why don't you assign transitive_dev_install_time_child = transitive_dev_install_times[spec] first and then conditionally update it, instead of calling transitive_dev_install_times[spec] 3 times?
| # Say x->y->z, you force uninstall y, then you change the source | ||
| # of z (which is develop). x should be reinstalled, but not because | ||
| # of y. Therefore, we wait until now to bail on uninstalled specs. | ||
| if not db.installed(spec): |
There was a problem hiding this comment.
You're using db.installed followed by db.query_by_spec_hash on three occasions. Just use _, record = db.query_by_spec_hash(...) then and replace if not db.installed(...) with if record is None or not record.installed
| # time, then overwrite | ||
| _, record = db.query_by_spec_hash(spec.dag_hash()) | ||
| when_this_spec_was_installed = record.installation_time | ||
| if when_this_spec_was_installed < transitive_dev_install_times[spec]: |
There was a problem hiding this comment.
Instead of looking up this value yet again, why not return it from update_transitive_dev_install_times.
In fact you can also delay looping over dependents() until after this branch, since it's redundant if this branch is followed.
|
Some small requests, and then let's get this pr in. FWIW, I'm generally not a fan of using |
ce8e704 to
fcc4534
Compare
|
I have fixed the bug mentioned in #35442 (comment)
I have tried to refactor the code with these requests in mind as well, but as for why I did it the way I did it:
? |
If you have packages
X->Y, Y is in development (i.e. withspack develop), and you change Y and then specifically install it, a subsequent call ofspack installwould not reinstall X. i.e. for the following commands:Xwill not be reinstalled.This adds a check where a dependent is now also overwritten if any in-development dependency has a newer installation date than the dependent.