OSX + homebrew + CMake + libpng version mismatch issue
Asked Answered
G

3

9

I'm having a rather strange issue while building a C++ project on OSX using CMake, while pulling in libpng as a dependency. I have libpng 1.6.21 installed via homebrew and the following CMake rules:

FIND_PACKAGE(PNG REQUIRED)
INCLUDE_DIRECTORIES(${PNG_INCLUDE_DIRS})
LINK_DIRECTORIES(${PNG_LIBRARY_DIRS})
ADD_DEFINITIONS(${PNG_DEFINITIONS})

When CMake starts to build and finds the dependencies, it outputs:

-- Found PNG: /usr/local/lib/libpng.dylib (found version "1.4.12") 

Investigating further, /usr/local/lib/libpng.dylib is a symlink to brew's 1.6 version:

$ ls -l /usr/local/lib/libpng.dylib 
lrwxr-xr-x  1 fluffy  admin  40 Apr  9 16:06 /usr/local/lib/libpng.dylib -> ../Cellar/libpng/1.6.21/lib/libpng.dylib

However, it appears that it is the incorrect png.h that is being included, as printing out PNG_LIBPNG_VER_STRING at startup outputs 1.4.12. And, of course, when I try running my program, I get a version mismatch and the library fails to work:

libpng warning: Application built with libpng-1.4.12 but running with 1.6.21
libc++abi.dylib: terminating with uncaught exception of type std::runtime_error: [write_png_file] png_create_write_struct failed

Using FIND_PACKAGE(PNG), the -I declarations never appear in my build line when I build with VERBOSE=1. However, if I use the PkgConfig approach:

FIND_PACKAGE(PkgConfig)
PKG_CHECK_MODULES(LIBPNG libpng16 REQUIRED)
INCLUDE_DIRECTORIES(${LIBPNG_INCLUDE_DIRS})
LINK_DIRECTORIES(${LIBPNG_LIBRARY_DIRS})
LINK_LIBRARIES(${LIBPNG_LIBRARIES})
ADD_DEFINITIONS(${LIBPNG_DEFINITIONS})

the correct -I flag does appear, and yet it's still using the system png.h instead of Homebrew's.

Is there any way to force the compiler to use homebrew's png.h? I can't simply uninstall the homebrew libpng since some of my other packages depend on it, including other libraries that this program makes use of.

EDIT: As a temporary workaround I've just added /usr/local/include to my INCLUDE_DIRS() and included libpng16/png.h instead, but this is a fragile hack.

Girhiny answered 9/4, 2016 at 23:22 Comment(8)
Possible duplicate for CMake compile options for libpngCacao
@joel It is not a duplicate, this is an OSX-specific issue; the answer in that question is what isn't working for me.Girhiny
Your problem is platform independentCacao
@Cacao Except it isn't. OSX provides a system libpng, homebrew provides a different version. And look at the answers on the linked one and compare it to the CMake fragment I'm using here...Girhiny
If pkg-config method fails then something screwed you png paths. A correct png libraries installation ensure to run multiple libpng threads.Cacao
Turns out the problem wasn't the linker, but the compiler finding the wrong png.h. Still, getting CMake and CLANG to see the right png.h is proving difficult at best.Girhiny
Did you ever find a non-hacky solution?Fixed
@S.S.Anne Unfortunately, no, I haven't been doing any C++ and libpng stuff in the four years since I posted this question. It's a bit disheartening to hear that this issue still exists.Girhiny
G
6

Have stumbled on this infuriating bug today and spent some time to get to the end of it. The problem is that classical cmake-style Find*.cmake search for the headers and libraries separately - and the results MAY and WILL mismatch in some cases. MacOS exaggerates the problem by having special case of frameworks, being searched BEFORE other locations by default.

In my case cmake finds headers from /Library/Frameworks/Mono.framework, which of course are outdated and doesn't have the libraries at all.

You have the options of:

  1. set(CMAKE_FIND_FRAMEWORK LAST) This fixes the issue with the rogue frameworks like this(in my case) This is the fast dirty fix.

  2. Use PackageConfig as you originally did - This is the recommended long-term solution. Using PackageConfig prevents lib/headers/flags mismatch. The only thing you should do is ensure the include path is passed to the compiler right before the system ones.

  3. remove the offending framework/path/library (e.g. zlib sometimes packages libpng too!)

  4. include a copy of libpng in your repo and use it

Gest answered 24/9, 2020 at 21:11 Comment(3)
2. seems like the best approach for systems that have pkg-config, I think I'll switch to that for non-WindowsFixed
I ended up using FindPNG as a fallback if pkg-config didn't work. Thanks!Fixed
I would suggest also to set CMAKE_FIND_FRAMEWORK externally as cmake executable argument -DCMAKE_FIND_FRAMEWORK=NEVER (or -DCMAKE_FIND_FRAMEWORK=LAST), so it's not that dirty fix, especially when running on public continuous integration environments, for example Github Actions, where possibly everything to be found should be present in brew packages and not to be randomly found because of pre-installed frameworks (for example Mono, which is cluttering the build with many libraries)Ringent
D
2

Requirements

  • the header version matches the version used to build the libpng library
  • also it is mentioned in bounty description, that solution shouldn't involve PkgConfig

Since PkgConfig is usually the preferred solution, two solutions are presented - one with and one without using PkgConfig.

The first requirement can be represented in some C code like this:

#include <stdio.h>
#include <png.h>

int main(void) {
    printf("libpng version of lib (%u):%s", 
           png_access_version_number(), 
           png_get_header_version(NULL));
    printf("used libpng version in app (%d):%s", 
           PNG_LIBPNG_VER, 
           PNG_HEADER_VERSION_STRING);
    return 0;
}

Build

To get some debug output, you could use a small script that creates the application:

#!/bin/zsh
rm -rf build
cmake -B build
cmake --build build -v
build/png_app

Solution with PkgConfig

cmake_minimum_required(VERSION 3.17)
project(png_app C)

set(CMAKE_C_STANDARD 99)

find_package(PkgConfig REQUIRED)
pkg_check_modules(PNG libpng16 REQUIRED)

add_executable(png_app main.c)
target_include_directories(png_app PRIVATE ${PNG_INCLUDE_DIRS})
target_link_directories(png_app PRIVATE ${PNG_LIBRARY_DIRS})
target_link_libraries(png_app ${PNG_LIBRARIES})

Test Solution 1

libpng was installed with homebrew like this:

brew install libpng

The output is of the program run is:

libpng version of lib (10637): libpng version 1.6.37 - April 14, 2019
used libpng version in app (10637): libpng version 1.6.37 - April 14, 2019

So we can see that it works like intended! Header version and used library version match.

Note: in the debug output we can see, that cc is called with

.../cc  -I/usr/local/Cellar/libpng/1.6.37/include/libpng16 ...

and linking happens like this:

.../cc  ... main.c.o -o png_app -L/usr/local/Cellar/libpng/1.6.37/lib -Wl,-rpath,/usr/local/Cellar/libpng/1.6.37/lib -lpng16 -lz

Note: if you get an error during building:

Could NOT find PkgConfig (missing: PKG_CONFIG_EXECUTABLE)

then install it also with brew:

brew install PkgConfig

Solution 2

The second solution is without PkgConfig by using hard coded paths. If a new version of the library is installed, the PNG_BASEPATH must be adapted.

cmake_minimum_required(VERSION 3.17)
project(png_app C)

set(CMAKE_C_STANDARD 99)

set(PNG_BASEPATH /usr/local/Cellar/libpng/1.6.37)
set(PNG_LIBRARIES png16)

add_executable(png_app main.c)
target_include_directories(png_app PRIVATE ${PNG_BASEPATH}/include)
target_link_directories(png_app PRIVATE ${PNG_BASEPATH}/lib)
target_link_libraries(png_app ${PNG_LIBRARIES})

Why does find_package not work?

When I try to use the find_package variant from the OP question: You can see what the problem is with the output of the verbose build command :

The compile command looks like:

.../cc  -I/Library/Frameworks/Mono.framework/Headers ... -o .../main.c.o -c .../main.c

So it tries to use libpng14.14 from /Library/Frameworks/Mono.framework instead of the version installed by homebrew.

Dot answered 25/9, 2020 at 12:46 Comment(1)
hmm, maybe I was misguided to think that I shouldn't do it with pkgconfigFixed
M
0

I met the same issues while setting up the CI on github Actions.

At the end, again, Mono was conflicting. After a few hours trying to work around this conflict using various flags, I still got differences between build and runtime. For those without local MacOs to debug and test, here is a quick solution: https://gist.github.com/nicerobot/1515915

It removes mono. It does not touch anything else, problem solved. Please do not do that for anything else, this is the most ugliest way to fix things. :)

Midi answered 26/8, 2021 at 9:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.