diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/spack/docs/packaging_guide.rst | 121 | ||||
-rw-r--r-- | lib/spack/spack/hooks/licensing.py | 136 | ||||
-rw-r--r-- | lib/spack/spack/package.py | 33 |
3 files changed, 286 insertions, 4 deletions
diff --git a/lib/spack/docs/packaging_guide.rst b/lib/spack/docs/packaging_guide.rst index 1b7941ab24..650e0ee3b2 100644 --- a/lib/spack/docs/packaging_guide.rst +++ b/lib/spack/docs/packaging_guide.rst @@ -703,6 +703,127 @@ Fetching a revision Subversion branches are handled as part of the directory structure, so you can check out a branch or tag by changing the ``url``. + +.. _license: + +Licensed software +------------------------------------------ + +In order to install licensed software, Spack needs to know a few more +details about a package. The following class attributes should be defined. + +``license_required`` +~~~~~~~~~~~~~~~~~~~~~ + +Boolean. If set to ``True``, this software requires a license. If set to +``False``, all of the following attributes will be ignored. Defaults to +``False``. + +``license_comment`` +~~~~~~~~~~~~~~~~~~~~~ + +String. Contains the symbol used by the license manager to denote a comment. +Defaults to ``#``. + +``license_files`` +~~~~~~~~~~~~~~~~~~~~~ + +List of strings. These are files that the software searches for when +looking for a license. All file paths must be relative to the installation +directory. More complex packages like Intel may require multiple +licenses for individual components. Defaults to the empty list. + +``license_vars`` +~~~~~~~~~~~~~~~~~~~~~ + +List of strings. Environment variables that can be set to tell the software +where to look for a license if it is not in the usual location. Defaults +to the empty list. + +``license_url`` +~~~~~~~~~~~~~~~~~~~~~ + +String. A URL pointing to license setup instructions for the software. +Defaults to the empty string. + +For example, let's take a look at the package for the PGI compilers. + +.. code-block:: python + + # Licensing + license_required = True + license_comment = '#' + license_files = ['license.dat'] + license_vars = ['PGROUPD_LICENSE_FILE', 'LM_LICENSE_FILE'] + license_url = 'http://www.pgroup.com/doc/pgiinstall.pdf' + +As you can see, PGI requires a license. Its license manager, FlexNet, uses +the ``#`` symbol to denote a comment. It expects the license file to be +named ``license.dat`` and to be located directly in the installation prefix. +If you would like the installation file to be located elsewhere, simply set +``PGROUPD_LICENSE_FILE`` or ``LM_LICENSE_FILE`` after installation. For +further instructions on installation and licensing, see the URL provided. + +Let's walk through a sample PGI installation to see exactly what Spack is +and isn't capable of. Since PGI does not provide a download URL, it must +be downloaded manually. It can either be added to a mirror or located in +the current directory when ``spack install pgi`` is run. See :ref:`mirrors` +for instructions on setting up a mirror. + +After running ``spack install pgi``, the first thing that will happen is +Spack will create a global license file located at +``$SPACK_ROOT/etc/spack/licenses/pgi/license.dat``. It will then open up the +file using the editor set in ``$EDITOR``, or vi if unset. It will look like +this: + +.. code-block:: + + # A license is required to use pgi. + # + # The recommended solution is to store your license key in this global + # license file. After installation, the following symlink(s) will be + # added to point to this file (relative to the installation prefix): + # + # license.dat + # + # Alternatively, use one of the following environment variable(s): + # + # PGROUPD_LICENSE_FILE + # LM_LICENSE_FILE + # + # If you choose to store your license in a non-standard location, you may + # set one of these variable(s) to the full pathname to the license file, or + # port@host if you store your license keys on a dedicated license server. + # You will likely want to set this variable in a module file so that it + # gets loaded every time someone tries to use pgi. + # + # For further information on how to acquire a license, please refer to: + # + # http://www.pgroup.com/doc/pgiinstall.pdf + # + # You may enter your license below. + +You can add your license directly to this file, or tell FlexNet to use a +license stored on a separate license server. Here is an example that +points to a license server called licman1: + +.. code-block:: + + SERVER licman1.mcs.anl.gov 00163eb7fba5 27200 + USE_SERVER + +If your package requires the license to install, you can reference the +location of this global license using ``self.global_license_file``. +After installation, symlinks for all of the files given in +``license_files`` will be created, pointing to this global license. +If you install a different version or variant of the package, Spack +will automatically detect and reuse the already existing global license. + +If the software you are trying to package doesn't rely on license files, +Spack will print a warning message, letting the user know that they +need to set an environment variable or pointing them to installation +documentation. + .. _patching: Patches diff --git a/lib/spack/spack/hooks/licensing.py b/lib/spack/spack/hooks/licensing.py new file mode 100644 index 0000000000..31f15cc3e9 --- /dev/null +++ b/lib/spack/spack/hooks/licensing.py @@ -0,0 +1,136 @@ +import os + +import spack +import llnl.util.tty as tty +from llnl.util.filesystem import join_path + + +def pre_install(pkg): + """This hook handles global license setup for licensed software.""" + if pkg.license_required: + set_up_license(pkg) + + +def set_up_license(pkg): + """Prompt the user, letting them know that a license is required. + + For packages that rely on license files, a global license file is + created and opened for editing. + + For packages that rely on environment variables to point to a + license, a warning message is printed. + + For all other packages, documentation on how to set up a license + is printed.""" + + # If the license can be stored in a file, create one + if pkg.license_files: + license_path = pkg.global_license_file + if not os.path.exists(license_path): + # Create a new license file + write_license_file(pkg, license_path) + # Open up file in user's favorite $EDITOR for editing + spack.editor(license_path) + tty.msg("Added global license file %s" % license_path) + else: + # Use already existing license file + tty.msg("Found already existing license %s" % license_path) + + # If not a file, what about an environment variable? + elif pkg.license_vars: + tty.warn("A license is required to use %s. Please set %s to the " + "full pathname to the license file, or port@host if you" + " store your license keys on a dedicated license server" % + (pkg.name, ' or '.join(pkg.license_vars))) + + # If not a file or variable, suggest a website for further info + elif pkg.license_url: + tty.warn("A license is required to use %s. See %s for details" % + (pkg.name, pkg.license_url)) + + # If all else fails, you're on your own + else: + tty.warn("A license is required to use %s" % pkg.name) + + +def write_license_file(pkg, license_path): + """Writes empty license file. + + Comments give suggestions on alternative methods of + installing a license.""" + + comment = pkg.license_comment + + # Global license directory may not already exist + if not os.path.exists(os.path.dirname(license_path)): + os.makedirs(os.path.dirname(license_path)) + license = open(license_path, 'w') + + # License files + license.write("""\ +{0} A license is required to use {1}. +{0} +{0} The recommended solution is to store your license key in this global +{0} license file. After installation, the following symlink(s) will be +{0} added to point to this file (relative to the installation prefix): +{0} +""".format(comment, pkg.name)) + + for filename in pkg.license_files: + license.write("{0}\t{1}\n".format(comment, filename)) + + license.write("{0}\n".format(comment)) + + # Environment variables + if pkg.license_vars: + license.write("""\ +{0} Alternatively, use one of the following environment variable(s): +{0} +""".format(comment)) + + for var in pkg.license_vars: + license.write("{0}\t{1}\n".format(comment, var)) + + license.write("""\ +{0} +{0} If you choose to store your license in a non-standard location, you may +{0} set one of these variable(s) to the full pathname to the license file, or +{0} port@host if you store your license keys on a dedicated license server. +{0} You will likely want to set this variable in a module file so that it +{0} gets loaded every time someone tries to use {1}. +{0} +""".format(comment, pkg.name)) + + # Documentation + if pkg.license_url: + license.write("""\ +{0} For further information on how to acquire a license, please refer to: +{0} +{0}\t{1} +{0} +""".format(comment, pkg.license_url)) + + license.write("""\ +{0} You may enter your license below. + +""".format(comment)) + + license.close() + + +def post_install(pkg): + """This hook symlinks local licenses to the global license for + licensed software.""" + if pkg.license_required: + symlink_license(pkg) + + +def symlink_license(pkg): + """Create local symlinks that point to the global license file.""" + target = pkg.global_license_file + for filename in pkg.license_files: + link_name = join_path(pkg.prefix, filename) + if os.path.exists(target): + os.symlink(target, link_name) + tty.msg("Added local symlink %s to global license file" % + link_name) diff --git a/lib/spack/spack/package.py b/lib/spack/spack/package.py index 8e6cf32954..8f8eb6db64 100644 --- a/lib/spack/spack/package.py +++ b/lib/spack/spack/package.py @@ -374,6 +374,22 @@ class Package(object): if not hasattr(self, 'list_depth'): self.list_depth = 1 + # Set default licensing information + if not hasattr(self, 'license_required'): + self.license_required = False + + if not hasattr(self, 'license_comment'): + self.license_comment = '#' + + if not hasattr(self, 'license_files'): + self.license_files = [] + + if not hasattr(self, 'license_vars'): + self.license_vars = [] + + if not hasattr(self, 'license_url'): + self.license_url = None + # Set up some internal variables for timing. self._fetch_time = 0.0 self._total_time = 0.0 @@ -382,6 +398,16 @@ class Package(object): spack.repo.get(self.extendee_spec)._check_extendable() @property + def global_license_file(self): + """Returns the path where a global license file should be stored.""" + if not self.license_files: + return + spack_root = ancestor(__file__, 4) + global_license_dir = join_path(spack_root, 'etc', 'spack', 'licenses') + return join_path(global_license_dir, self.name, + os.path.basename(self.license_files[0])) + + @property def version(self): if not self.spec.versions.concrete: raise ValueError("Can only get of package with concrete version.") @@ -882,6 +908,7 @@ class Package(object): def build_process(): """Forked for each build. Has its own process and python module space set up by build_environment.fork().""" + start_time = time.time() if not fake: if not skip_patch: @@ -1320,11 +1347,9 @@ class Package(object): def rpath(self): """Get the rpath this package links with, as a list of paths.""" rpaths = [self.prefix.lib, self.prefix.lib64] - rpaths.extend(d.prefix.lib - for d in self.spec.traverse(root=False) + rpaths.extend(d.prefix.lib for d in self.spec.traverse(root=False) if os.path.isdir(d.prefix.lib)) - rpaths.extend(d.prefix.lib64 - for d in self.spec.traverse(root=False) + rpaths.extend(d.prefix.lib64 for d in self.spec.traverse(root=False) if os.path.isdir(d.prefix.lib64)) return rpaths |