Build systems: add MSBuild and update NMake (#34659)

Add/update build systems used to build packages on Windows.
This commit is contained in:
John W. Parent 2023-01-10 20:03:15 -05:00 committed by GitHub
parent b94030cc5d
commit 7365d138fb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 225 additions and 53 deletions

View file

@ -584,14 +584,19 @@ def set_module_variables_for_package(pkg):
m.make = MakeExecutable("make", jobs) m.make = MakeExecutable("make", jobs)
m.gmake = MakeExecutable("gmake", jobs) m.gmake = MakeExecutable("gmake", jobs)
m.ninja = MakeExecutable("ninja", jobs, supports_jobserver=False) m.ninja = MakeExecutable("ninja", jobs, supports_jobserver=False)
# TODO: johnwparent: add package or builder support to define these build tools
# for now there is no entrypoint for builders to define these on their
# own
if sys.platform == "win32":
m.nmake = Executable("nmake")
m.msbuild = Executable("msbuild")
# analog to configure for win32
m.cscript = Executable("cscript")
# Find the configure script in the archive path # Find the configure script in the archive path
# Don't use which for this; we want to find it in the current dir. # Don't use which for this; we want to find it in the current dir.
m.configure = Executable("./configure") m.configure = Executable("./configure")
if sys.platform == "win32":
m.nmake = Executable("nmake")
m.msbuild = Executable("msbuild")
# Standard CMake arguments # Standard CMake arguments
m.std_cmake_args = spack.build_systems.cmake.CMakeBuilder.std_args(pkg) m.std_cmake_args = spack.build_systems.cmake.CMakeBuilder.std_args(pkg)
m.std_meson_args = spack.build_systems.meson.MesonBuilder.std_args(pkg) m.std_meson_args = spack.build_systems.meson.MesonBuilder.std_args(pkg)

View file

@ -0,0 +1,120 @@
# Copyright 2013-2022 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
class MSBuildPackage(spack.package_base.PackageBase):
"""Specialized class for packages built using Visual Studio project files or solutions."""
#: This attribute is used in UI queries that need to know the build
#: system base class
build_system_class = "MSBuildPackage"
build_system("msbuild")
conflicts("platform=linux", when="build_system=msbuild")
conflicts("platform=darwin", when="build_system=msbuild")
conflicts("platform=cray", when="build_system=msbuild")
@spack.builder.builder("msbuild")
class MSBuildBuilder(BaseBuilder):
"""The MSBuild builder encodes the most common way of building software with
Mircosoft's MSBuild tool. It has two phases that can be overridden, if need be:
1. :py:meth:`~.MSBuildBuilder.build`
2. :py:meth:`~.MSBuildBuilder.install`
It is usually necessary to override the :py:meth:`~.MSBuildBuilder.install`
phase as many packages with MSBuild systems neglect to provide an install
target. The default install phase will attempt to invoke an install target
from MSBuild. If none exists, this will result in a build failure
For a finer tuning you may override:
+-----------------------------------------------+---------------------+
| **Method** | **Purpose** |
+===============================================+=====================+
| :py:attr:`~.MSBuildBuilder.build_targets` | Specify ``msbuild`` |
| | targets for the |
| | build phase |
+-----------------------------------------------+---------------------+
| :py:attr:`~.MSBuildBuilder.install_targets` | Specify ``msbuild`` |
| | targets for the |
| | install phase |
+-----------------------------------------------+---------------------+
| :py:meth:`~.MSBuildBuilder.build_directory` | Directory where the |
| | project sln/vcxproj |
| | is located |
+-----------------------------------------------+---------------------+
"""
phases = ("build", "install")
#: Targets for ``make`` during the :py:meth:`~.MSBuildBuilder.build` phase
build_targets: List[str] = []
#: Targets for ``msbuild`` during the :py:meth:`~.MSBuildBuilder.install` phase
install_targets: List[str] = ["INSTALL"]
@property
def build_directory(self):
"""Return the directory containing the MSBuild solution or vcxproj."""
return self.pkg.stage.source_path
@property
def toolchain_version(self):
"""Return currently targeted version of MSVC toolchain
Override this method to select a specific version of the toolchain or change
selection heuristics.
Default is whatever version of msvc has been selected by concretization"""
return self.compiler.msvc_version
@property
def std_msbuild_args(self):
"""Return common msbuild cl arguments, for now just toolchain"""
return [self.define("PlatformToolset", self.toolchain_version)]
def define_targets(self, *targets):
return "/target:" + ";".join(targets) if targets else ""
def define(self, msbuild_arg, value):
return "/p:{}={}".format(msbuild_arg, value)
def msbuild_args(self):
"""Define build arguments to MSbuild. This is an empty list by default.
Individual packages should override to specify MSBuild args to command line
PlatformToolset is already defined an can be controlled via the `toolchain_version`
property"""
return []
def msbuild_install_args(self):
"""Define install arguments to MSBuild outside of the INSTALL target. This is the same
as `msbuild_args` by default."""
return self.msbuild_args()
def build(self, pkg, spec, prefix):
"""Run "msbuild" on the build targets specified by the builder."""
with fs.working_dir(self.build_directory):
inspect.getmodule(self.pkg).msbuild(
*self.std_msbuild_args,
*self.msbuild_args(),
self.define_targets(*self.build_targets),
)
def install(self, pkg, spec, prefix):
"""Run "msbuild" on the install targets specified by the builder.
This is INSTALL by default"""
with fs.working_dir(self.build_directory):
inspect.getmodule(self.pkg).msbuild(
*self.msbuild_install_args(), self.define_targets(*self.install_targets)
)

View file

@ -3,7 +3,7 @@
# #
# SPDX-License-Identifier: (Apache-2.0 OR MIT) # SPDX-License-Identifier: (Apache-2.0 OR MIT)
import inspect import inspect
from typing import List from typing import List # novm
import llnl.util.filesystem as fs import llnl.util.filesystem as fs
@ -19,7 +19,7 @@ class NMakePackage(spack.package_base.PackageBase):
#: This attribute is used in UI queries that need to know the build #: This attribute is used in UI queries that need to know the build
#: system base class #: system base class
build_system_class = "NmakePackage" build_system_class = "NMakePackage"
build_system("nmake") build_system("nmake")
conflicts("platform=linux", when="build_system=nmake") conflicts("platform=linux", when="build_system=nmake")
@ -30,73 +30,119 @@ class NMakePackage(spack.package_base.PackageBase):
@spack.builder.builder("nmake") @spack.builder.builder("nmake")
class NMakeBuilder(BaseBuilder): class NMakeBuilder(BaseBuilder):
"""The NMake builder encodes the most common way of building software with """The NMake builder encodes the most common way of building software with
NMake on Windows. It has three phases that can be overridden, if need be: Mircosoft's NMake tool. It has two phases that can be overridden, if need be:
1. :py:meth:`~.NMakeBuilder.edit` 1. :py:meth:`~.NMakeBuilder.build`
2. :py:meth:`~.NMakeBuilder.build` 2. :py:meth:`~.NMakeBuilder.install`
3. :py:meth:`~.NMakeBuilder.install`
It is usually necessary to override the :py:meth:`~.NMakeBuilder.edit` It is usually necessary to override the :py:meth:`~.NMakeBuilder.install`
phase (which is by default a no-op), while the other two have sensible defaults. 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: For a finer tuning you may override:
+--------------------------------------------+--------------------+ +-----------------------------------------------+---------------------+
| **Method** | **Purpose** | | **Method** | **Purpose** |
+============================================+====================+ +===============================================+=====================+
| :py:attr:`~.NMakeBuilder.build_targets` | Specify ``nmake`` | | :py:attr:`~.NMakeBuilder.build_targets` | Specify ``nmake`` |
| | targets for the | | | targets for the |
| | build phase | | | build phase |
+--------------------------------------------+--------------------+ +-----------------------------------------------+---------------------+
| :py:attr:`~.NMakeBuilder.install_targets` | Specify ``nmake`` | | :py:attr:`~.NMakeBuilder.install_targets` | Specify ``nmake`` |
| | targets for the | | | targets for the |
| | install phase | | | install phase |
+--------------------------------------------+--------------------+ +-----------------------------------------------+---------------------+
| :py:meth:`~.NMakeBuilder.build_directory` | Directory where the| | :py:meth:`~.NMakeBuilder.build_directory` | Directory where the |
| | Makefile is located| | | project makefile |
+--------------------------------------------+--------------------+ | | is located |
+-----------------------------------------------+---------------------+
""" """
phases = ("edit", "build", "install") phases = ("build", "install")
#: Names associated with package methods in the old build-system format
legacy_methods = ("check", "installcheck")
#: Names associated with package attributes in the old build-system format
legacy_attributes = (
"build_targets",
"install_targets",
"build_time_test_callbacks",
"install_time_test_callbacks",
"build_directory",
)
#: Targets for ``make`` during the :py:meth:`~.NMakeBuilder.build` phase #: Targets for ``make`` during the :py:meth:`~.NMakeBuilder.build` phase
build_targets: List[str] = [] build_targets: List[str] = []
#: Targets for ``make`` during the :py:meth:`~.NMakeBuilder.install` phase #: Targets for ``make`` during the :py:meth:`~.NMakeBuilder.install` phase
install_targets = ["install"] install_targets: List[str] = ["INSTALL"]
#: Callback names for build-time test @property
build_time_test_callbacks = ["check"] def ignore_quotes(self):
"""Control whether or not Spack warns about quoted arguments passed to
#: Callback names for install-time test build utilities. If this is True, spack will not warn about quotes.
install_time_test_callbacks = ["installcheck"] This is useful in cases with a space in the path or when build scripts
require quoted arugments."""
return False
@property @property
def build_directory(self): def build_directory(self):
"""Return the directory containing the main Makefile.""" """Return the directory containing the makefile."""
return self.pkg.stage.source_path return self.pkg.stage.source_path if not self.makefile_root else self.makefile_root
def edit(self, pkg, spec, prefix): @property
"""Edit the Makefile before calling make. The default is a no-op.""" def std_nmake_args(self):
pass """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_dir
@property
def nmakefile_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 ""
def define(self, nmake_arg, value):
"""Helper method to format arguments to nmake command line"""
return "{}={}".format(nmake_arg, value)
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)
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 []
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 []
def build(self, pkg, spec, prefix): def build(self, pkg, spec, prefix):
"""Run "make" on the build targets specified by the builder.""" """Run "nmake" on the build targets specified by the builder."""
opts = self.std_nmake_args
opts += self.nmake_args()
if self.nmakefile_name:
opts.append("/f {}".format(self.nmakefile_name))
with fs.working_dir(self.build_directory): with fs.working_dir(self.build_directory):
inspect.getmodule(self.pkg).nmake(*self.build_targets) inspect.getmodule(self.pkg).nmake(
*opts, *self.build_targets, ignore_quotes=self.ignore_quotes
)
def install(self, pkg, spec, prefix): def install(self, pkg, spec, prefix):
"""Run "make" on the install targets specified by the builder.""" """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.nmakefile_name:
opts.append("/f {}".format(self.nmakefile_name))
opts.append(self.define("PREFIX", prefix))
with fs.working_dir(self.build_directory): with fs.working_dir(self.build_directory):
inspect.getmodule(self.pkg).nmake(*self.install_targets) inspect.getmodule(self.pkg).nmake(
*opts, *self.install_targets, ignore_quotes=self.ignore_quotes
)

View file

@ -45,6 +45,7 @@
from spack.build_systems.makefile import MakefilePackage from spack.build_systems.makefile import MakefilePackage
from spack.build_systems.maven import MavenPackage from spack.build_systems.maven import MavenPackage
from spack.build_systems.meson import MesonPackage from spack.build_systems.meson import MesonPackage
from spack.build_systems.msbuild import MSBuildPackage
from spack.build_systems.nmake import NMakePackage from spack.build_systems.nmake import NMakePackage
from spack.build_systems.octave import OctavePackage from spack.build_systems.octave import OctavePackage
from spack.build_systems.oneapi import ( from spack.build_systems.oneapi import (