-
Notifications
You must be signed in to change notification settings - Fork 27
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
iOS/OSX universal binaries handling #59
Comments
cc/ @tru |
Yes, I think in that case (approach 1), the Logic for having packages that matches different settings can be done in the def package_id(self):
if "arm" in self.settings.arch:
self.info.settings.arch = "AnyARM" This will build one package for any arm architecture, and one package for x86, and another for x86_64 and so on. I like the approach (1), it is explicit, reads well. You say it is much more complex, but the intention is so evident and explicit, you can perfectly know what the recipe is doing. And it is just a few lines. So IMHO, it will be more robust and easy to maintain that other approaches. If sharing code, maybe reusing such code via shared python packages could be an option. You can see that the explicit build of different configurations is what I am proposing for multi-config (debug/release) packages in https://github.com/conan-io/docs/pull/161/files#diff-e4c22b2beb97bec45bf31f8a032893afR120 (PR to the docs, with some new 0.20.0 features) |
@memsharded sounds good, I am going to try approach with package_id() |
Yes, those utility functions can be added to tools, are the same concept as |
I am very interested in this considering I was pestering @memsharded about this the other day. I think the tricky parts are the following:
Just spitballing here, but the "perfect" setup would actually be if conan could do the following:
This could be done by adding a new property This would make the conanfile much much nicer since it can reuse the same methods without a lot of extra code for one specific platform. If |
Here is a mock example of how it could look: class MultipleVariant(ConanFile):
name = "testmultiple"
version = "1.0"
settings = "os", "compiler", "build_type", "arch"
def configure(self):
if self.settings.os == "iOS":
self.variants = ["arm7", "arm64", "x86_64"]
elif self.settings.os == "MacOS":
self.variants = ["x86", "x86_64"]
def source(self):
tools.download("http://foo.com/bar.bz", "bar.bz")
tools.unzip("bar.bz")
def build(self, variant):
self.run("./configure --arch={0}".format(variant))
self.run("make")
def package(self, variant):
self.copy("*.dylib", src="lib", dst="tmp/" + variant)
def combine_package(self):
if not self.variants:
return
libs = [l for l in os.listdir("tmp/x86_64") if l.endswith(".dylib")]
for lib in libs:
self.run("lipo -o lib/{0} {1}".format(lib, [os.path.join("tmp", var, lib) for var in self.variants]))
shutil.rmtree("tmp") |
@tru that sounds like a good addition as well. currently in my scripts I am building into the $TMPDIR/{arch}, which is obviously outside of conan source directory. adding variants and post-processing method to the conanfile sounds like a great idea. |
This can be used for much more than just iOS/MacOS binaries as well. I have at least one other package where I need to build it twice right now that could benefit. |
For extra points I guess variants could be set on a global level in the profile or something and the packages can choose to use it by adding the |
Why do you need to build twice that package? Remember that we support in 0.20.0 the
I'm not sure about a parallel implementation to support variant overriding. It could be done with envionment variable, remember that now you can use profiles to assign package level variables: Profile:
Conanfile:
I like the idea, but I need to think about possible problems implementing this variants feature. |
This seems like the opposite behavior of the one implemented with the new |
HI, I've been talking with @memsharded and we are not sure about the variants. Please, look at this example code and tell me the problems that you see: class MultipleVariant(ConanFile):
name = "testmultiple"
version = "1.0"
settings = "os", "compiler", "build_type", "arch"
def package_id(self):
if self.settings.arch in self.variants:
self.info.settings.arch = ",".join(self.variants)
def source(self):
tools.download("http://foo.com/bar.bz", "bar.bz")
tools.unzip("bar.bz")
def build(self):
for variant in self.variants:
variant_dir = self.variant_dir(variant)
mkdir(variant_dir)
copy_build_files_to(variant_dir, exclude="tmp_*") # <= Pseudocode, could provide some tool
with tools.chdir(variant_dir):
self.run("./configure --arch={0}".format(variant))
self.run("make")
def package(self, variant):
for variant in self.variants:
variant_dir = self.variant_dir(variant)
self.copy("*.dylib", src="%s/lib" % variant_dir, dst="tmp/" + variant)
self.combine_package()
@property
def variants(self):
if self.settings.os == "iOS":
return ["arm7", "arm64", "x86_64"]
elif self.settings.os == "MacOS":
return ["x86", "x86_64"]
return [self.settings.arch]
def variant_dir(self, variant):
return "tmp_%s" % variant
def combine_package(self):
if not self.variants:
return
libs = [l for l in os.listdir("tmp/x86_64") if l.endswith(".dylib")]
for lib in libs:
self.run("lipo -o lib/{0} {1}".format(lib, [os.path.join("tmp", var, lib) for var in self.variants]))
shutil.rmtree("tmp")
We think that it's more explicit and do not introduce more complexity in the conan model/processes. |
Yeah I don't see a huge problem with what you layed out there. The only thing that I need to dig into is how easy it is to copy the sources without doing it a great many times. I could centralize some of those functions into my own conantools package as well so I think it will be OK. |
Ok, feel free to share with us your tool to copy the sources, we can put it in the conans.tools package. |
by the way, usually it even is't required to copy sources - e.g. with most configure-style projects "--prefix" can be used to ensure "make install" copies headers, libraries and other stuff into the required directory, and "make clean"/"make distclean" ensures that working copy has no leftover after the previous build(s). |
It's a great theory - but I have seen many problems with distclean not cleaning out extra files. I probably want to copy sources anyway to be 100% sure. |
yeah - that's okay and I think is up to package author whether to copy sources or just use --prefix. sometimes it's just waste of time that should be avoided (e.g. in case of boost, which also provides b2 clean). |
That's another reason to keep it explicit, we could provide some tool to copy sources, but if any package only needs to change the prefix (or any other mechanism) won't be forced to copy again the sources. |
Related question to this: I am setting my CFLAGS from my profile, but now when we build all ARCH's in one "go" I wonder what the best way to have variables in the profiles could be, consider this from my profile:
changed to:
now I need a way to expand that variable, one idea is to do:
anyone can think of anything better? |
I would leave CFLAGS in profile without arch, and append arch in conanfile, e.g.:
actually, there are more flags to be set depends on variant - e.g. -isysroot is usually required |
Yeah that might be better. |
It passed quite some time since last comment and yet there are no changes related to this issue in the |
Recently, @SSE4 was doing some great work related to apple tools in: conan-io/conan#1815. I think there is still interest, but the problem might be more challenging than seems at first sight, as the packaging many-to-one flow is not defined in conan so far. So depending on what would be the approach, it might be very difficult. Having some helpers to build all the required variants in the same package, that seems doable. |
I analyzed this issue for a bit and yes, it looks quite hard to find a generic solution. |
Looks like CMake has direct support for fat binaries now https://cmake.org/cmake/help/latest/variable/CMAKE_OSX_ARCHITECTURES.html |
This kind of many to one flow is still needed on Android when building with the ndk provided cmake toolchain (which is required if you want to build for a recent NDK). Another approach would be to allow requirements to specify settings. So I could create a parent package that would require a child with android abi armeabi-v7a and the same child with arm64-v8a. Then assemble them in an aar file for Android distribution. so I could do a
|
Sorry to necropost, but did this issue ever end up bearing fruit? How are other Conan users generating universal binaries containing x86_64 and arm64 code for platforms like macOS? |
For my project, we solved this by using conan profiles, and adding missing symbols manually.
This allows universal binaries building for macos. |
In the end I opted to spend a day writing my own package manager that provides us with the functionality we need without all this hassle. :) |
@memsharded my goal (which I imagine is a common one) is to build an application with many CCI dependencies. The app's recipe should then generate an Xcode project with CMake that will link a fat binary. Xcode can sign the application bundle and upload to the app store (especially for iOS developers). I think having a multi-arch Xcode project will make this more familiar. My pre-2.0 attempt (which is a huge hack) worked like this:
Here's the code. The lipo stuff is pretty useful. The rest is a mess but it does work. I'm not sure if this is the workflow others envision but I can try to move my lipo code to a deployer. I'm not sure how to communicate these fat library paths to the CMake tool. |
@gmeeker, Thank you so much! We are desperate for something like this and your biggest cheerleaders! ;-) If it helps at all, we have 2 very different use cases that would both benefit:
|
Is it possible to define new arch type for universal binary? For example: |
Thanks for the suggestion @zhangyiant but I don't think it would be possible. I think I have seen users doing fat binaries for more configurations too, like should they bundle debug and release too? ios/watchos? It it is only one and exactly one configuration for all Mac users we could consider it, but I am not sure it will be the case. |
@memsharded Regardless of what you think you've seen people doing, fat binaries are designed to combine different architectures. Not platforms. Not configurations. Just architectures. And if Conan isn't willing to support that, then it's not fit for purpose. Forcing people to complicate their pipelines and build processes to fit the limitations of a single tool will very quickly ensure that tool is replaced. |
Fat binaries are fundamentally squashed together binaries indexed by cpu type. There have been lots of theoretically useful selections (list is not exhaustive):
where I have ios you can sub in tvOS or similar However, most of these combinations are not useful any more in the general case.
To solve the iOS plus simulator use case apple introduced a folder structure called xcframework which has multiple binaries for each platform (this is how you can do multiplatform now). But it is not supported by CMake yet (maybe some other build systems have special support). So as far as I’m concerned there is one remaining important universal binary platform, which is universal 2 (intel 64 plus arm 64 macOS) |
Yes. For macOS, only universal 2(x86_64 and arm64) is useful nowadays I think. |
iOS + iOS Simulator is still used nowadays (at least on x86_64 Macs, as not everyone upgraded to M1 yet). |
With conan v2.0, you can use the compatibility plugin to define that your arch |
I think there is a big difference between this is "the most common/important/mainstream way nowadays" and "this is the only way nowadays". Given the very large user base, and from my experience talking to many hundreds of users, there will still be some of them that are dealing with other variants rather than If any of that happens, then this approach would be quite poor, as the combinatorics and evolution to take into account other architectures will make it a dirty hack. If we then try to deprecate it, we will get the fire from users relying on that, and not concerned that the solution is bad for other users. If we don't, then the complexity starts to pile up, becomes technical debt, slows down the team and affects morale... You don't have to deal with it, but we do, and it is not a pleasant experience. The larger the Conan user base, the most important is to carefully consider the alternatives, approaches and solutions and make sure they scale to the wider community, and they are not just partial or incomplete, because those rotten very quickly. I am fine with doing an experimental try of adding this combined architecture for apple, and see how it goes, I'll take it to the team for discussion. |
Is anybody actively working on this? I am happy to be a guinea pig if it helps. This is a huge need for us at multiple levels... both to create a single universal set of libs, exes and installers... for a macOS app, but also for mobile SDKs... Ideally I'd love to even see another extension to wrap multiple Frameworks into an xcframework (i.e. macOS + iOS + ...) but that's maybe wishful thinking.. ;-) |
Hi @gegles Sorry, we haven't had the time to prioritize this yet. Still in our roadmap, but team still over capacity with other priorities. After sharing with the team and carefully analyzing all the feedback, it seems that managing internal Conan binaries as universal binaries is not a viable approach, and doesn't provide much value in the developer stage, while adding a ton of complexities to Conan internal, and it will be likely fragile and problematic to scale. So we will focus on the automation for creating universal binaries out of the Conan single-architecture binaries, in an easy way that allows Conan users to create and deliver their binaries (outside of Conan) as universal binaries. The first development stage would probably be an extension (likely a deployer, maybe a custom command) in https://github.com/conan-io/conan-extensions repo. If anyone wants to start contributing something in this line, it would be welcomed. |
I've had other priorities, but I'm planning to investigate this soon, maybe over the summer. See my comment in March 4th. I think my previous work could be partially adapted to a 2.0 deployer and custom command. However, my goal was to make the final app recipe build as universal and consume lipo'd packages, not build the app and then lipo it. The idea was to generate an Xcode project that could code sign, submit to the app store, etc. I'll focus on the lipo deployer first and that much might be useful to others. |
We are in the process of upgrading our current packages to conan 2, with this issue still giving me the biggest headache here. Currently we build all our libraries as fat libs, so we have custom Python code in each package (not a good approach) and we still have the problem that we currently can't support the iOS simulator on ARM-based Macs (the new default development machines). A deployer could be a solution if:
I currently haven't seen a way for a deployer to consume the libs from different configurations, or for a deployer to create the needed information to consume the deployed artifacts. |
I've finally made some progress on the first stage here. A custom deployer isn't terribly useful because it's getting passed a single arch from conan. Instead I wrote a custom command that acts like If this seems like a useful design, I can contribute it to the conan-extensions repo above. (I think the _lipo.py helper should end up in mainline conan under tools.apple so I'm leaving it in a separate file for now.) I haven't looked at iOS at all. Note that this does not integrate with generators in any way, but at least it seems to fit in with conan's design goals. I do plan to pursue that next, but it's probably still going to involve some ugly hacks like in my BuildConan repo above, such as patching the output files of generators to point to the universal libraries. I haven't been able to get Conan 2.0's CMake or Xcode tools to allow multiple archs (like the previous hack to use a custom toolchain). In the meantime, look at the xcode example in conan-deploy-lipo. You deploy the universal binaries using conan and then you develop using a standalone Xcode project. It might be enough some purposes (very small number of conan dependencies or a project that isn't cross platform). |
@gmeeker, thanks for working on this! Here's the hack I've been using to support multiple architectures on iOS in Conan 2.0 CMake: @memsharded I know Conan is supposed to be build-system agnostic, but if there was a way to pass a Anyway, whichever method is used to fix this, I'll help where I can! |
Yikes! Patching individual recipes in CCI sounds like quite a rabbit hole. I looked into that, and other options, but we have too many dependencies. I'll put up a PR in conan-extensions soon for the deployment custom command. What I did here:
My next plan is to try rewriting that for 2.0 and see what still needs to access conan internals. Hopefully it's cleaner this time, and if so maybe could be considered for conan-extensions. |
@ssrobins Oh, thanks for the tip for setting CMAKE_OSX_ARCHITECTURES! I'll try to spend some time on the rest of it soon. |
Unfortunately, I also had to patch a lot of open-source projects' build systems in order to get a correctly built universal binary. The problem is that CMake will "support" multi-arch build even with ninja, but this does not generate separate build tasks for each architecture - instead, it simply puts both architectures' flags to the clang invocation. This works OK for generic code, but doesn't work for source files that have specific intel or ARM optimizations, like those in OpenSSL, libjpeg-turbo, OpenCV, and similar high-performance libraries. Even if conan supported passing this variable, it would work only with packages that use CMake as their build system and whose CMake script is written in a "modern" way using generator expressions rather than depending on |
Interesting point, I was not aware of that.
I've not run into issues with |
It depends on the project. Ninja/Make will add both
The last point is especially important for projects like set_target_properties( simd PROPERTIES
XCODE_ATTRIBUTE_EXCLUDED_SOURCE_FILE_NAMES[arch=x86_64] "${ARM_COMMON_SRCS} ${ARM64_SRCS} ${ARM_SRCS} ${INTEL32_SRCS}"
XCODE_ATTRIBUTE_EXCLUDED_SOURCE_FILE_NAMES[arch=i386] "${ARM_COMMON_SRCS} ${ARM64_SRCS} ${ARM_SRCS} ${INTEL64_SRCS}"
XCODE_ATTRIBUTE_EXCLUDED_SOURCE_FILE_NAMES[arch=armv7] "${INTEL64_SRCS} ${INTEL32_SRCS} ${intel_generic_sources} ${ARM64_SRCS}"
XCODE_ATTRIBUTE_EXCLUDED_SOURCE_FILE_NAMES[arch=armv7s] "${INTEL64_SRCS} ${INTEL32_SRCS} ${intel_generic_sources} ${ARM64_SRCS}"
XCODE_ATTRIBUTE_EXCLUDED_SOURCE_FILE_NAMES[arch=arm64] "${INTEL64_SRCS} ${INTEL32_SRCS} ${intel_generic_sources} ${ARM_SRCS}"
XCODE_ATTRIBUTE_GCC_PREPROCESSOR_DEFINITIONS[arch=armv7] "$(GCC_PREPROCESSOR_DEFINITIONS) NEON_INTRINSICS=1"
XCODE_ATTRIBUTE_GCC_PREPROCESSOR_DEFINITIONS[arch=armv7s] "$(GCC_PREPROCESSOR_DEFINITIONS) NEON_INTRINSICS=1"
XCODE_ATTRIBUTE_GCC_PREPROCESSOR_DEFINITIONS[arch=arm64] "$(GCC_PREPROCESSOR_DEFINITIONS) NEON_INTRINSICS=1"
) This of course works only with the Xcode generator, and I really have no idea how to apply that to Ninja/Make with the The problem is not in Conan - it's usually in the packages and their build scripts. The only thing I think Conan could help with here is the idea that was already mentioned above - add "built-in" support for obtaining both arm64 and x86_64 dependency graphs and generate a But, on the other hand, it should also be possible to write a custom conan command that performs multiple arch installs and then post-processes the generated An alternative is to use the custom deployer that smashes binaries together using You should choose which approach you should use depending on what suits you best. The deployer approach seems easier but AFAIK it won't work in an environment where some packages are already universal, and some are not. The postprocessor step in the custom command could possibly handle that (it only needs to check if a certain package has a |
Moved this ticket to the |
Any thoughts about this limited approach in #52? |
universal (fat) binaries are pretty common for Apple platforms - that's just binaries for several architectures combined together.
sorry for the long text - but I am trying to describe my use case as detailed as possible :)
e.g. for OSX it's common to have x86 + x64 universal binaries, while for iOS it's usually armv7 + armv7s + arm64/armv8 combined together, plus optionally x86 and x64, if iOS simulator is requied (so from 3 to 5 architectues). same story for other Apple platforms, such as watchOS and tvOS.
usually, universal binaries are produced by running multiple builds and then running lipo utility on resulting shared/static libraries. there is alternate approach to run single universal build, but it tends to be complicated and error-prone with configure (autotools) style projects - e.g. such configure scripts need to detect size of pointer, which is ambiguous in case of universal binaries (sizeof(void*) - 4 or 8?).
I need some advice on how to proceed with universal binaries in conan. there are several approaches how it could be done:
this is the way I am currently using. typical conan file may look like:
and then for OSX/iOS it will become much more complicated, like:
there are few disadvantages of such approach:
the idea is to build conan packages for each architecture (armv7, arm64, etc) as usual and then somehow combine them into aggregated multi-architecture conan package. I am not sure if it's even possible now to have some approach for conan package to support multiple settings at the same time, but probably there are more use cases for such combined packages (e.g. Windows tools which may run on both x86 and x64, but not on ARM might have combined x86+x64 indication). probably this approach ends up in some new conan command line, like "conan combine" or "conan lipo" which will run lipo for all libraries from packages to be combined.
some unclear points about this approach:
The text was updated successfully, but these errors were encountered: