Skip to content

Commit c7dfd1d

Browse files
committed
Merge pull request #1 from boegel/pgi
sync with develop + fix remarks for support for CrayPGI toolchain
2 parents bcec98c + 5a44647 commit c7dfd1d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1303
-500
lines changed

.travis.yml

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
language: python
2+
python: 2.6
3+
env:
4+
matrix:
5+
# purposely specifying slowest builds first, to gain time overall
6+
- LMOD_VERSION=5.6.3 TEST_EASYBUILD_MODULES_TOOL=Lmod
7+
- LMOD_VERSION=6.3.1 TEST_EASYBUILD_MODULES_TOOL=Lmod
8+
- LMOD_VERSION=6.3.1 TEST_EASYBUILD_MODULES_TOOL=Lmod TEST_EASYBUILD_MODULE_SYNTAX=Lua
9+
- ENV_MOD_VERSION=3.2.10
10+
- ENV_MOD_TCL_VERSION=1.147 TEST_EASYBUILD_MODULES_TOOL=EnvironmentModulesTcl
11+
matrix:
12+
# mark build as finished as soon as job has failed
13+
fast_finish: true
14+
include:
15+
# also test default configuration with Python 2.7
16+
- python: 2.7
17+
env: ENV_MOD_VERSION=3.2.10
18+
addons:
19+
apt:
20+
packages:
21+
# for environment modules/Lmod
22+
- tcl8.5
23+
# for EasyBuild
24+
- python-setuptools
25+
# for GitPython, python-hglib
26+
- git
27+
- mercurial
28+
# for GC3Pie (optional dep for EasyBuild)
29+
- time
30+
before_install:
31+
# keyring is required to provide GitHub token to EasyBuild;
32+
# keyring v5.7.1 is last version to be compatible with py2.6;
33+
# for recent versions of keyring, keyrings.alt must be installed too
34+
- if [ "x$TRAVIS_PYTHON_VERSION" == 'x2.6' ]; then pip install keyring==5.7.1; else pip install keyring keyrings.alt; fi
35+
# optional Python packages for EasyBuild
36+
- pip install autopep8 GC3Pie GitPython python-graph-dot python-hglib PyYAML
37+
# git config is required to make actual git commits (cfr. tests for GitRepository)
38+
- git config --global user.name "Travis CI"
39+
- git config --global user.email "travis@travis-ci.org"
40+
install:
41+
# install vsc-base (& vsc-install) dependencies for EasyBuild
42+
- easy_install vsc-base
43+
# install environment modules or Lmod
44+
- export INSTALL_DEP=$TRAVIS_BUILD_DIR/easybuild/scripts/install_eb_dep.sh
45+
- if [ ! -z $ENV_MOD_VERSION ]; then source $INSTALL_DEP modules-${ENV_MOD_VERSION} $HOME; fi
46+
- if [ ! -z $LMOD_VERSION ]; then source $INSTALL_DEP lua-5.1.4.8 $HOME; fi
47+
- if [ ! -z $LMOD_VERSION ]; then source $INSTALL_DEP Lmod-${LMOD_VERSION} $HOME; fi
48+
- if [ ! -z $ENV_MOD_TCL_VERSION ]; then source $INSTALL_DEP modules-tcl-${ENV_MOD_TCL_VERSION} $HOME; fi
49+
script:
50+
# set up environment for modules tool (if $MOD_INIT is defined)
51+
- if [ ! -z $MOD_INIT ]; then source $MOD_INIT; fi
52+
# set up environment for EasyBuild framework tests
53+
- export PATH=$TRAVIS_BUILD_DIR:$PATH
54+
- export PYTHONPATH=$TRAVIS_BUILD_DIR
55+
# install GitHub token
56+
- if [ ! -z $GITHUB_TOKEN ]; then
57+
if [ "x$TRAVIS_PYTHON_VERSION" == 'x2.6' ];
58+
then SET_KEYRING="keyring.set_keyring(keyring.backends.file.PlaintextKeyring())";
59+
else SET_KEYRING="import keyrings; keyring.set_keyring(keyrings.alt.file.PlaintextKeyring())";
60+
fi;
61+
python -c "import keyring; $SET_KEYRING; keyring.set_password('github_token', 'easybuild_test', '$GITHUB_TOKEN')";
62+
fi
63+
# run test suite
64+
- python -O -m test.framework.suite
65+
# configure EasyBuild before running bootstrap (modules tool/syntax to use)
66+
- if [ ! -z $TEST_EASYBUILD_MODULES_TOOL ]; then EASYBUILD_MODULES_TOOL=$TEST_EASYBUILD_MODULES_TOOL; fi
67+
- if [ ! -z $TEST_EASYBUILD_MODULE_SYNTAX ]; then EASYBUILD_MODULE_SYNTAX=$TEST_EASYBUILD_MODULE_SYNTAX; fi
68+
# move outside of checkout of easybuild-framework repository, weird place to run bootstrap
69+
- cd $HOME
70+
# test bootstrap script
71+
- python $TRAVIS_BUILD_DIR/easybuild/scripts/bootstrap_eb.py /tmp/$TRAVIS_JOB_ID
72+
# unset $PYTHONPATH to avoid mixing two EasyBuild 'installations' when testing bootstrapped EasyBuild module
73+
- unset PYTHONPATH
74+
# simply sanity check on bootstrapped EasyBuild module
75+
- module use /tmp/$TRAVIS_JOB_ID/modules/all; module load EasyBuild; eb --version

