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

do_install : allow for an arbitrary number of phases #1186

Merged
merged 50 commits into from
Oct 25, 2016

Conversation

alalazo
Copy link
Member

@alalazo alalazo commented Jul 7, 2016

TLDR : have a look at the <name>/package.py files to have an idea of what this PR does 😄

Description

This PR allows sub-classes of PackageBase (ex Package) to have an arbitrary number of phases.

It also add a convenient syntax to register arbitrary methods as preconditions or sanity_checks, meaning that they will be run before or after the phases they are bound to.

In the end it should give more flexibility for further development of features like the ones discussed in #169 or #1305 and reduce the boiler-plate code needed in many packages.

A few packages have been converted to exemplify some of the benefits of this refactoring.

Modifications
  • Package has been renamed PackageBase, it has logic to be extended by subclasses that are tailored to specific build systems
  • decorator syntax to register preconditions and sanity checks to a given phase
  • added Package : simple subclass having only an install phase (backward-compatible)
  • added EditableMakefile : examples in astyle/package.py
  • added AutotoolsPackage : examples in gmp/package.py and others
  • added CMakePackage : examples in openjpeg/package.py and others
  • log_output has been turned into a "double" context manager : the external context spawns a daemon that wait for things to write to a file, the internal plug stderr and stdout to that file
  • all the changes that have been extracted in refactoring : removed explicits os.fork(), exceptions are propagated from child process to parent #1228
  • unit tests for the new feature
  • brief documentation of the new subclasses

@alalazo
Copy link
Member Author

alalazo commented Jul 7, 2016

@citibeth I'll probably need to ask you an example of use for ibmisc, as it is the only package currently using CMakePackage.

# # Create a dummy file so the build doesn't fail.
# # That way, the module file will also be created.
# with open(os.path.join(prefix, 'dummy'), 'w') as fout:
# pass
Copy link
Member Author

Choose a reason for hiding this comment

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

This won't be needed anymore, as the logic in Package is generic enough to deal with CMakePackage directly

Copy link
Member

Choose a reason for hiding this comment

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

Sounds good to me.
On Jul 7, 2016 6:35 AM, "Massimiliano Culpo" notifications@github.com
wrote:

In lib/spack/spack/package.py:

+# if 'setup' in self._phases:
+# self.install_setup()
+#
+# if 'configure' in self._phases:
+# self.install_configure()
+#
+# if 'build' in self._phases:
+# self.install_build()
+#
+# if 'install' in self._phases:
+# self.install_install()
+# else:
+# # Create a dummy file so the build doesn't fail.
+# # That way, the module file will also be created.
+# with open(os.path.join(prefix, 'dummy'), 'w') as fout:
+# pass

This won't be needed anymore, as the logic in Package is generic enough
to deal with CMakePackage directly


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/LLNL/spack/pull/1186/files/aa3c58885b33e6e7f81e6ea95c72b091a792ea25#r69885174,
or mute the thread
https://github.com/notifications/unsubscribe/AB1cd-1cgfmbeB1wryd963HE75cyqDjxks5qTNZXgaJpZM4JG8iX
.

@citibeth
Copy link
Member

citibeth commented Jul 7, 2016

You mean how to use ibmisc once it is built?
On Jul 7, 2016 6:33 AM, "Massimiliano Culpo" notifications@github.com
wrote:

@citibeth https://github.com/citibeth I'll probably need to ask you an
example of use for ibmisc, as it is the only package currently using
CMakePackage.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#1186 (comment), or mute
the thread
https://github.com/notifications/unsubscribe/AB1cd-_2Okl5KQ0o_nOZwSuNzU3pUPVhks5qTNXWgaJpZM4JG8iX
.

@citibeth
Copy link
Member

citibeth commented Jul 7, 2016

See #1189 for samples that use CMakePackage.

@alalazo
Copy link
Member Author

alalazo commented Jul 7, 2016

@citibeth Thanks! I was more looking for how to use spack setup with a CMakePackage package, but I see from #543 that you already documented the use. I'll try to read from there. Thanks for the pointer to #1189

@alalazo
Copy link
Member Author

alalazo commented Jul 8, 2016

@adamjstewart I'll try to reply here to your comments in #1169

I took a look at #1186. I know arbitrary install phases are doable, but can you give me good use cases?

The good use case is just what has been already started as a discussion : having specialized classes for different build-systems. The only point I would like to make is that, if we want to go that direction, it is better to have a base class that knows how to be extended for different workflows, test it the best we can, and then leave it alone for the longest time possible (as per the open-closed principle).

Setting a base class that HAS an abstract workflow which may fit EVERY specialization we'll add in the future is instead very likely to end up in a Blob in the long term and will require tweaking the base class again if we need to add yet another step for a case that we didn't take into account today.

Basically, for me it boils down to general maintainability and design considerations.


# This will be used as a registration decorator in user
# packages, if need be
PackageBase.sanity_check('install')(PackageBase.sanity_check_prefix)
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 @citibeth @adamjstewart @davydden @nrichart Here's a first stub at how a specialized class may look like right now :

  • phases are described with a list of strings.
  • default implementations may (but need not to) be provided for every phase
  • methods can be registered at package definition time as either precondition or sanity_check for any number of phases

Comments ?

Copy link
Member

Choose a reason for hiding this comment

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

I still do not understand why this is useful, or how the user would control
things if phases could be named anything and differ between packages.
On Jul 8, 2016 9:58 AM, "Massimiliano Culpo" notifications@github.com
wrote:

In lib/spack/spack/package.py:

  • def configure_args(self):
  •    return list()
    
  • def configure(self, spec, prefix):
  •    options = ['--prefix={0}'.format(prefix)] + self.configure_args()
    
  •    inspect.getmodule(self).configure(*options)
    
  • def build(self, spec, prefix):
  •    inspect.getmodule(self).make()
    
  • def install(self, spec, prefix):
  •    inspect.getmodule(self).make('install')
    
  • This will be used as a registration decorator in user

  • packages, if need be

  • PackageBase.sanity_check('install')(PackageBase.sanity_check_prefix)

@tgamblin https://github.com/tgamblin @citibeth
https://github.com/citibeth @adamjstewart
https://github.com/adamjstewart @davydden https://github.com/davydden
@nrichart https://github.com/nrichart Here's a first stub at how a
specialized class may look like right now :

  • phases are described with a list of strings.
  • default implementations may (but need not to) be provided for every
    phase
  • methods can be registered at package definition time as either
    precondition or sanity_check for any number of phases

Comments ?


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/LLNL/spack/pull/1186/files/fa2783b9dc8c13e675a9b6791b240cdca61e4307#r70077657,
or mute the thread
https://github.com/notifications/unsubscribe/AB1cdy593mJztaO0eUhCWChaymSIb9Ihks5qTld_gaJpZM4JG8iX
.

Copy link
Member Author

Choose a reason for hiding this comment

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

@citibeth A package knows it's phases, so that they could be added to spack info. Adding logic to stop at a particular phase of the installation doesn't require every package to follow the same workflow.

Copy link
Member

Choose a reason for hiding this comment

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

@citibeth The way I understand it, PackageBase is like an abstract base class that allows arbitrary phases. Subclasses like AutotoolsPackage or PythonPackage define their own phases based on what is important for that particular build system. A user-defined software package can then extend AutotoolsPackage without having to define their own arbitrary phases. Of course, once in a while a package may want to add some phases, but most of the time the phases coming from AutotoolsPackage should be sufficient. At least that's the goal.

@adamjstewart
Copy link
Member

@alalazo You've convinced me! Now that I understand what you're trying to do, I like the idea of making an easily extendable abstract base class (Package), with other build-system specific classes extending it. That way we an encapsulate things like Python-specific RPATH handling into a PythonPackage, and CMake stuff into a CmakePackage. In a way this is just further integration of @citibeth's CmakePackage so that it becomes the default way to build Cmake packages.

Although it would break backwards compatibility a bit, this could be used to simplify a lot of the code, so that std_cmake_args or cmake() would only be available in packages that extend CmakePackage. We might want backwards compatibility breaking stuff to move to a second PR, so that we can get familiar with the new structure (cmake builds have to extend CmakePackage, etc), but I think this is a much better way of organizing things.

I'm a little swamped with software install requests at the moment (we're decommissioning a machine to make way for a new one), so I won't be able to help out with the actual coding too much, but once things are merged and tested properly, I can make the necessary changes to spack create so that Python build systems automatically extend PythonPackage, etc. I think that will reduce some of the complexity for new users just trying to add a package and get it working.

@nrichart
Copy link
Contributor

nrichart commented Jul 8, 2016

In fact with the class Package the inherit from PackageBase and defines only the phase install it should not break backward compatibility.

Just that spack create can take advantage of this by creating empty packages inheriting from CMakePackage or AutotoolPacckage.
Which was already the case with the PR of @citibeth, but with this PR the phases are not hardcoded so no useless code in *Packages that do not uses some particular phases.

@adamjstewart
Copy link
Member

@nrichart I know the current design doesn't break backwards compatibility. I'm suggesting that once this PR is merged and everyone becomes familiar with it, we start another PR to purposefully break backwards compatibility in order to simplify the code base. For example, only setup std_cmake_args for packages that extend CmakePackage. Obviously we will need to go through the packages currently in Spack and change them to extend CmakePackage instead of Package, but that shouldn't be difficult.

@alalazo Is the plan that most build systems will get their own class and any package that doesn't fit into a mold should extend Package or BasePackage? Either way, this should be easy for me to integrate into spack create because we already have the idea of an unknown build system.

from spack.stage import Stage, ResourceStage, StageComposite
from spack.util.compression import allowed_archive
from spack.util.environment import dump_environment
from spack.util.executable import ProcessError, Executable, which
from spack.util.executable import ProcessError, which
Copy link
Member

Choose a reason for hiding this comment

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

I'm trying to remember whether or not Executable was important... The last time I removed something like this due to Flake8, it resulted in #1095. If a user runs Executable in their package, will they still be able to find it if you remove it from here?

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks for the tip, I'll have a closer look at this

@adamjstewart
Copy link
Member

I like this design a lot. I think the example package changes for Szip and SwitftSim do a good job of outlining the advantage of this design. Another good example to include would be a lot of purely GNU packages like Gmp, which if it extended AutotoolsPackage would no longer need an install() at all! By my count, over 100 of the package currently in Spack are simple enough that they wouldn't need an install method at all anymore.

Another useful class might be LicensedPackage for licensed software.



class AutotoolsPackage(PackageBase):
phases = ['autoreconf', 'configure', 'build', 'install', 'log']
Copy link
Member

Choose a reason for hiding this comment

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

maybe a short description of phases? e.g. what's log 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.

@davydden log will very likely disappear from the explicit list (it's a function that copies spack logs in the installation folder). But I agree on the idea : we should be able to write short descriptions of the stages for later use

Copy link
Member

Choose a reason for hiding this comment

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

log == provenance? I think it's the part that copies build.out, build.env, spec.yaml, etc to prefix/.spack.

@alalazo
Copy link
Member Author

alalazo commented Jul 10, 2016

Because in easybuild, sanity checks fail all the time even though the
install was successful. Big royal pita. Especially on mac. Why should I
fix a zillion broken sanity checks if they never catch much anyway?

Maybe I was not clear on this. The code right now does EXACTLY what was done before in terms of sanity checks. Except that instead of having them in do_install under a conditional :

if 'install' in phases:
    do_something() # do_install must know that 'install' may be among the phases

they are attached to the phase they are related to. It's only a more modular design, not a change in behavior. Where do you see "a zillion broken sanity checks" ? The fact that EasyBuild may be broken on sanity checks is irrelevant here, right ?

Taat doesn't answer my question. The spack setup command looks for a stage
named 'configure'. If people can name stages whatever they like, then
spack setup will not work on them. I think we need a standardized set of
phase names so commands like 'install', 'setup', etc. can do their thing.

I still need to take a look at spack setup, but I don't see the problem you are raising as a big deal at the moment. As far as I can tell right now everything could be solved resorting to the FTSE :

"We can solve any problem by introducing an extra level of indirection"

We may have a dictionary in the setup command that maps to the right phase for any specialized class, or let specialized classes provide a member that return this information. It will be as simple as substituting a literal string with a variable name.

Whether we implement those names with subclassing and a million stub
methods, or with dynamic introspection, is an implementation detail. I
vote for the former because it is standard OO technique.

We don't need dynamic introspection or inheritance to select phase names in setup, see above.

Modifications :
- added EditableMakefile to PackageBase subclasses
- astyle modified as an example
- preliminary hook to stop at a certain phase of install
The mechanism would be simpler if we could leverage exceptions to raise signals. Unfortunately forking does not permit to do so.
Now uses a StopIteration exception as a signal
Conflicts:
	lib/spack/spack/package.py
@citibeth
Copy link
Member

Will this change require more iports in packcge files?
On Oct 22, 2016 11:26 AM, "Massimiliano Culpo" notifications@github.com
wrote:

@tgamblin https://github.com/tgamblin If it sounds good to you I'll
create a directory named build_systems at the same level of architectures
and move CMakePackage and friends there, each in its own module. I'll
wait for a green light on this...


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#1186 (comment), or mute
the thread
https://github.com/notifications/unsubscribe-auth/AB1cdy34DO5qHyn0LNOVfocfe95Tk5N9ks5q2iszgaJpZM4JG8iX
.

@citibeth
Copy link
Member

On Sat, Oct 22, 2016 at 10:50 AM, Massimiliano Culpo <
notifications@github.com> wrote:

@adamjstewart https://github.com/adamjstewart To implement a default
behavior I need to understand if it is possible to distinguish in a
reliable way a failure because make check was not there, e.g. :

$ make check
make: *** No rule to make target `check'. Stop.
$ echo $?
2

Yes it is possible... look for something like "no rule to make target...'
in the first line of STDOUT.

@tgamblin
Copy link
Member

Will this change require more iports in packcge files?

No. these packages are included in the spack namespace in __init__.py

@alalazo
Copy link
Member Author

alalazo commented Oct 25, 2016

It seems so. :-) Sorry if I am not that responsive, but I am mostly working
offline since this afternoon and still cannot reach github.

On Fri, Oct 21, 2016 at 5:56 PM, Todd Gamblin notifications@github.com
wrote:

Also thanks for the awesome refactoring!

One last question: I have not had a change to dig into ctrl-C behavior in
Spack, but I assumed it was because ctrl-C was being passed to child
processes and not propagated to the parent. Does this PR fix ctrl-C
issues, since it propagates exceptions back to the main Spack process?


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#1186 (comment), or mute
the thread
https://github.com/notifications/unsubscribe-auth/AEAVHfsHXZagUISv3I86SHV_EKx6YcEGks5q2OClgaJpZM4JG8iX
.

@citibeth
Copy link
Member

From your description, I surmise that 99% of packages don't crash or do
anything bad when make check is run. I would therefore make that the
default.

The only danger would be that we can't wholesale add make check to
existing packages without first checking that they don't crash. So
probably need to add the override to any existing packages being converted
to AutotoolsPackage, and then remove only when we verify it works.

@tgamblin https://github.com/tgamblin I think testing in-between make
and make install should be called check(), at least for the
AutotoolsPackage, as it is almost always make check. As for
post-installation testing, I would be fine with anything.

I agree. As much as I despise Autotools, it seems they've come up with
some good names for phases (configure, check, etc).

@citibeth
Copy link
Member

I thought I remembered some discussion on the issue. Please ignore if the
comment adds nothing.

On Fri, Oct 21, 2016 at 7:55 PM, Todd Gamblin notifications@github.com
wrote:

@citibeth https://github.com/citibeth: slightly confused. where is that
not the case (other than the ctrl-c bug we're trying to fix)?


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#1186 (comment), or mute
the thread
https://github.com/notifications/unsubscribe-auth/AB1cdxj74KTv3JXbHfcV7dnVj6DoTpWpks5q2VDzgaJpZM4JG8iX
.

@alalazo
Copy link
Member Author

alalazo commented Oct 25, 2016

It's nice to see that an e-mail that I sent when github was offline on Friday has been posted today 😄

@adamjstewart
Copy link
Member

@citibeth The way we ended up handling make check was to grep the Makefile for ^check:. If it's found, run it. If not, don't.

@tgamblin
Copy link
Member

Another reason maybe not to add make check to everything (yet): we support architectures like BG/Q, where everything's going to crash b/c it's cross-compiled.

@adamjstewart:I actually think it should just be disabled by default for this reason.

@adamjstewart
Copy link
Member

@tgamblin It is disabled by default. Packages will only run make check if you run spack install --run-tests.

@adamjstewart
Copy link
Member

adamjstewart commented Oct 25, 2016

And yes, if the Makefile contains targets for both check and test it will run both:
https://github.com/LLNL/spack/pull/1186/files#diff-1130a0ab0d86a67ad5e4540d42514823R102

Although maybe this isn't such a good idea. In a lot of packages, test is just another name for check and it will probably run the same thing twice. gmp is an exception in that it has both check and test which are not the same.

@alalazo
Copy link
Member Author

alalazo commented Oct 25, 2016

Hold on, I'll try to clarify : the code that is triggered is in PackageBase._if_make_target_execute and it runs make <target> if ^<target>: is a match for at least a line in the Makefile.

Then for AutotoolsPackage:

def check(self):
        """Default test : search the Makefile for targets `test` and `check`
        and run them if found.
        """
        self._if_make_target_execute('test')
        self._if_make_target_execute('check')

for CMakePackage:

def check(self):
        """Default test : search the Makefile for the target `test`
        and run them if found.
        """
        with working_dir(self.build_directory()):
            self._if_make_target_execute('test')

These function can be overridden in packages and extended, see hdf5. They will run only if --run-tests is explicitly specified in packages.

@alalazo
Copy link
Member Author

alalazo commented Oct 25, 2016

Some packages call it "test", i would say an empty default behavior is a safe bet.

I don't agree because :

  1. what we have is not doing damage if you don't have targets.
  2. you don't pay for scanning Makefiles if you don't pass --run-tests
  3. yet right now a lot of CMakePackages and AutotoolsPackages will behave correctly under spack install <package> --run-tests and packagers don't have to code a line

@davydden
Copy link
Member

davydden commented Oct 25, 2016

Some packages call it "test", i would say an empty default behavior is a safe bet.

I don't agree because :

that's a few days old comment which is outdated by now, GitHub hiccups.

p.s. I agree with your comments.

@alalazo
Copy link
Member Author

alalazo commented Oct 25, 2016

Ah, sorry, same problem as me 😄

@citibeth
Copy link
Member

Should autoreconf and configure stages be merged?
On Oct 24, 2016 4:16 PM, "Massimiliano Culpo" notifications@github.com
wrote:

@tgamblin https://github.com/tgamblin I think this one is ready if it
looks good to you.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#1186 (comment), or mute
the thread
https://github.com/notifications/unsubscribe-auth/AB1cdz49CY5zsJ491xAJ_3fimE30kqs_ks5q3RIwgaJpZM4JG8iX
.

@alalazo
Copy link
Member Author

alalazo commented Oct 25, 2016

Should autoreconf and configure stages be merged?

I wouldn't go for that, but we could add a default behavior to autoreconf.

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

8 participants