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

Xcode 10 can't build C++ for OSX < 10.9 #51

Open
katjav opened this issue Mar 20, 2019 · 14 comments
Open

Xcode 10 can't build C++ for OSX < 10.9 #51

katjav opened this issue Mar 20, 2019 · 14 comments

Comments

@katjav
Copy link
Contributor

katjav commented Mar 20, 2019

Reportedly, option -mmacosx-version-min can't be defined lower than 10.9 when building C++ code with Xcode 10. I don't want to set -mmacosx-version-min=10.9 for all Pd libs and regardless of Xcode version. On the other hand I don't know right away how to define it conditionally, which would require two extra checks:

  • is there any C++ code involved in the build?
  • what Xcode version is being used?

Even if these conditions can be checked, the total set of conditional checks for minimum OSX version will become rather kludgy. So I'm hoping that someone comes up with a better idea. In the meantime you may need to override the minimum OSX version as defined in the makefile. This can be done on command line like so:

$ make version.flag="-mmacosx-version-min=10.9"

(edit: version.flag is not considered an 'API' variable, use only as temporary workaround)

If your library needs a specific minimum version anyway, it can be defined in the lib Makefile:

define forDarwin
cflags = -mmacosx-version-min=10.9
endef
@umlaeute
Copy link
Contributor

version.flag strikes me as quite a generic name. probably osversion.flags would be better.

apart from that, i think the strength of pd-lib-builder is it's "simplicity". If you want to have checks for this and that and everything, there are more mature solutions available (autotools, CMake,...).
i'd vote for unsetting the macosx-version-min by default, and allow an easy (and well-documented) way to specify that value if needed (by the user who compiles)

@megrimm
Copy link

megrimm commented Mar 21, 2019

i'd vote for unsetting the macosx-version-min by default, and allow an easy (and well-documented) way to specify that value

+1 on this

as I have been going through a few different libs replacing build systems with pd-lib-builder there seems to be a lot of inconsistency with what the min version should be.

for example one lib might need -mmacosx-version-min=10.6 the next -mmacosx-version-min=10.9 and then the next -mmacosx-version-min=10.13

this actually happened so the case for "well documented" over adding additional complexity is strong because the min version seems to be on a case-by-case basis.

@katjav
Copy link
Contributor Author

katjav commented Mar 21, 2019

i'd vote for unsetting the macosx-version-min by default, and allow an easy (and well-documented) way to specify that value

Ah yes, that is the better idea I was hoping for, but didn't cross my mind. Users who only compile for private use don't need to define it at all. Devs and maintainers who distribute libs are probably aware of the need to support older versions. So they should either define it in the makefile if the library needs it, or else specify it at compile time. Every time. Hmmm... It's easy to forget.

@katjav
Copy link
Contributor Author

katjav commented Mar 21, 2019

version.flag strikes me as quite a generic name. probably osversion.flags would be better.

Totally agree. version.flag is a makefile-internal variable used in the OSX platform section, not considered an 'API' variable. Its use as mentioned above is a temporary workaround for the current issue.

@katjav
Copy link
Contributor Author

katjav commented Mar 22, 2019

Minimum OSX version definition should be simply replaced from pdlibbuilder to lib makefile. Considering that:

  1. for distributed libraries the parameter is too important to omit altogether, and
  2. it is very easy to forget defining this on command line, and
  3. minimum OSX version is (co)dependent on the library content...

... it is most logical to define it in each pdlibbuilder-including makefile, even when the lib itself isn't picky about minimum version. The role of pdlibbuilder is then to:

  1. facilitate definition of the flag in the lib makefile (what it already does)
  2. facilitate overriding the lib makefile's default in a convenient way (yet to be figured out)
  3. loudly recommend defining the flag in the lib makefile (probably in the readme)
  4. provide up to date information about which minimum version is most likely to work

For most lib makefiles this approach requires some extra work. But it is fair to delegate this responsability to the lib dev or maintainer, who knows the lib content (whether it is C, C++, C++11 etc.). Discussion of what version is required per project type can happen in a 'sticky issue' that will never be closed.

@katjav
Copy link
Contributor Author

katjav commented Mar 23, 2019

In the context of this issue I want to reference pull request #22, "Making minimum Mac OS X version configurable".

I'm always trying to keep the set of 'API' variables small. In this case I decided that pdlibbuilder should respect option -mmacosx-version-min when defined under the variable already available for the lib makefile: cflags. If a lib uses C++11 for example, you can specify the language option in cflags and for OSX add the flag for the minimum version that supports it. Like so:

cflags = -std=C++11

define forDarwin
cflags += -mmacosx-version-min=10.9
endef

At the time (2016) this seemed a satisfactory solution and the pull request proposing a dedicated variable was withdrawn. However, definition of -mmacosx-version-min is not easily overridable because it is in a category which may include other variables.

The problem with overriding individual flags is a general one. Gcc offers hundreds of options and build systems group them in categories according to purpose. The base division is between build phases (preprocessing, compilation, linking). But the lines become blurry very quickly. Some options are required both in compilation and linking. Some options are essential and others (notably optimizations) are optional. CFLAGS is by convention the category for non-essential compiler options

Pdlibbuilder defines certain essential flags for all pd libs and reserves variable cflags for lib-specific essential flags that shouldn't be overriden by CFLAGS, such as -std=C++11. But minimum OSX version, though essential for distributed builds, is not at all required for compilation per se. You could consider it an optimization of compatibility, wich should be overridable by CFLAGS.

I'm just thinking aloud here, no conclusion yet.

@danomatika
Copy link
Contributor

danomatika commented May 10, 2021

Without reading all the details in this discussion, here are some notes I have from adapting a C++-based external to pd-lib-builder on macOS 11 with Xcode 12.

Tests

Building with -stdlib=libc++ requires -mmacosx-version-min 10.7 or newer:

clang: error: invalid deployment target for -stdlib=libc++ (requires OS X 10.7 or later)

I would assume if I have an OSX 10.6 or 10.7 system, this would work fine. If -mmacosx-version-min is set to 10.9, this would probably break.


Building with -stdlib=libc++ and -mmacosx-version-min=10.7 is deprecated on macOS 10.9+ and throws an error on my macOS 11 system with Xcode 12 as deprecated lib was removed some system version ago:

clang: warning: libstdc++ is deprecated; move to libc++ with a minimum deployment target of OS X 10.9 [-Wdeprecated]
ld: library not found for -lstdc++

I would assume if I have an OSX 10.9 or 10.10 system, this would work fine. If -mmacosx-version-min is set to 10.9, this would probably also be fine. Off the top of my head, I think the (old) stdc++ lib was removed sometime around 10.13?


Building with -stdlib=libc++ and -mmacosx-version-min=10.9 is works fine.

Conclusion

Simplest solution is to detect the SDK we are building with and determine what's needed to compile C++ successfully. This should not rely on the macosx-version-min as this can be any number, really. I'm using Xcode (and command line utils) version checking for #69 so it could be used to set a higher version as needed.

So perhaps it could be something like:

ifneq ($(filter -stdlib=libc++, $(cflags)),)
  ifeq ($(shell [ $(xcode.ver) -gt 5 ] && echo 1), 1)
    version.flag = -mmacosx-version-min=10.9
  endif
endif

Xcode version 6 coincides with macOS 10.9.

I think it's important to stress to people in the documentation that building for a higher deployment version means older systems will not be able to load the external. We want the makefile to be simple and easy to use, but this should also be overridable and not surprise anyone.

@danomatika
Copy link
Contributor

To confirm, judging from this SO post, libc++ did became the default with macOS 10.9 / Xcode 6.

@umlaeute
Copy link
Contributor

umlaeute commented Jun 1, 2021

Without reading all the details in this discussion,

(i think) the gist is, that it is probably best to drop the minimum-version altogether, and let upstream (the library developers that use pd-lib-builder to build their stuff) declare it if they absolutely want to, like so:

cflags = -std=c++11

define forDarwin
cflags += -mmacosx-version-min=10.9
endef

consequences

if upstream adds a minimum version

  • pro: upstream can guarantee a minimum-version that the library will work on, with no additional thoughts from whoever compiles the library
  • con: it will inevitably fail if the used XCode version doesn't support the declared minimum-version any more

if no minimum-version is added

  • pro: for "local builds" (not to be distributed via deken), it will "just work".
  • con: distributed builds might not work on older OSXen if the maintainer (who built the library) compiled on a new macOS/XCode