easybuild/__init__.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,5 @@
2828
@author: Jens Timmerman (Ghent University)
2929
@author: Kenneth Hoste (Ghent University)
3030
"""
31-
import os
3231
import pkg_resources
33-
import sys
34-
35-
# check whether EasyBuild is being run from a directory that contains easybuild/__init__.py;
36-
# that doesn't work (fails with import errors), due to weirdness to Python packaging/setuptools/namespaces
37-
if __path__[0] == 'easybuild':
38-
sys.stderr.write("ERROR: Running EasyBuild from %s does not work (Python packaging weirdness)...\n" % os.getcwd())
39-
sys.exit(1)
40-
4132
pkg_resources.declare_namespace(__name__)

easybuild/framework/easyblock.py

Lines changed: 89 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -64,16 +64,16 @@
6464
from easybuild.tools.config import install_path, log_path, package_path, source_paths
6565
from easybuild.tools.environment import restore_env, sanitize_env
6666
from easybuild.tools.filetools import DEFAULT_CHECKSUM
67-
from easybuild.tools.filetools import adjust_permissions, apply_patch, convert_name, download_file, encode_class_name
68-
from easybuild.tools.filetools import extract_file, mkdir, move_logs, read_file, rmtree2
69-
from easybuild.tools.filetools import write_file, compute_checksum, verify_checksum, weld_paths
67+
from easybuild.tools.filetools import adjust_permissions, apply_patch, convert_name, derive_alt_pypi_url
68+
from easybuild.tools.filetools import download_file, encode_class_name, extract_file, is_alt_pypi_url, mkdir, move_logs
69+
from easybuild.tools.filetools import read_file, rmtree2, write_file, compute_checksum, verify_checksum, weld_paths
7070
from easybuild.tools.run import run_cmd
7171
from easybuild.tools.jenkins import write_to_xml
7272
from easybuild.tools.module_generator import ModuleGeneratorLua, ModuleGeneratorTcl, module_generator
7373
from easybuild.tools.module_naming_scheme.utilities import det_full_ec_version
7474
from easybuild.tools.modules import ROOT_ENV_VAR_NAME_PREFIX, VERSION_ENV_VAR_NAME_PREFIX, DEVEL_ENV_VAR_NAME_PREFIX
75-
from easybuild.tools.modules import get_software_root_env_var_name, get_software_version_env_var_name
76-
from easybuild.tools.modules import get_software_root, modules_tool
75+
from easybuild.tools.modules import invalidate_module_caches_for, get_software_root, get_software_root_env_var_name
76+
from easybuild.tools.modules import get_software_version_env_var_name
7777
from easybuild.tools.package.utilities import package
7878
from easybuild.tools.repository.repository import init_repository
7979
from easybuild.tools.toolchain import DUMMY_TOOLCHAIN_NAME
@@ -101,6 +101,9 @@
101101

102102
MODULE_ONLY_STEPS = [MODULE_STEP, PREPARE_STEP, READY_STEP, SANITYCHECK_STEP]
103103

104+
# string part of URL for Python packages on PyPI that indicates needs to be rewritten (see derive_alt_pypi_url)
105+
PYPI_PKG_URL_PATTERN = 'pypi.python.org/packages/source/'
106+
104107

105108
_log = fancylogger.getLogger('easyblock')
106109

@@ -154,8 +157,14 @@ def __init__(self, ec):
154157
self.skip = None
155158
self.module_extra_extensions = '' # extra stuff for module file required by extensions
156159

160+
# easyconfig for this application
161+
if isinstance(ec, EasyConfig):
162+
self.cfg = ec
163+
else:
164+
raise EasyBuildError("Value of incorrect type passed to EasyBlock constructor: %s ('%s')", type(ec), ec)
165+
157166
# modules interface with default MODULEPATH
158-
self.modules_tool = modules_tool()
167+
self.modules_tool = self.cfg.modules_tool
159168
# module generator
160169
self.module_generator = module_generator(self, fake=True)
161170

@@ -170,12 +179,6 @@ def __init__(self, ec):
170179
if modules_header_path is not None:
171180
self.modules_header = read_file(modules_header_path)
172181

173-
# easyconfig for this application
174-
if isinstance(ec, EasyConfig):
175-
self.cfg = ec
176-
else:
177-
raise EasyBuildError("Value of incorrect type passed to EasyBlock constructor: %s ('%s')", type(ec), ec)
178-
179182
# determine install subdirectory, based on module name
180183
self.install_subdir = None
181184

@@ -622,6 +625,16 @@ def obtain_file(self, filename, extension=False, urls=None):
622625
self.log.warning("Source URL %s is of unknown type, so ignoring it." % url)
623626
continue
624627

628+
# PyPI URLs may need to be converted due to change in format of these URLs,
629+
# cfr. https://bitbucket.org/pypa/pypi/issues/438
630+
if PYPI_PKG_URL_PATTERN in fullurl and not is_alt_pypi_url(fullurl):
631+
alt_url = derive_alt_pypi_url(fullurl)
632+
if alt_url:
633+
_log.debug("Using alternate PyPI URL for %s: %s", fullurl, alt_url)
634+
fullurl = alt_url
635+
else:
636+
_log.debug("Failed to derive alternate PyPI URL for %s, so retaining the original", fullurl)
637+
625638
if self.dry_run:
626639
self.dry_run_msg(" * %s will be downloaded to %s", filename, targetpath)
627640
if extension and urls:
@@ -693,6 +706,13 @@ def short_mod_name(self):
693706
"""
694707
return self.cfg.short_mod_name
695708

