Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
166 changes: 166 additions & 0 deletions easybuild/easyblocks/generic/juliapackage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
##
# Copyright 2022-2022 Vrije Universiteit Brussel
#
# This file is part of EasyBuild,
# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en),
# with support of Ghent University (http://ugent.be/hpc),
# the Flemish Supercomputer Centre (VSC) (https://www.vscentrum.be),
# Flemish Research Foundation (FWO) (http://www.fwo.be/en)
# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en).
#
# https://github.com/easybuilders/easybuild
#
# EasyBuild is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation v2.
#
# EasyBuild is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with EasyBuild. If not, see <http://www.gnu.org/licenses/>.
##
"""
EasyBuild support for Julia Packages, implemented as an easyblock

@author: Alex Domingo (Vrije Universiteit Brussel)
"""
import os

from distutils.version import LooseVersion

import easybuild.tools.environment as env
from easybuild.framework.easyconfig import CUSTOM
from easybuild.framework.extensioneasyblock import ExtensionEasyBlock
from easybuild.tools.build_log import EasyBuildError
from easybuild.tools.modules import get_software_root, get_software_version
from easybuild.tools.filetools import copy_dir
from easybuild.tools.run import run_cmd

EXTS_FILTER_JULIA_PACKAGES = ("julia -e 'using %(ext_name)s'", "")


class JuliaPackage(ExtensionEasyBlock):
"""Builds and installs Julia Packages."""

@staticmethod
def extra_options(extra_vars=None):
"""Extra easyconfig parameters specific to JuliaPackage."""
extra_vars = ExtensionEasyBlock.extra_options(extra_vars=extra_vars)
extra_vars.update({
'download_pkg_deps': [
False, "Let Julia download and bundle all needed dependencies for this installation", CUSTOM
],
})
return extra_vars

def set_pkg_offline(self):
"""Enable offline mode of Julia Pkg"""
if get_software_root('Julia') is None:
raise EasyBuildError("Julia not included as dependency!")

if not self.cfg['download_pkg_deps']:
julia_version = get_software_version('Julia')
if LooseVersion(julia_version) >= LooseVersion('1.5'):
# Enable offline mode of Julia Pkg
# https://pkgdocs.julialang.org/v1/api/#Pkg.offline
env.setvar('JULIA_PKG_OFFLINE', 'true')
else:
errmsg = (
"Cannot set offline mode in Julia v%s (needs Julia >= 1.5). "
"Enable easyconfig option 'download_pkg_deps' to allow installation "
"with any extra downloaded dependencies."
)
raise EasyBuildError(errmsg, julia_version)

def prepare_step(self, *args, **kwargs):
"""Prepare for installing Julia package."""
super(JuliaPackage, self).prepare_step(*args, **kwargs)
self.set_pkg_offline()

def configure_step(self):
"""No separate configuration for JuliaPackage."""
pass

def build_step(self):
"""No separate build procedure for JuliaPackage."""
pass

def test_step(self):
"""No separate (standard) test procedure for JuliaPackage."""
pass

def install_step(self):
"""Install Julia package with Pkg"""

# prepend installation directory to Julia DEPOT_PATH
# see https://docs.julialang.org/en/v1/manual/environment-variables/#JULIA_DEPOT_PATH
depot_path = os.getenv('JULIA_DEPOT_PATH')
if depot_path is not None:
depot_path = ':'.join([self.installdir, depot_path])
env.setvar('JULIA_DEPOT_PATH', depot_path)

# command sequence for Julia.Pkg
julia_pkg_cmd = ['using Pkg']
if os.path.isdir(os.path.join(self.start_dir, '.git')):
# sources from git repos can be installed as any remote package
self.log.debug('Installing Julia package in normal mode (Pkg.add)')

julia_pkg_cmd.extend([
# install package from local path preserving existing dependencies
'Pkg.add(url="%s"; preserve=Pkg.PRESERVE_ALL)' % self.start_dir,
])
else:
# plain sources have to be installed in develop mode
# copy sources to install directory and install
self.log.debug('Installing Julia package in develop mode (Pkg.develop)')

install_pkg_path = os.path.join(self.installdir, 'packages', self.name)
copy_dir(self.start_dir, install_pkg_path)

julia_pkg_cmd.extend([
'Pkg.develop(PackageSpec(path="%s"))' % install_pkg_path,
'Pkg.build("%s")' % self.name,
])

julia_pkg_cmd = ';'.join(julia_pkg_cmd)
cmd = ' '.join([
self.cfg['preinstallopts'],
"julia -e '%s'" % julia_pkg_cmd,
self.cfg['installopts'],
])
(out, _) = run_cmd(cmd, log_all=True, simple=False)

return out

def run(self):
"""Install Julia package as an extension."""

if not self.src:
errmsg = "No source found for Julia package %s, required for installation. (src: %s)"
raise EasyBuildError(errmsg, self.name, self.src)
ExtensionEasyBlock.run(self, unpack_src=True)

self.set_pkg_offline()
self.install_step()

def sanity_check_step(self, *args, **kwargs):
"""Custom sanity check for JuliaPackage"""

pkg_dir = os.path.join('packages', self.name)

custom_paths = {
'files': [],
'dirs': [pkg_dir],
}
kwargs.update({'custom_paths': custom_paths})

return ExtensionEasyBlock.sanity_check_step(self, EXTS_FILTER_JULIA_PACKAGES, *args, **kwargs)

def make_module_extra(self):
"""Prepend installation directory to JULIA_DEPOT_PATH in module file."""
txt = super(JuliaPackage, self).make_module_extra()
txt += self.module_generator.prepend_paths('JULIA_DEPOT_PATH', [''])
return txt
6 changes: 6 additions & 0 deletions test/easyblocks/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
from easybuild.base import fancylogger
from easybuild.base.testing import TestCase
from easybuild.easyblocks.generic.gopackage import GoPackage
from easybuild.easyblocks.generic.juliapackage import JuliaPackage
from easybuild.easyblocks.generic.intelbase import IntelBase
from easybuild.easyblocks.generic.pythonbundle import PythonBundle
from easybuild.easyblocks.gcc import EB_GCC
Expand Down Expand Up @@ -286,6 +287,11 @@ def template_module_only_test(self, easyblock, name, version='1.3.2', extra_txt=
os.environ['EBROOTGO'] = '/fake/install/prefix/Go/1.14'
os.environ['EBVERSIONGO'] = '1.14'

elif app_class == JuliaPackage:
# $EBROOTJULIA must be set for JuliaPackage easyblock
os.environ['EBROOTJULIA'] = '/fake/install/prefix/Julia/1.6.7'
os.environ['EBVERSIONJULIA'] = '1.6.7'

elif app_class == EB_OpenFOAM:
# proper toolchain must be used for OpenFOAM(-Extend), to determine value to set for $WM_COMPILER
write_file(os.path.join(tmpdir, 'GCC', '4.9.3-2.25'), '\n'.join([
Expand Down