Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

OSX + homebrew + CMake + libpng version mismatch issue

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.

like image 673
fluffy Avatar asked Apr 09 '16 23:04

fluffy


2 Answers

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

like image 168
Kikaxa Avatar answered Oct 26 '22 03:10

Kikaxa


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.

like image 39
Stephan Schlecht Avatar answered Oct 26 '22 02:10

Stephan Schlecht