diff --git a/easybuild/easyblocks/generic/gopackage.py b/easybuild/easyblocks/generic/gopackage.py new file mode 100644 index 0000000000..f447922f2c --- /dev/null +++ b/easybuild/easyblocks/generic/gopackage.py @@ -0,0 +1,148 @@ +## +# Copyright 2009-2020 Ghent University +# +# 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 . +## +""" +EasyBuild support for Go packages, implemented as an EasyBlock + +@author: Pavel Grochal (INUITS) +""" +import os +from distutils.version import LooseVersion + +import easybuild.tools.environment as env +from easybuild.framework.easyblock import EasyBlock +from easybuild.framework.easyconfig import CUSTOM +from easybuild.tools.build_log import EasyBuildError +from easybuild.tools.modules import get_software_root, get_software_version +from easybuild.tools.run import run_cmd + + +class GoPackage(EasyBlock): + """Builds and installs a Go package, and provides a dedicated module file.""" + + @staticmethod + def extra_options(extra_vars=None): + """Easyconfig parameters specific to Go packages.""" + if extra_vars is None: + extra_vars = {} + extra_vars.update({ + 'modulename': [None, "Module name of the Go package, when building non-native module", CUSTOM], + 'forced_deps': [None, "Force specific version of Go package, when building non-native module", CUSTOM], + }) + return extra_vars + + def prepare_step(self, *args, **kwargs): + """Go-specific preparations.""" + super(GoPackage, self).prepare_step(*args, **kwargs) + + if get_software_root('Go') is None: + raise EasyBuildError("Failed to pick go command to use. Is it listed in dependencies?") + + if LooseVersion(get_software_version('Go')) < LooseVersion("1.11"): + raise EasyBuildError("Go version < 1.11 doesn't support installing modules from go.mod") + + def configure_step(self): + """Configure Go package build/install.""" + + # enforce use of go modules + env.setvar('GO111MODULE', 'on', verbose=False) + # set bin folder + env.setvar('GOBIN', os.path.join(self.installdir, 'bin'), verbose=False) + + # creates log entries for go being used, for debugging + run_cmd("go version", verbose=False, trace=False) + run_cmd("go env", verbose=False, trace=False) + + def build_step(self): + """If Go package is not native go module, lets try to make the module.""" + + go_mod_file = 'go.mod' + go_sum_file = 'go.sum' + + if not os.path.exists(go_mod_file) or not os.path.isfile(go_mod_file): + self.log.warn("go.mod not found! This is not natively supported go module. Trying to init module.") + + if self.cfg['modulename'] is None: + raise EasyBuildError("Installing non-native go module. You need to specify 'modulename' in easyconfig") + + # for more information about migrating to go modules + # see: https://blog.golang.org/migrating-to-go-modules + + # go mod init + cmd = ' '.join(['go', 'mod', 'init', self.cfg['modulename']]) + run_cmd(cmd, log_all=True, simple=True) + + if self.cfg['forced_deps']: + for dep in self.cfg['forced_deps']: + # go get specific dependencies which locks them in go.mod + cmd = ' '.join(['go', 'get', '%s@%s' % dep]) + run_cmd(cmd, log_all=True, simple=True) + + # note: ... (tripledot) used below is not a typo, but go wildcard pattern + # which means: anything you can find in this directory, including all subdirectories + # see: 'go help packages' or https://golang.org/pkg/cmd/go/internal/help/ + # see: https://stackoverflow.com/a/28031651/2047157 + + # building and testing will add packages to go.mod + run_cmd('go build ./...', log_all=True, simple=True) + run_cmd('go test ./...', log_all=True, simple=True) + + # tidy up go.mod + run_cmd('go mod tidy', log_all=True, simple=True) + + # build and test again, to ensure go mod tidy didn't removed anything needed + run_cmd('go build ./...', log_all=True, simple=True) + run_cmd('go test ./...', log_all=True, simple=True) + + self.log.warn('Include generated go.mod and go.sum via patch to ensure locked dependencies ' + 'and run this easyconfig again.') + run_cmd('cat go.mod', log_all=True, simple=True) + run_cmd('cat go.sum', log_all=True, simple=True) + + if not os.path.exists(go_sum_file) or not os.path.isfile(go_sum_file): + raise EasyBuildError("go.sum not found! This module has no locked dependency versions.") + + def install_step(self): + """Install Go package to a custom path""" + + # actually install Go package + cmd = ' '.join([ + self.cfg['preinstallopts'], + 'go', + 'install', + self.cfg['installopts'], + ]) + run_cmd(cmd, log_all=True, log_ok=True, simple=True) + + def sanity_check_step(self): + """Custom sanity check for Go package.""" + + # Check if GoPackage produced binary and can run help on it + custom_paths = { + 'files': ['bin/%s' % self.name.lower()], + 'dirs': [], + } + custom_commands = ['%s --help' % self.name.lower()] + + super(GoPackage, self).sanity_check_step(custom_paths=custom_paths, custom_commands=custom_commands) diff --git a/test/easyblocks/module.py b/test/easyblocks/module.py index 52e1a351d1..b3a955a021 100644 --- a/test/easyblocks/module.py +++ b/test/easyblocks/module.py @@ -41,6 +41,7 @@ import easybuild.tools.toolchain.utilities as tc_utils from easybuild.base import fancylogger from easybuild.base.testing import TestCase +from easybuild.easyblocks.generic.gopackage import GoPackage from easybuild.easyblocks.generic.intelbase import IntelBase from easybuild.easyblocks.generic.pythonbundle import PythonBundle from easybuild.easyblocks.imod import EB_IMOD @@ -234,6 +235,11 @@ def template_module_only_test(self, easyblock, name='foo', version='1.3.2', extr # $EBROOTPYTHON must be set for PythonBundle easyblock os.environ['EBROOTPYTHON'] = '/fake/install/prefix/Python/2.7.14-foss-2018a' + elif app_class == GoPackage: + # $EBROOTGO must be set for GoPackage easyblock + os.environ['EBROOTGO'] = '/fake/install/prefix/Go/1.14' + os.environ['EBVERSIONGO'] = '1.14' + 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([