Skip to content

Some Macos build patches#2049

Draft
rhoneyager-tomorrow wants to merge 3 commits into
JCSDA:release/2.1from
rhoneyager-tomorrow:bugfix/release-2.1-macos
Draft

Some Macos build patches#2049
rhoneyager-tomorrow wants to merge 3 commits into
JCSDA:release/2.1from
rhoneyager-tomorrow:bugfix/release-2.1-macos

Conversation

@rhoneyager-tomorrow

Copy link
Copy Markdown

Description

Hi guys. I recently tried to build ioda-converters using spack-stack 2.1 on an arm64 mac laptop. I'm not trying to build one of the heavier environments / this was a test of a more limited package subset. I encountered some errors when building & linking oops and ioda, and here are two potential patches.

The oops change

The oops patch is an easy one. It is backported from JCSDA/oops@f378c46, and this change was made in response to ecmwf/atlas#307.

The underlying bug was never incorporated into a stable JCSDA tag of oops. It only affects the code around spack-stack's 2025-08-27 oops commit.

Users might wonder why using int64_t in oops triggers this compiler-time bug. This is because int64_t is a typedef to a fundamental C++ type, and depending on the platform it may point to either long or long long. It doesn't matter that long and long long are frequently the same size nowadays. They are distinct types.

The ioda change

The initial filter_file command can't find the target file. ioda-import.cmake isn't always installed into lib64/cmake/ioda/, and there is likely a lib vs lib64 platform-specific issue.

The patch file is a bit terse, so here's an explanation of the problematic code in cmake/ioda-import.cmake.in:

# Export ioda YAML validation files directory
if( IS_ABSOLUTE "@CMAKE_INSTALL_DATADIR@")
	set( IODA_YAML_ROOT "@CMAKE_INSTALL_DATADIR@/ioda/yaml" )
else()
	if ( DEFINED _IMPORT_PREFIX )
		# We are in a CMake install tree
		set( IODA_YAML_ROOT "${_IMPORT_PREFIX}/@CMAKE_INSTALL_DATADIR@/ioda/yaml" )
	else()
		# We are in a CMake build tree. Ergo, we can use the variable set in
		# IODA's top-level CMakeLists.txt that points to the YAML root path
		# in the source tree.
		#
		# Note: a small downside of this approach is that a source tree path is hardcoded
		# in the ioda-post-import.cmake file, even if it is never again used. Some
		# tools (like Spack) will complain about this path and may indicate that ioda
		# is a non-relocatable package, even though it is completely relocatable.
		set( IODA_YAML_ROOT "@IODA_YAML_ROOT@" )
	endif()
endif()

IODA_YAML_ROOT is set in ioda's share/CMakeLists.txt file to ${CMAKE_CURRENT_BINARY_DIR}/test/testinput/. If you're in a build mode (i.e. not install mode), ioda-import.cmake should be placed in at the top of the project's build tree, so IODA_YAML_ROOT can always point to ${CMAKE_CURRENT_LIST_DIR}/share/test/testinput/.

I'm redoing my stack, so I haven't tested this in a bundle build and can't recall whether ecbuild is forcing the *-import.cmake files to be placed in CMAKE_BINARY_DIR or PROJECT_BINARY_DIR. That might change the patch slightly, but you should get the gist.

One other issue with scipy (not addressed here)

spack-stack doesn't seem to pin a specific py-scipy version. Someone should review that recipe, as not all patches apply to py-scipy@1.16.0. Some of the files to be patched have moved or no longer exist.

Dependencies

None

Issues addressed

None

Applications affected

None operational. This is mostly for macs.

Systems affected

Macos systems

Testing

  • CI: Note whether the automatic tests (GitHub actions tests that run automatically for every commit) pass or not
    • GitHub actions CI tests pass
    • GitHub actions CI tests do not pass (provide explanation)
    • GitHub actions CI tests skipped (provide explanation if necessary)
  • New tests added: List and describe any new tests added to GitHub actions
    • ...
  • Additional testing: Add information on any additional tests conducted
    • ...

Checklist

  • This PR addresses one issue/problem/enhancement or has a very good reason for not doing so.
  • These changes have been tested on the affected systems and applications.
  • All dependency PRs/issues have been resolved and this PR can be merged.
  • All necessary updates to the documentation (spack-stack wiki) will be made when this PR is merged

@climbfuji

Copy link
Copy Markdown
Collaborator

@rhoneyager-tomorrow Thanks for contributing these updates. We will want to test those thoroughly, especially the IODA patch change. I put that in place so that we can run the unit tests in spack as part of the build.

I noticed that your changes are for release/2.1. I suggest redirecting them to develop; the JEDI packages are not a supported feature in release/2.1. Also note that I will be adding new versions for all these packages in the next weeks (one set of interim versions and the versions from the recent JEDI release).

@climbfuji climbfuji left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The modified IODA logic does not work, because the build directory (not the install directory) ends up in the ioda-converters test path.

At least when running spack install --test=root ioda-converters against this change.

196/197 Test #374: iodaconv_validate_testoutput_viirs_jpss1_oc_l3.nc ................................***Failed    0.03 sec
test 375
        Start 375: iodaconv_validate_testoutput_wmo_raob_double.nc4

375: Test command: /home/dom/work/spack-stack/spst-ryan-oops-ioda/envs/ne-oneapi-2025.3.0-build/install/__spack_path_placeholder__/__spack_path_placeholder__/__spack_path_placeholder__/__spack_path_placeholder__/__spack/intel-oneapi-compilers/2025.3.0/ioda-2.9.0.20250826-n4txycc/bin/ioda-validate.x "--ignore-warn" "--ignore-error" "/home/dom/work/spack-stack/spst-ryan-oops-ioda/envs/ne-oneapi-2025.3.0-build/install/__spack_path_placeholder__/__spack_path_placeholder__/__spack_path_placeholder__/__spack_path_placeholder__/__spack/intel-oneapi-compilers/2025.3.0/ioda-2.9.0.20250826-n4txycc/lib64/cmake/ioda/share/test/testinput//validation/ObsSpace.yaml" "/home/dom/work/spack-stack/spst-ryan-oops-ioda/cache/build_stage/spack-stage-ioda-converters-0.0.1.20250830-3ku2x2766x4svs2hrj3rs2quuudjkhhp/spack-src/test/testoutput/wmo_raob_double.nc4"
375: Working Directory: /home/dom/work/spack-stack/spst-ryan-oops-ioda/cache/build_stage/spack-stage-ioda-converters-0.0.1.20250830-3ku2x2766x4svs2hrj3rs2quuudjkhhp/spack-build-3ku2x27/test
375: Environment variables:
375:  ECKIT_COLOUR_OUTPUT=1
375:  OMP_NUM_THREADS=1
375: Test timeout computed to be: 1500
375: Reading YAML from /home/dom/work/spack-stack/spst-ryan-oops-ioda/envs/ne-oneapi-2025.3.0-build/install/__spack_path_placeholder__/__spack_path_placeholder__/__spack_path_placeholder__/__spack_path_placeholder__/__spack/intel-oneapi-compilers/2025.3.0/ioda-2.9.0.20250826-n4txycc/lib64/cmake/ioda/share/test/testinput/validation/ObsSpace.yaml
375: Exception: Cannot open /home/dom/work/spack-stack/spst-ryan-oops-ioda/envs/ne-oneapi-2025.3.0-build/install/__spack_path_placeholder__/__spack_path_placeholder__/__spack_path_placeholder__/__spack_path_placeholder__/__spack/intel-oneapi-compilers/2025.3.0/ioda-2.9.0.20250826-n4txycc/lib64/cmake/ioda/share/test/testinput/validation/ObsSpace.yaml  (No such file or directory)
375: Cannot open /home/dom/work/spack-stack/spst-ryan-oops-ioda/envs/ne-oneapi-2025.3.0-build/install/__spack_path_placeholder__/__spack_path_placeholder__/__spack_path_placeholder__/__spack_path_placeholder__/__spack/intel-oneapi-compilers/2025.3.0/ioda-2.9.0.20250826-n4txycc/lib64/cmake/ioda/share/test/testinput/validation/ObsSpace.yaml  (No such file or directory)

@rhoneyager-tomorrow

Copy link
Copy Markdown
Author

Huh. Then the comments in the initial ioda-import.cmake file are erroneous, and the We are in a CMake install tree vs build tree check is not working properly. I'll take a deeper look.

@rhoneyager-tomorrow

rhoneyager-tomorrow commented Jul 8, 2026

Copy link
Copy Markdown
Author

Alright, looking further it's apparent that ioda is currently relying on unsupported / unstandardized behavior to make the import work, and if ( DEFINED _IMPORT_PREFIX ) is a bad check. I wrote that bit of code five years ago, and while it worked then CMake has changed to the point that it's no longer sufficient.

_IMPORT_PREFIX is defined in ioda-targets.cmake, which itself is a special file generated by CMake. Its usage is hazardous because it's an internal variable in a build-system-generated file. In some versions of CMake this value may be set and can be used in downstream processing, but in others the variable is removed / reset to nothing towards the end of ioda-targets.cmake.

There's a separate use-before-definition bug that might also be CMake-dependent. When CMake "installs" / "packages" ioda, it writes out a set of CMake import files in lib/cmake/ioda. These are:

ioda-config-version.cmake
ioda-config.cmake
ioda-import.cmake
ioda-post-import.cmake
ioda-targets-{release|debug|relwithdebinfo}.cmake
ioda-targets.cmake

Of these, ioda-import.cmake and ioda-post-import.cmake are developer-managed (https://github.com/JCSDA/ioda/tree/develop/cmake). The others are dynamically generated by CMake. When a developer writes something like find_package(ioda), CMake finds and loads ioda-config.cmake first. This file then loads the others in a particular order. Here's an excerpt:

### include the <project>-import.cmake file if there is one      
if(EXISTS ${ioda_CMAKE_DIR}/ioda-import.cmake)
  set(ioda_IMPORT_FILE "${ioda_CMAKE_DIR}/ioda-import.cmake")
  include(${ioda_IMPORT_FILE})
endif()

### insert definitions for IMPORTED targets
if(NOT ioda_BINARY_DIR)
  find_file(ioda_TARGETS_FILE    
    NAMES ioda-targets.cmake 
    HINTS ${ioda_CMAKE_DIR}
    NO_DEFAULT_PATH)
  if(ioda_TARGETS_FILE)
    include(${ioda_TARGETS_FILE})
  endif()
endif()

### include the <project>-post-import.cmake file if there is one
if(EXISTS ${ioda_CMAKE_DIR}/ioda-post-import.cmake)
  set(ioda_POST_IMPORT_FILE "${ioda_CMAKE_DIR}/ioda-post-import.cmake")
  include(${ioda_POST_IMPORT_FILE})
endif()

This shows the second problem: even if the definition for _IMPORT_PREFIX would persist, it is defined in ioda-targets.cmake, which is loaded after ioda-import.cmake. Anything depending on _IMPORT_PREFIX would have to be located in ioda-post-import.cmake for it to be useful.

@rhoneyager-tomorrow

Copy link
Copy Markdown
Author

Thankfully, there is a solution in https://cmake.org/cmake/help/latest/module/CMakePackageConfigHelpers.html. Added in version 3.30: The variable PACKAGE_PREFIX_DIR will always be defined after the @PACKAGE_INIT@ line. It will hold the value of the base install location. In general, variables defined via the PATH_VARS mechanism should be used instead, but PACKAGE_PREFIX_DIR can be used for those cases not easily handled by PATH_VARS, such as for files installed directly to the base install location rather than a subdirectory of it.

@climbfuji

Copy link
Copy Markdown
Collaborator

Thankfully, there is a solution in https://cmake.org/cmake/help/latest/module/CMakePackageConfigHelpers.html. Added in version 3.30: The variable PACKAGE_PREFIX_DIR will always be defined after the @PACKAGE_INIT@ line. It will hold the value of the base install location. In general, variables defined via the PATH_VARS mechanism should be used instead, but PACKAGE_PREFIX_DIR can be used for those cases not easily handled by PATH_VARS, such as for files installed directly to the base install location rather than a subdirectory of it.

We can reasonably expect or set a minimum on cmake version 3.30 if we haven't done so already.

@rhoneyager-tomorrow rhoneyager-tomorrow marked this pull request as draft July 8, 2026 21:02
@rhoneyager-tomorrow

rhoneyager-tomorrow commented Jul 9, 2026

Copy link
Copy Markdown
Author

@climbfuji okay, I've tested both inside a jedi bundle and within spack builds. Let me know if it's working on your end.

Some errata (nothing here is affected by this PR):

  • I'm seeing errors in my local macos invocation of spack install --test=root ioda-converters related to RPATH-related bugs specific to ioda-converters. I think some of the newer NCAR converters are setting relative paths with $ORIGIN, but on macos they need to use @loader_path. Since the ioda-converters spack recipe uses the development branch, I think I'll submit a patch over there.
  • bufr_query: has #include references that point to nonexistent files. Looks like source code bugs.
  • jedi_cmake_ROOT confusingly exists alongside jedicmake_ROOT, and these two variables have different purposes & point to different locations in the jedi-cmake package.
    • The jedi packages all call find_package( jedicmake QUIET ), which is influenced by jedicmake_ROOT. Here, jedicmake_ROOT is a path to the folder containing jedicmake-config.cmake.
    • Bundles frequently have include( $ENV{jedi_cmake_ROOT}/share/jedicmake/Functions/git_functions.cmake ). In this context, jedi_cmake_ROOT is a base path.
      • jedicmake exports its CMake functions through a standard CMake interface, and developers really should be using the jedicmake_FUNCTIONS variable instead of constructing a path manually. jedicmake_FUNCTIONS is always available after calling find_package( jedicmake ).

@climbfuji

Copy link
Copy Markdown
Collaborator

@climbfuji okay, I've tested both inside a jedi bundle and within spack builds. Let me know if it's working on your end.

Some errata (nothing here is affected by this PR):

  • I'm seeing errors in my local macos invocation of spack install --test=root ioda-converters related to RPATH-related bugs specific to ioda-converters. I think some of the newer NCAR converters are setting relative paths with $ORIGIN, but on macos they need to use @loader_path. Since the ioda-converters spack recipe uses the development branch, I think I'll submit a patch over there.

Sounds good

  • bufr_query: has #include references that point to nonexistent files. Looks like source code bugs.

Something to raise in the OMD (was: EMC) repo?

  • jedi_cmake_ROOT confusingly exists alongside jedicmake_ROOT, and these two variables have different purposes & point to different locations in the jedi-cmake package.

    • The jedi packages all call find_package( jedicmake QUIET ), which is influenced by jedicmake_ROOT. Here, jedicmake_ROOT is a path to the folder containing jedicmake-config.cmake.

    • Bundles frequently have include( $ENV{jedi_cmake_ROOT}/share/jedicmake/Functions/git_functions.cmake ). In this context, jedi_cmake_ROOT is a base path.

      • jedicmake exports its CMake functions through a standard CMake interface, and developers really should be using the jedicmake_FUNCTIONS variable instead of constructing a path manually. jedicmake_FUNCTIONS is always available after calling find_package( jedicmake ).

I believe that by CMake convention, and certainly by Spack convention, every package sets an environment variable <package_name>_ROOT. Per CMake standard, that variable is supposed to be case-sensitive. In mainline spack, it's uppercased. In spack-stack, we decided to follow CMake conventions and maintain this difference to mainline Spack to avoid hundreds of CMake warnings.

Since the package is called jedi-cmake and not jedicmake, the environment variable jedi_cmake_ROOT is always set in the Spack-generated modulefile in spack-stack.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants