Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reworking of lapack_shared_libs and similar properties #1682

Merged
merged 8 commits into from
Sep 21, 2016

Conversation

alalazo
Copy link
Member

@alalazo alalazo commented Aug 31, 2016

Fixes #1670

Modifications

  • added class LibraryList + unit tests
  • added convenience functions find_libraries and dedupe
  • modifed non Intel blas/lapack providers to not use setup_dependent_package and define properties instead
  • modified packages using blas_shared_libs and similar functions
  • updated intel-parallel-studio and mkl
  • updated variants in atlas

from llnl.util.filesystem import LibraryList


class LibraryListTest(unittest.TestCase):
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@davydden Can you take a look at this test file and tell me if there are other operations you would like to perform on a list of libraries ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure, will have a look tomorrow...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as far as i can tell,

  • get list of names with defined separator, i.e. foo bar baz
  • get list of directories where libraries are (i.e. /path/to/openblas/lib)
  • complete link flags -L/path/to/dir -lfoo -lbar -lbaz
  • list of full paths with separator, i.e. path/to/libfoo.so path/to/libbar.so

Looks like this should be enough.

@davydden
Copy link
Member

there are two functions in the utilities to_lib_name and to_link_flags, which you could substitute with functionality of the new class.

self.libraries = list(libraries)

@property
def directories(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

donno what's the convention, but maybe one-line doc-string for properties? Especially for things like libnames vs names.

@davydden
Copy link
Member

Looks good!

@alalazo
Copy link
Member Author

alalazo commented Sep 1, 2016

@davydden Thanks for the thorough review! I'll start working on the PR + cleaning and documenting the code.I'll come back to you when all this will be ready for a test-drive 😄

])
options.extend([
'--with-blas={0}'.format(
' '.join(spec['blas'].blas_libs.ld_flags)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before anybody says anything : since ' '.join(ld_flags) seems a common idiom I'll modify the PR to avoid having to write the join in the package 😄

def blas_libs(self):
shared = True if '+shared' in self.spec else False
suffix = dso_suffix if '+shared' in self.spec else 'a'
mkl_integer = ['libmkl_intel_lp64'] if '+ilp64' in self.spec else ['libmkl_intel_ilp64'] # NOQA: ignore=E501
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if ~ilp64

@alalazo alalazo changed the title [WIP] Reworking of lapack_shared_libs and similar properties Reworking of lapack_shared_libs and similar properties Sep 3, 2016
@alalazo
Copy link
Member Author

alalazo commented Sep 3, 2016

@tgamblin @adamjstewart ready to be reviewed

@davydden
Copy link
Member

davydden commented Sep 3, 2016

@alalazo is it rebased on top of develop? I can test it on macOS now.

@alalazo
Copy link
Member Author

alalazo commented Sep 3, 2016

@davydden Yes, it should be

@alalazo
Copy link
Member Author

alalazo commented Sep 21, 2016

Would there be any incentive to add support for include flags in addition to library linker flags?

@adamjstewart I would do it in another PR, as it requires some thinking on the searching logic

@alalazo alalazo force-pushed the features/share_lib_as_iterables branch from 39df558 to be14686 Compare September 21, 2016 14:07
@tgamblin
Copy link
Member

@KineticTheory: if this does not clobber your changes from #1539, I think it's ready to merge. Can you take a look?

@adamjstewart
Copy link
Member

This PR looks pretty good. There's only a couple of things I think it's missing. I would like find_libraries to accept either a list or a string, since most of the time we will be looking for a single library. Right now it only accepts a list or other sequence. I also think we could use quite a bit more documentation on what methods are available and what they return.

@davydden
Copy link
Member

@adamjstewart let's get it merged and then fix minor issues in follow up PRs.

@tgamblin
Copy link
Member

tgamblin commented Sep 21, 2016

NOTE: this is not a request for changes. This is a proposal for how this PR should evolve because I think it's heading in the right direction and it solves a problem that other packages have.

One thing I thought about when looking at this PR: I don't really like it that some dependents still search for their dependencies libraries, though I realize that it can be necessary. Basically there are two guiding principles here:

  1. The dependencies know themselves best, and if they can expose their build information consistently, they should do that and let dependents access it.
  2. Dependents should prefer to ask dependencies for build information (lib locations, etc.) and only search if necessary. Searching complicates build logic and Spack should already know where things are because it already installed the dependencies.

(2) breaks down for external packages, which Spack didn't install and which may even be laid out differently than Spack would lay them out (e.g. a install of boost might have al the weird lib suffixes that boost supports, and I think Spack wouldn't currently know what to do with that). Even better is that find_libraries is hidden behind a property interface so the client code doesn't know how the library names are being assigned.

This PR follows both of these principles for LAPACK and BLAS. Using properties on specs for things like blas_libs and lapack_libs is great. It doesn't follow these for fftw, arpack, superlu, etc. See the install method for armadillo, which does its own find routines.

I think this PR makes it clearer to me what a good convention for passing args among packages in a build should look like. You want to ask the package for a property, and the package needs to know a) the property name and b) the vdep it's being asked as. For non-virtual packages, (b) doesn't matter and is always just the plain package name, but when you're asked to provide the libs/headers for a particular interface, you need to know the interface name.

I guess the question is whether this should be a convention or a more formal interface. The convention currently looks like:

    spec['blas'].blas_libs
    spec['lapack'].lapack_libs

Slightly annoying things about it:

  1. You have to write the interface twice even though you know what you asked for.
  2. The interface name is written once as a string and once as a property name prefix.

So, think we could have a convention like this:

def provider_property(function):
    """Decorator for provider properties that get info from dependencies."""
    def __init__(self, name):
        self.name = name

    def __call__(self, provider):
        """Get provider-specific property if available, or default to generic property."""
        specific = provider.replace('-', '_') + '_' + self.name
        dep = spec[provider]
        if hasattr(dep, specific):
            return getattr(dep, specific)
        else:
            return getattr(dep, self.name)  # default to generic attribute


class Spec(object):
    ...
    libs = provider_property('libs')
    includes = provider_property('includes')

Now the interface looks like this:

    spec.libs('blas')
    spec.libs('lapack')

And you've got a convention for properties to add to packages (libs, <provider>_libs, etc.). Package could provide a default implementation:

class Package(object):
    ...
    def libs(self):
        # requiring installation allows us to be more generic b/c we can search,
        # but it does restrict when you can call the method.
        assert(self.installed)    

        return find_libraries(... something that returns all the libs in `lib` or `lib64`...)

And packages that need to can add things like blas_libs, lapack_libs, superlu_dist_libs, etc. as they do in this PR. Packages like arpack and superlu can do nothing b/c we find their libs automatically and they're available through, e.g. spec.libs('arpack').

I guess the question is whether the interface above is so much better than the simple convention we've got that it's worthwhile. The current convention is not so bad. I think it boils down which is simpler to document and teach to package authors.

The other question is whether this convention is worthwhile for non-virtuals. Sometimes the client needs to be specific about which lib names it needs, in which case find_libraries seems like the right thing to do.

What do you all think?

@tgamblin tgamblin merged commit d848559 into spack:develop Sep 21, 2016
@davydden
Copy link
Member

  1. You have to write the interface twice even though you know what you asked for.

one have to do that because a single package can provide different virtual packages (e.g. mkl provides blas and lapack and scalapack). Would the convention you suggested work here as well?

The other question is whether this convention is worthwhile for non-virtuals. Sometimes the client needs to be specific about which lib names it needs, in which case find_libraries seems like the right thing to do.

i would say for most packages the library names are simple (libarpack, libsuperlu_dist, etc) and for simple configure systems -L%s -larpack' % spec['arpac'].prefix.lib will do. For complicated cases like trilinos and oce, which ave tens (!) of libraries, dependents (i.e. dealii) know which libs to pick up from a prefix. So using this class is an overkill and hard to maintain as the list of libraries may change between versions.

