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

spec : interface for build information #1875

Merged
merged 16 commits into from
Mar 2, 2017

Conversation

alalazo
Copy link
Member

@alalazo alalazo commented Sep 29, 2016

TLDR

This PR provides a general mechanism to forward queries from Spec to Packages using the descriptor ForwardQueryToPackage. It implements most of the ideas discussed in #1821 (see details below), including a default behavior for the methods spec.libs and spec.cppflags.

To showcase an example some of the packages in the dag:

$ spack spec -Il pexsi
Input spec
--------------------------------
     fse2tj5  pexsi

Normalized
--------------------------------
     ge7amsh  pexsi
     4av5qrp      ^parmetis
     wji7giq          ^cmake@2.8:
     qfnm56h          ^metis@5:
     2xnea7r          ^mpi

Concretized
--------------------------------
[+]  m6mbmik  pexsi@0.9.0%gcc@4.8 arch=linux-ubuntu14-x86_64
[+]  ua5fwf7      ^parmetis@4.0.3%gcc@4.8~debug~gdb+shared arch=linux-ubuntu14-x86_64
[+]  t5oqkdp          ^cmake@3.6.1%gcc@4.8~doc+ncurses+openssl+ownlibs~qt arch=linux-ubuntu14-x86_64
[+]  qwcc7xb              ^ncurses@6.0%gcc@4.8 arch=linux-ubuntu14-x86_64
[-]  23ldpkv              ^openssl@system%gcc@4.8 arch=linux-ubuntu14-x86_64
[+]  sj5ayy3          ^metis@5.1.0%gcc@4.8~debug~gdb~idx64~real64+shared arch=linux-ubuntu14-x86_64
[+]  u4ku7al          ^openmpi@2.0.1%gcc@4.8~mxm~pmi~psm~psm2~slurm~sqlite3~thread_multiple~tm~verbs+vt arch=linux-ubuntu14-x86_64
[+]  cbteeox              ^hwloc@1.11.4%gcc@4.8 arch=linux-ubuntu14-x86_64
[+]  6ryt5eq                  ^libpciaccess@0.13.4%gcc@4.8 arch=linux-ubuntu14-x86_64
[+]  2ffyrzb                      ^libtool@2.4.6%gcc@4.8 arch=linux-ubuntu14-x86_64
[+]  zfagq4d                          ^m4@1.4.17%gcc@4.8+sigsegv arch=linux-ubuntu14-x86_64
[+]  m3xfbqd                              ^libsigsegv@2.10%gcc@4.8 arch=linux-ubuntu14-x86_64
[+]  4swdmfk                      ^pkg-config@0.29.1%gcc@4.8+internal_glib arch=linux-ubuntu14-x86_64
[+]  bunu7o5                      ^util-macros@1.19.0%gcc@4.8 arch=linux-ubuntu14-x86_64
[+]  yhtbrhi      ^superlu-dist@3.3%gcc@4.8 arch=linux-ubuntu14-x86_64
[+]  idsxmtv          ^openblas@0.2.19%gcc@4.8+fpic~openmp+shared arch=linux-ubuntu14-x86_64

have been reworked to make use of the new features.

Analysis and requirements

There are a number of things that emerged from #1682 and #1821 concerning build information, that I'll try to recap below for ease of reference :

  1. packages know themselves better than anybody else and thus they should handle requests that are based on their state
  2. the syntax in Reworking of lapack_shared_libs and similar properties #1682 is needlessly redundant (e.g. spec['blas'].blas_libs mentions blas twice)
  3. only blas and lapack providers are taken care of properly, while normal packages are not
  4. use of __getattr__ to intercept calls to non-existing attributes in Spec should be avoided
  5. if we add a mechanism to query for build information, then it should be possible to opt-out of it (see case for dealii and trilinos)

