# Copyright 2013-2024 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import inspect
from typing import List # novm
import llnl.util.filesystem as fs
import spack.builder
import spack.package_base
from spack.directives import build_system, conflicts
from ._checks import BaseBuilder
[docs]
class NMakePackage(spack.package_base.PackageBase):
"""Specialized class for packages built using a Makefiles."""
#: This attribute is used in UI queries that need to know the build
#: system base class
build_system_class = "NMakePackage"
build_system("nmake")
conflicts("platform=linux", when="build_system=nmake")
conflicts("platform=darwin", when="build_system=nmake")
conflicts("platform=cray", when="build_system=nmake")
[docs]
@spack.builder.builder("nmake")
class NMakeBuilder(BaseBuilder):
"""The NMake builder encodes the most common way of building software with
Mircosoft's NMake tool. It has two phases that can be overridden, if need be:
1. :py:meth:`~.NMakeBuilder.build`
2. :py:meth:`~.NMakeBuilder.install`
It is usually necessary to override the :py:meth:`~.NMakeBuilder.install`
phase as many packages with NMake systems neglect to provide an install
target. The default install phase will attempt to invoke an install target
from NMake. If none exists, this will result in a build failure
For a finer tuning you may override:
+-----------------------------------------------+---------------------+
| **Method** | **Purpose** |
+===============================================+=====================+
| :py:attr:`~.NMakeBuilder.build_targets` | Specify ``nmake`` |
| | targets for the |
| | build phase |
+-----------------------------------------------+---------------------+
| :py:attr:`~.NMakeBuilder.install_targets` | Specify ``nmake`` |
| | targets for the |
| | install phase |
+-----------------------------------------------+---------------------+
| :py:meth:`~.NMakeBuilder.build_directory` | Directory where the |
| | project makefile |
| | is located |
+-----------------------------------------------+---------------------+
"""
phases = ("build", "install")
#: Targets for ``make`` during the :py:meth:`~.NMakeBuilder.build` phase
build_targets: List[str] = []
#: Targets for ``make`` during the :py:meth:`~.NMakeBuilder.install` phase
install_targets: List[str] = ["INSTALL"]
@property
def ignore_quotes(self):
"""Control whether or not Spack warns about quoted arguments passed to
build utilities. If this is True, spack will not warn about quotes.
This is useful in cases with a space in the path or when build scripts
require quoted arugments."""
return False
@property
def build_directory(self):
"""Return the directory containing the makefile."""
return (
fs.windows_sfn(self.pkg.stage.source_path)
if not self.makefile_root
else fs.windows_sfn(self.makefile_root)
)
@property
def std_nmake_args(self):
"""Returns list of standards arguments provided to NMake
Currently is only /NOLOGO"""
return ["/NOLOGO"]
@property
def makefile_root(self):
"""The relative path to the directory containing nmake makefile
This path is relative to the root of the extracted tarball,
not to the ``build_directory``. Defaults to the current directory.
"""
return self.stage.source_path
@property
def makefile_name(self):
"""Name of the current makefile. This is currently an empty value.
If a project defines this value, it will be used with the /f argument
to provide nmake an explicit makefile. This is usefule in scenarios where
there are multiple nmake files in the same directory."""
return ""
[docs]
def define(self, nmake_arg, value):
"""Helper method to format arguments to nmake command line"""
return "{}={}".format(nmake_arg, value)
[docs]
def override_env(self, var_name, new_value):
"""Helper method to format arguments for overridding env variables on the
nmake command line. Returns properly formatted argument"""
return "/E{}={}".format(var_name, new_value)
[docs]
def nmake_args(self):
"""Define build arguments to NMake. This is an empty list by default.
Individual packages should override to specify NMake args to command line"""
return []
[docs]
def nmake_install_args(self):
"""Define arguments appropriate only for install phase to NMake.
This is an empty list by default.
Individual packages should override to specify NMake args to command line"""
return []
[docs]
def build(self, pkg, spec, prefix):
"""Run "nmake" on the build targets specified by the builder."""
opts = self.std_nmake_args
opts += self.nmake_args()
if self.makefile_name:
opts.append("/F{}".format(self.makefile_name))
with fs.working_dir(self.build_directory):
inspect.getmodule(self.pkg).nmake(
*opts, *self.build_targets, ignore_quotes=self.ignore_quotes
)
[docs]
def install(self, pkg, spec, prefix):
"""Run "nmake" on the install targets specified by the builder.
This is INSTALL by default"""
opts = self.std_nmake_args
opts += self.nmake_args()
opts += self.nmake_install_args()
if self.makefile_name:
opts.append("/F{}".format(self.makefile_name))
opts.append(self.define("PREFIX", prefix))
with fs.working_dir(self.build_directory):
inspect.getmodule(self.pkg).nmake(
*opts, *self.install_targets, ignore_quotes=self.ignore_quotes
)