709+
@property
710+
def mod_subdir(self):
711+
"""
712+
Subdirectory in module install path
713+
"""
714+
return self.cfg.mod_subdir
715+
696716
@property
697717
def moduleGenerator(self):
698718
"""
@@ -905,7 +925,7 @@ def make_module_dep(self, unload_info=None):
905925
self.log.debug("Full list of dependencies: %s" % deps)
906926

907927
# exclude dependencies that extend $MODULEPATH and form the path to the top of the module tree (if any)
908-
full_mod_subdir = os.path.join(self.installdir_mod, self.cfg.mod_subdir)
928+
full_mod_subdir = os.path.join(self.installdir_mod, self.mod_subdir)
909929
init_modpaths = mns.det_init_modulepaths(self.cfg)
910930
top_paths = [self.installdir_mod] + [os.path.join(self.installdir_mod, p) for p in init_modpaths]
911931
excluded_deps = self.modules_tool.path_to_top_of_module_tree(top_paths, self.cfg.short_mod_name,
@@ -1132,7 +1152,14 @@ def load_module(self, mod_paths=None, purge=True):
11321152
if mod_paths is None:
11331153
mod_paths = []
11341154
all_mod_paths = mod_paths + ActiveMNS().det_init_modulepaths(self.cfg)
1135-
mods = [self.full_mod_name]
1155+
1156+
# for flat module naming schemes, we can load the module directly;
1157+
# for non-flat (hierarchical) module naming schemes, we may need to load the toolchain module first
1158+
# to update $MODULEPATH such that the module can be loaded using the short module name
1159+
mods = [self.short_mod_name]
1160+
if self.mod_subdir and self.toolchain.name != DUMMY_TOOLCHAIN_NAME:
1161+
mods.insert(0, self.toolchain.det_short_module_name())
1162+
11361163
self.modules_tool.load(mods, mod_paths=all_mod_paths, purge=purge, init_env=self.initial_environ)
11371164
else:
11381165
self.log.warning("Not loading module, since self.full_mod_name is not set.")
@@ -1148,7 +1175,7 @@ def load_fake_module(self, purge=False):
11481175
fake_mod_path = self.make_module_step(fake=True)
11491176

11501177
# load fake module
1151-
self.modules_tool.prepend_module_path(fake_mod_path)
1178+
self.modules_tool.prepend_module_path(os.path.join(fake_mod_path, self.mod_subdir))
11521179
self.load_module(purge=purge)
11531180

11541181
return (fake_mod_path, env)
@@ -1159,16 +1186,16 @@ def clean_up_fake_module(self, fake_mod_data):
11591186
"""
11601187
fake_mod_path, env = fake_mod_data
11611188
# unload module and remove temporary module directory
1162-
# self.full_mod_name might not be set (e.g. during unit tests)
1163-
if fake_mod_path and self.full_mod_name is not None:
1189+
# self.short_mod_name might not be set (e.g. during unit tests)
1190+
if fake_mod_path and self.short_mod_name is not None:
11641191
try:
1165-
self.modules_tool.unload([self.full_mod_name])
1166-
self.modules_tool.remove_module_path(fake_mod_path)
1192+
self.modules_tool.unload([self.short_mod_name])
1193+
self.modules_tool.remove_module_path(os.path.join(fake_mod_path, self.mod_subdir))
11671194
rmtree2(os.path.dirname(fake_mod_path))
11681195
except OSError, err:
11691196
raise EasyBuildError("Failed to clean up fake module dir %s: %s", fake_mod_path, err)
1170-
elif self.full_mod_name is None:
1171-
self.log.warning("Not unloading module, since self.full_mod_name is not set.")
1197+
elif self.short_mod_name is None:
1198+
self.log.warning("Not unloading module, since self.short_mod_name is not set.")
11721199