On top of that I would add a couple more points :

  1. sometimes packages provide more than one service (API, functionality, etc.) and the few needed are only known from the calling scope (e.g. openmpi installs cxx, c and fortran APIs and a particular package may need to link with cxx)
  2. the actions required to compute build properties are the same for many simple and well-behaved packages (e.g. spec['name'].libs will require very often to search for lib{name}.{suffix} somewhere in prefix)
Design

I grabbed the basic idea that @tgamblin proposed in #1821 and expanded it further. What I didn't like there was just the syntax it would have imposed :

blas = spec['blas']  # to retrieve the spec providing blas we use __getitem__
blas_libs = spec.libs('blas') # ... but to ask for properties of 'blas' we use function calls
...

so I tried to achieve something like :

blas_libs = spec['blas'].libs # ask the blas provider for list of libraries
mpi_cxx_libs = spec['mpi:cxx'].libs # ask the mpi provider just for the libraries needed for the `cxx` API
mpi_cxx_and_fortran_libs = spec['mpi:cxx,fortran'].libs # ask the mpi provider `cxx` and fortran libs
metis_libs = spec['metis'].libs # I want to be able to do the same thing with a non-virtual package
...
# To opt-out of default behavor
class Trilinos(Package):
    libs = None

And have readable tracebacks in case of failures :

# Here I made openblas fail on purpose
Traceback (most recent call last):
  File "/home/mculpo/PycharmProjects/spack/lib/spack/spack/build_environment.py", line 511, in fork
    function()
  File "/home/mculpo/PycharmProjects/spack/lib/spack/spack/package.py", line 983, in build_process
    self.install(self.spec, self.prefix)
  File "/home/mculpo/PycharmProjects/spack/var/spack/repos/builtin/packages/pexsi/package.py", line 70, in install
    '@LAPACK_LIBS': self.spec['lapack'].libs.joined(),
  File "/home/mculpo/PycharmProjects/spack/lib/spack/spack/spec.py", line 650, in __get__
    raise AttributeError(message)
AttributeError: 'openblas' package has no relevant attribute 'libs'
    spec : 'openblas@0.2.19%gcc@6.2.0+fpic~openmp+shared arch=linux-Ubuntu14-x86_64'
    queried as : 'lapack'
    extra parameters : '[]'

The whole design is based on a few of simple concepts :

  • a Spec instance has now an internal state to keep track of 'queries' from client code, and a few attributes to retrieve, set or clear the query state
  • a descriptor takes care of forwarding queries from Spec to Package and uses a chain of responsibility (documented in the code) to serve them
  • Spec.__getitem__ automatically set a new query state in the required spec. If the spec being queried is virtual __getitem__ returns a copy instead of a reference to deal with the case of packages that provide more than one service.
Modifications
  • calls forwarded from Spec to Package are now explicit
  • added descriptor within Spec to manage forwarding
  • added state in Spec to maintain query information
  • modified a few packages (the one involved in spack install pexsi) to showcase changes
  • added unit tests for the new logic in __getitem__
  • added documentation on cppflags and libs

@alalazo
Copy link
Member Author

alalazo commented Sep 29, 2016

@tgamblin @davydden @adamjstewart @scheibelp @becker33 If you have time I would be interested in knowing what you think about this PR

Copy link
Member

@davydden davydden left a comment

Choose a reason for hiding this comment

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

Regarding the opt-out, probably depends on how many packages actually can implement this. Probably keeping it on by default is ok.

One more thing you may consider is extra link flags. blas/lapack compiled with openmp need to add extra flags self.compiler.openmp_flag. So everyone who use blas/lapack should be able to use those, see https://github.com/LLNL/spack/blob/develop/var/spack/repos/builtin/packages/openblas/package.py#L128-L129 . I don't think you want to mix them with cppflags.

Also we need to agree on naming, cppflags return a string, whereas libs return an array/list or alike. This is not clear from names. One way would be to return a string for singulars (cppflag or ld_flag) and arrays/lists for plurals (libs, dirs).

shared = True if '+shared' in self.spec else False
return find_libraries(
['libopenblas'], root=self.prefix, shared=shared, recurse=True
)

@property
def lapack_libs(self):
Copy link
Member

@davydden davydden Sep 29, 2016

Choose a reason for hiding this comment

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

your current design appears to be limited in case of a single provider supplying several things, like mkl does for blas, lapack and scalapack. In this case, the libs for the last one (scalapack) are certainly different from those used in blas and lapack. In other words
spec['blas'].libs, spec['lapack'].libs and spec['scalapack'].libs could all be provided by the same package and potentially could all be different. Note that it's different from what you do with a query parameter.

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 Limited in which sense ? Suppose mkl is used, then if you code :

l = spec['scalapack'].libs

this will in order try to grab / call:

Mkl.scalapack_libs
Mkl.libs
_libs_default_handler

If you need specialization you can code :

class Mkl(Package):
   @property
   def scalapack_libs(self):
      ...

   @property
   def lapack_libs(self):
      ...

and take appropriate measures based on what you were asked.

There's a little caveat in that I didn't take care so far to return copied specs on __getitem__ call, so that things like :

a = spec['blas']
b = spec['lapack']
...

don't interfere with each other if they are provided by the same package. That is a detail I'll work on later if this syntax / machinery looks good enough...

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 To add more on that : openblas is fairly well-behaved, so with this implementation you can even remove Openblas.libs and have the code still working. The default handler will do just the right thing...

Copy link
Member

Choose a reason for hiding this comment

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

oh, i see, thanks for clarifying. Then this

Mkl.scalapack_libs
Mkl.libs
_libs_default_handler

should be perfectly fine indeed.

@alalazo
Copy link
Member Author

alalazo commented Sep 29, 2016

Also we need to agree on naming, cppflags return a string, whereas libs return an array/list or alike. This is not clear from names.

This is definitely one of the thing we should discuss carefully iff the general idea is good enough. I already don't agree with singulars vs. plurals but let's save this fight for later in case 😄

@@ -537,9 +538,120 @@ def __str__(self):
["^" + self[name].format() for name in sorted(self.keys())])


def _libs_default_handler(descriptor, spec, cls):
"""Default handler when looking for 'libs' attribute
Copy link
Member

Choose a reason for hiding this comment

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

i see now what you mean about openblas being simple. 👍

@davydden
Copy link
Member

davydden commented Sep 29, 2016

For partial opt-out one could do

 @property
 def libs(self):
    // throw an error here

This would prevent anyone from using [package].libs, but would still allow to use cppflags, for example.

@@ -46,24 +46,25 @@ class SuperluDist(Package):
depends_on('metis@5:')

def install(self, spec, prefix):
lapack_blas = spec['lapack'].lapack_libs + spec['blas'].blas_libs
lapack_blas = spec['lapack'].libs + spec['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.

neat!

@alalazo
Copy link
Member Author

alalazo commented Sep 29, 2016

@davydden

class MyPackage(Package):
    libs = None
    ...

will have the same effect : retrieving MyPackage.libs will return a value (None). Being false-ish this will raise the AttributeError in the description

@alalazo alalazo force-pushed the features/libs_for_non_virtual branch from c7b5574 to 3ef07a6 Compare October 5, 2016 08:50
@alalazo alalazo changed the title [WIP] Spec interface for build information : first tentative implementation Spec interface for build information : first tentative implementation Oct 13, 2016
@alalazo alalazo force-pushed the features/libs_for_non_virtual branch from 3ef07a6 to 2e7f958 Compare October 15, 2016 13:42
@davydden
Copy link
Member

davydden commented Nov 8, 2016

Came across another reason why this PR is good to have: on OpenSuse tcl installs libraries into lib64, whereas dependents like environment-modules expect it in lib. So if each packages would know everything about itself, we could remove hardcoded prefix/lib paths to make Spack more robust.

@alalazo
Copy link
Member Author

alalazo commented Nov 25, 2016

@tgamblin I've noticed this entered the project v0.10 in the Nice to have column. Do you want me to address this next ? I don't think there's much work to be done here to make it ready for a first review...

@davydden
Copy link
Member

@tgamblin @alalazo : it was me who added it to Nice to have, feel free to remove it if you find it more appropriate.

@alalazo alalazo force-pushed the features/libs_for_non_virtual branch from 8c47aee to 09f7405 Compare November 28, 2016 15:07
@alalazo alalazo mentioned this pull request Nov 29, 2016
4 tasks
@alalazo alalazo changed the title Spec interface for build information : first tentative implementation spec : interface for build information Nov 30, 2016
@alalazo alalazo added ready and removed WIP labels Nov 30, 2016
@alalazo
Copy link
Member Author

alalazo commented Nov 30, 2016

@tgamblin @adamjstewart @becker33 @scheibelp I think this is ready for review. I edited the description above to have all the information I can remember right now.

I think the method you would like to pay the most attention when reviewing is Spec.__getitem__, which sets the query state for the package. Well, fire questions / comments at will 😄

'@BLAS_LIBS': self.spec['lapack'].blas_libs.joined(),
'@LAPACK_LIBS': self.spec['lapack'].libs.joined(),
'@BLAS_LIBS': self.spec['blas'].libs.joined(),
# FIXME : what to do with compiler provided libraries ?
'@STDCXX_LIB': ' '.join(self.compiler.stdcxx_libs)
Copy link
Member

Choose a reason for hiding this comment

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

What to do with compiler provided libraries?

Not sure, but it definitely needs some work. For example, we just added self.compiler.pic_flag because compilers like NAG use -PIC instead of -fPIC. The problem is that if I mix compilers (GCC + NAG), the flag isn't right for each compiler.

Copy link
Member Author

Choose a reason for hiding this comment

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

I agree and the FIXME is there because I think compiler flags deserve a PR on their own. Especially if the effort to turn them into some sort of dependencies already started.

msg += ' At most one is admitted.'
raise KeyError(msg)

name, query_parameters = query_parameters[0], query_parameters[1:]
Copy link
Member

Choose a reason for hiding this comment

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

It looks like query_parameters[1:] could just be query_parameters[1]

And actually since this must be of length two, you can just do:

name, query_parameters = query_parameters

Copy link
Member Author

Choose a reason for hiding this comment

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

@scheibelp The most frequent case is the one with zero query parameters:

>>> l = [1]
>>> a, b = l[0], l[1:]
>>> print(a, b)
(1, [])

Copy link
Member

Choose a reason for hiding this comment

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

Got it - thanks!

@scheibelp
Copy link
Member

scheibelp commented Nov 30, 2016

At a high level, I'm curious why this approach is preferable to e.g. keeping the getattr syntax and then for example changing Openmpi.libs to be a function that accepts arguments like 'cxx'. I suppose that forces users to say something like spec['openmpi'].libs() instead of spec['openmpi'].libs although IMO that's not a big problem (and perhaps providing an object which overwrites iter and call would get around that).

I'm curious because in my opinion this approach adds complexity relative to sticking with an override of getattr. For example stuff like:

if is_virtual and self.concrete:
    # deepcopy above doesn't treat this correctly
    value.package.spec = value

makes me cautious. Perhaps I'm overlooking other major benefits that the getattr approach lacks.

@alalazo alalazo force-pushed the features/libs_for_non_virtual branch from 3b5282c to 605aeb4 Compare March 2, 2017 08:10
@alalazo
Copy link
Member Author

alalazo commented Mar 2, 2017

ping

@tgamblin tgamblin merged commit ed582ce into spack:develop Mar 2, 2017
@tgamblin
Copy link
Member

tgamblin commented Mar 2, 2017

@alalazo: I think there is still some refactoring that can be done here w.r.t. consolidating the handlers, ForwardQueryToPackage, and SpecWrapper, but we can do that in another PR. Right now it's a little messy how they're all related.

I want to get the feature in, though.

Can you add docs for users in another PR? The docs should probably just talk about the conventions (add a method to your package so you can get at it via spec) and not the mechanism -- I think the mechanism is going to confuse packagers.

@alalazo
Copy link
Member Author

alalazo commented Mar 2, 2017

@tgamblin Will do. Do you have any preference where to put the docs?

@alalazo alalazo deleted the features/libs_for_non_virtual branch March 2, 2017 18:08
@tgamblin
Copy link
Member

tgamblin commented Mar 2, 2017

@alalazo: probably should be a section in the packaging guide; maybe it could be explained along with setup_dependent_package and friends.

bvanessen added a commit to bvanessen/spack that referenced this pull request Mar 8, 2017
diaena pushed a commit to diaena/spack that referenced this pull request May 26, 2017
- Added a new interface for Specs to pass build information
  - Calls forwarded from Spec to Package are now explicit
  - Added descriptor within Spec to manage forwarding
  - Added state in Spec to maintain query information
  - Modified a few packages (the one involved in spack install pexsi) to showcase changes

- This uses an object wrapper to `spec` to implement the `libs` sub-calls.
  - wrapper is returned from `__getitem__` only if spec is concrete
  - allows packagers to access build information easily
diaena pushed a commit to diaena/spack that referenced this pull request May 26, 2017
xavierandrade pushed a commit to xavierandrade/spack that referenced this pull request Jun 16, 2017
- Added a new interface for Specs to pass build information
  - Calls forwarded from Spec to Package are now explicit
  - Added descriptor within Spec to manage forwarding
  - Added state in Spec to maintain query information
  - Modified a few packages (the one involved in spack install pexsi) to showcase changes

- This uses an object wrapper to `spec` to implement the `libs` sub-calls.
  - wrapper is returned from `__getitem__` only if spec is concrete
  - allows packagers to access build information easily
xavierandrade pushed a commit to xavierandrade/spack that referenced this pull request Jun 16, 2017
EmreAtes pushed a commit to EmreAtes/spack that referenced this pull request Jul 10, 2017
- Added a new interface for Specs to pass build information
  - Calls forwarded from Spec to Package are now explicit
  - Added descriptor within Spec to manage forwarding
  - Added state in Spec to maintain query information
  - Modified a few packages (the one involved in spack install pexsi) to showcase changes

- This uses an object wrapper to `spec` to implement the `libs` sub-calls.
  - wrapper is returned from `__getitem__` only if spec is concrete
  - allows packagers to access build information easily
EmreAtes pushed a commit to EmreAtes/spack that referenced this pull request Jul 10, 2017
amklinv pushed a commit that referenced this pull request Jul 17, 2017
- Added a new interface for Specs to pass build information
  - Calls forwarded from Spec to Package are now explicit
  - Added descriptor within Spec to manage forwarding
  - Added state in Spec to maintain query information
  - Modified a few packages (the one involved in spack install pexsi) to showcase changes

- This uses an object wrapper to `spec` to implement the `libs` sub-calls.
  - wrapper is returned from `__getitem__` only if spec is concrete
  - allows packagers to access build information easily
amklinv pushed a commit that referenced this pull request Jul 17, 2017
healther pushed a commit to electronicvisions/spack that referenced this pull request Jul 26, 2017
- Added a new interface for Specs to pass build information
  - Calls forwarded from Spec to Package are now explicit
  - Added descriptor within Spec to manage forwarding
  - Added state in Spec to maintain query information
  - Modified a few packages (the one involved in spack install pexsi) to showcase changes

- This uses an object wrapper to `spec` to implement the `libs` sub-calls.
  - wrapper is returned from `__getitem__` only if spec is concrete
  - allows packagers to access build information easily
healther pushed a commit to electronicvisions/spack that referenced this pull request Jul 26, 2017
@tgamblin tgamblin added this to the v0.11.0 milestone Nov 12, 2017
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

5 participants