Skip to content

Commit

Permalink
Merge branch 'easybuilders:develop' into python27
Browse files Browse the repository at this point in the history
  • Loading branch information
Flamefire authored Jun 6, 2024
2 parents c240aec + 35c7f33 commit a8df8d8
Show file tree
Hide file tree
Showing 13 changed files with 170 additions and 41 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/eb_command.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-20.04
strategy:
matrix:
python: [3.5, 3.6, 3.7, 3.8, 3.9, '3.10', '3.11']
python: [3.6, 3.7, 3.8, 3.9, '3.10', '3.11']
fail-fast: false
steps:
- uses: actions/checkout@v3
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/linting.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-20.04
strategy:
matrix:
python-version: [3.5, 3.6, 3.7, 3.8, 3.9, '3.10', '3.11']
python-version: [3.6, 3.7, 3.8, 3.9, '3.10', '3.11']

steps:
- uses: actions/checkout@v3
Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/unit_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@ jobs:
lc_all: [""]
include:
# Test different Python 3 versions with Lmod 8.x
- python: 3.5
modules_tool: ${{needs.setup.outputs.lmod8}}
- python: 3.7
modules_tool: ${{needs.setup.outputs.lmod8}}
- python: 3.8
Expand Down
53 changes: 22 additions & 31 deletions easybuild/framework/easyblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -1068,7 +1068,7 @@ def make_builddir(self):
self.log.info("Overriding 'cleanupoldinstall' (to False), 'cleanupoldbuild' (to True) "
"and 'keeppreviousinstall' because we're building in the installation directory.")
# force cleanup before installation
if build_option('module_only'):
if build_option('module_only') or self.cfg['module_only']:
self.log.debug("Disabling cleanupoldbuild because we run as module-only")
self.cfg['cleanupoldbuild'] = False
else:
Expand Down Expand Up @@ -1139,7 +1139,7 @@ def make_dir(self, dir_name, clean, dontcreateinstalldir=False):
if self.cfg['keeppreviousinstall']:
self.log.info("Keeping old directory %s (hopefully you know what you are doing)", dir_name)
return
elif build_option('module_only'):
elif build_option('module_only') or self.cfg['module_only']:
self.log.info("Not touching existing directory %s in module-only mode...", dir_name)
elif clean:
remove_dir(dir_name)
Expand Down Expand Up @@ -1365,7 +1365,7 @@ def make_module_dep(self, unload_info=None):
multi_dep_mod_names[dep['name']].append(dep['short_mod_name'])

multi_dep_load_defaults = []
for depname, depmods in sorted(multi_dep_mod_names.items()):
for _, depmods in sorted(multi_dep_mod_names.items()):
stmt = self.module_generator.load_module(depmods[0], multi_dep_mods=depmods,
recursive_unload=recursive_unload,
depends_on=depends_on)
Expand Down Expand Up @@ -1769,10 +1769,7 @@ def make_extension_string(self, name_version_sep='-', ext_sep=', ', sort=True):
return ext_sep.join(exts_list)

def prepare_for_extensions(self):
"""
Also do this before (eg to set the template)
"""
pass
"""Ran before installing extensions (eg to set templates)"""

def skip_extensions(self):
"""
Expand Down Expand Up @@ -2114,7 +2111,7 @@ def guess_start_dir(self):
start_dir = ''
# do not use the specified 'start_dir' when running as --module-only as
# the directory will not exist (extract_step is skipped)
if self.start_dir and not build_option('module_only'):
if self.start_dir and not build_option('module_only') and not self.cfg['module_only']:
start_dir = self.start_dir

if not os.path.isabs(start_dir):
Expand Down Expand Up @@ -2198,9 +2195,9 @@ def handle_iterate_opts(self):
self.log.info("Current iteration index: %s", self.iter_idx)

# pop first element from all iterative easyconfig parameters as next value to use
for opt in self.iter_opts:
if len(self.iter_opts[opt]) > self.iter_idx:
self.cfg[opt] = self.iter_opts[opt][self.iter_idx]
for opt, value in self.iter_opts.items():
if len(value) > self.iter_idx:
self.cfg[opt] = value[self.iter_idx]
else:
self.cfg[opt] = '' # empty list => empty option as next value
self.log.debug("Next value for %s: %s" % (opt, str(self.cfg[opt])))
Expand All @@ -2212,12 +2209,12 @@ def post_iter_step(self):
"""Restore options that were iterated over"""
# disable templating, since we're messing about with values in self.cfg
with self.cfg.disable_templating():
for opt in self.iter_opts:
self.cfg[opt] = self.iter_opts[opt]
for opt, value in self.iter_opts.items():
self.cfg[opt] = value

# also need to take into account extensions, since those were iterated over as well
for ext in self.ext_instances:
ext.cfg[opt] = self.iter_opts[opt]
ext.cfg[opt] = value

self.log.debug("Restored value of '%s' that was iterated over: %s", opt, self.cfg[opt])

Expand Down Expand Up @@ -2349,7 +2346,7 @@ def check_readiness_step(self):
self.log.info("No module %s found. Not skipping anything." % self.full_mod_name)

# remove existing module file under --force (but only if --skip is not used)
elif build_option('force') or build_option('rebuild'):
elif (build_option('force') or build_option('rebuild')) and not build_option('dump_env_script'):
self.remove_module_file()

def fetch_step(self, skip_checksums=False):
Expand Down Expand Up @@ -2751,10 +2748,7 @@ def _test_step(self):
self.report_test_failure(err)

def stage_install_step(self):
"""
Install in a stage directory before actual installation.
"""
pass
"""Install in a stage directory before actual installation."""

def install_step(self):
"""Install built software (abstract method)."""
Expand Down Expand Up @@ -3248,7 +3242,7 @@ def sanity_check_linked_shared_libs(self, subdirs=None):
required_libs.extend(self.cfg['required_linked_shared_libs'])

# early return if there are no banned/required libraries
if not (banned_libs + required_libs):
if not banned_libs + required_libs:
self.log.info("No banned/required libraries specified")
return []
else:
Expand Down Expand Up @@ -3795,7 +3789,7 @@ def make_module_step(self, fake=False):
try:
self.make_devel_module()
except EasyBuildError as error:
if build_option('module_only'):
if build_option('module_only') or self.cfg['module_only']:
self.log.info("Using --module-only so can recover from error: %s", error)
else:
raise error
Expand Down Expand Up @@ -3903,7 +3897,7 @@ def skip_step(self, step, skippable):
"""Dedice whether or not to skip the specified step."""
skip = False
force = build_option('force')
module_only = build_option('module_only')
module_only = build_option('module_only') or self.cfg['module_only']
sanity_check_only = build_option('sanity_check_only')
skip_extensions = build_option('skip_extensions')
skip_test_step = build_option('skip_test_step')
Expand Down Expand Up @@ -4463,7 +4457,7 @@ def copy_easyblocks_for_reprod(easyblock_instances, reprod_dir):
else:
easyblock_paths.add(easyblock_path)
for easyblock_path in easyblock_paths:
easyblock_basedir, easyblock_filename = os.path.split(easyblock_path)
easyblock_filename = os.path.basename(easyblock_path)
copy_file(easyblock_path, os.path.join(reprod_easyblock_dir, easyblock_filename))
_log.info("Dumped easyblock %s required for reproduction to %s", easyblock_filename, reprod_easyblock_dir)

Expand Down Expand Up @@ -4594,10 +4588,7 @@ def build_easyconfigs(easyconfigs, output_dir, test_results):


class StopException(Exception):
"""
StopException class definition.
"""
pass
"""Exception thrown to stop running steps"""


def inject_checksums_to_json(ecs, checksum_type):
Expand Down Expand Up @@ -4645,14 +4636,14 @@ def inject_checksums_to_json(ecs, checksum_type):

# actually inject new checksums or overwrite existing ones (if --force)
existing_checksums = app.get_checksums_from_json(always_read=True)
for filename in checksums:
for filename, checksum in checksums.items():
if filename not in existing_checksums:
existing_checksums[filename] = checksums[filename]
existing_checksums[filename] = checksum
# don't do anything if the checksum already exist and is the same
elif checksums[filename] != existing_checksums[filename]:
elif checksum != existing_checksums[filename]:
if build_option('force'):
print_warning("Found existing checksums for %s, overwriting them (due to --force)..." % ec_fn)
existing_checksums[filename] = checksums[filename]
existing_checksums[filename] = checksum
else:
raise EasyBuildError("Found existing checksum for %s, use --force to overwrite them" % filename)

Expand Down
1 change: 1 addition & 0 deletions easybuild/framework/easyconfig/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@
'hidden': [False, "Install module file as 'hidden' by prefixing its version with '.'", BUILD],
'installopts': ['', 'Extra options for installation', BUILD],
'maxparallel': [None, 'Max degree of parallelism', BUILD],
'module_only': [False, 'Only generate module file', BUILD],
'parallel': [None, ('Degree of parallelism for e.g. make (default: based on the number of '
'cores, active cpuset and restrictions in ulimit)'), BUILD],
'patches': [[], "List of patches to apply", BUILD],
Expand Down
2 changes: 2 additions & 0 deletions easybuild/framework/easyconfig/easyconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -2027,12 +2027,14 @@ def resolve_template(value, tmpl_dict):
# '%(name)s' -> '%(name)s'
# '%%(name)s' -> '%%(name)s'
if '%' in value:
orig_value = value
value = re.sub(re.compile(r'(%)(?!%*\(\w+\)s)'), r'\1\1', value)

try:
value = value % tmpl_dict
except KeyError:
_log.warning("Unable to resolve template value %s with dict %s", value, tmpl_dict)
value = orig_value # Undo "%"-escaping
else:
# this block deals with references to objects and returns other references
# for reading this is ok, but for self['x'] = {}
Expand Down
17 changes: 14 additions & 3 deletions easybuild/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@
from easybuild.tools.repository.repository import init_repository
from easybuild.tools.systemtools import check_easybuild_deps
from easybuild.tools.testing import create_test_report, overall_test_report, regtest, session_state
from easybuild.tools.version import EASYBLOCKS_VERSION, FRAMEWORK_VERSION, UNKNOWN_EASYBLOCKS_VERSION
from easybuild.tools.version import different_major_versions


_log = None
Expand Down Expand Up @@ -440,9 +442,9 @@ def process_eb_args(eb_args, eb_go, cfg_settings, modtool, testing, init_session
dry_run_mode = options.dry_run or options.dry_run_short or options.missing_modules

keep_available_modules = any((
forced, dry_run_mode, options.extended_dry_run, any_pr_option_set, options.copy_ec, options.inject_checksums,
options.sanity_check_only, options.inject_checksums_to_json)
)
forced, dry_run_mode, any_pr_option_set, options.copy_ec, options.dump_env_script, options.extended_dry_run,
options.inject_checksums, options.inject_checksums_to_json, options.sanity_check_only
))

# skip modules that are already installed unless forced, or unless an option is used that warrants not skipping
if not keep_available_modules:
Expand Down Expand Up @@ -618,6 +620,15 @@ def main(args=None, logfile=None, do_build=None, testing=False, modtool=None, pr
(build_specs, _log, logfile, robot_path, search_query, eb_tmpdir, try_to_generate,
from_pr_list, tweaked_ecs_paths) = cfg_settings

# compare running Framework and EasyBlocks versions
if EASYBLOCKS_VERSION == UNKNOWN_EASYBLOCKS_VERSION:
# most likely reason is running framework unit tests with no easyblocks installation
# so log a warning, to avoid test related issues
_log.warning("Unable to determine EasyBlocks version, so we'll assume it is not different from Framework")
elif different_major_versions(FRAMEWORK_VERSION, EASYBLOCKS_VERSION):
raise EasyBuildError("Framework (%s) and EasyBlock (%s) major versions are different." % (FRAMEWORK_VERSION,
EASYBLOCKS_VERSION))

# load hook implementations (if any)
hooks = load_hooks(options.hooks)

Expand Down
1 change: 1 addition & 0 deletions easybuild/tools/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ def mk_full_default_path(name, prefix=DEFAULT_PREFIX):
'debug',
'debug_lmod',
'dump_autopep8',
'dump_env_script',
'enforce_checksums',
'experimental',
'extended_dry_run',
Expand Down
31 changes: 31 additions & 0 deletions easybuild/tools/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
from easybuild.tools.py2vs3 import HTTPError, URLError, ascii_letters, urlopen
from easybuild.tools.systemtools import UNKNOWN, get_tool_version
from easybuild.tools.utilities import nub, only_if_module_is_available
from easybuild.tools.version import FRAMEWORK_VERSION, different_major_versions


_log = fancylogger.getLogger('github', fname=False)
Expand Down Expand Up @@ -587,9 +588,39 @@ def fetch_files_from_pr(pr, path=None, github_user=None, github_account=None, gi
else:
raise EasyBuildError("Couldn't find path to patched file %s", full_path)

if github_repo == GITHUB_EASYCONFIGS_REPO:
ver = _get_version_for_repo(os.path.join(final_path, 'setup.py'))
elif github_repo == GITHUB_EASYBLOCKS_REPO:
ver = _get_version_for_repo(os.path.join(final_path, 'easybuild', 'easyblocks', '__init__.py'))

if different_major_versions(FRAMEWORK_VERSION, ver):
raise EasyBuildError("Framework (%s) is a different major version than used in %s/%s PR #%s (%s)",
FRAMEWORK_VERSION, github_account, github_repo, pr, ver)

return files


def _get_version_for_repo(filename):
"""Extract version from filename."""
_log.debug("Extract version from %s" % filename)

try:
ver_line = ""
with open(filename) as f:
for line in f.readlines():
if line.startswith("VERSION "):
ver_line = line
break

# version can be a string or LooseVersion
res = re.search(r"""^VERSION = .*['"](.*)['"].?$""", ver_line)

_log.debug("PR target version is %s" % res.group(1))
return res.group(1)
except Exception:
raise EasyBuildError("Couldn't determine version of PR from %s" % filename)


def fetch_easyblocks_from_pr(pr, path=None, github_user=None):
"""Fetch patched easyblocks for a particular PR."""
return fetch_files_from_pr(pr, path, github_user, github_repo=GITHUB_EASYBLOCKS_REPO)
Expand Down
14 changes: 13 additions & 1 deletion easybuild/tools/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
# This causes problems further up the dependency chain...
VERSION = LooseVersion('4.9.2.dev0')
UNKNOWN = 'UNKNOWN'
UNKNOWN_EASYBLOCKS_VERSION = '0.0.UNKNOWN.EASYBLOCKS'


def get_git_revision():
Expand Down Expand Up @@ -87,7 +88,7 @@ def get_git_revision():
try:
from easybuild.easyblocks import VERBOSE_VERSION as EASYBLOCKS_VERSION
except Exception:
EASYBLOCKS_VERSION = '0.0.UNKNOWN.EASYBLOCKS' # make sure it is smaller then anything
EASYBLOCKS_VERSION = UNKNOWN_EASYBLOCKS_VERSION # make sure it is smaller then anything


def this_is_easybuild():
Expand All @@ -103,3 +104,14 @@ def this_is_easybuild():
msg = msg.encode('ascii')

return msg


def different_major_versions(v1, v2):
"""Compare major versions"""
# Deal with version instances being either strings or LooseVersion
if isinstance(v1, str):
v1 = LooseVersion(v1)
if isinstance(v2, str):
v2 = LooseVersion(v2)

return v1.version[0] != v2.version[0]
2 changes: 1 addition & 1 deletion test/framework/easyblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -1685,7 +1685,7 @@ def test_fetch_patches(self):
self.assertEqual(eb.patches[1]['level'], 4)
self.assertEqual(eb.patches[2]['name'], toy_patch)
self.assertEqual(eb.patches[2]['sourcepath'], 'foobar')
self.assertEqual(eb.patches[3]['name'], 'toy-0.0.tar.gz'),
self.assertEqual(eb.patches[3]['name'], 'toy-0.0.tar.gz')
self.assertEqual(eb.patches[3]['copy'], 'some/path')
self.assertEqual(eb.patches[4]['name'], toy_patch)
self.assertEqual(eb.patches[4]['level'], 0)
Expand Down
24 changes: 24 additions & 0 deletions test/framework/easyconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -3655,6 +3655,30 @@ def test_resolve_template(self):
# '%(name)' is not a correct template spec (missing trailing 's')
self.assertEqual(resolve_template('%(name)', tmpl_dict), '%(name)')

# Correct (un)escaping
values = (
('10%', '10%'),
('%of', '%of'),
('10%of', '10%of'),
('%s', '%s'),
('%%(name)s', '%(name)s'),
('%%%(name)s', '%FooBar'),
('%%%%(name)s', '%%(name)s'),
# It doesn't matter what is resolved
('%%(invalid)s', '%(invalid)s'),
('%%%%(invalid)s', '%%(invalid)s'),
)
for value, expected in values:
self.assertEqual(resolve_template(value, tmpl_dict), expected)
# Templates are resolved
value += ' %(name)s'
expected += ' FooBar'
self.assertEqual(resolve_template(value, tmpl_dict), expected)

# On unknown values the value is returned unchanged
for value in ('%(invalid)s', '%(name)s %(invalid)s', '%%%(invalid)s', '% %(invalid)s', '%s %(invalid)s'):
self.assertEqual(resolve_template(value, tmpl_dict), value)

def test_det_subtoolchain_version(self):
"""Test det_subtoolchain_version function"""
_, all_tc_classes = search_toolchain('')
Expand Down
Loading

0 comments on commit a8df8d8

Please sign in to comment.