python: simplify libs() and headers() methods in python/package.py

- [x] Rework `headers` to search a sequence of directories and to display all searched
      locations on error, as opposed to handling each directory with a variable

- [x] Make `headers` and `libs` do the same type of search and raise the same sort of
      errors.

Co-authored-by: Todd Gamblin <tgamblin@llnl.gov>
This commit is contained in:
Adam J. Stewart 2022-08-03 10:28:14 -07:00 committed by Todd Gamblin
parent 30585890a8
commit 50f8a0f0d6

View File

@ -1044,6 +1044,12 @@ def find_library(self, library):
win_bin_dir = self.config_vars["BINDIR"] win_bin_dir = self.config_vars["BINDIR"]
directories = [libdir, libpl, frameworkprefix, macos_developerdir, win_bin_dir] directories = [libdir, libpl, frameworkprefix, macos_developerdir, win_bin_dir]
# The Python shipped with Xcode command line tools isn't in any of these locations
for subdir in ["lib", "lib64"]:
directories.append(os.path.join(self.config_vars["base"], subdir))
directories = dedupe(directories)
for directory in directories: for directory in directories:
path = os.path.join(directory, library) path = os.path.join(directory, library)
if os.path.exists(path): if os.path.exists(path):
@ -1051,52 +1057,64 @@ def find_library(self, library):
@property @property
def libs(self): def libs(self):
# The +shared variant isn't always reliable, as `spack external find` py_version = self.version.up_to(2)
# currently can't detect it. If +shared, prefer the shared libraries, but check
# for static if those aren't found. Vice versa for ~shared.
# The values of LDLIBRARY and LIBRARY also aren't reliable. Intel Python uses a # The values of LDLIBRARY and LIBRARY aren't reliable. Intel Python uses a
# static binary but installs shared libraries, so sysconfig reports # static binary but installs shared libraries, so sysconfig reports
# libpythonX.Y.a but only libpythonX.Y.so exists. # libpythonX.Y.a but only libpythonX.Y.so exists. So we add our own paths, too.
shared_libs = [ shared_libs = [
self.config_vars["LDLIBRARY"], self.config_vars["LDLIBRARY"],
"libpython{}.{}".format(self.version.up_to(2), dso_suffix), "libpython{}.{}".format(py_version, dso_suffix),
] ]
static_libs = [ static_libs = [
self.config_vars["LIBRARY"], self.config_vars["LIBRARY"],
"libpython{}.a".format(self.version.up_to(2)), "libpython{}.a".format(py_version),
] ]
if "+shared" in self.spec:
libraries = shared_libs + static_libs
else:
libraries = static_libs + shared_libs
libraries = dedupe(libraries)
for library in libraries: # The +shared variant isn't reliable, as `spack external find` currently can't
lib = self.find_library(library) # detect it. If +shared, prefer the shared libraries, but check for static if
# those aren't found. Vice versa for ~shared.
if "+shared" in self.spec:
candidates = shared_libs + static_libs
else:
candidates = static_libs + shared_libs
candidates = dedupe(candidates)
for candidate in candidates:
lib = self.find_library(candidate)
if lib: if lib:
return lib return lib
msg = "Unable to locate {} libraries in {}" raise spack.error.NoLibrariesError(
libdir = self.config_vars["LIBDIR"] "Unable to find {} libraries with the following names:".format(self.name),
raise spack.error.NoLibrariesError(msg.format(self.name, libdir)) "\n".join(candidates),
)
@property @property
def headers(self): def headers(self):
# Location where pyconfig.h is _supposed_ to be # Locations where pyconfig.h could be
config_h = self.config_vars["config_h_filename"] # This varies by system, especially on macOS where the command line tools are
if os.path.exists(config_h): # installed in a very different directory from the system python interpreter.
headers = HeaderList(config_h) py_version = str(self.version.up_to(2))
candidates = [
os.path.dirname(self.config_vars["config_h_filename"]),
self.config_vars["INCLUDEPY"],
self.config_vars["CONFINCLUDEPY"],
os.path.join(self.config_vars["base"], "include", py_version),
os.path.join(self.config_vars["base"], "Headers"),
]
candidates = list(dedupe(candidates))
for directory in candidates:
headers = find_headers("pyconfig", directory)
if headers:
config_h = headers[0]
break
else: else:
# If not, one of these config vars should contain the right directory raise spack.error.NoHeadersError(
for var in ["INCLUDEPY", "CONFINCLUDEPY"]: "Unable to locate {} headers in any of these locations:".format(self.name),
headers = find_headers("pyconfig", self.config_vars[var]) "\n".join(candidates),
if headers: )
config_h = headers[0]
break
else:
msg = "Unable to locate {} headers in {}"
raise spack.error.NoHeadersError(msg.format(self.name, directory))
headers.directories = [os.path.dirname(config_h)] headers.directories = [os.path.dirname(config_h)]
return headers return headers