11731200
# restore original environment
11741201
restore_env(env)
@@ -1581,6 +1608,9 @@ def extensions_step(self, fetch=False):
15811608
if not self.dry_run:
15821609
fake_mod_data = self.load_fake_module(purge=True)
15831610

1611+
# also load modules for build dependencies again, since those are not loaded by the fake module
1612+
self.modules_tool.load(dep['short_mod_name'] for dep in self.cfg['builddependencies'])
1613+
15841614
self.prepare_for_extensions()
15851615

15861616
if fetch:
@@ -1666,6 +1696,17 @@ def extensions_step(self, fetch=False):
16661696
msg = "\n* installing extension %s %s using '%s' easyblock\n" % (ext['name'], ext['version'], eb_class)
16671697
self.dry_run_msg(msg)
16681698

1699+
self.log.debug("List of loaded modules: %s", self.modules_tool.list())
1700+
1701+
# prepare toolchain build environment, but only when not doing a dry run
1702+
# since in that case the build environment is the same as for the parent
1703+
if self.dry_run:
1704+
self.dry_run_msg("defining build environment based on toolchain (options) and dependencies...")
1705+
else:
1706+
# don't reload modules for toolchain, there is no need since they will be loaded already;
1707+
# the (fake) module for the parent software gets loaded before installing extensions
1708+
inst.toolchain.prepare(onlymod=self.cfg['onlytcmod'], silent=True, loadmod=False)
1709+
16691710
# real work
16701711
inst.prerun()
16711712
txt = inst.run()
@@ -1951,10 +1992,19 @@ def make_module_step(self, fake=False):
19511992

