Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add logic to detect libomp installs in homebrew and macports on macOS #83

Merged
merged 15 commits into from
Mar 20, 2023

Conversation

rayg-ssec
Copy link
Contributor

When building using Clang, libomp is the preferred OpenMP library, though it's a separate build from the main compiler.

macOS defaults to clang (though GCC can be made available), but unless libomp is installed (often but not always through Homebrew or MacPorts), building without OMP is the baseline.

In a manual install of libomp, you need slightly different compile flags -Xpreprocessor -fopenmp and appropriate linkage to libomp, with include and library search paths set in CFLAGS and LDFLAGS environment variables.

This patch attempts to retain backward compatible behavior, but extend USE_OMP beyond 1/0 to "clang" or "omp", "gcc" or "gomp" to be more specific with respect to compiler options.

It also, on macOS, attempts to detect libomp if it is installed via brew or port by listing the manifest of the installed packages, and generating CFLAGS and LDFLAGS that would have been needed to satisfy -Xpreprocessor -fopenmp and -lomp compiler/linker directives. If USE_OMP is not specified, it will probe to see if USE_OMP=omp can be inferred by checking for libomp include and library paths.

This means pip install . on macOS systems with libomp available should succeed with OpenMP enabled by default, while permitting non-macOS systems using clang to set CFLAGS/LDFLAGS and USE_OMP=omp . USE_OMP=0 should also still be obeyed. A brief debug line is included when -v is enabled on the pip install.

…nistically.

USE_OMP=1 and USE_OMP=0 behave according to previous expectation.
USE_OMP=clang or USE_OMP=omp forces use of libomp (OpenMP implementation for Clang)
USE_OMP=gcc or USE_OMP=gomp forces use of libgomp (OpenMP for GCC)
unspecified USE_OMP is gomp for Conda builds and non-macOS builds (per prior default),
                       omp on macOS if a libomp is found
Copy link
Collaborator

@djhoese djhoese left a comment

Choose a reason for hiding this comment

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

This is adding so much needed functionality. Thanks for starting down this path and trying to combine all the various ways this can go. I've made a lot of comments about other possibilities for parts of this. Let me know what you think.

@rayg-ssec rayg-ssec requested a review from djhoese March 11, 2023 18:29
@djhoese djhoese added enhancement CI/Build dependencies Pull requests that update a dependency file labels Mar 13, 2023
@djhoese djhoese self-assigned this Mar 13, 2023
Copy link
Collaborator

@djhoese djhoese left a comment

Choose a reason for hiding this comment

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

This is really looking clean. I have a couple requests, but the majority of the functionality makes sense to me. I'm just hoping it can be cleaned up a bit more for future flexibility (if needed) while also avoiding running things at import/load time.

rayg-ssec and others added 2 commits March 17, 2023 14:18
Co-authored-by: David Hoese <[email protected]>
Also absorb msvc OpenMP options into the probe continuum
@djhoese djhoese changed the title Draft: feat: detect libomp installs in homebrew and macports on macOS Add logic to detect libomp installs in homebrew and macports on macOS Mar 18, 2023
Copy link
Collaborator

@djhoese djhoese left a comment

Choose a reason for hiding this comment

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

More questions and comments. Getting really close though.

setup.py Outdated
def build_extensions(self):
comp = self.compiler.compiler_type
use_omp, omp_comp, omp_link = self._omp_compile_link_args(comp)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Besides the print statement use_omp isn't used anymore, right? It makes me wonder if those if statements in the above helper method can be simplified even further. Perhaps getting OMP_COMPILE_ARGS.get(use_omp, []) earlier in the method and then adding macos_compile_args later on if needed...hhmmm except you might force omp later. Not sure.

The print could maybe be moved up into the helper or removed? Thoughts?

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 print only shows up if you add the -v to the pip, currently; if there's a better way to have an "info" message during a setup.py, that would be preferred. Having it as optional debug is useful, to show concisely what choices have been taken. Placing it inside the _omp_compile_link_args would require a refactoring to avoid a DRY violation. Might be straying a bit too far into the white glove treatment, in other words.

I have more concerns about making sure Windows builds still work, and edge cases like "I have a linux distro that's clang-focused" (OpenMandriva?) have a path to build without having to edit the setup.py. I think that's the case, given I can swap between gcc/gomp and clang/omp on macos successfully by just changing environment values.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Placing it inside the _omp_compile_link_args would require a refactoring to avoid a DRY violation.

Why? Because there are two return statements? My instinct is to think any refactoring down those lines would only make the code cleaner. For example, right now you set use_omp = None in an else statement, but that has no usage or meaning anywhere else in the code. If you wrapped the mac stuff into a new function or had macOS_omp_options_from_probe return empty lists instead of None then the rest of the function/if statements just goes away. Or at least you could do something like:

compile_args = []
link_args = []
...

macos_compile_args, macos_link_args = macOS_omp_options_from_probe()
if macos_compile_args and macos_link_args:
    compile_args += macos_compile_args
    link_args += macos_link_args

...

Hm maybe this doesn't clean it up this much. I completely didn't include the OMP_COMPILE_ARGS. I think there is a slightly cleaner way to structure it, but maybe it isn't worth the effort.

Copy link
Collaborator

@djhoese djhoese Mar 18, 2023

Choose a reason for hiding this comment

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

Assuming MSVC can handle loading openmp by default as you have it, what if it was split up like this:

if use_omp == "probe":
    if is_macOS() and not is_conda_interpreter():
        use_omp = "omp"
    elif compiler == "msvc":
        use_omp = "msvc"
    else:
        use_omp = "gomp"
    print(f"Probing OpenMP support will attempt to use {use_omp}")
compile_args = []
link_args = []
if use_omp == "omp" and is_macOS():
    extra_compile_args, extra_link_args = macOS_omp_options_from_probe()
    if not extra_compile_args:
        print("Could not find OpenMP on MacOS")
    compile_args += extra_compile_args
    link_args += extra_link_args
compile_args += OMP_COMPILE_ARGS.get(use_omp, [])
link_args += OMP_LINK_ARGS.get(use_comp, [])

Put in to two (or three) separate helper functions this clears up "guess OpenMP usage based on platform/compiler" and "collect all compile and link args for OpenMP".

setup.py Outdated
def build_extensions(self):
comp = self.compiler.compiler_type
use_omp, omp_comp, omp_link = self._omp_compile_link_args(comp)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Placing it inside the _omp_compile_link_args would require a refactoring to avoid a DRY violation.

Why? Because there are two return statements? My instinct is to think any refactoring down those lines would only make the code cleaner. For example, right now you set use_omp = None in an else statement, but that has no usage or meaning anywhere else in the code. If you wrapped the mac stuff into a new function or had macOS_omp_options_from_probe return empty lists instead of None then the rest of the function/if statements just goes away. Or at least you could do something like:

compile_args = []
link_args = []
...

macos_compile_args, macos_link_args = macOS_omp_options_from_probe()
if macos_compile_args and macos_link_args:
    compile_args += macos_compile_args
    link_args += macos_link_args

...

Hm maybe this doesn't clean it up this much. I completely didn't include the OMP_COMPILE_ARGS. I think there is a slightly cleaner way to structure it, but maybe it isn't worth the effort.

rayg-ssec and others added 4 commits March 18, 2023 20:04
Also macOS automatic brew/port checks are only done with 'probe' mode,
which means explicitly setting USE_OMP=clang may require CFLAGS and LDFLAGS be set.
The default is USE_OMP=probe.
@rayg-ssec
Copy link
Contributor Author

The most recent commits refactor it down to a lot of what you've recommended. I also addressed the "I know what I'm doing and don't want args probed" expert user case - brew and ports checks are only done in probe mode.

@djhoese
Copy link
Collaborator

djhoese commented Mar 19, 2023

Did you see my pull request here: rayg-ssec#1

@rayg-ssec
Copy link
Contributor Author

Did you see my pull request here: rayg-ssec#1

No, ironically I decided to add the GitHub app to my phone and tablet today in hopes of getting better notifications and work queue. Didn't see the PR until you pointed it out.

djhoese and others added 2 commits March 19, 2023 20:09
Refactor OpenMP detection to follow clean code practices
@rayg-ssec
Copy link
Contributor Author

The merge of your changes ran with no issue through my 5 local test cases. The README update is much appreciated.

(I still think reads-like-a-novel is easier to follow than reads-like-a-newspaper however!)

Copy link
Collaborator

@djhoese djhoese left a comment

Choose a reason for hiding this comment

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

Amazing! I think this is as good as we can get this without getting more user feedback. I'll merge this and then try to do a patch release. Then...we'll wait for complaints.

@djhoese djhoese merged commit 94cd4de into storpipfugl:master Mar 20, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CI/Build dependencies Pull requests that update a dependency file enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants