refactor openfoam packages (#3669)

* Several improvements for the openfoam packages

--

Refactor openfoam packages by adding an OpenfoamArch class

Use separate configure, build, install phases.

Provide FOAM_PROJECT_DIR dependent env for openfoam packages
- easier way to locate

Eliminate intermediate installation directories
- unneeded clutter.
- makes it less than easy to find the etc/bashrc file

Add versioning for all openfoam patches
- no certainty which parts (if any) will be needed in future versions,
  especially if we strive to ensure that the upstream version builds
  well with spack to begin with.

Support build of develop branches
- helps track build regressions for future openfoam releases

STYLE: use common/ and assets/ to provide additional (build) resources ...

* - adjust OpenFOAM provider

Move openfoam-com up front since this is the one being used as a base
for the others
This commit is contained in:
Mark Olesen
2017-06-21 17:35:31 +01:00
committed by Adam J. Stewart
parent b1fceb75d0
commit 1f2e56e1f3
15 changed files with 1313 additions and 754 deletions

View File

@@ -48,16 +48,17 @@
# - reworked to mirror the openfoam-com package.
# If changes are needed here, consider if they need applying there too.
#
# Known issues
# - Combining +parmgridgen with +float32 probably won't work.
#
##############################################################################
from spack import *
from spack.environment import *
import multiprocessing
import glob
import re
import shutil
import os
from os.path import isdir, isfile
from spack.pkg.builtin.openfoam_com import *
@@ -77,10 +78,9 @@ class FoamExtend(Package):
version('3.0', git='http://git.code.sf.net/p/foam-extend/foam-extend-3.0')
# variant('int64', default=False,
# description='Compile with 64-bit labels')
# description='Compile with 64-bit label')
variant('float32', default=False,
description='Compile with 32-bit scalar (single-precision)')
variant('paraview', default=False,
description='Build paraview plugins (eg, paraFoam)')
variant('scotch', default=True,
@@ -96,10 +96,6 @@ class FoamExtend(Package):
variant('source', default=True,
description='Install library/application sources and tutorials')
#: Map spack compiler names to OpenFOAM compiler names
# By default, simply capitalize the first letter
compiler_mapping = {'intel': 'icc'}
provides('openfoam')
depends_on('mpi')
depends_on('python')
@@ -111,25 +107,22 @@ class FoamExtend(Package):
depends_on('scotch~metis+mpi', when='+ptscotch')
depends_on('metis@5:', when='+metis')
depends_on('parmetis', when='+parmetis')
depends_on('parmgridgen', when='+parmgridgen')
# mgridgen is statically linked
depends_on('parmgridgen', when='+parmgridgen', type='build')
depends_on('paraview@:5.0.1', when='+paraview')
# Some user settings, to be adjusted manually or via variants
foam_cfg = {
'WM_COMPILER': 'Gcc', # <- %compiler
'WM_ARCH_OPTION': '64', # (32/64-bit on x86_64)
# FUTURE? 'WM_LABEL_SIZE': '32', # <- +int64
'WM_PRECISION_OPTION': 'DP', # <- +float32
'WM_COMPILE_OPTION': 'SPACKOpt', # Do not change
'WM_MPLIB': 'USER', # USER | USERMPI
# General patches
common = ['spack-Allwmake', 'README-spack']
assets = []
# Some user config settings
config = {
'label-size': False, # <- No int32/int64 support
'mplib': 'USERMPI', # USER | USERMPI
}
# The system description is frequently needed
foam_sys = {
'WM_ARCH': None,
'WM_COMPILER': None,
'WM_OPTIONS': None,
}
# The openfoam architecture, compiler information etc
_foam_arch = None
# Content for etc/prefs.{csh,sh}
etc_prefs = {}
@@ -137,163 +130,54 @@ class FoamExtend(Package):
# Content for etc/config.{csh,sh}/ files
etc_config = {}
build_script = './spack-Allwmake' # <- Generated by patch() method.
# phases = ['configure', 'build', 'install']
# build_system_class = 'OpenfoamCom'
phases = ['configure', 'build', 'install']
build_script = './spack-Allwmake' # <- Added by patch() method.
#
# - End of definitions / setup -
#
def setup_environment(self, spack_env, run_env):
run_env.set('FOAM_INST_DIR', self.prefix)
run_env.set('FOAM_INST_DIR', os.path.dirname(self.projectdir)),
run_env.set('FOAM_PROJECT_DIR', self.projectdir)
run_env.set('WM_PROJECT_DIR', self.projectdir)
for d in ['wmake', self.archbin]: # bin already added automatically
run_env.prepend_path('PATH', join_path(self.projectdir, d))
run_env.set('MPI_BUFFER_SIZE', "20000000")
@property
def _canonical(self):
"""Canonical name for this package and version"""
return 'foam-extend-{0}'.format(self.version.up_to(2))
def setup_dependent_environment(self, spack_env, run_env, dependent_spec):
"""Provide location of the OpenFOAM project.
This is identical to the WM_PROJECT_DIR value, but we avoid that
variable since it would mask the normal OpenFOAM cleanup of
previous versions.
"""
spack_env.set('FOAM_PROJECT_DIR', self.projectdir)
@property
def projectdir(self):
"""Absolute location of project directory: WM_PROJECT_DIR/"""
return join_path(self.prefix, self._canonical) # <- prefix/canonical
return self.prefix # <- install directly under prefix
@property
def etc(self):
"""Absolute location of the OpenFOAM etc/ directory"""
return join_path(self.projectdir, 'etc')
def foam_arch(self):
if not self._foam_arch:
self._foam_arch = OpenfoamArch(self.spec, **self.config)
return self._foam_arch
@property
def archbin(self):
"""Relative location of architecture-specific executables"""
wm_options = self.set_openfoam()
return join_path('applications', 'bin', wm_options)
return join_path('applications', 'bin', self.foam_arch)
@property
def archlib(self):
"""Relative location of architecture-specific libraries"""
wm_options = self.set_openfoam()
return join_path('lib', wm_options)
@property
def wm_options(self):
"""The architecture+compiler+options for OpenFOAM"""
opts = self.set_openfoam()
return opts
@property
def rpath_info(self):
"""Define 'SPACKOpt' compiler optimization file to have wmake
use spack information with minimum modifications to OpenFOAM
"""
build_libpath = join_path(self.stage.source_path, self.archlib)
install_libpath = join_path(self.projectdir, self.archlib)
# 'DBUG': rpaths
return '{0}{1} {2}{3}'.format(
self.compiler.cxx_rpath_arg, install_libpath,
self.compiler.cxx_rpath_arg, build_libpath)
def openfoam_arch(self):
"""Return an architecture value similar to what OpenFOAM does in
etc/config.sh/settings, but slightly more generous.
Uses and may adjust foam_cfg[WM_ARCH_OPTION] as a side-effect
"""
# spec.architecture.platform is like `uname -s`, but lower-case
platform = self.spec.architecture.platform
# spec.architecture.target is like `uname -m`
target = self.spec.architecture.target
if platform == 'linux':
if target == 'i686':
self.foam_cfg['WM_ARCH_OPTION'] = '32' # Force consistency
elif target == 'x86_64':
if self.foam_cfg['WM_ARCH_OPTION'] == '64':
platform += '64'
elif target == 'ia64':
platform += 'ia64'
elif target == 'armv7l':
platform += 'ARM7'
elif target == ppc64:
platform += 'PPC64'
elif target == ppc64le:
platform += 'PPC64le'
elif platform == 'darwin':
if target == 'x86_64':
platform += 'Intel'
if self.foam_cfg['WM_ARCH_OPTION'] == '64':
platform += '64'
# ... and others?
return platform
def openfoam_compiler(self):
"""Capitalized version of the compiler name, which usually corresponds
to how OpenFOAM will camel-case things.
Use compiler_mapping to handing special cases.
Also handle special compiler options (eg, KNL)
"""
comp = self.compiler.name
if comp in self.compiler_mapping:
comp = self.compiler_mapping[comp]
comp = comp.capitalize()
if '+knl' in self.spec:
comp += 'KNL'
return comp
# For foam-extend: does not yet support +int64
def set_openfoam(self):
"""Populate foam_cfg, foam_sys according to
variants, architecture, compiler.
Returns WM_OPTIONS.
"""
# Run once
opts = self.foam_sys['WM_OPTIONS']
if opts:
return opts
wm_arch = self.openfoam_arch()
wm_compiler = self.openfoam_compiler()
compileOpt = self.foam_cfg['WM_COMPILE_OPTION']
# Insist on a wmake rule for this architecture/compiler combination
archCompiler = wm_arch + wm_compiler
compiler_rule = join_path(
self.stage.source_path, 'wmake', 'rules', archCompiler)
if not isdir(compiler_rule):
raise RuntimeError(
'No wmake rule for {0}'.format(archCompiler))
if not re.match(r'.+Opt$', compileOpt):
raise RuntimeError(
"WM_COMPILE_OPTION={0} is not type '*Opt'".format(compileOpt))
# Adjust for variants
# FUTURE? self.foam_cfg['WM_LABEL_SIZE'] = (
# FUTURE? '64' if '+int64' in self.spec else '32'
# FUTURE? )
self.foam_cfg['WM_PRECISION_OPTION'] = (
'SP' if '+float32' in self.spec else 'DP'
)
# ----
# WM_OPTIONS=$WM_ARCH$WM_COMPILER$WM_PRECISION_OPTION$WM_COMPILE_OPTION
# ----
self.foam_sys['WM_ARCH'] = wm_arch
self.foam_sys['WM_COMPILER'] = wm_compiler
self.foam_cfg['WM_COMPILER'] = wm_compiler # For bashrc,cshrc too
self.foam_sys['WM_OPTIONS'] = ''.join([
wm_arch,
wm_compiler,
self.foam_cfg['WM_PRECISION_OPTION'],
# FUTURE? 'Int', self.foam_cfg['WM_LABEL_SIZE'], # Int32/Int64
compileOpt
])
return self.foam_sys['WM_OPTIONS']
return join_path('lib', self.foam_arch)
def patch(self):
"""Adjust OpenFOAM build for spack. Where needed, apply filter as an
alternative to normal patching.
"""
self.set_openfoam() # May need foam_cfg/foam_sys information
"""Adjust OpenFOAM build for spack.
Where needed, apply filter as an alternative to normal patching."""
add_extra_files(self, self.common, self.assets)
# Adjust ParMGridGen - this is still a mess
files = [
@@ -319,23 +203,7 @@ def patch(self):
filter_file(
r'#if YY_FLEX_SUBMINOR_VERSION < 34',
r'#if YY_FLEX_MAJOR_VERSION <= 2 && YY_FLEX_MINOR_VERSION <= 5 && YY_FLEX_SUBMINOR_VERSION < 34', # noqa: E501
f, backup=False
)
# Build wrapper script
with open(self.build_script, 'w') as out:
out.write(
"""#!/bin/bash
export FOAM_INST_DIR=$(cd .. && pwd -L)
. $PWD/etc/bashrc '' # No arguments
mkdir -p $FOAM_APPBIN $FOAM_LIBBIN 2>/dev/null # Allow interrupt
echo Build openfoam with SPACK
echo WM_PROJECT_DIR = $WM_PROJECT_DIR
./Allwmake # No arguments
#
""")
set_executable(self.build_script)
self.configure(self.spec, self.prefix) # Should be a separate phase
f, backup=False)
def configure(self, spec, prefix):
"""Make adjustments to the OpenFOAM configuration files in their various
@@ -343,8 +211,6 @@ def configure(self, spec, prefix):
don't properly fit get placed in the etc/prefs.sh file (similiarly for
csh).
"""
self.set_openfoam() # Need foam_cfg/foam_sys information
# Content for etc/prefs.{csh,sh}
self.etc_prefs = {
'000': { # Sort first
@@ -373,7 +239,7 @@ def configure(self, spec, prefix):
},
}
# Adjust configuration via prefs - sort second
self.etc_prefs['001'].update(self.foam_cfg)
self.etc_prefs['001'].update(self.foam_arch.foam_dict())
if '+scotch' in spec or '+ptscotch' in spec:
pkg = spec['scotch'].prefix
@@ -434,41 +300,33 @@ def configure(self, spec, prefix):
posix=join_path('etc', 'prefs.sh'),
cshell=join_path('etc', 'prefs.csh'))
archCompiler = self.foam_sys['WM_ARCH'] + self.foam_sys['WM_COMPILER']
compileOpt = self.foam_cfg['WM_COMPILE_OPTION']
# general_rule = join_path('wmake', 'rules', 'General')
compiler_rule = join_path('wmake', 'rules', archCompiler)
generate_mplib_rules(compiler_rule, self.spec)
generate_compiler_rules(compiler_rule, compileOpt, self.rpath_info)
# Record the spack spec information
with open("log.spack-spec", 'w') as outfile:
outfile.write(spec.tree())
def build(self, spec, prefix):
"""Build using the OpenFOAM Allwmake script, with a wrapper to source
its environment first.
Only build if the compiler is known to be supported.
"""
self.set_openfoam() # Force proper population of foam_cfg/foam_sys
self.foam_arch.has_rule(self.stage.source_path)
self.foam_arch.create_rules(self.stage.source_path, self)
args = []
if self.parallel: # Build in parallel? - pass via the environment
os.environ['WM_NCOMPPROCS'] = str(self.make_jobs) \
if self.make_jobs else str(multiprocessing.cpu_count())
os.environ['WM_NCOMPPROCS'] = str(make_jobs)
builder = Executable(self.build_script)
builder(*args)
def install(self, spec, prefix):
"""Install under the projectdir (== prefix/name-version)"""
self.build(spec, prefix) # Should be a separate phase
opts = self.wm_options
"""Install under the projectdir"""
opts = str(self.foam_arch)
# Fairly ugly since intermediate targets are scattered inside sources
appdir = 'applications'
projdir = os.path.basename(self.projectdir)
mkdirp(self.projectdir, join_path(self.projectdir, appdir))
# Retain build log file
out = "spack-build.out"
if isfile(out):
install(out, join_path(self.projectdir, "log." + opts))
# Filtering: bashrc, cshrc
edits = {
'WM_PROJECT_INST_DIR': os.path.dirname(self.projectdir),
'WM_PROJECT_DIR': join_path('$WM_PROJECT_INST_DIR', projdir),
}
# All top-level files, except spack build info and possibly Allwmake
if '+source' in spec:
@@ -477,36 +335,52 @@ def install(self, spec, prefix):
ignored = re.compile(r'^(Allclean|Allwmake|spack-).*')
files = [
f for f in glob.glob("*") if isfile(f) and not ignored.search(f)
f for f in glob.glob("*")
if os.path.isfile(f) and not ignored.search(f)
]
for f in files:
install(f, self.projectdir)
# Install directories. install applications/bin directly
for d in ['bin', 'etc', 'wmake', 'lib', join_path(appdir, 'bin')]:
# Install 'etc' before 'bin' (for symlinks)
for d in ['etc', 'bin', 'wmake', 'lib', join_path(appdir, 'bin')]:
install_tree(
d,
join_path(self.projectdir, d))
join_path(self.projectdir, d),
symlinks=True)
if '+source' in spec:
subitem = join_path(appdir, 'Allwmake')
install(subitem, join_path(self.projectdir, subitem))
ignored = [opts] # Intermediate targets
ignored = [opts] # Ignore intermediate targets
for d in ['src', 'tutorials']:
install_tree(
d,
join_path(self.projectdir, d),
ignore=shutil.ignore_patterns(*ignored))
ignore=shutil.ignore_patterns(*ignored),
symlinks=True)
for d in ['solvers', 'utilities']:
install_tree(
join_path(appdir, d),
join_path(self.projectdir, appdir, d),
ignore=shutil.ignore_patterns(*ignored))
ignore=shutil.ignore_patterns(*ignored),
symlinks=True)
etc_dir = join_path(self.projectdir, 'etc')
rewrite_environ_files( # Adjust etc/bashrc and etc/cshrc
edits,
posix=join_path(etc_dir, 'bashrc'),
cshell=join_path(etc_dir, 'cshrc'))
self.install_links()
def install_links(self):
"""Add symlinks into bin/, lib/ (eg, for other applications)"""
return
# Make build log visible - it contains OpenFOAM-specific information
with working_dir(self.projectdir):
os.symlink(
join_path('.spack', 'build.out'),
join_path('log.' + str(self.foam_arch)))
# -----------------------------------------------------------------------------