cmux-dev-workflow

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

cmux Dev Workflow

cmux 开发工作流

Initial setup

初始设置

Run the setup script to initialize submodules, build GhosttyKit, and install the pbxproj normalization pre-commit hook:
bash
./scripts/setup.sh
运行setup脚本以初始化子模块、构建GhosttyKit并安装pbxproj规范化的pre-commit钩子:
bash
./scripts/setup.sh

Xcode toolchain

Xcode工具链

The team is pinned to Xcode 26.x.
.xcode-version
records the major;
cmux.xcodeproj/project.pbxproj
carries
objectVersion = 60
, which is what Xcode 26 writes by default. (objectVersion 77 is reserved for projects that adopt synchronized folder groups, which cmux does not use yet. Bumping to a different value requires a deliberate team decision.)
scripts/setup.sh
installs a tracked pre-commit hook (
scripts/git-hooks/pre-commit
) that runs
scripts/normalize-pbxproj.py
on any staged
cmux.xcodeproj/project.pbxproj
, sorting the high-churn sections so Xcode's nondeterministic reordering never reaches a commit. The hook is idempotent. CI runs
scripts/check-pbxproj.sh
to enforce both the
objectVersion
pin and normalization, so anyone who skips the hook (or never ran setup) gets a clear failure on their PR.
.xcode-version
is the single source of truth. To bump the pin: edit
.xcode-version
, open
cmux.xcodeproj
in the new Xcode (which rewrites
objectVersion
automatically when it touches the file), and add a case for the new Xcode major in
scripts/check-pbxproj.sh
mapping it to the
objectVersion
that major writes.
团队固定使用Xcode 26.x。
.xcode-version
记录主版本号;
cmux.xcodeproj/project.pbxproj
objectVersion = 60
,这是Xcode 26默认生成的值。(objectVersion 77是为采用同步文件夹组的项目预留的,cmux目前尚未使用。更改此值需要团队做出明确决策。)
scripts/setup.sh
会安装一个受追踪的pre-commit钩子(
scripts/git-hooks/pre-commit
),该钩子会对任何已暂存的
cmux.xcodeproj/project.pbxproj
运行
scripts/normalize-pbxproj.py
,对高变动部分进行排序,从而避免Xcode的非确定性排序提交到代码库。这个钩子是幂等的。CI会运行
scripts/check-pbxproj.sh
来强制验证
objectVersion
的固定值和规范化,因此任何跳过钩子(或从未运行过setup)的人在提交PR时都会收到明确的失败提示。
.xcode-version
是唯一的事实来源。要更新固定版本:编辑
.xcode-version
,用新版本的Xcode打开
cmux.xcodeproj
(当它修改文件时会自动重写
objectVersion
),并在
scripts/check-pbxproj.sh
中添加新Xcode主版本的对应case,将其映射到该版本生成的
objectVersion

Sidebar extension point (dev tagging)

侧边栏扩展点(开发标签)

Each tagged dev build gets its own ExtensionKit sidebar extension point so concurrent dev builds don't collide. Three build settings drive this:
  • CMUX_SIDEBAR_EXTENSION_POINT_ID
    (default
    com.cmuxterm.app.cmux.sidebar
    ): the extension point identifier baked into Info.plist at build time.
  • CMUX_BUNDLE_ID_SUFFIX
    (default empty): inserted into the app and appex bundle ids so a tagged extension gets a distinct identity that pkd records separately.
  • CMUX_DISPLAY_NAME_SUFFIX
    (default empty): appended to the appex
    CFBundleDisplayName
    . The OS groups sidebar extensions by display name for the enable/disable + availability counts the host reads (
    AppExtensionIdentity
    exposes only
    bundleIdentifier
    ,
    localizedName
    ,
    extensionPointIdentifier
    ,
    id
    — cmux already keys its own identity off the stable
    bundleIdentifier
    , but the OS-level grouping is by name). Two same-named appexes installed side by side (a base build and a tagged build) are treated as one logical extension, so toggling one perturbs the other; a per-tag display name keeps them distinct.
