Skip to content

Conversation

@kwryankrattiger
Copy link
Contributor

Extracted updates to GL detection from #192

Comment on lines 63 to 67
def _dependencies_cmake_extra_args(pkg: PackageBase, args: List[str]) -> None:
for dep in pkg.spec.traverse(deptype=("build", "link")):
extra_args = getattr(dep.package, "cmake_extra_args", None)
if extra_args:
args.extend(extra_args)
Copy link
Member

@alalazo alalazo Oct 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, do we really need something this general only for mesa? Can't we use one of the other callbacks?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't see other callbacks for injecting cmake configure flags. And this is can be used for any package that doesn't provide a cmake config file, which is many, to control options in built-in cmake find modules. Mesa just happens to be the first one.

I can't use link flags, because that will still result in the current issue where libOpenGL and libGL both get linked, and then cause runtime errors.

I am open to other suggestions, but this is fixing one of the longest standing GL issues in Spack so I would rather not hold it up too long. And when I say long standing, I mean since CMake FindOpenGL started supporting GLVND (3.10, circa 2017).

Since it is packages repo API, there is no guarantee on stability, so if we get a better idea later there isn't really much harm in deprecating/dropping it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alalazo do you have a better solution here? If not, I would really like to move this forward for the next release of packages as it fixes some bugs that have been around for a couple of years.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm looking at this again, and it seems very controversial to me:

  • Why build, link dependencies only? Don't we ever need run dependencies?
  • Why do we go beyond direct dependencies?
  • There is no way to turn off this behavior from a dependent, e.g. if we don't want extra arguments to be injected
  • The name cmake_extra_args is confusing, since it's not clear these arguments will be injected to transitive dependencies

Copy link
Contributor Author

@kwryankrattiger kwryankrattiger Nov 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree this can probably be direct deps only. The reason for only build/link is I copied the requirement for python. I didn't convert python to do this because it was outside the immediate scope of the change, but I can to reduce the number of different ways this is handled.

If GL is provided by Spack built mesa, the only option is LEGACY and not having this set for a FindOpenGL will result in borked builds. So there is no option for this, it is what it has to be.

I would say we have now increased the use case to two for this feature, both Mesa and Python. It would acually probably be nice to also provide this for MPI to set MPI_C_COMPILER and MPI_CXX_COMPILIER hints since that is something that is done in a number of places for fix FindMPI. So the usefulness of this type of feature is beyond just GL.

For the API and function, it would probably be named better. set_dependent_cmake_args(self, pkg, args). This would allow something like this, so it is up to the provider whether or not it is optional.

def set_dependent_cmake_args(self, pkg: PackageBase, cmake_args: List[str]):
    if getattr(pkg, "find_python_hints", True):
        return

    cmake_args.extend([...])

Comment on lines 63 to 67
def _dependencies_cmake_extra_args(pkg: PackageBase, args: List[str]) -> None:
for dep in pkg.spec.traverse(deptype=("build", "link")):
extra_args = getattr(dep.package, "cmake_extra_args", None)
if extra_args:
args.extend(extra_args)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm looking at this again, and it seems very controversial to me:

  • Why build, link dependencies only? Don't we ever need run dependencies?
  • Why do we go beyond direct dependencies?
  • There is no way to turn off this behavior from a dependent, e.g. if we don't want extra arguments to be injected
  • The name cmake_extra_args is confusing, since it's not clear these arguments will be injected to transitive dependencies


_conditional_cmake_defaults(pkg, args)
_maybe_set_python_hints(pkg, args)
_dependencies_cmake_extra_args(pkg, args)
Copy link
Member

@alalazo alalazo Nov 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you have a better solution here?

Can't we just deal with this in the same way we do for Python above, and code a _maybe_set_mesa_args? If we get more cases we can think about generalizing the mechanism. In any case it's probably sensible to stick to just direct deps.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also want to do this for MPI, there are 27 packages where we do the same thing we do for python for MPI. That is at least three packages that I deal with prefequently enough that are trying to inject spack related hints into the CMake configure.

I think it makes some sense to put this on the packages themselves. a) it makes the CMakePackage more adaptable and b) changing the configure flags in the providing package is more likely to trigger a rebuild of the things that depended on it than changing the CMakePackage

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the implementation, I think we can inline _dependencies_cmake_extra_args.