19521993
else:
19531994
write_file(mod_filepath, txt)
1954-
19551995
self.log.info("Module file %s written: %s", mod_filepath, txt)
19561996

1957-
# only update after generating final module file
1997+
# invalidate relevant 'module avail'/'module show' cache entries
1998+
modpath = self.module_generator.get_modules_path(fake=fake)
1999+
# consider both paths: for short module name, and subdir indicated by long module name
2000+
paths = [modpath]
2001+
if self.mod_subdir:
2002+
paths.append(os.path.join(modpath, self.mod_subdir))
2003+
2004+
for path in paths:
2005+
invalidate_module_caches_for(path)
2006+
2007+
# only update after generating final module file
19582008
if not fake:
19592009
self.modules_tool.update()
19602010

@@ -1999,6 +2049,22 @@ def permissions_step(self):
19992049
adjust_permissions(self.installdir, perms, add=False, recursive=True, relative=True, ignore_errors=True)
20002050
self.log.info("Successfully removed write permissions recursively for group/other on install dir.")
20012051

2052+
# add read permissions for everybody on all files, taking into account group (if any)
2053+
perms = stat.S_IRUSR | stat.S_IRGRP
2054+
self.log.debug("Ensuring read permissions for user/group on install dir (recursively)")
2055+
if self.group is None:
2056+
perms |= stat.S_IROTH
2057+
self.log.debug("Also ensuring read permissions for others on install dir (no group specified)")
2058+
2059+
umask = build_option('umask')
2060+
if umask is not None:
2061+
# umask is specified as a string, so interpret it first as integer in octal, then take complement (~)
2062+
perms &= ~int(umask, 8)
2063+
self.log.debug("Taking umask '%s' into account when ensuring read permissions to install dir", umask)
2064+
2065+
adjust_permissions(self.installdir, perms, add=True, recursive=True, relative=True, ignore_errors=True)
2066+
self.log.info("Successfully added read permissions '%s' recursively on install dir", oct(perms))
2067+
20022068
def test_cases_step(self):
20032069
"""
20042070
Run provided test cases.

easybuild/framework/easyconfig/easyconfig.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,8 @@ def __init__(self, path, extra_options=None, build_specs=None, validate=True, hi
264264
self.rawtxt = rawtxt
265265
self.log.debug("Supplied raw easyconfig contents: %s" % self.rawtxt)
266266

267+
self.modules_tool = modules_tool()
268+
267269
# use legacy module classes as default
268270
self.valid_module_classes = build_option('valid_module_classes')
269271
if self.valid_module_classes is not None:
@@ -643,7 +645,8 @@ def toolchain(self):
643645
tcdeps = tc_ec['dependencies']
644646
self.log.debug("Toolchain dependencies based on easyconfig: %s", tcdeps)
645647

646-
self._toolchain = get_toolchain(self['toolchain'], self['toolchainopts'], mns=ActiveMNS(), tcdeps=tcdeps)
648+
self._toolchain = get_toolchain(self['toolchain'], self['toolchainopts'],
649+
mns=ActiveMNS(), tcdeps=tcdeps, modtool=self.modules_tool)
647650
tc_dict = self._toolchain.as_dict()
648651
self.log.debug("Initialized toolchain: %s (opts: %s)" % (tc_dict, self['toolchainopts']))
649652
return self._toolchain
@@ -808,7 +811,7 @@ def _parse_dependency(self, dep, hidden=False, build_only=False):
808811
dependency, tc)
809812
# update the toolchain with the minimal value
810813
orig_tc = tc
811-
tc = robot_find_minimal_toolchain_of_dependency(dependency, parent_tc=tc)
814+
tc = robot_find_minimal_toolchain_of_dependency(dependency, self.modules_tool, parent_tc=tc)
812815
if tc is None:
813816
raise EasyBuildError("No easyconfig for %s that matches toolchain hierarchy generated by %s",
814817
dependency, orig_tc)
@@ -1267,7 +1270,7 @@ def robot_find_easyconfig(name, version):
12671270
return res
12681271

12691272

1270-
def robot_find_minimal_toolchain_of_dependency(dep, parent_tc=None):
1273+
def robot_find_minimal_toolchain_of_dependency(dep, modtool, parent_tc=None):
12711274
"""
12721275
Find the minimal toolchain of a dependency
12731276
@@ -1278,7 +1281,6 @@ def robot_find_minimal_toolchain_of_dependency(dep, parent_tc=None):
12781281
if parent_tc is None:
12791282
parent_tc = dep['toolchain']
12801283

