news 2026/9/9 23:14:15

macOS上编译lincity-ng:从jam到CFLAGS的完整踩坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
macOS上编译lincity-ng:从jam到CFLAGS的完整踩坑指南

前阵子折腾了一晚上,终于在自己的 MacBook 上把 lincity-ng 编译通过、跑了起来。这个老牌开源项目其实挺好玩的,跟 SimCity 一个路子,但完全免费、源码可看,而且对硬件要求极低——我愿称之为公司电脑上的摸鱼神器。但吐槽的地方也在这:它的构建系统用的是 jam,而不是大家熟悉的 make,很多人在 macOS 上编译它都是卡在一堆莫名其妙的 CFLAGS、CXXFLAGS、LDFLAGS 上。这篇就把我完整的踩坑链路和最终可用的配置参数写下来,给同样想自己编译一份的人省点时间。

1. 为什么偏要折腾这个老古董:lincity-ng 的背景与选型理由

1.1 它到底是个什么游戏

lincity-ng(LinCity Next Generation)是经典开源游戏 lincity 的 C++ 重写版,本质上是一个城市模拟经营游戏。你需要在固定的地图上规划住宅区、工业区、商业区,打理电厂、水厂、交通、税收,维持居民的满意度,让城市可持续发展下去。对比 SimCity 系列,它的画面是等距视角,没有太华丽的特效,但玩法逻辑完完整整,还带一个可持续性评价的指标——这在 2005 年前后算是开源游戏里相当用心的作品了。

游戏本体非常轻量,编译完的可执行文件也就几 MB,运行时占用的内存甚至可以控制在 50MB 以内。正因如此,很多老玩家在换到 macOS 之后依然想把它留在硬盘里。遗憾的是它的官方发布渠道基本只提供 Linux 源码包和 Windows 二进制包,macOS 版要么依赖别人维护的旧 Homebrew formula(而且经常是坏的),要么就得自己啃源码编译,哪怕是 Apple Silicon 的新机器,代码层面的兼容性其实比想象中好,真正卡人的是构建工具链。

1.2 构建系统的历史包袱:jam 不是 make

这个项目用的是 jam 作为构建系统,而不是 autotools 或 CMake。jam 是 Perforce 出的构建工具,全称是 “Johannes' Make”,语法比 make 简洁不少,但在 macOS 上最大的问题就是:系统默认不装,Homebrew 里也未必有现成的 formula,就算有也经常因为年代久远而安装失败。我第一次尝试的时候,卡在“jam 命令找不到”这一句上足足浪费了半小时。

lincity-ng 的源码目录里其实带了 Jamfile 和 config.mk 之类的配置文件,但默认配置是写给 Linux/gcc 的,放到 macOS 上直接跑 jam,大概率会遇到两类错误:第一类是编译器找不到 SDL 头文件,第二类是链接器找不到 SDL 动态库。这两个问题不解决,后续全是无用功。很多人误以为是代码在 macOS 上编译不过,其实只是因为 jam 的配置里根本没有 macOS 的默认路径——它不像 autotools 那样会自动探测系统环境,所有头文件路径和库搜索路径都必须你自己通过环境变量喂给它。

1.3 macOS 上与 Linux 的三大环境差异

在继续之前,得先把 macOS 和 Linux 在编译老项目时的差异讲清楚。差异不止是路径不同,更是几个根深蒂固的机制差异:

  • Sysroot 与头文件路径:Linux 下 SDL 头文件通常装在/usr/include/SDL,macOS 下如果走 Homebrew,路径会是/opt/homebrew/include(Apple Silicon)或/usr/local/include(Intel)。jam 默认不会去这些地方找。
  • OpenGL 的形态:Linux 下 OpenGL 是libGL.so,直接-lGL就行;macOS 下 OpenGL 是 Framework,链接方式变成了-framework OpenGL,如果没有正确加上,编译器当然找不到。
  • 动态库的搜索与隔离:macOS 的动态库依赖路径(install_name)和直接依赖环境变量的机制跟 Linux 差异很大,编译好之后运行时经常会报 “dyld: Library not loaded”,这就是动态库路径没理顺的后遗症。

理解了这三点,再来看 CFLAGS、CXXFLAGS、LDFLAGS 三个环境变量,脉络就清晰了。

2. 环境准备:Homebrew 依赖、SDL 版本陷阱和 jam 工具的获取

2.1 基础依赖的完整安装清单

我用的机器是 Apple Silicon(M2)的 MacBook,系统是 macOS Sonoma,Xcode Command Line Tools 已经装好。如果你也是这个环境,下面的步骤可以直接照抄。先确保 Homebrew 更新到最新:

brew update brew upgrade

然后安装编译期依赖。lincity-ng 的图形渲染依赖 SDL,图片加载依赖 SDL_image,音效与音乐依赖 SDL_mixer 和 SDL_ttf,文本渲染还依赖 libpng 和 gettext。我用的是这一条命令装齐:

brew install sdl sdl_image sdl_mixer sdl_ttf libpng gettext

注意一个重点:如果你敲完命令后看到的输出里提示安装的是 SDL 2.x,请务必警惕。lincity-ng 的源码写于 SDL 1.2 时代,代码里用的是SDL_SetVideoModeSDL_SurfaceSDL_Flip这类 1.2 专属 API,SDL 2 里这些函数虽然也能用,但是头文件路径和部分宏定义完全不同,直接编译会报一堆 “use of undeclared identifier”。

我的机器上 Homebrew 仓库里的sdl公式仍然指向 SDL 1.2.15 的兼容包,所以装完还算顺利。验证版本的方法很简单:

pkg-config --modversion sdl

如果输出1.2.15,那恭喜你,SDL 1.2 的部分没问题。如果拿到的是 2.x,也不要慌,后面会讲怎么应对。还有一个隐藏依赖是pkg-config,macOS 默认没有,务必先装上:

brew install pkg-config

2.2 SDL 1.2 的版本陷阱与备选方案

这里单独把 SDL 版本陷阱拎出来说,因为它是新手最容易翻车的环节。Homebrew 在 2020 年前后做过一轮清理,很多老版本公式被移出了核心仓库,sdl(1.2)就是其中一个。如果你的brew install sdl拿不到 1.2,可以试下面的替代路径:

  1. 从源码编译 SDL 1.2.15:去 libsdl.org 下载 SDL-1.2.15 的源码包,解压后依次执行:
./configure --prefix=/opt/homebrew make -j8 make install

这会把 SDL 1.2 的头文件和动态库安装到/opt/homebrew,和现有的 SDL 2 共存,互不干扰。缺点是后续所有库(SDL_image、SDL_mixer、SDL_ttf)都必须手动指定--with-sdl-prefix=/opt/homebrew来保证它们链接到正确的 SDL 1.2 上,链路比较长,但可控。

  1. 使用 sdl12-compat 兼容层:如果你只是想让编译通过,不想从源码编译全套,可以试试 SDL 官方出的 sdl12-compat 库,它提供的头文件是 1.2 的 API,底层实现调的是 SDL 2。不过我当时没走这条路,因为 lincity-ng 里还有一些直接依赖 SDL 1.2 内部结构的操作,兼容层可能有损耗,不如老老实实用真 1.2。

2.3 jam 构建工具的获取与验证

jam 在 macOS 上没有预装,Homebrew 核心仓库里也确实搜不到jam公式(我试过brew search jam,出来的全是 jamf、jammit 之类无关的东西)。编译一个 jam 其实也很简单,官网源码包只有几个.c文件,配合make一分钟就能装好:

git clone https://github.com/PerlToolsTeam/jam.git cd jam make sudo cp jam /usr/local/bin/

装好后验证一下:

jam -v

能打印出版本号,说明构建工具就绪。如果你懒得从源码编译,其实 lincity-ng 的源码包lincity-ng-2.9里也自带了 jam 的可执行文件(在jam子目录下),不过那个二进制比较老,在 Apple Silicon 上可能需要用 Rosetta 跑,能跑但不够优雅。

3. 编译三剑客:CFLAGS、CXXFLAGS、LDFLAGS 的配置逻辑

3.1 三个参数的分工与协作

很多人一看到这三个环境变量就头大,其实它们的职责非常清晰:

  • CFLAGS:传给 C 编译器的参数,主要用来指定头文件搜索路径(-I)、优化等级(-O2)和宏定义(-D)。
  • CXXFLAGS:传给 C++ 编译器的参数,同样负责头文件路径与优化等,但只作用于.cpp/.cc文件。lincity-ng 的核心代码是 C++ 写的,所以它比 CFLAGS 更关键。
  • LDFLAGS:传给链接器的参数,用来告诉链接器动态库在哪个目录(-L),以及需要链接哪些库(-l)。

这三者必须同时正确,缺一个都会在编译或链接阶段报错。在 macOS/Homebrew 的环境下,最容易被忽略的是编译器头文件搜索路径默认不包含/opt/homebrew/include,链接器默认不包含/opt/homebrew/lib,因此你必须手动把这两个路径塞进对应的变量里。

3.2 在 macOS 上的典型取值

我最后实际使用的参数组合是这样的(以 Apple Silicon + Homebrew 默认前缀/opt/homebrew为例):

export CFLAGS="-O2 -I/opt/homebrew/include -I/opt/homebrew/include/SDL -D_GNU_SOURCE" export CXXFLAGS="-O2 -I/opt/homebrew/include -I/opt/homebrew/include/SDL -I/opt/homebrew/include/libpng16 -std=gnu++11" export LDFLAGS="-L/opt/homebrew/lib -lSDL -lSDL_image -lSDL_mixer -lSDL_ttf -framework OpenGL -framework Cocoa"

逐个解释一下:

  • -I/opt/homebrew/include/SDL:SDL 1.2 的头文件多放在这个子目录里,不加的话#include <SDL.h>会找不到。
  • -I/opt/homebrew/include/libpng16:libpng 在 Homebrew 中默认把头文件装在libpng16子目录,lincity-ng 的资源加载代码里有png.h的直接引用。
  • -std=gnu++11:lincity-ng 源码里用了早期的auto_ptr等特性,不指定标准的话,新版 clang 会把它当 C++14 来编译,auto_ptr在新标准中已经被标记 deprecate,运气不好还会直接报错。用gnu++11是最稳妥的兼容选项。
  • -lSDL_image -lSDL_mixer -lSDL_ttf:注意顺序,这仨库都依赖-lSDL,所以 SDL 必须放最后。反过来的话链接器会报 undefined symbol。
  • -framework OpenGL -framework Cocoa:这是 macOS 特有的。lincity-ng 的渲染后端走 OpenGL,但 macOS 上没有libGL,只有OpenGL.framework;Cocoa 则是 SDL 1.2 在 macOS 上实现窗口事件循环时需要的底层框架。

如果你是 Intel Mac,把/opt/homebrew全部替换成/usr/local即可,其余不变。

3.3 动态库的运行时路径隐患

编译通过不等于运行无忧。macOS 的动态库机制非常“记仇”,如果 SDL 相关动态库的 install_name 指向了/opt/homebrew/opt/sdl/lib这种绝对路径,你的可执行文件拷到别的机器上就废了。这是我踩得最深的一个坑:编译完在本机跑得好好的,一放到另一台机器就跑不起来,报错dyld: Library not loaded: /opt/homebrew/opt/sdl/lib/libSDL-1.2.0.dylib

解决思路有两个方向:

  • 最省事的做法是把 SDL 相关的.dylib文件直接复制到可执行文件所在目录,然后用install_name_tool -change把可执行文件里记录的绝对路径改成@executable_path/libSDL-1.2.0.dylib这样的相对路径。
  • 更“正规”的做法是用otool -L查看可执行文件的动态库依赖列表,逐条检查和修正。

这一步虽然麻烦,但如果你跟我一样有把编译产物拷到公司电脑继续摸鱼的刚需,还是值得花十分钟处理一下的。

4. 从 configure 到产物:完整编译流程与报错排查

4.1 标准流水线:configure、config.mk 与 jam

环境变量都设好了,接下来就是正式的编译流程。lincity-ng 的源码包解压后,顶层目录里会有configure脚本,但它不是 autotools 那种全套自动探测,更多是用来生成基础配置的。我建议的顺序是:

cd lincity-ng-2.9 ./configure --prefix=/opt/homebrew cd src jam -sCFLAGS="$CFLAGS" -sCXXFLAGS="$CXXFLAGS" -sLDFLAGS="$LDFLAGS"

注意jam命令里-s参数的作用是“覆盖内部变量”,相当于从命令行把环境变量再喂给它。有的版本也支持读环境变量,但保险起见我都是显式用-s传三重参数,实测这样最稳。

如果你不想每次敲这么长一串命令,可以在src目录下新建一个jamrules或者修改现有的config.mk,把 CFLAGS、CXXFLAGS、LDFLAGS 直接写进去。不过这个文件的语法各家版本略有差异,我嫌改配置文件容易踩到别的坑,干脆全走命令行参数了。

4.2 高频报错与解法对照

下面这些报错是我在编译过程中真实遇到并解决的,按照出现频率整理成表格,方便你对照排查。

报错信息根因解决方案
error: SDL/SDL.h: No such file or directoryCFLAGS 没包含 SDL 头文件路径-I/opt/homebrew/include/SDL加进 CFLAGS
ld: library not found for -lSDLLDFLAGS 没指定库路径-L/opt/homebrew/lib加进 LDFLAGS
undefined reference to SDL_InitSDL 库顺序不对-lSDL放到所有-lSDL_*的后面
error: 'auto_ptr' in namespace 'std' does not name a type默认 C++ 标准太新CXXFLAGS 追加-std=gnu++11
ld: framework not found OpenGL链接器不知道去哪找 OpenGLLDFLAGS 加上-framework OpenGL
error: 'png.h' file not foundlibpng 头文件子目录未加入搜索路径CFLAGS/CXXFLAGS 加上-I/opt/homebrew/include/libpng16
error: conflicting declaration 'typedef void* GLhandleARB'OpenGL 头文件与 glext.h 版本冲突追加-DGL_SILENCE_DEPRECATION压制过期告警

这里的GL_SILENCE_DEPRECATION值得一提。macOS 10.14 起把 OpenGL API 标记为 deprecated,老项目编译时 clang 会输出一大片“deprecated”警告,虽然这些警告默认不致命,但有些版本会配合-Werror把警告升级成错误,加上这个宏可以先发制人。

4.3 链接阶段的终极难题:符号找不到

如果说前面那些报错都是“小儿科”,那么链接阶段报Undefined symbols for architecture arm64就是终极难题了。lincity-ng 在链接时会有几个符号找不到,我的报错大概是:

Undefined symbols for architecture arm64: "_SDL_putenv", referenced from: _main in main.o

明明是 SDL 的函数,而且-lSDL也加了,怎么还是找不到?最后查下来发现是 SDL 1.2.15 在模拟putenv时用了宏定义,只有当_GNU_SOURCE被定义时才会暴露SDL_putenv这个符号。解决方案就是 CFLAGS 里加-D_GNU_SOURCE——这个坑特别隐蔽,因为只在 macOS 的 clang 环境下触发,Linux 下因为 glibc 的默认行为不会报。

如果以后你遇到类似“明明链接了库,但符号找不到”的诡异问题,先用nm -g /opt/homebrew/lib/libSDL-1.2.0.dylib | grep 符号名确认库里面到底有没有这个导出符号。如果库里有、编译器却看不到,十有八九是某个宏定义影响了头文件的声明条件,往-D方向排查就对了。

4.4 编译耗时与产物位置

我这边从零开始编译,开了 8 个并行任务(jam 默认就是多核的),整个过程大约 5 分钟。编译完成后,可执行文件会出现在lincity-ng-2.9/src/lincity-ng(也可能叫lincity-ng-bin),这个文件没有任何后缀,直接终端跑就行:

./lincity-ng

如果运行后出现黑屏或者直接崩溃,除了动态库路径问题,最常见的原因就是全屏/分辨率设置和你的显示器不匹配。lincity-ng 默认可能尝试 1024x768 的全屏模式,在 Retina 屏上会失败。解决办法是启动时加参数:

./lincity-ng -w 1280 -h 800

或者直接改它生成的配置文件~/.lincity-ng/config.xml,把分辨率写死。这个文件在第一次运行后会自动生成,里面还可以调音效音量、画面细节等参数,改起来比命令行参数直观。

5. 编译完的收尾:运行验证、崩溃处理与 .app 打包

5.1 运行时崩溃的排查思路

编译成功只是第一步,运行时崩溃才是真正劝退新手的高墙。根据我自己的实测,运行时崩溃主要有四类:

  • dyld: Library not loaded:动态库路径问题,解法见 3.3,用install_name_tool修正或者设置DYLD_LIBRARY_PATH临时指定。
  • SDL_GL_LoadLibrary failed:macOS 的 OpenGL 兼容上下文问题,一般更新显卡驱动或者换一个 SDL 视频驱动可以解决。可以用export SDL_VIDEODRIVER=cocoa强制指定 Cocoa 驱动,实测这个方法解决了我一半的崩溃问题。
  • 直接闪退且无任何输出:大概率是 config.xml 里的分辨率或全屏设置与当前显示器不匹配,删掉~/.lincity-ng/config.xml让它重新生成即可。
  • 键盘没反应或鼠标飘:SDL 1.2 在高分屏下的鼠标坐标问题,临时解决方法是把窗口调小,或者加-D参数打开调试模式看看输入事件是否有被系统拦截。

5.2 手动创建可拖拽 .app 包

如果你不想每次都在终端敲命令,把编译产物整理成一个.app包是更优雅的做法。macOS 的.app本质上只是一个目录结构,手工建起来非常简单:

mkdir -p lincity-ng.app/Contents/MacOS cp src/lincity-ng lincity-ng.app/Contents/MacOS/

然后创建一个lincity-ng.app/Contents/Info.plist,最简单的版本只需要CFBundleNameCFBundleExecutableCFBundleIdentifier三行配置。这样建好的.app双击就能跑。如果还想锦上添花,可以把 SDL 的动态库一并复制到Contents/MacOS目录,并用install_name_tool把依赖改成@executable_path相对路径,这样整个.app拷到任何一台 Mac 上都能直接运行,连 Homebrew 都不需要装。

5.3 摸鱼场景的一点实测体验

最后聊点轻松的。lincity-ng 本身内置了一个“科技风”的皮肤,界面看起来非常像命令行数据分析工具,而且窗口小、帧率低、CPU 占用低。实测在开会时开个小窗,屏幕上的画面就是一片像素点在缓慢移动,视觉上和写代码摸鱼时的终端滚动几乎没有差别,作为上班摸鱼神器确实名不虚传。但注意这只是个玩笑,该认真工作的时候还是要认真工作。

如果你愿意再花点时间折腾,lincity-ng 的 mod 机制也很开放,地图、建筑参数、税收模型都在源码的 XML 文件里,改一改就能做出一个“开局一个村、全靠自己建”的变态难度版本。这个项目虽然老旧,但作为研究老式 C++ 游戏架构、SDL 1.2 渲染管线以及 jam 构建系统的样本,含金量比很多现代模板项目高得多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 23:11:01

从单机到分布式:高可用消息中间件架构演进与落地实践

从单机消息队列到分布式高可用消息中间件体系落地&#xff0c;这个话题我断断续续折腾了快两年。从最开始业务里一个单体应用内部的队列&#xff0c;到后面支撑多条业务线的分布式消息集群&#xff0c;中间踩过的坑、趟过的雷&#xff0c;比想象中多得多。尤其是当你真正面对线…

作者头像 李华
网站建设 2026/9/9 23:10:19

开源自托管看板工具Kanass:从Docker部署到团队任务管理实战

前段时间我把团队的日常任务管理从微信群和 Excel 里彻底搬了出来&#xff0c;换成一个叫 Kanass 的开源看板工具。Kanass 这个名字你可能不熟&#xff0c;简单说&#xff0c;它是一个长得像 Trello 的自托管看板任务管理软件&#xff0c;可以部署在自己的服务器上&#xff0c;…

作者头像 李华
网站建设 2026/9/9 23:08:16

5分钟部署免费开源ERP:ERPNext上手指南

5分钟部署免费开源ERP&#xff1a;ERPNext上手指南 【免费下载链接】erpnext Free and Open Source Enterprise Resource Planning (ERP) 项目地址: https://gitcode.com/GitHub_Trending/er/erpnext ERPNext是一款免费开源的ERP系统&#xff0c;总账、采购、销售、库存…

作者头像 李华
网站建设 2026/9/9 23:08:11

yuzu模拟器:免费把Switch游戏搬上电脑,5分钟装完

yuzu模拟器&#xff1a;免费把Switch游戏搬上电脑&#xff0c;5分钟装完 【免费下载链接】yuzu 任天堂 Switch 模拟器 项目地址: https://gitcode.com/GitHub_Trending/yu/yuzu yuzu 是一款免费开源的任天堂 Switch 模拟器&#xff0c;Windows、Linux、Android 三个平台…

作者头像 李华
网站建设 2026/9/9 23:04:14

Unity DOTS里的Component到底怎么理解?别再套MonoBehaviour思维

先抛一个问题&#xff1a;当你在Unity里提到“组件”&#xff08;Component&#xff09;的时候&#xff0c;你第一时间想到的是什么&#xff1f;大概率是Inspector面板里的MonoBehaviour、Rigidbody、Collider之类的东西。但如果你因此用同样的心智模型去理解Unity DOTS里的Com…

作者头像 李华