Also, given we are generalizing the way dependents inject arguments, we should extract the boost related bits of _conditional_cmake_defaults and move them to the boost package.

@kwryankrattiger kwryankrattiger force-pushed the gl_updates_from_192 branch 2 times, most recently from 4fc65ed to a3ddb48 Compare November 21, 2025 15:01
]
)
def _dependencies_cmake_extra_args(pkg: PackageBase, args: List[str]) -> None:
for dep in pkg.spec.dependencies(deptype=("build", "link")):
Copy link
Member

@alalazo alalazo Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not use arbitrarily build,link to traverse dependencies. Maybe it's better to decide when to perform a callback using the UseMode defined in spack.build_environment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is UseMode in the package API? I am not seeing it imported in package.py.

I am also not sure how that would be used here, this particular function is run as a part of the setup for the cmake configure. My understanding is core shouldn't know anything about since it is a repo defined extension of the package API.

The choice to use build, link here is not arbitrary, we are looking at the dependencies that are needing during the build/link of the package. Packages that marked run shouldn't influence the build imo. If there is an exception to that later that can be addressed, but I am not thinking of any case for that now.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about test dependencies? Also, is there no case where we should record a PATH or anything of a run dependency?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am also not sure how that would be used here, this particular function is run as a part of the setup for the cmake configure. My understanding is core shouldn't know anything about since it is a repo defined extension of the package API.

In general core can prescribe new workflows, e.g. "build system methods listed in ... will be called at this point with this signature", so if we want to go for UseMode I guess we can.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about test dependencies?

I would classify those the same as run for injecting cmake args.

Is an example of a test-only dep that would need to inject cmake arguments? It seems very unlikely to be the case where a test dep that is not also a build or link dep would influence build system flags.

if we want to go for UseMode I guess we can.

I still don't see how this would be better, or even implemented. We are injecting args into build system details that are more or less opaque to core. I am also uncertain how many other cases outside of CMake are going to have this kind of standard configure customization that can be applied like this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem to be answered:

Also, is there no case where we should record a PATH or anything of a run dependency?

I still feel like build,link is rather arbitrary as a choice. What if a tool needs python or perl only as a run dependency, and those packages want to inject some flag to CMake to be found properly?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about it the best choice would probably be to iterate over all dependencies, and let each package decide when to inject arguments or not.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The selection of build and link here are not arbitrary at all. As I pointed out previously I was quite intentional picking those.

My feeling is if a package is a run dep only then CMake shouldn't be finding them in the first place, or aware of them at all. If CMake is being used to generate runtime launcher scripts that run python or perl or something else then those should be labeled as build deps since we need the information about the python at build time to generate the files.

I think if there is ever a real case where we need to collect cmake flags from a dependency that is only labeled run then we can update the API accordingly, but I think the use case you are describing will likely only arise in ill-constrained dependencies and we should fix those dependency relationships rather than work around them with more complexity.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about it the best choice would probably be to iterate over all dependencies, and let each package decide when to inject arguments or not.

☝️ This is such that each package is responsible for deciding when to return arguments (the base class asks all dependencies and each one decides what to return).

If a package knows it doesn't need to inject flags when it is a run dependency (why?) it can inspect pkg and return nothing. Likewise, if the flags we need are different depending on the version of CMake being used (PYTHON_EXECUTABLE?), we can inspect pkg and avoid injecting redundant flags.

Also, we don't account for test dependencies which may be used for build time tests, right? What if python is needed to run some test script etc ? Why are we creating a self imposed constraint instead of being agnostic of the dependency type and let the package decide?

Copy link
Member

@haampie haampie Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that pkg.spec.dependencies() with all deptypes is more straight-forward and least surprising.

My feeling is if a package is a run dep only then CMake shouldn't be finding them in the first place

In practice this is not the case. There are enough instances of a run type dep where CMake still wants to know things about its executables. Sometimes people like to create absolute shebangs, etc. Specifying those are "build" deps would be wrong conceptually, as those are for the host, whereas the shebangs are for the target arch (would we ever do cross compiling)

Test deps should definitely be included.

UseMode should not be relevant here, given that the package being considered is always the one that's being built. UseMode is only relevant if you care about runtime vs build time and packages a few deps deep from the root.

