Allow for extensions on a per filesystem view basis#3227
Allow for extensions on a per filesystem view basis#3227obreitwi wants to merge 1 commit intospack:developfrom
Conversation
|
This looks broadly similar to my code mentioned in #3245. One thing I did add later was removing absolute references from merged |
|
This is clearly the right direction. |
|
@citibeth wrote:
We started to get quite unhappy when we crossed the 50 entries per environment variable barrier (we basically dropped all system-installed software and rely 100% on spack for all our software dependencies). Due to @obreitwi's change we (@electronicvisions) are all happy again ;) |
|
Hmm.... I believe I've passed that threshold already, but I'm not unhappy.
Maybe it depends on the fileserver being used, I'm running off a local
drive.
…On Tue, Feb 28, 2017 at 10:14 AM, Eric Müller ***@***.***> wrote:
@citibeth <https://github.com/citibeth> wrote:
[...] (I personally have been happy with loading modules).
We started to get quite unhappy when we crossed the 50 entries per
environment variable barrier (we basically dropped all system-installed
software and rely 100% on spack for all our software dependencies).
→ You cannot open manpages anymore, loading python gets incredible slow, …
Due to @obreitwi <https://github.com/obreitwi>'s change we (
@electronicvisions <https://github.com/electronicvisions>) are all happy
again ;)
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#3227 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AB1cdxzKPPRiZyxcUmrA7tRXIXcBZUmmks5rhDnwgaJpZM4MKE5G>
.
|
fe0cc72 to
e2d3ed6
Compare
|
@citibeth yeah, we use NFS and it is unbearable... Just added a missing feature that locally and globally activated extensions are handled correctly (forgot about this as we do not plan on using any globally activated extensions). @paulhopkins how about using |
8ce59bc to
216f074
Compare
|
Some further functionality (plus a rebase on current
|
|
Actually, I just noticed #3587, which solves the problem of filesystem views being unusable with python extensions in general, still, we need this PR to have different sets of activated extensions in each view within one spack installation. |
216f074 to
87f610f
Compare
|
I've spoken with @tgamblin and I will be reviewing this over the next couple days. Two initial notes:
|
87f610f to
03724ff
Compare
|
Thank you for taking the time.
I will test this out as soon as I have time! |
EDIT: my mistake - Package should deal with this (and you shouldn't have to) |
Oh I see what you're saying there. Yeah in this case that is more likely something that should be handled in package.py |
I edited my follow-up comment but I also wanted to make explicit that this is not an issue with this PR. |
It shouldn't need to call I can avoid the error I mentioned by updating Package. I'll try to get a PR out for it by the end of tomorrow. |
| continue | ||
|
|
||
| paths.append(line) | ||
| line = os.path.realpath(line) |
There was a problem hiding this comment.
Some of the easy-install.pth files contain lines like:
./Babel-2.3.4-py2.7.egg
In which case realpath may expand relative to the current directory (vs. the directory where the easy-install.pth file is)
As you mention, it appears that #3587 will do away with easy-install.pth files. I'll add a comment there that perhaps this entire function should disappear. If it can't for some reason then IMO this issue should be fixed.
I don't think #3587 fully resolves this, since you take the effort to use I'm noticing an extra file that is added is |
|
Yeah, apparently there are still some cases left where `easy_install.pth`s need to be merged.
The `.spack-empty` token-files are so that `LinkTree` does not remove empty folders when unmerging (at least as far as I understood).
…On 14 April 2017 04:32:54 CEST, scheibelp ***@***.***> wrote:
> I just noticed #3587, which solves the problem of filesystem views
being unusable with python extensions in general,
I don't think #3587 fully resolves this, since you take the effort to
use ```activate``` logic for extensions you benefit from whatever files
they choose not to merge. In the case of python modules that includes
"site.py"
I'm noticing an extra file that is added is ```.spack-empty``` - do you
know where that comes from?
--
You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub:
#3227 (comment)
|
|
When I merge #3853 into your PR I can add git to a view
In that case, does my note about |
|
Yes, we should just remove duplicate lines from the resulting
easy_install.pth, imho.
…On Fri, Apr 14, 2017 at 05:33:04PM -0700, scheibelp wrote:
When I merge #3853 into your PR I can add git to a view
> apparently there are still some cases left where `easy_install.pth`s need to be merged.
In that case, does my note about ```realpath``` make sense?
--
You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub:
#3227 (comment)
|
To be clear I think there is an issue
With your PR, then The problematic line IMO is |
03724ff to
6f6b167
Compare
|
Yes, you are completely correct. I removed the call to |
c1ef696 to
083a466
Compare
|
Okay, thanks, in the meantime I'll see to it that Travis has nothing to complain about. |
083a466 to
6a47234
Compare
|
Generally I like the reorganization but I'm still seeing the issue I mentioned earlier:
|
6a47234 to
24bea31
Compare
|
Oh, I guess I couldn't see the forest for the trees. All extensions were linked into the meta-folder of the extendee instead of the root of the view. Should be fixed now. |
scheibelp
left a comment
There was a problem hiding this comment.
Like I mentioned before: I really like the general organization and refactoring. IMO all requests here are minor except where I press on my original request not to manipulate a global variable to control whether global or view extensions are being used; as I mention in the relevant comment, if you are OK with me changing this in a follow-up PR I consider this less of an issue. However if you think it is a bad idea to do this, then I'd like to talk about it a bit more.
I'll also start thinking of tests to suggest and I'll add an update on that by the end of tomorrow.
lib/spack/spack/filesystem_view.py
Outdated
| to_keep = all_specs - to_deactivate | ||
|
|
||
| # remove all packages depending on the ones to remove | ||
| to_deactivate.update(find_dependents(to_keep, to_deactivate)) |
There was a problem hiding this comment.
I thought this was originally only automatically done if the user enabled an option (and that otherwise Spack would complain if there were "leftover" dependents).
There was a problem hiding this comment.
Up until now, there was no check on packages depending on the ones to remove. There was a simple --dependencies switch (enabled by default), that would remove dependencies of packages to remove.
However, if you removed the dependency of another package, rendering it unusable, there was no check performed and hence no warning.
There was a problem hiding this comment.
Got it. I would prefer there would be an option for this like --remove-dependents. If it is true by default, warn the user when you are automatically removing dependents; if it is false by default, warn the user when you are leaving "detached" dependents around.
There was a problem hiding this comment.
Okay, I added the option (default True), so that the user does not have to worry about removing the dangling packages and the view remains usable. People who claim to know what they are doing should put in the extra work :)
lib/spack/spack/filesystem_view.py
Outdated
|
|
||
| return True | ||
|
|
||
| def activate_standalone(self, spec): |
There was a problem hiding this comment.
My understanding is that this is the function used to add non-extension packages to views. I'd prefer using a term other than "activate", like add_package_to_view and add_extension_to_view, because as of now Package.activate is associated with extensions. This is a minor preference though, since eventually Package.activate could account for the details of adding non-extension packages to views.
Likewise, I'd prefer YamlFilesystemView.add_packages_to_view vs. YamlFilesystemView.activate
There was a problem hiding this comment.
When I decided on the name I wanted to keep in the spirit of extensions in that activating a package is linking it somewhere else, but I have no strong opinion on the issue.
Your concern is very valid that it might confuse people → I'll change it.
lib/spack/spack/filesystem_view.py
Outdated
| raise NotImplementedError | ||
|
|
||
|
|
||
| def with_extensions(f): |
There was a problem hiding this comment.
While I think this is nearly the clearest possible way to handle this by modifying global state, I still feel that the general approach of modifying a global variable which implicitly alters the behavior of the Package.do_activate function is less clear than just passing in the ExtensionsLayout as a parameter to Package.do_activate. IMO the user wouldn't get the impression that this could happen based on looking at Package.do_activate by itself so it would be easy to get confused. I've voiced that opinion before but hopefully this at least makes my reasoning clear, so you can point out if you think it is flawed, or compare it to the disadvantages of my suggestion to make ExtensionsLayout a parameter.
Overall I imagine this would only require changing about 20 lines or so (not that this implies that it's "OK", but if you get a different sense of the work involved I'd be interested to know). I should say that if your are fine with me making this change in a follow-up PR, then I'm less-inclined to make an issue out of it here.
There was a problem hiding this comment.
Hm… I wanted to mirror the "global" behavior of the DirectoryLayout/databases etc. Why should extensions be passed explicitly but everything else remains in global objects residing under spack.store - seems a bit asymmetric.
My actual primary motivation was to keep Package.activated a property, but you are right, explicit is better than implicit.
lib/spack/llnl/util/filesystem.py
Outdated
| return parent | ||
|
|
||
|
|
||
| def assuredir(path): |
There was a problem hiding this comment.
Can this be removed? As far as I can tell, this function is no longer called based on the updates in this PR.
There was a problem hiding this comment.
Oh, yes, I moved it from cmd/view.py to utils and then forgot to delete it.
| raise ValueError("%s is not a link tree!" % dest) | ||
| os.remove(dest) | ||
| # only remove if src actually points to dest | ||
| if filecmp.cmp(src, dest, shallow=True): |
There was a problem hiding this comment.
Would it be clearer to use if os.path.realpath(dest) == src? IMO it is sufficient if dest is in fact a symlink to src; if you agree with me on that IMO this is more straightforward since it takes more effort to understand the implications of filecmp.cmp.
There was a problem hiding this comment.
Using realpath would be sufficient if we only ever used symlinks, but for hardlinks the realpaths differ. I updated the comment accordingly.
Although I do admit that this should only ever be an issue when adding (and subsequently removing) packages containing conflicting files (that are important), possibly with hardlinks. In all other cases the original os.remove would have been sufficient.
There was a problem hiding this comment.
I updated the comment accordingly.
Sounds good - thanks!
lib/spack/llnl/util/link_tree.py
Outdated
| Note that files blocking directories will still cause an error. | ||
| """ | ||
| kwargs['order'] = 'pre' | ||
| report_existing = kwargs.get("report_existing", False) |
There was a problem hiding this comment.
IMO this should also be called ignore_conflicts. The variable definitely results in reporting existing files, but IMO the most important effect is that this option allows for conflicts to occur.
There was a problem hiding this comment.
I don't know why I did not choose that name, but I renamed it now…
I'll keep thinking on it. Let me know if you think these aren't useful or aren't in scope for your PR. Also if you think the tests are useful but want to chat about how to implement them let me know. |
5db2ea9 to
9ee2c9f
Compare
|
Okay, just updated the branch. Now global state is not modified, the After making my head hurt at first (I wanted to not remove dependencies that have non-user-specified dependents) I decided to make it nice and simple:
Basically I don't see users removing packages from views that much anyway. It is just too easy to delete everything and add exactly what you want. Now it is the most conservative approach (which happens to delete more rather than having unusable packages linger). If the PR is fine otherwise I'll have a closer look at the tests in the coming days. |
lib/spack/spack/database.py
Outdated
| Return the specs of all packages that extend | ||
| the given spec | ||
| """ | ||
| # TODO: Shouldn't this function be called "activated_extensions_for"? |
There was a problem hiding this comment.
Yes - if you could change the name I'd appreciate it.
lib/spack/llnl/util/link_tree.py
Outdated
| if not os.path.islink(dest): | ||
| raise ValueError("%s is not a link tree!" % dest) | ||
| os.remove(dest) | ||
| # only remove if src actually points to dest |
There was a problem hiding this comment.
IIRC dest is actually the file/symlink in the view, so IMO it would be clearer if this read "if dest points to src" or perhaps even better is "only remove if dest is a hardlink/symlink to src" and it occurs to me that it would also be useful to explain how this can occur since it is not expected to be a common situation. So in all:
"remove if dest is a hardlink/symlink to src; this will only be false if two packages are merged into a prefix and have a conflicting file"
| tty.warn(self._croot + | ||
| "The following dependents will be removed: %s" | ||
| % ", ".join((s.name for s in dependents))) | ||
| to_deactivate.update(dependents) |
There was a problem hiding this comment.
Since you go through the work of aggregating dependents here, my understanding is that if you sorted them based on a post-order traversal of the dependency DAG that calling Package.do_deactivate with with_dependents=True would not be required. I don't think there's any harm in doing it this way although the redundancy may be confusing. If I'm right I think it would make sense to make a comment on it (I don't necessarily think it requires a change).
There was a problem hiding this comment.
Yeah, you are correct: Prior to this PR there was an exception thrown if an activated dependent was detected, so I added the option to remove dependents instead (back when this PR was a quick hack).
I am unsure as to whether the method you describe will be reliable enough (I have yet to fully understand all code related to the DAG), but I'll add a comment that this should be done in the future if performance is too detrimental.
lib/spack/spack/directory_layout.py
Outdated
| raise NotImplementedError | ||
|
|
||
| @property | ||
| def hidden_file_paths(self): |
There was a problem hiding this comment.
I don't think either implementation of the interface implements this method. Is there some reason it is here?
There was a problem hiding this comment.
Oh, right, that was erroneously copied from DirectoryLayout, removed.
Makes sense to me
Added a few more comments. IMO they are all minor issues. |
Resolves spack#1789 In our scenario we have a a globally installed (i.e., read-only) set of packages from which each user should then be able to add a subset to local filesystem views. However, the current extension mechanism only allows for one set of extensions to be activated for any given extendee. One way to circumvent this issue is to use a module system, but this way environment variables tend to get very long after a short while. Furthermore, since an error is raised when two files have the same target in the view, the user can immediately spot when multiple specs from one package are loaded (an error that sill creeps up from time to time). In order to support multiple (and maybe mutually exclusive) configurations of extensions for a given extendee, the following changes were made. The aim is to silently activate any specified extensions when adding them to the filesystem view; no actions from the user required. If an extension is globally activated, it will not be linked again by the view. Note that the view might break, however, if the extension gets (globally) deactivated later on. TL;DR: Adding python packages built with py-setuptools to fileystem views without globally activating them should "just work". * Split extension-functionality into seperate `ExtensionLayout` and `YamlExtensionLayout` classes that reside under `spack.store.extensions`. * `ExtensionLayout` has a `extendee_target_directory(extendee)` method to specify to what target directory files should be linked upon package activation. * All filesystem view functionality has been reimplemented in `spack/filesystem_view.py`: `FilesystemView`/`YamlFilesystemView` govern all operations preformed on views. * `LinkTree`: `merge`-method now accepts a link argument to allow for both symlinks and hardlinks. * `view`: All linking operations have been refactored to use `LinkTree`s instead of custom code. * `view`: Check extensions prior to linking packages, activate extensions in filesystem view afterwards. * `view statlink`: The `--verbose` option now prints the `(c)short_spec` of packages linked. * `view statlink`: `remove` now accepts `--all` argument to display the status of/remove all packages in the filesystem view. `statlink` displays all specs when none are supplied. * `view statlink`: Now uses `display_specs`-function for prettier output. * `view remove`: Only removes packages specified by user, packages depending on those packages and packages that only user-specified packages depend on. The latter can be adjusted via `-d` command line option. * Added `-i/--ignore-conflicts` command line option to print warnings instead of raising an exception whenever conflicting files are found. Please note that this only works for linking non-extensions as the activation-procedure should take care of conflicting files in extensions. * To that end, LinkTree now has the option to `ignore_conflicts` and report the conflicts instead of raising assertions when `merge`-ing.
9ee2c9f to
8c0c520
Compare
|
After having had a quick look at the testing routines, some questions/thoughts came up:
|
IMO you don't need to test
IMO no
I could see how the last two could be tested on specs without install files. The first one would be harder assuming we're talking about file conflict detection (vs. detecting whether some other version of a package has been added to a view), although IMO you don't have to work with specs (could just be one of the link_tree tests)
If by "Only user based input with builtin.mock" you mean invocations of cmd.view I don't think it has to be at that level. Direct testing of filesystem_view seems good to me.
While |
|
I've rebased this and split it into multiple commits in #5415. |
|
#5415 went in. Can we close this? |
|
Superseded by #5415 |
Resolves #1789
In our scenario we have a a globally installed (i.e., read-only) set of
packages from which each user should then be able to add a subset to
local filesystem views. However, the current extension mechanism only
allows for one set of extensions to be activated for any given extendee.
One way to circumvent this issue is to use a module system, but this way
environment variables tend to get very long after a short while.
Furthermore, since an error is raised when two files have the same
target in the view, the user can immediately spot when multiple specs
from one package are loaded (an error that sill creeps up from time to
time).
In order to support multiple (and maybe mutually exclusive)
configurations of extensions for a given extendee, the following changes
were made. The aim is to silently activate any specified extensions when
adding them to the filesystem view; no actions from the user required.
TL;DR: Adding python packages built with py-setuptools to fileystem
views without globally activating them should "just work".
DirectoryLayout/Package: All extension-related command accept apath_viewargument restricting extension operations to the givenfilesystem view.
Package:{do_,}{de,}activate-functions also accept apath_viewargument. If
path_viewisnot None, the necessary extension filesare linked into the view instead of the extendee's install directory.
LinkTree:merge-method now accepts a link argument to allow forboth symlinks and hardlinks.
view: All linking operations have been refactored to useLinkTreesinstead of custom code.
view: Check extensions prior to linking packages, activateextensions in filesystem view afterwards.
view statlink: The--verboseoption now prints theshort_specofpackages linked.
view statlink:statusandremovenow accept--allargument todisplay the status of/remove all packages in the filesystem view.
view statlink: Now usesdisplay_specs-function for prettieroutput.
view remove: Only removes packages specified by user, packagesdepending on those packages and packages that only user-specified
packages depend on.
I am currently working on some meaningful tests (I did not find any for the
viewcommand on its own to start from), but would appreciate some feedback in the meantime.