with my (Debian) maintainer hat on, i think it is the task of a maintainer to make sure the artifacts they produce are usable on the target machines.
otoh, (proper) Debian Maintainers are trained for that specific task, which is not something the Pd ecosystem currently provides (and probably won't ever)

@danomatika
Copy link
Contributor

I disagree, partially. There are quite a number of consequences when the min version is missing, especially now that a certain version is required in order to load within a notarized setting without additional permissions/entitlements.

At the very least, I prefer the current default which sets the min version to that used by Pd itself, if not specified.

As for automatic trickery, it's not strictly required and could become a headache, yes. Doable for sure but, as you mentioned, we can quickly enter the realm of auto tools. This is why I also think issues like these are also solved in the realm of documentation, ie. "if you are using C++ on macOS, look at this example to set the correct min version and C++ lib flags."

@umlaeute
Copy link
Contributor

umlaeute commented Jun 1, 2021

I disagree, partially.

that's fine :-)
i mainly wanted to give you a summary of what you seemed to have skipped.

There are quite a number of consequences when the min version is missing,

could you explain these consequences? (or even better: point to some documentation that explains them).
my understanding was that a missing min-version would default to the current version. is that wrong?

especially now that a certain version is required in order to load within a notarized setting without additional permissions/entitlements.

but isn't that mostly an issue with the host application (in our case: Pd), rather than the plugins (which is what pd-lib-builder targets)?

afaiu, you can only notarize a binary that was built with a given min version.
however pd-libraries are (currently!) typically unsigned and unnotarized (although i have recently started to sign/notarize libraries produced at the iem), and the Pd host application therefore has an entitlement to allow loading of such plugins.

what exactly are the problems here? (i probably already read about it, and discarded it soon afterwards)

At the very least, I prefer the current default which sets the min version to that used by Pd itself, if not specified.

which problem would that solve? (i'm honestly curious; it's obviously related to the above questions)
could we query the Pd binary to see which min-version it used?

@umlaeute
Copy link
Contributor

umlaeute commented Jun 1, 2021

cflags = -std=c++11

define forDarwin
cflags += -mmacosx-version-min=10.9
endef

for what it is worth: from an "API" pov, i would prefer something like:

cflags = -std=c++11
macos.minversion=10.9

and then let pd-lib-builder handle the adding of the correct flag for Darwin-builds only.
if the variable is unset, don't set the min-version (and eventually let the build auto-explode, so the maintainer knows that they need to add it).

i think the forDarwin define is rather brittle. (#61 and the like)

@danomatika
Copy link
Contributor

danomatika commented Jun 1, 2021

for what it is worth: from an "API" pov, i would prefer something like:

cflags = -std=c++11
macos.minversion=10.9

This is good.


There are quite a number of consequences when the min version is missing,

could you explain these consequences? (or even better: point to some documentation that explains them).
my understanding was that a missing min-version would default to the current version. is that wrong?

As far as I can tell, it handles deprecation checks, API availability, weak importing, etc. (SO post) Pd itself is compiled targeting 10.6, so leaving this off is very likely to result in an external building without trying to maintain some semblance of backward compatibility. (Blog post

When making anything with Xcode itself, the min version is always set.

especially now that a certain version is required in order to load within a notarized setting without additional permissions/entitlements.

but isn't that mostly an issue with the host application (in our case: Pd), rather than the plugins (which is what pd-lib-builder targets)?

afaiu, you can only notarize a binary that was built with a given min version.
however pd-libraries are (currently!) typically unsigned and unnotarized (although i have recently started to sign/notarize libraries produced at the iem), and the Pd host application therefore has an entitlement to allow loading of such plugins.

From what I (fuzzily) remember the last time I went through this both for Pd and a project at work:

  • notarized apps on newer systems will not load dynamic libraries compiled with min version < 10.9 by default
  • to allow this, an entitlement needs to be set (which I did for Pd)
  • if we were to move the min version to 10.9 for all externals, this would probably be smoother for Pd people but break Pd on older systems, ie. libs can't load for sure < 10.9

At the very least, I prefer the current default which sets the min version to that used by Pd itself, if not specified.
which problem would that solve? (i'm honestly curious; it's obviously related to the above questions)

The issue of stuff built on new systems not loading on older ones because build min version > system min version.

could we query the Pd binary to see which min-version it used?

Google/SO say it's possible via otool: https://stackoverflow.com/questions/17143373/determine-minimum-osx-version-a-binary-was-compiled-for

@umlaeute
Copy link
Contributor

umlaeute commented Jun 2, 2021

As far as I can tell, it handles deprecation checks, API availability, weak importing, etc.

well yes, that's what setting the min-version does. my question was rather about what is implied by not setting it.

Pd itself is compiled targeting 10.6, so leaving this off is very likely to result in an external building without trying to maintain some semblance of backward compatibility.

that's actually the idea: to optimize for "local builds" (that are to be used on the machine that executed the build), and move the "distribution maintainers" burden (make sure that things run on some random user's machine who refused to upgrade for the last 15 years) to humans.

the reason for shifting the responsibility to humans is that the problem seems to be hard to solve on a generic level, but rather easy for a specific compiler.
if we shift the responsibility, the api should make it simple enough to perform the necessary steps.

When making anything with Xcode itself, the min version is always set.

that is good to know.

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

No branches or pull requests

4 participants