@kwryankrattiger kwryankrattiger force-pushed the gl_updates_from_192 branch 2 times, most recently from f0e93b2 to a6a39fd Compare November 24, 2025 23:10
@kwryankrattiger
Copy link
Contributor Author

@spackbot run pipeline

@spackbot-app
Copy link

spackbot-app bot commented Nov 26, 2025

I've started that pipeline for you!

@@ -1289,6 +1280,26 @@ def setup_dependent_package(self, module, dependent_spec):
module.python_platlib = join_path(dependent_spec.prefix, self.platlib)
module.python_purelib = join_path(dependent_spec.prefix, self.purelib)

def dependent_cmake_args(self, pkg: PackageBase) -> List[str]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I may be misunderstanding, but it seems like this injects flags to 99% of CMake packages that depend on Python unless they explicitly disable this? I don't love injecting non-standard flags, as they end up as red herring warning messages for downstream packages. I would much rather opt-in, and opt-in one flag at a time. spec['python'] already has an easy and convenient way to grab the Python command. I think packages should manually use this only when needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is already true, I just moved the logic for this from build_systems/cmake.py to python/package.py. This change specifically should amount to a noop.

Without these flags, cmake cannot find spack installed python. It is a similar issue for MPI, which is why you see so many cmake projects passing MPI hints in a few different iterations of the same general form (see the mpi/package.py changes for a flavor of it)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I'm okay with no-ops, but I also think we should remove this and only add it to packages that need it. Doesn't have to be in this PR though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure what you mean by this.

These flags are only added to projects that directly depend on python (or whatever project has the cmake args function) and are CMake projects. Of those projects, very few don't need to have these hints passed, and even fewer will have issues if the are passed. And for the projects where that is the case, we already of that opt-out option to set the package property find_python_hints = False.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very few need all 3 flags, and often only need them when +python.

Copy link
Contributor Author

@kwryankrattiger kwryankrattiger Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. This is only applied when the spec has a python build or link dep. For packages with a python variant that will usually only be true for the +python. I have a feeling there are vanishingly few cases where the conditions for these flags are not met under those conditions.

We could be smarter about which flags we pass, they could be conditionally applied based on the CMake version. But since it doesn't hurt anything to pass all three all the time I don't know if there is value in making it more complicated.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only applied when the spec has a python build or link dep.

I think the Python package should have code to decide when to inject these flags, if that is relevant.

If there is any need to check e.g. the cmake version being used or the dependency types of python, before injecting Python flags, we should do it here. The code in the build system should probably be as agnostic as possible, and just perform a callback on packages implementing the method.

@kwryankrattiger
Copy link
Contributor Author

@spackbot run pipeline

@spackbot-app
Copy link

spackbot-app bot commented Dec 18, 2025

I've started that pipeline for you!

Copy link
Member

@alalazo alalazo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a few more comments. Summary:

  1. I think we should use this PR only for changes related to the cmake build system, anything else should be extracted in a separate PR and merged
  2. The build system should not make assumption on when to perform the callback depending on the dependency type. It should be on the called package to decide whether it wants to return something or not
  3. No broad Exception catch in build systems, which could mask other errors

I also asked @haampie to give a second review to this PR. Since it touches a part of the repo that is used in many other places.

]
)
def _dependencies_cmake_extra_args(pkg: PackageBase, args: List[str]) -> None:
for dep in pkg.spec.dependencies(deptype=("build", "link")):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem to be answered:

Also, is there no case where we should record a PATH or anything of a run dependency?

I still feel like build,link is rather arbitrary as a choice. What if a tool needs python or perl only as a run dependency, and those packages want to inject some flag to CMake to be found properly?

for dep in pkg.spec.dependencies(deptype=("build", "link")):
try:
args.extend(dep.package.dependent_cmake_args(pkg, args))
except Exception:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is definitely too broad. Can we use a narrower list of exceptions?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe even better would be to not use exceptions at all:

if not hasattr(dep.package, "dependent_cmake_args"):
    continue

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh wow, I think I was tired when I wrote that or something, 100% agree..

]
)
def _dependencies_cmake_extra_args(pkg: PackageBase, args: List[str]) -> None:
for dep in pkg.spec.dependencies(deptype=("build", "link")):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about it the best choice would probably be to iterate over all dependencies, and let each package decide when to inject arguments or not.


_conditional_cmake_defaults(pkg, args)
_maybe_set_python_hints(pkg, args)
_dependencies_cmake_extra_args(pkg, args)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the implementation, I think we can inline _dependencies_cmake_extra_args.

Also, given we are generalizing the way dependents inject arguments, we should extract the boost related bits of _conditional_cmake_defaults and move them to the boost package.

Comment on lines +18 to +21
def setup_dependent_build_environment(
self, env: EnvironmentModifications, dependent_spec: Spec
):
env.prepend_path("OpenGL_ROOT", self.prefix)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be extracted in a separate PR to be merged straight away?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I can do that.

patch("fontconfig.patch", when="@1.16.0:1.17.2")
# Don't regenerate docs to avoid a dependency on gtk-doc
patch("disable-gtk-docs.patch", when="build_system=autotools ^autoconf@2.70:")
patch("disable-gtk-docs.patch", when="build_system=autotools")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we extract also this seemingly unrelated change? If we are adding a new feature to the CMake build system, I'd like the PR to contain only changes relevant for that feature.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah makes sense, this was all migrated out of another PR, so what is a couple more 😄

@@ -1289,6 +1280,26 @@ def setup_dependent_package(self, module, dependent_spec):
module.python_platlib = join_path(dependent_spec.prefix, self.platlib)
module.python_purelib = join_path(dependent_spec.prefix, self.purelib)

def dependent_cmake_args(self, pkg: PackageBase) -> List[str]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only applied when the spec has a python build or link dep.

I think the Python package should have code to decide when to inject these flags, if that is relevant.

If there is any need to check e.g. the cmake version being used or the dependency types of python, before injecting Python flags, we should do it here. The code in the build system should probably be as agnostic as possible, and just perform a callback on packages implementing the method.

homepage = "https://www.mpi-forum.org/"
virtual = True

def dependent_cmake_args(self, pkg: PackageBase) -> List[str]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this actually being called? If I read code correctly we just iterate on .dependencies:

Spack version 1.2.0.dev0
Python 3.13.2, Linux x86_64
>>> from spack.concretize import concretize_one
>>> s = concretize_one("hdf5")
>>> s.dependencies()
[cmake@3.31.6/ju2z2nr3nclj2ip2rhexrthq6mt7flt6, zlib-ng@2.2.4/3mewdwgmlykrd2ie4cftrpifzovbs2er, pkgconf@2.5.1/iguplooome2oktywbgjvoq5nwxmgdw6y, openmpi@5.0.9/tk7gfkssl7mkbglkbitxu6l4i3gpgysa, gcc@15.1/5mmswryndxgqgzxf4vhd27drooj2zywh, glibc@2.31/nm2n32h34mhcte6fm3s4fjjnnrdyqcvz, gcc-runtime@15.1/4obv4eoaimwzbfcgq3s7lf3wvrpd5emq, compiler-wrapper@1.0/tidruedm3z3lhfee2vyu3baxfpilprh7, gmake@4.4.1/x6dhwcecsmwwctcho5mmmvp4cmbryyqk]

and that doesn't include virtual packages, right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to iterate on edges and call the virtual package explicitly if the virtual is provided, if we want this to take effect (unless I'm forgetting some magic we do).


# Allow packages to disable these hints be setting a the member variable
# ``find_python_hints`` to False. By default this is always applied.
if not getattr(pkg, "find_python_hints", True):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another thought I had is that the CMakePackage class should define an attribute, for instance:

class CMakePackage:
    disable_hints_from: List[str] = []

and we should check in the base class whether to make the callback or not. This would save use from inventing a new attribute name for each package defining this method, and having this check at the top of each implementation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me personally, I think the class attribute is fine, because:

  1. Easier to document
  2. Editor completion in derived classes from CMakePackage
  3. I don't think the list will be long

Copy link
Member

@haampie haampie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the very least, this PR should remove the MPI related changes. I think it will annoy people to see this compiler wrapper pop up. MPI is just a library like any other dependency.

In general, I would say: split the functionality you're introducing from the use cases you're contributing. Make the PR small, so it's merged faster.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants