1. 项目概述当 Plone 开发从“拼图式搭建”走向“开箱即用”在 Plone 社区里但凡做过三个以上正式项目的开发者大概率都经历过这样一幕凌晨两点你刚把project.policy的权限配置调通正准备启动 Zope 实例终端突然报错——ImportError: No module named project.content。你一拍脑袋忘了在buildout.cfg里给mr.developer加那行auto-checkout project.content再补上又发现setup.py里install_requires漏写了plone.app.dexterity好不容易跑起来主题却没加载翻日志才发现zopeskel.diazotheme生成的configure.zcml路径写错了层级……这不是玄学这是传统 Plone 项目初始化的真实切片。它背后是一套高度耦合、多仓库、多步骤的手动组装流程而 Six Feet Up 团队过去十年里正是踩着这套流程走过来的。我们熟悉的 Templer/ZopeSkel 是 Plone 生态的“瑞士军刀”但它默认产出的是“标准件”——每个包policy/content/theme各自为政各自建 Git 仓库各自维护setup.py和configure.zcml再靠mr.developer在 buildout 层强行缝合。这种模式在单人小项目里尚可运转一旦进入团队协作、CI/CD 流水线、版本回滚等真实生产场景立刻暴露出三大硬伤一是原子性缺失——改一行 policy 权限、换一张主题图片却要提交三个仓库的 commit无法保证“这次发布只包含这组变更”二是发布成本高——上线前得分别 tag 三个 repo 的特定 commit再更新 buildout 的 pinned 版本漏一个就导致环境不一致三是新人上手门槛陡峭——实习生花两天搞懂“为什么我的 theme 不生效”本质是在补全一套隐性的、文档里没写的集成逻辑。SixieSkel 就是我们在这种痛感中长出来的“止痛针”。它不是重写 ZopeSkel而是站在巨人肩膀上做精准外科手术把团队十年沉淀下来的“最佳实践”——比如 policy 包必须预置plone.app.workflow配置、content 包默认启用 Dexterity 而非 Archetypes、theme 包强制使用 Diazo 规则而非 Classic Theme——全部固化进模板逻辑。而这次升级的核心突破在于它首次将“统一构建”unified buildout从一种手动操作规范变成了一个可一键生成、零配置启动的默认选项。当你输入templer sfu_buildout mysite它不再问“你要不要 policy 包”而是直接问“这是统一构建吗要 content 吗要 policy 吗要 theme 吗”然后自动生成一个根目录下带setup.py、子目录里按约定结构存放所有包、buildout.cfg里已预置好develop src/*和eggs mysite.policy mysite.content mysite.theme的完整骨架。你敲下bin/buildout再bin/instance fg一个能登录、能创建内容、能切换主题的完整站点就跑起来了——中间没有vim buildout.cfg没有git submodule add没有python setup.py develop。这听起来像魔法但它的每一步都是对 Plone 构建哲学的一次务实重构把“人肉 glue code”变成“机器生成 glue logic”让开发者专注业务而不是构建系统本身。2. 统一构建设计思路为什么放弃“多仓库缝合”选择“单库内聚”2.1 传统多仓库模式的结构性缺陷在深入 SixieSkel 新版实现前必须先看清旧模式为何不可持续。我们曾用mr.developer管理三个独立仓库的典型工作流如下初始化git clone https://github.com/team/project.policy.git git clone https://github.com/team/project.content.git git clone https://github.com/team/project.theme.git集成编辑buildout.cfg添加[buildout] extensions mr.developer auto-checkout project.policy project.content project.theme [sources] project.policy git https://github.com/team/project.policy.git project.content git https://github.com/team/project.content.git project.theme git https://github.com/team/project.theme.git依赖声明每个包的setup.py必须显式声明install_requires[Plone]且project.policy还需声明install_requires[project.content]形成强依赖链。发布上线前需执行三步 tag 操作cd project.policy git tag 1.2.0 git push --tags同理对 content 和 theme再更新buildout.cfg中versions.cfg对应的 pinned 版本号。这个流程表面清晰实则暗藏四重风险依赖地狱Dependency Hellproject.policy声明依赖project.content2.1.0但project.content自身又依赖plone.app.contenttypes2.3.0而project.theme却要求plone.app.contenttypes2.3.0。mr.developer只管拉代码不解决 Python 包冲突最终bin/buildout报错时你得手动二分排查哪个包的setup.py写错了约束。版本漂移Version Drift开发中project.content的master分支修复了一个关键 bug但project.policy的master仍引用旧版content。mr.developer的auto-checkout默认指向master导致测试环境与生产环境行为不一致——前者用新 content后者用旧 content。原子性幻觉Atomic Illusion你提交了policy仓库的一个 commit声称“修复了用户组权限”但该修复依赖content仓库里尚未合并的 PR。mr.developer会静默拉取content的master而那个master里根本没有你的依赖项结果权限修复根本无效。CI/CD 断点CI/CD BreakpointJenkins 流水线需配置三个 Git SCM 源触发条件复杂任一仓库有 push 就触发还是需同时满足。更糟的是流水线构建时若project.theme的master有 breaking change而project.policy还未适配整个构建就失败但错误日志里根本看不出是哪个包的变更引发的连锁反应。提示我们曾在一个金融客户项目中因此类问题导致三次上线回滚。最后一次排查耗时 17 小时根源是project.theme的setup.py里误删了一行include_package_dataTrue导致 Diazo 规则文件未被安装但错误只在bin/instance fg启动后才暴露bin/buildout阶段完全静默。2.2 统一构建的底层逻辑以 Python 包管理范式重构 Plone 工程SixieSkel 的统一构建方案本质是将 Plone 项目回归到 Python 社区最成熟、最稳定的工程范式——单一代码库Monorepo 标准化包结构 setuptools 驱动。其核心设计决策有三第一根目录setup.py是唯一的真相源Single Source of Truth。传统模式中buildout.cfg是构建入口但它只负责“拉取”和“安装”不定义“什么是项目”。统一构建下根目录setup.py承担起元数据中枢角色# setup.py (root) from setuptools import setup, find_packages setup( namemysite, version1.0.0, packagesfind_packages(src), namespace_packages[mysite], package_dir{: src}, install_requires[ Plone, mysite.policy 1.0.0, mysite.content 1.0.0, mysite.theme 1.0.0, ], entry_points{ z3c.autoinclude.plugin: [target plone], } )这里的关键在于packagesfind_packages(src)和package_dir{: src}。它告诉 setuptools所有 Python 包都在src/目录下且src/下的每个子目录如src/mysite.policy都是一个独立包。install_requires则用精确锁定版本彻底杜绝依赖漂移。buildout.cfg不再需要mr.developer只需简单声明[buildout] develop src/* parts instance [instance] recipe plone.recipe.zope2instance eggs mysitedevelop src/*让 buildout 将src/下所有目录作为开发包加载eggs mysite则通过根setup.py的install_requires自动解析出全部依赖。整个过程无需人工干预路径或版本。第二src/目录是物理与逻辑的统一容器。SixieSkel 生成的目录结构严格遵循 PEP 420 隐式命名空间包规范mysite/ ├── buildout.cfg ├── setup.py # 根包协调全局 ├── src/ │ ├── mysite.policy/ # 独立包含 setup.py, configure.zcml │ ├── mysite.content/ # 独立包含 setup.py, profiles/default │ └── mysite.theme/ # 独立包含 setup.py, theme/rules.xml └── docs/每个子包如mysite.policy都拥有完整的setup.py但其install_requires不再声明对其他子包的依赖如mysite.content因为它们同属mysite根包由根setup.py统一管理依赖关系。这种设计解耦了“包间依赖”与“构建集成”——依赖由根setup.py控制构建由buildout.cfg的develop指令控制二者职责分明。第三原子提交与语义化发布成为默认能力。由于所有代码都在一个 Git 仓库一次git commit -m feat: add user profile form可同时修改mysite.content的 schema 定义、mysite.policy的权限设置、mysite.theme的模板渲染逻辑。git tag 1.2.0即代表该版本下所有包的精确状态。发布时只需git push --tagsCI 流水线拉取 tagged commit执行bin/buildout即可复现完全一致的环境。我们内部统计显示采用统一构建后发布流程平均耗时从 42 分钟降至 6 分钟回滚成功率从 68% 提升至 100%。注意统一构建并非否定模块化。mysite.policy依然只处理权限和 workflowmysite.content专注 Dexterity 类型定义mysite.theme专司 Diazo 主题。区别在于它们的“模块边界”由 Python 包机制import mysite.policy保障而非 Git 仓库边界。这更符合软件工程中“高内聚、低耦合”的本质。3. SixieSkel 新版实操详解从命令行到可运行站点的完整闭环3.1 环境准备与工具链安装在动手前请确保你的开发机已具备以下基础环境。这不是可选步骤而是 SixieSkel 统一构建能稳定运行的前提——我们曾因忽略其中一项在客户现场耗费 3 小时排查。Python 与 pip 版本SixieSkel 依赖 Python 2.7 或 Python 3.6Plone 5.2 推荐 Python 3.8。务必验证pip是否为最新版旧版pip在处理find_packages(src)时存在路径解析 Bug# 检查并升级 $ python --version Python 3.8.10 $ pip --version pip 21.3.1 from /usr/local/lib/python3.8/site-packages/pip (python 3.8) $ pip install --upgrade pip setuptools wheelTempler 与 SixieSkel 安装SixieSkel 是 Templer 的插件需先安装 Templer 核心。注意不要使用pip install templer.core官方 PyPI 上的templer.core已多年未维护与 Plone 5 不兼容。必须使用 Six Feet Up 维护的定制版# 添加 Six Feet Up 的私有索引源 $ pip install --extra-index-url http://dist.sixfeetup.com/public -U sixieskel # 验证安装 $ templer --list | grep sfu sfu_buildout Six Feet Up Plone Buildout (Unified) sfu_policy Six Feet Up Plone Policy Package sfu_content Six Feet Up Plone Content Package sfu_theme Six Feet Up Plone Theme Package若templer --list未显示sfu_*命令说明http://dist.sixfeetup.com/public源未生效。此时需检查~/.pip/pip.conf文件确保[global]段包含index-url https://pypi.org/simple/且extra-index-url正确指向 Six Feet Up 源。系统级依赖Plone 编译 C 扩展如lxml,zc.buildout需系统工具链。Ubuntu/Debian 用户执行$ sudo apt-get update sudo apt-get install -y \ build-essential \ python3-dev \ libxml2-dev \ libxslt1-dev \ libjpeg-dev \ libpng-dev \ libfreetype6-dev \ libldap2-dev \ libsasl2-devmacOS 用户Homebrew$ brew install python3 libxml2 libxslt jpeg libpng freetype openldap $ export LIBXML2_INCLUDE_DIR$(brew --prefix libxml2)/include/libxml2 $ export LIBXSLT_INCLUDE_DIR$(brew --prefix libxslt)/include/libxslt实操心得我们曾遇到 macOS 用户因libxml2头文件路径未导出导致lxml编译失败错误信息晦涩fatal error: libxml/xmlversion.h file not found。导出LIBXML2_INCLUDE_DIR后问题立即解决。这是 SixieSkel 生成的buildout.cfg无法自动规避的系统层问题必须前置处理。3.2 创建统一构建项目四问定乾坤一切就绪后执行核心命令$ templer sfu_buildout mysite此时 SixieSkel 会启动交互式向导四个关键问题决定项目基因。请务必理解每个问题的含义而非盲目回车Q1: Is this a unified buildout? (Y/n)这是开关。输入Y默认即启用统一构建SixieSkel 将生成src/目录结构和根setup.py输入n则退化为传统多包模式不推荐。强烈建议始终选Y这是享受所有优势的前提。Q2: Add a content package? (Y/n)决定是否生成src/mysite.content/。如果你的项目需要自定义内容类型如“新闻稿”、“产品目录”选Y若纯静态展示站可选n。SixieSkel 会基于plone.app.dexterity模板生成包含profiles/default/types/下的 XML 定义和browser/下的视图。Q3: Add a policy package? (Y/n)决定是否生成src/mysite.policy/。Policy 包是 Plone 项目的“宪法”负责安装 workflow、权限映射、默认内容如首页、第三方产品集成如collective.cover。95% 的生产项目都需此包除非你明确知道自己的需求极简。Q4: Add a theme package? (Y/n)决定是否生成src/mysite.theme/。Theme 包提供 Diazo 主题包含theme/rules.xml规则文件、theme/index.htmlHTML 模板、theme/css/样式。若项目需定制 UI必选Y若用 Plone 默认主题可跳过。假设你回答Y Y Y YSixieSkel 将在当前目录创建mysite/文件夹并生成以下关键文件文件路径作用六年经验提示mysite/setup.py根包元数据定义项目名、版本、依赖version1.0.0是占位符发布前必须按语义化版本SemVer更新如1.2.0mysite/buildout.cfgBuildout 配置develop src/*已预置eggs mysite行不可删除它是启动实例的入口mysite/src/mysite.content/setup.pyContent 包元数据其install_requires仅含Plone不依赖其他子包mysite/src/mysite.policy/profiles/default/metadata.xmlPolicy 包安装元数据version1000是 Plone 的内部版本号每次修改 profile 必须递增否则portal_setup不重装3.3 首次构建与启动见证“零配置”时刻进入mysite/目录执行两步# 第一步运行 buildout生成 bin/ 目录 $ cd mysite $ python bootstrap-buildout.py $ bin/buildout # 第二步启动实例 $ bin/instance fg若一切顺利终端将输出类似2023-10-15 10:23:45 INFO ZServer Medusa HTTP Server at http://0.0.0.0:8080/ 2023-10-15 10:23:45 INFO ZServer Medusa FTP Server at ftp://0.0.0.0:8021/ 2023-10-15 10:23:45 INFO Zope Ready to handle requests此时打开浏览器访问http://localhost:8080你将看到一个完全可操作的 Plone 站点登录http://localhost:8080/login默认管理员账号admin密码admin首次启动时自动生成并打印在终端。进入Site Setup Add-onsmysite.policy、mysite.content、mysite.theme均已列出并处于“已安装”状态。进入Site Setup Themesmysite.theme主题已激活页面呈现 Diazo 渲染效果。这背后发生了什么bin/buildout执行时develop src/*指令让 buildout 将src/下所有目录mysite.policy,mysite.content,mysite.theme作为开发包加载到 Python path。eggs mysite则触发根setup.py的install_requires解析出mysite.policy 1.0.0等依赖并确保它们被正确注册为 Zope 产品。bin/instance fg启动时Zope 的z3c.autoinclude.plugin机制扫描所有已安装 eggs自动加载每个包的configure.zcml完成全部集成。整个过程无任何手动编辑这就是 SixieSkel “四问”设计的威力——它把所有集成逻辑编码进了模板而非文档。注意若启动失败最常见的原因是bin/buildout未成功执行。请检查终端最后几行是否有Generated script .../bin/buildout字样。若无说明bootstrap-buildout.py或buildout.cfg有误需重新运行python bootstrap-buildout.py。4. 统一构建下的日常开发与维护从“救火队员”到“功能交付者”4.1 日常开发工作流Git、IDE 与调试技巧统一构建不仅简化了初始创建更重塑了日常开发节奏。以下是团队内部验证过的高效工作流Git 操作聚焦功能告别跨库同步分支策略采用main生产 develop集成 feature/*特性。所有变更policy/content/theme均在feature/login-flow分支中完成。提交粒度一个 commit 应覆盖一个完整功能闭环。例如“实现用户登录后自动跳转至个人资料页”需同时包含src/mysite.policy/browser/login.py自定义登录视图src/mysite.policy/profiles/default/actions.xml注册新 actionsrc/mysite.theme/theme/rules.xml调整跳转逻辑的 Diazo 规则Pull RequestPR审查PR 描述必须明确列出所修改的包及文件如Modified: mysite.policy.browser.login, mysite.theme.theme.rules。Reviewer 可直接在 GitHub 查看所有变更无需切换多个仓库。IDE 配置让 PyCharm/VSCode 理解src/结构PyCharm 用户需在File Project Structure中将mysite/src设为Sources Root。VSCode 用户需在工作区.vscode/settings.json中添加{ python.defaultInterpreterPath: ./bin/python, python.extraPaths: [src] }此举让 IDE 的代码跳转CtrlClick、自动补全from mysite.content import ...和调试器能正确定位src/下的包。调试技巧直击 Zope 内部当页面报错AttributeError: NoneType object has no attribute title传统方式需猜是哪个包的问题。统一构建下可快速定位在bin/instance fg启动时添加-D参数启用详细日志bin/instance fg -D错误栈中会明确显示文件路径如/path/to/mysite/src/mysite.theme/theme/views.py直接对应到src/mysite.theme/在对应文件中设断点用bin/instance debug启动交互式调试器bin/instance debug然后输入import pdb; pdb.set_trace()。实操心得我们曾用此法在 5 分钟内定位一个因mysite.content的IContentSchema接口未被mysite.policy的configure.zcml正确 include 导致的InterfaceError。传统多仓库模式下此问题需在三个仓库间反复grep接口名耗时超 1 小时。4.2 发布与部署从git tag到 CI 流水线统一构建让发布变得极其轻量。以下是我们的标准发布脚本release.sh#!/bin/bash # release.sh: 用于发布 mysite 项目 VERSION$1 if [ -z $VERSION ]; then echo Usage: $0 version exit 1 fi # 1. 更新根 setup.py 版本 sed -i s/version[0-9.]\/version$VERSION/g setup.py # 2. 更新所有子包 setup.py 版本保持一致 for pkg in src/mysite.*; do if [ -d $pkg ]; then sed -i s/version[0-9.]\/version$VERSION/g $pkg/setup.py fi done # 3. 提交版本变更 git add setup.py src/mysite.*/setup.py git commit -m chore(release): bump to $VERSION # 4. 打 tag 并推送 git tag -a $VERSION -m Release $VERSION git push origin main $VERSION执行./release.sh 1.2.0后CI 流水线Jenkins/GitHub Actions自动触发拉取 tagged commit如1.2.0执行python bootstrap-buildout.py bin/buildout运行bin/test执行所有包的单元测试mysite.policy.tests,mysite.content.tests若测试通过将bin/instance打包为 Docker 镜像推送到私有 registryK8s 集群拉取新镜像滚动更新 Pod整个过程全自动无需人工介入buildout.cfg或versions.cfg。我们内部统计发布频率从每月 1-2 次提升至每周 2-3 次且零发布事故。4.3 常见问题与排查技巧实录在 SixieSkel 统一构建落地过程中我们积累了大量实战经验。以下是高频问题与独家解决方案问题现象根本原因解决方案六年避坑技巧bin/instance fg启动后访问http://localhost:8080显示503 Service Unavailablemysite.policy的profiles/default/metadata.xml中version未递增导致portal_setup未重装 profile进入 ZMI (http://localhost:8080/portal_setup)点击mysite.policy的defaultprofile勾选Reinstall all steps点击Import建立 pre-commit hook在.git/hooks/pre-commit中加入 grep -q version[^]* src/mysite.policy/profiles/default/metadata.xmlbin/test运行时报ImportError: No module named mysite.contentsrc/mysite.content/目录名与setup.py中namemysite.content不一致或src/未被正确识别为包路径检查src/mysite.content/setup.py的name字段确保与目录名完全匹配区分大小写确认buildout.cfg中develop src/*无拼写错误在buildout.cfg中添加验证在[buildout]段加入extends http://dist.sixfeetup.com/public/validate-src.cfgSixieSkel 提供的验证扩展它会在bin/buildout时检查src/下所有目录是否符合命名规范修改src/mysite.theme/theme/rules.xml后刷新页面无变化Diazo 缓存未清除或mysite.theme未被正确激活进入Site Setup Themes点击mysite.theme勾选Development mode点击Save或在http://localhost:8080/portal_css中点击Clear cache开发期强制禁用缓存在buildout.cfg的[instance]段添加environment-vars PLONE_THEME_DEVELOPMENT_MODEon重启实例后Diazo 始终从文件系统读取规则无需手动开关bin/buildout时lxml编译失败报xcode-select: error: no developer tools were foundmacOSXcode Command Line Tools 未安装或损坏运行xcode-select --install安装工具再执行sudo xcode-select --reset重置路径CI 环境预装脚本在 Jenkins agent 的初始化脚本中加入 xcode-select --install最后分享一个小技巧当需要快速验证某个包的变更是否生效不必重启整个bin/instance。进入 ZMI 的portal_quickinstaller找到对应包如mysite.content点击Uninstall再点击Install。这会触发 Zope 重新加载该包的configure.zcml效果等同于重启但耗时仅 2 秒。这是我们在紧急修复线上 bug 时的救命招数。5. 后续演进与社区协作从内部工具到生态共建SixieSkel 的统一构建方案已不仅是 Six Feet Up 团队的内部效率工具它正逐步融入 Plone 社区主流实践。我们观察到两个清晰趋势第一Plone 官方工具链的收敛。Plone 6 的plonecli工具已开始借鉴统一构建思想。其plonecli create addon命令虽仍生成独立包但plonecli build子命令新增了--monorepo选项可将多个 addon 合并到一个 buildout。这印证了 SixieSkel 的设计前瞻性——它用六年时间验证了一条可行路径如今正被更广泛的生态所接纳。第二社区协作模式的转变。过去贡献一个 Plone 功能需 fork 三个仓库、提三个 PR、等待三个 maintainer 审核。现在SixieSkel 用户可直接在mysite/src/mysite.policy/下开发新 workflow提交一个 PR 到mysite仓库maintainer 一次审核即可合并。我们已将mysite仓库开源并在 README 中明确“欢迎提交 PR请确保 PR 同时更新setup.py版本号和metadata.xml版本号”。这种“单仓库、单 PR、单审核”的模式显著降低了社区参与门槛。对我个人而言SixieSkel 的最大价值是让我从“Plone 构建系统工程师”回归到“Plone 应用开发者”。我不再需要深夜研究mr.developer的sources.cfg语法也不必为zc.buildout的extends机制头疼。我可以把全部精力投入到mysite.content的 Dexterity schema 设计、mysite.policy的 workflow 优化、mysite.theme的用户体验打磨上。当客户说“我们需要一个能支持 10 万用户的新闻门户”我思考的是架构、性能、可扩展性而不是“这次 buildout 要配几个mr.developer源”。这个转变就是 SixieSkel 统一构建带给 Plone 开发者的终极礼物让技术回归服务业务的本质让开发者重获创造的纯粹快乐。