OSX + homebrew + CMake + libpng版本不匹配问题

8

我在使用CMake在OSX上构建一个C++项目时遇到了一个比较奇怪的问题,这个项目需要依赖libpng。我已经通过homebrew安装了libpng 1.6.21,并使用以下CMake规则:

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

当CMake开始构建并找到依赖项时,它会输出:
-- Found PNG: /usr/local/lib/libpng.dylib (found version "1.4.12") 

进一步调查发现,/usr/local/lib/libpng.dylib 是到 brew 的 1.6 版本的符号链接:

$ 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

然而,似乎被包含的是错误的png.h文件。在启动时打印PNG_LIBPNG_VER_STRING的输出是1.4.12。当我尝试运行程序时,出现版本不匹配的问题,这导致库无法工作。请注意保留HTML标记。
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

使用FIND_PACKAGE(PNG)时,当我使用VERBOSE=1进行构建时,-I声明从未出现在我的构建行中。但是,如果我使用PkgConfig方法:

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})

正确的-I标志确实出现了,但它仍然使用系统的png.h而不是Homebrew的。有没有办法强制编译器使用Homebrew的png.h?我不能简单地卸载Homebrew的libpng,因为我的其他一些包依赖它,包括这个程序使用的其他库。
编辑:作为一个临时解决方法,我只是将/usr/local/include添加到我的INCLUDE_DIRS()中,并包含了libpng16/png.h,但这是一个脆弱的hack。

1
@joel 这不是重复的问题,这是一个特定于OSX的问题;那个问题中的答案对我没有起作用。 - fluffy
你的问题与平台无关。 - Joel
1
除了这不是。OSX提供了一个系统libpng,homebrew提供了一个不同的版本。看看链接中的答案,再将其与我在这里使用的CMake片段进行比较... - fluffy
如果 pkg-config 方法失败,则说明某些东西损坏了您的 png 路径。正确安装 png 库可以确保运行多个 libpng 线程。 - Joel
1
@S.S.Anne 不幸的是,自从我发布这个问题以来的四年里,我没有做过任何关于C++和libpng的东西。听到这个问题仍然存在有点让人沮丧。 - fluffy
显示剩余3条评论
3个回答

6
今天我遇到了一个令人恼火的错误,花费了一些时间才解决了它。问题在于传统的cmake-style Find*.cmake 分别搜索头文件和库文件-在某些情况下结果可能不匹配。
在MacOS上,这个问题被夸大了,因为特殊情况下的framework会优先搜索,而不是其他位置。
在我的情况下,cmake会从/Library/Frameworks/Mono.framework中找到头文件,这些头文件当然已经过时,并且没有库文件。
你有以下几个选择:
1. set(CMAKE_FIND_FRAMEWORK LAST) 这样可以解决类似我的这个框架问题,是快速脏修复。
2. 使用PackageConfig像你最初做的那样 - 这是推荐的长期解决方案。 使用PackageConfig可以避免 lib/headers/flags 不匹配的问题。 唯一要做的是确保在系统路径之前将包含路径传递给编译器。
3. 删除有问题的framework/path/library(例如zlib有时也会打包libpng!)
4. 在你的repo中包含一个libpng的副本并使用它。

1
似乎对于有pkg-config的系统来说,这是最好的方法,我认为我会在非Windows系统上切换到这种方法。 - S.S. Anne
我最终使用FindPNG作为备用,如果pkg-config无法工作。谢谢! - S.S. Anne
我建议将CMAKE_FIND_FRAMEWORK设置为外部的cmake可执行参数-DCMAKE_FIND_FRAMEWORK=NEVER(或-DCMAKE_FIND_FRAMEWORK=LAST),这样就不会那么脏了,特别是在公共持续集成环境(例如Github Actions)上运行时,可能需要在brew包中提前安装所有要使用的内容,而不是因为预安装的框架(例如Mono,它会使构建中出现许多库)而随机找到。 - ceztko

2

要求

  • 头文件版本必须与构建libpng库使用的版本匹配
  • 赏金描述中还提到,解决方案不应涉及PkgConfig

由于通常情况下PkgConfig是首选解决方案,因此提供了两种解决方案-一种使用PkgConfig,另一种不使用。

第一个要求可以用一些C代码表示如下:

#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;
}

构建

为了获得一些调试输出,您可以使用一个小脚本来创建应用程序:

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

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})

测试方案1

使用Homebrew安装libpng的方法如下:

brew install libpng

程序运行的输出结果如下:
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

我们可以看到它按照预期工作!标题版本和使用的库版本匹配。

注意:在调试输出中,我们可以看到cc被调用并带有

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

而链接的实现方式如下:
.../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

注意:如果在构建过程中出现错误:
Could NOT find PkgConfig (missing: PKG_CONFIG_EXECUTABLE)

然后也使用brew安装它:

brew install PkgConfig

解决方案2

第二个解决方案是不使用PkgConfig并使用硬编码路径。如果安装了库的新版本,则必须调整PNG_BASEPATH。

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})

为什么find_package无法工作?

当我尝试使用OP问题中的find_package变量时,您可以通过详细构建命令的输出看到问题所在:

编译命令如下:

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

因此,它尝试使用位于 /Library/Frameworks/Mono.framework 的 libpng14.14 版本,而不是 homebrew 安装的版本。

1
也许我误以为不应该使用pkgconfig来完成它。 - S.S. Anne

0

在设置 GitHub Actions 的 CI 时,我遇到了相同的问题。

最后,Mono 仍然存在冲突。尝试使用各种标志解决此冲突几个小时后,我仍然会在构建和运行时遇到差异。对于那些没有本地 MacOs 进行调试和测试的人,这里有一个快速解决方案: https://gist.github.com/nicerobot/1515915

它会移除 Mono。它不会改变其他任何东西,问题得到解决。请不要为其他事情这样做,这是最丑陋的解决方法。 :)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接