The host resolves its point id at runtime from the Info.plist key
CMUXSidebarExtensionPointIdentifier
via
CmuxSidebarExtensionPoint.identifier(in:)
.
./scripts/reload.sh --tag <tag>
scopes the host point to
com.cmuxterm.app.debug.<tag>.cmux.sidebar
.
./scripts/reload-extension.sh --tag <tag> [--host-bundle-id <id>] [--example sample|tabs|both]
builds a matching tag-scoped sample extension, passing
CMUX_SIDEBAR_EXTENSION_POINT_ID=<host-bundle-id>.cmux.sidebar
,
CMUX_BUNDLE_ID_SUFFIX=.<tag>
, and
CMUX_DISPLAY_NAME_SUFFIX=" <tag>"
. It installs exactly what xcodebuild produced (xcodebuild ad-hoc signs with entitlements intact) — it does NOT re-sign, because a bare
codesign --force --sign -
strips the appex entitlements and the extension then drops its host XPC connection. pkd ingests the tagged copy because its bundle id is distinct. Verify with
pluginkit -m -p <host-bundle-id>.cmux.sidebar
.
To author a NEW sample extension that is tag-ready:
  • appex Info.plist:
    EXAppExtensionAttributes:EXExtensionPointIdentifier = $(CMUX_SIDEBAR_EXTENSION_POINT_ID)
    .
  • add
    CMUX_SIDEBAR_EXTENSION_POINT_ID
    (default
    com.cmuxterm.app.cmux.sidebar
    ),
    CMUX_BUNDLE_ID_SUFFIX
    (default empty), and
    CMUX_DISPLAY_NAME_SUFFIX
    (default empty) build settings to the app and appex targets in all build configs.
  • PRODUCT_BUNDLE_IDENTIFIER
    =
    <appBase>$(CMUX_BUNDLE_ID_SUFFIX)
    for the app target and
    <appBase>$(CMUX_BUNDLE_ID_SUFFIX).<leaf>
    for the appex (suffix before the appex leaf so the appex id stays prefixed by the app id).
  • appex
    INFOPLIST_KEY_CFBundleDisplayName
    (or the
    CFBundleDisplayName
    Info.plist value) =
    <Name>$(CMUX_DISPLAY_NAME_SUFFIX)
    .
  • it must be ad-hoc signed by xcodebuild (Info.plist bound, entitlements intact) for pkd to ingest the tagged copy; do not re-sign post-build.
每个带标签的开发构建都有自己的ExtensionKit侧边栏扩展点,这样并发的开发构建不会冲突。三个构建设置驱动这一机制:
  • CMUX_SIDEBAR_EXTENSION_POINT_ID
    (默认值
    com.cmuxterm.app.cmux.sidebar
    ):构建时嵌入到Info.plist中的扩展点标识符。
  • CMUX_BUNDLE_ID_SUFFIX
    (默认空):插入到app和appex的bundle id中,使带标签的扩展获得一个独特的标识,以便pkd单独记录。
  • CMUX_DISPLAY_NAME_SUFFIX
    (默认空):附加到appex的
    CFBundleDisplayName
    后。操作系统会按显示名称对侧边栏扩展进行分组,用于主机读取的启用/禁用和可用性计数(
    AppExtensionIdentity
    仅暴露
    bundleIdentifier
    localizedName
    extensionPointIdentifier
    id
    ——cmux已经基于稳定的
    bundleIdentifier
    标识自身,但操作系统层面是按名称分组)。如果两个同名的appex并排安装(一个基础构建和一个带标签的构建),会被视为一个逻辑扩展,因此切换其中一个会影响另一个;每个标签对应一个显示名称可使它们保持区分。
主机通过
CmuxSidebarExtensionPoint.identifier(in:)
从Info.plist的
CMUXSidebarExtensionPointIdentifier
键在运行时解析其扩展点ID。
./scripts/reload.sh --tag <tag>
会将主机扩展点限定为
com.cmuxterm.app.debug.<tag>.cmux.sidebar
./scripts/reload-extension.sh --tag <tag> [--host-bundle-id <id>] [--example sample|tabs|both]
会构建一个匹配标签范围的示例扩展,传入
CMUX_SIDEBAR_EXTENSION_POINT_ID=<host-bundle-id>.cmux.sidebar
CMUX_BUNDLE_ID_SUFFIX=.<tag>
CMUX_DISPLAY_NAME_SUFFIX=" <tag>"
。它会安装xcodebuild生成的完整产物(xcodebuild会进行临时签名并保留权限)——不会重新签名,因为单纯的
codesign --force --sign -
会移除appex的权限,导致扩展断开与主机的XPC连接。由于其bundle id是唯一的,pkd会加载带标签的副本。可以用
pluginkit -m -p <host-bundle-id>.cmux.sidebar
进行验证。
要创建一个支持标签的新示例扩展:
  • appex的Info.plist中设置
    EXAppExtensionAttributes:EXExtensionPointIdentifier = $(CMUX_SIDEBAR_EXTENSION_POINT_ID)
  • 在所有构建配置中,为app和appex目标添加
    CMUX_SIDEBAR_EXTENSION_POINT_ID
    (默认
    com.cmuxterm.app.cmux.sidebar
    )、
    CMUX_BUNDLE_ID_SUFFIX
    (默认空)和
    CMUX_DISPLAY_NAME_SUFFIX
    (默认空)构建设置。
  • app目标的
    PRODUCT_BUNDLE_IDENTIFIER
    =
    <appBase>$(CMUX_BUNDLE_ID_SUFFIX)
    ,appex的为
    <appBase>$(CMUX_BUNDLE_ID_SUFFIX).<leaf>
    (后缀在appex的leaf之前,这样appex的ID始终以app的ID为前缀)。
  • appex的
    INFOPLIST_KEY_CFBundleDisplayName
    (或Info.plist中的
    CFBundleDisplayName
    值) =
    <Name>$(CMUX_DISPLAY_NAME_SUFFIX)
  • 必须由xcodebuild进行临时签名(绑定Info.plist,保留权限),pkd才能加载带标签的副本;构建后不要重新签名。