So i would not adopt this approach to every package, but only to a few virtuals (fftw is probably the only one missing).

What should be systematically addressed is lib folder. I don't have a good overview, but i guess libraires may end up both in prefix/lib and prefix/lib64 on 64bit systems. In some packages %s/lib is hardcoded, in others it is used as spec[package].prefix.lib, but I am not sure that each package somehow checks whether or not things are installed in lib or lib64. If that's the case already, then spec[package].prefix.lib should be used throughout.

@davydden
Copy link
Member

@tgamblin p.s. maybe you copy-paste your comment into a separate issue to continue the discussion?

@adamjstewart
Copy link
Member

@alalazo @tgamblin I just updated to the latest develop and now I'm seeing infinite error messages:

Exception RuntimeError: 'maximum recursion depth exceeded while calling a Python object' in <type 'exceptions.AttributeError'> ignored

If I checkout the commit before this PR was merged, everything works fine. There must be some infinite recursion going on here.

@adamjstewart
Copy link
Member

To reproduce the bug, just try installing any package. Even ones that don't depend on blas/lapack/etc. Maybe a getattribute problem?

# This line is to avoid infinite recursion in case package is
# not present among self attributes
package = super(Spec, self).__getattribute__('package')
return getattr(package, item)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've confirmed that these changes caused the bug I'm seeing. When I comment this function out, Spack works again. What was this added for?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Today I succeeded installing cp2k before pushing changes and right now I correctly installed hdf5 on a fresh develop checkout... sorry I can't reproduce the problem...

The two lines are needed to forward requests for non-existing attributes from Spec to Package. It's what makes :

blas = spec['blas'].blas_libs

possible instead of :

blas = spec['blas'].package.blas_libs

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, this is weird. Spack works on my laptop but not on our cluster. Going to investigate further.

Copy link
Member Author

@alalazo alalazo Sep 21, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ouch... could reproduce with python@2.6. I wonder why Travis never fired up a warning about that

EDIT : Travis seems to be running with 2.6.9 and no issues. Python 2.6.6 instead shows the problem.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On my cluster, Spack works with Python 2.7 but crashes with the system Python 2.6 Can you try 2.6 on your end to see if you can reproduce the problem?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'm running Python 2.6.6 too.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alalazo: if you're working on a fix I'll merge it asap, otherwise I'll work on it.

Copy link
Member

@tgamblin tgamblin Sep 21, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EDIT : Travis seems to be running with 2.6.9 and no issues. Python 2.6.6 instead shows the problem.

If only we could just drop 2.6. Sigh.

Copy link
Member Author

@alalazo alalazo Sep 21, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tgamblin Sorry for the trouble. I couldn't find a solution that works for both python versions right now. If you couldn't solve it right away, feel free to revert the merge and I'll work it out without impacting everyone...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tgamblin I think #1823 will solve this issue.

suffix = dso_suffix if '+shared' in self.spec else 'a'
mkl_integer = ['libmkl_intel_ilp64'] if '+ilp64' in self.spec else ['libmkl_intel_lp64'] # NOQA: ignore=E501
mkl_threading = ['libmkl_sequential']
if '+openmp' in spec:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alalazo : this should be self.spec ?

 if '+openmp' in spec:

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pramodk True! Feel free to submit a PR to fix this. Otherwise, if you prefer, I'll take care of that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am testing now Atlas and MKL and i will create a PR today, need to fix a few extra minor things in MKL.

choice of ``Blas`` implementation, each package which provides it
sets up ``self.spec.blas_shared_lib`` and ``self.spec.blas_static_lib`` to
point to the shared and static ``Blas`` libraries, respectively. The same
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blas-->BLAS

choice of ``Blas`` implementation, each package which provides it
sets up ``self.spec.blas_shared_lib`` and ``self.spec.blas_static_lib`` to
point to the shared and static ``Blas`` libraries, respectively. The same
applies to packages which provide ``Lapack``. Package developers are advised to
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lapack-->LAPACK

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants