tclmodules : added hooks to process EnvironmentModifications objects

This commit is contained in:
alalazo 2016-03-16 15:19:13 +01:00
parent b45ec3f04e
commit 597727f8be
3 changed files with 133 additions and 50 deletions

View File

@ -47,18 +47,18 @@
__all__ = ['EnvModule', 'Dotkit', 'TclModule'] __all__ = ['EnvModule', 'Dotkit', 'TclModule']
import os import os
import os.path
import re import re
import textwrap
import shutil import shutil
import textwrap
from glob import glob from glob import glob
import llnl.util.tty as tty import llnl.util.tty as tty
import spack
from spack.environment import *
from llnl.util.filesystem import join_path, mkdirp from llnl.util.filesystem import join_path, mkdirp
import spack # Registry of all types of modules. Entries created by EnvModule's metaclass
"""Registry of all types of modules. Entries created by EnvModule's
metaclass."""
module_types = {} module_types = {}
@ -79,6 +79,32 @@ def print_help():
"") "")
class PathInspector(object):
dirname2varname = {
'bin': ('PATH',),
'man': ('MANPATH',),
'lib': ('LIBRARY_PATH', 'LD_LIBRARY_PATH'),
'lib64': ('LIBRARY_PATH', 'LD_LIBRARY_PATH'),
'include': ('CPATH',),
'pkgconfig': ('PKG_CONFIG_PATH',)
}
def __call__(self, env, directory, names):
for name in names:
variables = PathInspector.dirname2varname.get(name, None)
if variables is None:
continue
absolute_path = join_path(os.path.abspath(directory), name)
for variable in variables:
env.prepend_path(variable, absolute_path)
def inspect_path(path):
env, inspector = EnvironmentModifications(), PathInspector()
os.path.walk(path, inspector, env)
return env
class EnvModule(object): class EnvModule(object):
name = 'env_module' name = 'env_module'
@ -88,21 +114,27 @@ def __init__(cls, name, bases, dict):
if cls.name != 'env_module': if cls.name != 'env_module':
module_types[cls.name] = cls module_types[cls.name] = cls
def __init__(self, spec=None): def __init__(self, spec=None):
# category in the modules system # category in the modules system
# TODO: come up with smarter category names. # TODO: come up with smarter category names.
self.category = "spack" self.category = "spack"
# Descriptions for the module system's UI
self.short_description = ""
self.long_description = ""
# dict pathname -> list of directories to be prepended to in # dict pathname -> list of directories to be prepended to in
# the module file. # the module file.
self._paths = None self._paths = None
self.spec = spec self.spec = spec
self.pkg = spec.package # Just stored for convenience
# short description default is just the package + version
# packages can provide this optional attribute
self.short_description = spec.format("$_ $@")
if hasattr(self.pkg, 'short_description'):
self.short_description = self.pkg.short_description
# long description is the docstring with reduced whitespace.
self.long_description = None
if self.spec.package.__doc__:
self.long_description = re.sub(r'\s+', ' ', self.spec.package.__doc__)
@property @property
def paths(self): def paths(self):
@ -130,26 +162,19 @@ def add_path(path_name, directory):
add_path(var, directory) add_path(var, directory)
# Add python path unless it's an actual python installation # Add python path unless it's an actual python installation
# TODO: is there a better way to do this? # TODO : is there a better way to do this?
# FIXME : add PYTHONPATH to every python package
if self.spec.name != 'python': if self.spec.name != 'python':
site_packages = glob(join_path(self.spec.prefix.lib, "python*/site-packages")) site_packages = glob(join_path(self.spec.prefix.lib, "python*/site-packages"))
if site_packages: if site_packages:
add_path('PYTHONPATH', site_packages[0]) add_path('PYTHONPATH', site_packages[0])
# FIXME : Same for GEM_PATH
if self.spec.package.extends(spack.spec.Spec('ruby')): if self.spec.package.extends(spack.spec.Spec('ruby')):
add_path('GEM_PATH', self.spec.prefix) add_path('GEM_PATH', self.spec.prefix)
# short description is just the package + version
# TODO: maybe packages can optionally provide it.
self.short_description = self.spec.format("$_ $@")
# long description is the docstring with reduced whitespace.
if self.spec.package.__doc__:
self.long_description = re.sub(r'\s+', ' ', self.spec.package.__doc__)
return self._paths return self._paths
def write(self): def write(self):
"""Write out a module file for this object.""" """Write out a module file for this object."""
module_dir = os.path.dirname(self.file_name) module_dir = os.path.dirname(self.file_name)
@ -160,9 +185,18 @@ def write(self):
if not self.paths: if not self.paths:
return return
with open(self.file_name, 'w') as f: # Construct the changes that needs to be done on the environment for
self._write(f) env = inspect_path(self.spec.prefix)
# FIXME : move the logic to inspection
env.prepend_path('CMAKE_PREFIX_PATH', self.spec.prefix)
# FIXME : decide how to distinguish between calls done in the installation and elsewhere
env.extend(self.spec.package.environment_modifications(None))
# site_specific = ...`
if not env:
return
with open(self.file_name, 'w') as f:
self._write(f, env)
def _write(self, stream): def _write(self, stream):
"""To be implemented by subclasses.""" """To be implemented by subclasses."""
@ -175,14 +209,12 @@ def file_name(self):
where this module lives.""" where this module lives."""
raise NotImplementedError() raise NotImplementedError()
@property @property
def use_name(self): def use_name(self):
"""Subclasses should implement this to return the name the """Subclasses should implement this to return the name the
module command uses to refer to the package.""" module command uses to refer to the package."""
raise NotImplementedError() raise NotImplementedError()
def remove(self): def remove(self):
mod_file = self.file_name mod_file = self.file_name
if os.path.exists(mod_file): if os.path.exists(mod_file):
@ -205,7 +237,7 @@ def use_name(self):
self.spec.compiler.version, self.spec.compiler.version,
self.spec.dag_hash()) self.spec.dag_hash())
def _write(self, dk_file): def _write(self, dk_file, env):
# Category # Category
if self.category: if self.category:
dk_file.write('#c %s\n' % self.category) dk_file.write('#c %s\n' % self.category)
@ -231,6 +263,10 @@ def _write(self, dk_file):
class TclModule(EnvModule): class TclModule(EnvModule):
name = 'tcl' name = 'tcl'
path = join_path(spack.share_path, "modules") path = join_path(spack.share_path, "modules")
formats = {
PrependPath: 'prepend-path {0.name} \"{0.path}\"\n',
SetEnv: 'setenv {0.name} \"{0.value}\"\n'
}
@property @property
def file_name(self): def file_name(self):
@ -244,25 +280,56 @@ def use_name(self):
self.spec.compiler.version, self.spec.compiler.version,
self.spec.dag_hash()) self.spec.dag_hash())
def process_environment_command(self, env):
for command in env:
# FIXME : how should we handle errors here?
yield self.formats[type(command)].format(command)
def _write(self, m_file): def _write(self, module_file, env):
# TODO: cateogry? """
m_file.write('#%Module1.0\n') Writes a TCL module file for this package
Args:
module_file: module file stream
env: list of environment modifications to be written in the module file
"""
# TCL Modulefile header
module_file.write('#%Module1.0\n')
# TODO : category ?
# Short description # Short description
if self.short_description: if self.short_description:
m_file.write('module-whatis \"%s\"\n\n' % self.short_description) module_file.write('module-whatis \"%s\"\n\n' % self.short_description)
# Long description # Long description
if self.long_description: if self.long_description:
m_file.write('proc ModulesHelp { } {\n') module_file.write('proc ModulesHelp { } {\n')
doc = re.sub(r'"', '\"', self.long_description) doc = re.sub(r'"', '\"', self.long_description)
m_file.write("puts stderr \"%s\"\n" % doc) module_file.write("puts stderr \"%s\"\n" % doc)
m_file.write('}\n\n') module_file.write('}\n\n')
# Path alterations # Environment modifications
for var, dirs in self.paths.items(): for line in self.process_environment_command(env):
for directory in dirs: module_file.write(line)
m_file.write("prepend-path %s \"%s\"\n" % (var, directory))
m_file.write("prepend-path CMAKE_PREFIX_PATH \"%s\"\n" % self.spec.prefix) # FIXME : REMOVE
# def _write(self, m_file):
# # TODO: cateogry?
# m_file.write('#%Module1.0\n')
#
# # Short description
# if self.short_description:
# m_file.write('module-whatis \"%s\"\n\n' % self.short_description)
#
# # Long description
# if self.long_description:
# m_file.write('proc ModulesHelp { } {\n')
# doc = re.sub(r'"', '\"', self.long_description)
# m_file.write("puts stderr \"%s\"\n" % doc)
# m_file.write('}\n\n')
#
# # Path alterations
# for var, dirs in self.paths.items():
# for directory in dirs:
# m_file.write("prepend-path %s \"%s\"\n" % (var, directory))
#
# m_file.write("prepend-path CMAKE_PREFIX_PATH \"%s\"\n" % self.spec.prefix)

View File

@ -25,6 +25,7 @@
from spack import * from spack import *
import os import os
class Mpich(Package): class Mpich(Package):
"""MPICH is a high performance and widely portable implementation of """MPICH is a high performance and widely portable implementation of
the Message Passing Interface (MPI) standard.""" the Message Passing Interface (MPI) standard."""
@ -48,11 +49,25 @@ class Mpich(Package):
def environment_modifications(self, dependent_spec): def environment_modifications(self, dependent_spec):
env = super(Mpich, self).environment_modifications(dependent_spec) env = super(Mpich, self).environment_modifications(dependent_spec)
env.set_env('MPICH_CC', os.environ['CC'])
env.set_env('MPICH_CXX', os.environ['CXX']) if dependent_spec is None:
env.set_env('MPICH_F77', os.environ['F77']) # We are not using compiler wrappers
env.set_env('MPICH_F90', os.environ['FC']) cc = self.compiler.cc
env.set_env('MPICH_FC', os.environ['FC']) cxx = self.compiler.cxx
f77 = self.compiler.f77
f90 = fc = self.compiler.fc
else:
# Spack compiler wrappers
cc = os.environ['CC']
cxx = os.environ['CXX']
f77 = os.environ['F77']
f90 = fc = os.environ['FC']
env.set_env('MPICH_CC', cc)
env.set_env('MPICH_CXX', cxx)
env.set_env('MPICH_F77', f77)
env.set_env('MPICH_F90', f90)
env.set_env('MPICH_FC', fc)
return env return env
def module_modifications(self, module, spec, dep_spec): def module_modifications(self, module, spec, dep_spec):

View File

@ -91,13 +91,14 @@ def site_packages_dir(self):
def environment_modifications(self, extension_spec): def environment_modifications(self, extension_spec):
env = super(Python, self).environment_modifications(extension_spec) env = super(Python, self).environment_modifications(extension_spec)
# Set PYTHONPATH to include site-packages dir for the if extension_spec is not None:
# extension and any other python extensions it depends on. # Set PYTHONPATH to include site-packages dir for the
python_paths = [] # extension and any other python extensions it depends on.
for d in extension_spec.traverse(): python_paths = []
if d.package.extends(self.spec): for d in extension_spec.traverse():
python_paths.append(os.path.join(d.prefix, self.site_packages_dir)) if d.package.extends(self.spec):
env.set_env['PYTHONPATH'] = ':'.join(python_paths) python_paths.append(os.path.join(d.prefix, self.site_packages_dir))
env.set_env['PYTHONPATH'] = ':'.join(python_paths)
return env return env
def module_modifications(self, module, spec, ext_spec): def module_modifications(self, module, spec, ext_spec):