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

New feature: update exts list versions to latest #5

Closed
Closed
Changes from 1 commit
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
a710a8b
Add .venv* to .gitignore file
Oct 28, 2024
f47e912
Update exts_list versions implemented
Oct 28, 2024
51d521f
Minior fixes to match EB style
dagonzalezfo Oct 30, 2024
8e77236
Delete printing magic numbers offsets
Oct 31, 2024
4653be7
Change NotImplementedError for EasyBuildError
Oct 31, 2024
fa11840
Avoid direct references to class variables from outside the class.
Oct 31, 2024
fab8112
Improved variable string type check when writing down options in new …
Oct 31, 2024
7bbf2e4
Added support for bioconductor packages
Oct 31, 2024
df2b442
clean code
Nov 4, 2024
7eaa222
Merge pull request #7 from HPCNow/feature-update-exts-list-versions-t…
victormachadoperez Nov 7, 2024
3374b22
Update_exts_list outside of EasyBlock class
Nov 8, 2024
dbe9d65
Refactor magic string
Nov 8, 2024
0e30a6e
Getters for exts_list, exts_defaultclass and bioconductor_version.
Nov 11, 2024
bf56361
Do not update extensions that are on base R
Nov 14, 2024
7d01f4c
Fix update exts list with base R extensions
Nov 14, 2024
e9a60f9
Fix R base packages
Nov 14, 2024
1f9a1aa
Clarify case where extensions in exts_list is a string
Nov 15, 2024
663211a
Calculate MD5 checksum for R packages that do not have MD5 in their C…
Nov 15, 2024
9146cd2
Merge branch 'develop' into feature-update-exts-list-versions-to-latest
Nov 15, 2024
ee73df9
Clean code and minor comments
Nov 15, 2024
40bf8d2
Clean code and improvement of error handling
Nov 20, 2024
4b0ae39
Clean code and minor improvements
Nov 20, 2024
6888b8b
Delete get_exts_list console messages
Nov 20, 2024
886716d
spelling error
Nov 21, 2024
272c779
Added pythonBundle easyblock check for deducing exts_defaultclass in …
Nov 21, 2024
f4fb003
Add .vscode folder to the .gitignore file
Nov 22, 2024
5eca48d
Gather update exts feature into exts_tools.py file
Nov 22, 2024
83eb6fc
Add _get_extension_values function
Nov 22, 2024
09a74ae
Delete trail space
Nov 22, 2024
2182bd9
Get rid of _calculate_md5 function
Nov 22, 2024
d50b376
Unifying PRs
Nov 22, 2024
1da7dba
terminal output improvements
Nov 27, 2024
77569b1
Update extensions is now parallel
Dec 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 98 additions & 56 deletions easybuild/framework/easyblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -4853,13 +4853,13 @@ def make_checksum_lines(checksums, indent_level):

def get_updated_exts_list(exts_list, exts_defaultclass, bioconductor_version=None):
"""
Get a new exts_list with all extensions in exts_list to the latest version.
Get the list of all extensions in exts_list updated to their latest version.

:param exts_defaultclass: default class for the extensions ('RPackage', 'PythonPackage', 'PerlPackage', etc.)
:param exts_list: list of extensions to be updated
:param bioconductor_version: version of Bioconductor to use (if any)
:param exts_defaultclass: default class for the extensions ('RPackage', 'PythonPackage')
:param exts_list: list of extensions to be updated.
:param bioconductor_version: bioconductor's version to use (if any)

:return: updated list of extensions
:return: list with extensions updated to their latest versions.
"""

# check if the exts_list is empty
Expand All @@ -4878,18 +4878,20 @@ def get_updated_exts_list(exts_list, exts_defaultclass, bioconductor_version=Non
PKG_VERSION_OFFSET = 10
INFO_OFFSET = 20

# aesthetic print
# aesthetic terminal print
print()

# loop over all extensions and update their version
for ext in exts_list:

if isinstance(ext, str):
# if the extension is a string, the store it as is and skip further processing
# if the extension is a string, then store it as is and skip further processing
updated_exts_list.append({"name": ext, "version": None, "options": None})

# print message to the user
print_msg(
f"Package {ext:<{PKG_NAME_OFFSET}} v{('---'):<{PKG_VERSION_OFFSET}} {'letf as is':<{INFO_OFFSET}}", log=_log)

continue

elif isinstance(ext, tuple):
Expand All @@ -4906,7 +4908,7 @@ def get_updated_exts_list(exts_list, exts_defaultclass, bioconductor_version=Non
bioc_version=bioconductor_version)

if metadata:
# process the metadata and get new extension
# process the metadata and format it as an extension
updated_ext = format_metadata_as_extension(exts_defaultclass, metadata, bioconductor_version)

# print message to the user
Expand All @@ -4928,106 +4930,142 @@ def get_updated_exts_list(exts_list, exts_defaultclass, bioconductor_version=Non
# store the updated extension
updated_exts_list.append(updated_ext)

# aesthetic print
# aesthetic terminal print
print()

return updated_exts_list


def write_new_easyconfig_exts_list(path, new_exts_list):
def get_updated_easyconfig(ec, update_param, update_data):
"""
Write a new easyconfig file with the new extensions list.
Get a new Easyconfig with the updated given data.

:param ec: EasyConfig instance to update.
:param update_param: parameter to update in the EasyConfig.
:param update_data: data to update in the EasyConfig.

:param path: path to the easyconfig file
:param new_exts_list: list of new extensions to be written to the easyconfig file
:return: new EasyConfig instance with the updated data.
"""

# format the new exts_list to be written to the easyconfig file
exts_list_formatted = ['exts_list = [']
if not ec:
raise EasyBuildError("No EasyConfig instance provided to udpate Easyconfig")

# iterate over the new extensions list and format them
for ext in new_exts_list:
if not update_param:
raise EasyBuildError("No parameter provided to update Easyconfig")

if ext['version'] is None:
exts_list_formatted.append("%s'%s'," % (INDENT_4SPACES, ext['name']))
else:
# append name and version
exts_list_formatted.append("%s('%s', '%s', {" % (INDENT_4SPACES, ext['name'], ext['version']))
if not update_data:
raise EasyBuildError("No data provided to update Easyconfig")

if update_param == "exts_list":
# format the new exts_list to be written to the easyconfig file
exts_list_formatted = ['exts_list = [']

# iterate over the options and format them
for key, value in ext['options'].items():
# if value is a string, then add quotes so they are printed correctly
if isinstance(value, str):
value = "'%s'" % value
# iterate over the new extensions list and format them
for ext in update_data:

# append the key and value of the option
exts_list_formatted.append("%s'%s': %s," % (INDENT_4SPACES * 2, key, value))
if ext['version'] is None:
exts_list_formatted.append("%s'%s'," % (INDENT_4SPACES, ext['name']))
else:
# append name and version
exts_list_formatted.append("%s('%s', '%s', {" % (INDENT_4SPACES, ext['name'], ext['version']))

# iterate over the options and format them
for key, value in ext['options'].items():
# if value is a string, then add quotes so they are printed correctly
if isinstance(value, str):
value = "'%s'" % value

# close the extension
exts_list_formatted.append('%s}),' % (INDENT_4SPACES,))
# append the key and value of the option
exts_list_formatted.append("%s'%s': %s," % (INDENT_4SPACES * 2, key, value))

# close the exts_list
exts_list_formatted.append(']\n')
# close the extension
exts_list_formatted.append('%s}),' % (INDENT_4SPACES,))

# read the easyconfig file and replace the exts_list with the new one
regex = re.compile(r'^exts_list(.|\n)*?\n\]\s*$', re.M)
ectxt = regex.sub('\n'.join(exts_list_formatted), read_file(path))
# close the exts_list
exts_list_formatted.append(']\n')

# write the new easyconfig file
write_file(path, ectxt)
# read the easyconfig file and replace the exts_list with the new one
regex = re.compile(r'^exts_list(.|\n)*?\n\]\s*$', re.M)
new_ec = regex.sub('\n'.join(exts_list_formatted), read_file(ec['spec']))
else:
raise EasyBuildError("Invalid parameter to update Easyconfig")

return new_ec


def get_exts_list(ec):
"""
Get the extension list from an EasyConfig instance.
Get the extension list from the given EasyConfig instance.

:param ec: EasyConfig instance
:param ec: EasyConfig instance.

:return: exts_list of the given EasyConfig instance
:return: list of extensions from the given EasyConfig instance.
"""

exts_list = ec.get('ec', {}).get('exts_list', None)
if not ec:
raise EasyBuildError("No EasyConfig instance provided to retrieve extensions from")

if not exts_list:
raise EasyBuildError("No extension list found in easyconfig")
# get the extension list from the easyconfig file
exts_list = ec.get('ec', {}).get('exts_list', [])

if exts_list:
print_msg("Found %s extensions..." % len(exts_list), log=_log)
else:
print_warning("No extensions found in easyconfig...", log=_log)

return exts_list


def get_exts_list_class(ec):
"""
Get the exts_defaultclass or deduct it from an EasyConfig instance.
Get the exts_defaultclass or deduce it from the given EasyConfig instance.

:param ec: EasyConfig instance
:param ec: EasyConfig instance.

:return: the exts_defaultclass of the given EasyConfig instance
:return: the class of the extensions from the given EasyConfig instance.
"""

if not ec:
raise EasyBuildError("No EasyConfig instance provided to retrieve extensions from")

# get the extension list class from the easyconfig file
exts_list_class = ec.get('ec', {}).get('exts_defaultclass', None)

# if no exts_defaultclass is found, try to deduce it from the EasyConfig name
if not exts_list_class:

# get the name of the EasyConfig
name = ec.get('ec', {}).get('name', None)

if name:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Use this option to define exts_defaultclass for PythonBundles: Taken from PythonBundle EasyBlock
If ec.easyblock=PythonBundle,
elf.cfg['exts_defaultclass'] = 'PythonPackage'

Copy link
Collaborator

Choose a reason for hiding this comment

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

This is to use the same mechanism that PythonBundle EasyBlocks use to define exts_defaultclass value

if name == 'R' or name.startswith('R-'):
exts_list_class = 'RPackage'
if name == 'Python' or name.startswith('Python-'):
exts_list_class = 'PythonPackage'

if not exts_list_class:
raise EasyBuildError("No extension list class found in easyconfig")
if exts_list_class:
print_msg("Found extension list class: %s..." % exts_list_class, log=_log)
else:
print_warning("No extension list class found in Easyconfig...", log=_log)

return exts_list_class


def get_bioconductor_version(ec):
"""
Get the Bioconductor version from an EasyConfig instance.
Get the Bioconductor version stored in the local_biocver parameter from the given EasyConfig instance.

:param ec: EasyConfig instance
:param ec: EasyConfig instance.

:return: the Bioconductor version of the given EasyConfig instance (if any)
:return: The Bioconductor version of the given EasyConfig instance (if any).
"""

if not ec:
raise EasyBuildError("No EasyConfig instance provided to retrieve extensions from")

# get the Bioconductor version from the easyconfig file
# assume that the Bioconductor version is stored in the 'local_biocver' parameter
# as this is not a standard parameter we need to parse the raw text
rawtxt = getattr(ec['ec'], 'rawtxt', '')
match = re.search(r'local_biocver\s*=\s*([0-9.]+)', rawtxt)

Expand Down Expand Up @@ -5066,17 +5104,21 @@ def update_exts_list(ecs):
print_msg("Getting Bioconductor version (if any)...", log=_log)
bioconductor_version = get_bioconductor_version(ec)

# update the extensions in the exts_list to their latest version
# get a new exts_list with all extensions to their latest version.
print_msg("Updating extension list...", log=_log)
updated_exts_list = get_updated_exts_list(exts_list, exts_defaultclass, bioconductor_version)

# write the new easyconfig file
# get new easyconfig file with the updated extensions list
print_msg('Updating Easyconfig instance...', log=_log)
updated_easyconfig = get_updated_easyconfig(ec, "exts_list", updated_exts_list)

# back up the original easyconfig file
ec_backup = back_up_file(ec['spec'], backup_extension='bak_update')
print_msg("Backing up EasyConfig file at %s..." % ec_backup, log=_log)
print_msg("Backing up EasyConfig file at %s" % ec_backup, log=_log)

# write the new easyconfig file
print_msg('Writing updated EasyConfig file...', log=_log)
write_new_easyconfig_exts_list(ec['spec'], updated_exts_list)
write_file(ec['spec'], updated_easyconfig)

# success message
print_msg('EASYCONFIG SUCCESSFULLY UPDATED!\n', log=_log)