1281-
modtool = modules_tool()
12821284
avail_modules = []
12831285
if build_option('use_existing_modules') and not build_option('retain_all_deps'):
12841286
avail_modules = modtool.available()

easybuild/framework/easyconfig/tools.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,8 @@
8383
_log = fancylogger.getLogger('easyconfig.tools', fname=False)
8484

8585

86-
def skip_available(easyconfigs):
86+
def skip_available(easyconfigs, modtool):
8787
"""Skip building easyconfigs for existing modules."""
88-
modtool = modules_tool()
8988
module_names = [ec['full_mod_name'] for ec in easyconfigs]
9089
modules_exist = modtool.exist(module_names)
9190
retained_easyconfigs = []
@@ -98,7 +97,7 @@ def skip_available(easyconfigs):
9897
return retained_easyconfigs
9998

10099

101-
def find_resolved_modules(easyconfigs, avail_modules, retain_all_deps=False):
100+
def find_resolved_modules(easyconfigs, avail_modules, modtool, retain_all_deps=False):
102101
"""
103102
Find easyconfigs in 1st argument which can be fully resolved using modules specified in 2nd argument
104103
@@ -108,7 +107,6 @@ def find_resolved_modules(easyconfigs, avail_modules, retain_all_deps=False):
108107
"""
109108
ordered_ecs = []
110109
new_easyconfigs = []
111-
modtool = modules_tool()
112110
# copy, we don't want to modify the origin list of available modules
113111
avail_modules = avail_modules[:]
114112
_log.debug("Finding resolved modules for %s (available modules: %s)", easyconfigs, avail_modules)
@@ -434,7 +432,7 @@ def find_related_easyconfigs(path, ec):
434432

435433
regexes = []
436434
for version_pattern in version_patterns:
437-
common_pattern = r'^\S+/%s-%s%%s\.eb$' % (name, version_pattern)
435+
common_pattern = r'^\S+/%s-%s%%s\.eb$' % (re.escape(name), version_pattern)
438436
regexes.extend([
439437
common_pattern % (toolchain_pattern + versionsuffix),
440438
common_pattern % (toolchain_name_pattern + versionsuffix),

0 commit comments

Comments
 (0)