GTK+ 中的版本控制和长期稳定性承诺

本文中提出的计划取代了 2016 年 6 月在多伦多 GTK+ 黑客马拉松之后公开的计划。

本月,GTK+ 团队将发布一系列长期稳定版本中的第一个。这将使 GTK+ 更具可预测性和可靠性,同时不会妨碍 GTK+ 未来的改进。

这些计划是自去年 6 月在多伦多举行的 GTK+ 黑客马拉松上制定初步计划以来,与各利益相关者讨论的结果。

背景

自 2002 年 2.0 版本发布以来,GTK+ 遵循了一个相当直接的版本控制方案

  • 主版本指定了通用 API 版本
  • 次版本指定了开发周期(如果为奇数)和稳定周期(如果为偶数)
  • 微版本指定了错误修复更新

任何在 GTK+ 中引入的 API 都保证存在到下一个主版本;在开发周期中引入的 API 保证在稳定周期开始后保留。稳定周期不提供新功能或新 API。

此方案对我们很有用,但其问题在 3.x 系列中变得越来越明显,尤其是当与 6 个月基于时间的开发周期相结合时。在 2.x 周期中,GNOME 应用程序中的新功能被迫出现在额外的库中,因为工具包过于复杂或移动速度太慢。自 3.0 以来,GTK+ 的开发速度一直在加快。GTK+ 已被置于中心位置,每六个月都会引入新的小部件和新功能。但是,为了实现这些新的小部件和功能,工具包的一些内部结构一直处于变化状态。

从 GNOME 的角度来看,GTK+ 一直是一个相当稳定但不断变化的目标,因为 GNOME 开发人员和 GTK+ 开发人员可以共享反馈和提案,并快速跟上内部变化。另一方面,从社区的角度来看,关注 GTK+ 的开发更加痛苦。GNOME 项目之外的应用程序开发人员很难了解工具包中的哪些更改会影响他们的代码库。GTK+ 团队已尝试改进其沟通渠道,但就开发周期中的更改进行博客文章对 GTK+ 用户的更广泛社区来说还不够。

长期稳定的 GTK+ 版本

GTK+ 有三个主要利益相关者:想要功能和 API 稳定版本的应用程序开发人员;想要访问 GTK+ 开发版本以便快速引入新功能的桌面开发人员(包括大多数 GNOME 项目);以及 GTK+ 团队本身,它需要在较长的开发周期中迭代库的内部结构。

引入长期稳定的 GTK+ 版本旨在确保 GTK+ 在每个受众之间取得良好的平衡。特别是,应用程序开发人员将可以访问一个稳定的平台,该平台仍然可以访问在 3.x 系列中开发的新 GTK+ 功能,例如 CSS 样式、触摸屏支持、HiDPI 显示支持、Wayland 支持、新小部件、GTK+ 检查器等等。

GTK+ 将继续发布主版本、次版本和微版本。新的主版本将在新功能稳定后发布,预计大约每 2-3 年发布一次。当升级到新的主版本时,将删除已弃用的 API。之后,此 API 系列将被视为稳定。新的次版本可能会引入新的小部件,或更新 GDK 后端中窗口系统协议的实现,但不允许添加其他功能或主题更改。虽然以前次版本每六个月发布一次,但现在它们将在必要时生成。我们还将至少三年继续发布用于错误修复和安全问题的微版本。此后的维护可能会继续,具体取决于可用的志愿者资源量。具有超过三年长期支持发布周期的操作系统发行商可能需要联系 GTK 团队以建立向后移植策略。

长期稳定系列中的更新将是 ABI 稳定的。在这些稳定系列的同时,GTK+ 的开发将在半稳定的开发系列中继续进行。这些开发版本在次版本之间将包含一些 API 更改,尽管更改将在可能的情况下受到限制。这是我们期望 GNOME 应用程序采用的路径,但其他应用程序开发人员如果想要访问最新功能,也可以选择此选项,但代价是每次次版本都需要进行一些潜在的移植工作。

虽然 GTK+ 团队保留在开发系列中更改 API 的权利,但这并不意味着整个 GTK+ API 会在每次发布时不断中断;只有特定的、希望很少使用的 API 部分可能会更改,如果更改过于广泛,它们很可能会延迟到下一个主要开发周期。我们将确保提前充分沟通这些更改。

新的版本控制方案

新的 GTK+ 版本控制方案是我们迄今为止遵循的“语义版本控制”方案的修改。一旦发布新的主要稳定版本,开发周期就会开始,我们将

  • 将 pkg-config 文件更新为新的主版本,以允许 GNOME 开发人员在开发过程中以新 API 为目标
  • 将现有的主版本保持在相同的数字
  • 将次版本更新为 90 以指示开发版本

例如,在 3.22.0 版本发布之后,并在新的开发周期开始时,pkg-config 文件将被称为 gtk+-4.0,并且 configure.ac 文件中的版本将设置为 3.90

每六个月将发布一个新的偶数开发版本,例如 x.90x.92x.94,直到 GTK+ 团队确信新的 API 和功能集是稳定的。这些次版本中的每一个都将增加共享库的 soname,以确保自动化工具可以获取最终更改并通知发行商和维护人员。一旦我们达到 API 和功能集足够稳定以供更广泛的社区使用的程度,我们将发布新的主版本 (x + 1).0 并声明 API 稳定。

一旦完成此点零版本,将创建一个新的稳定分支,并且主分支将升级到下一个点九十版本并开始新的开发周期。点九十版本将与之前的稳定版本并行安装。

gtk-versioning-scheme

3.22 将是 3.x 系列的最后一个次版本,新的版本控制方案将从 3.90 开始生效。3.22 版本在该方案中是不规则的,因为它是一个长期稳定版本,但不会收到进一步的次版本,并且没有 .0 版本号。这是一个必要的过渡步骤。

下一步是什么

有关这些计划的更多详细信息,包括库开发人员和发行打包程序的具体信息,将在后续的博客文章中发布。GTK+ 开发博客还将继续提供有关 GTK+ 本身技术更改的更新,以便提供有关每个即将发布的主版本中将出现的更改的信息。

我们对这些计划感到兴奋,并希望它们能为 GTK+ 迎来一个新的时代,在这个时代,应用程序作者可以对我们的平台更有信心,同时仍然允许我们在 3.x 系列中看到的快速开发速度。

本文中提出的计划取代了 2016 年 6 月在多伦多 GTK+ 黑客马拉松之后公开的计划。

Adwaita

今天,来自 GNOME 设计团队的 Jakub Steiner 将讨论 Adwaita,GTK+ 的默认主题;设计师可以用来设置 GTK+ 样式的工具;以及工具包如何更改以允许更好的设计工作流程。

Adwaita 是 GTK+ 的用户界面。过去,GTK+ 没有面孔;工具包没有明确定义的外观。就像 FOSS 世界中的许多事物一样,它是自带的。有一个Raleigh,如果主题或系统设置出现问题,它才会出现的后备皮肤。你真的不想看到那个。

Adwaita

CSS

随着 GTK+ 3.0 的推出,一项大胆的新努力开始了。这是一项让视觉设计师使用他们理解的工具来负责视觉设计的努力。不是求助于主题引擎来绘制独特的控件,而是选择了网络上使用的样式引擎。“一切皆为盒子”的 CSS 模型非常适用于 GTK+。这花费了大量精力,主要由 Benjamin Otte 承担,多年来他设法为我们提供了我们梦想的东西:一个类似 CSS 的盒子模型,允许我们使用内边距、外边距、边框和最小宽度等巧妙的功能来间隔元素/控件。在选择器方面,我们不是处理每次发布都会更改的直接嵌套小部件结构,而是被赋予了一个抽象的、类似 HTML 的DOM结构,其中包含节点和类。节点也始终携带状态,并且更容易制作动画。

SCSS

在 GTK+ 中,有很多控件看起来像按钮,但不是按钮。每个程序员都很懒惰,这是一件好事。设计师也没什么不同。有一个缩写词来表示它,这是很积极的,DRY — 不要重复自己。因此,在旧的 Adwaita 中,当我们设计一些看起来相同的东西的外观时,我们只有一个属性块和大量选择器 — 该外观的目标。按钮、下拉列表,随便你说。不需要太多输入,但更改起来很疯狂。

SASS 的出现解决了这个问题,它提供了一种方式,可以一次定义通用的绘制过程,并在结构良好的样式表中重复使用。你可以绘制“像按钮一样”的东西,但不会将其定义为按钮。你仍然可以在下拉菜单部分找到一个语义组织良好的下拉菜单。SASS 将这些宏称为混入(mixins),你可以在 src/gtk/theme/Adwaita/_drawing.scss 中找到我们的绘制混入。

/* Switch Slider being a button */
slider {
/* ... */
@include button(normal, $edge: $shadow_color);
}
检查器

检查器的引入极大地改进了设计师的工作流程。检查器是一个非常有价值的工具,可以交互式地测试新样式,或找出某个特定选择器不起作用的原因。它提供了一些强大的工具:

  • 部件选择器。你可以交互式地指向一个部件,了解它的属性或它在部件树堆栈中的位置。从 3.20 版本开始,你还可以了解它的 CSS 节点、它可以达到的各种状态,以及它被分配的所有类。它还可以告诉你设置的属性在样式表中的定义位置。这有助于你找出你的选择器不起作用的原因。某种程度上是这样。如果能看到所有匹配的选择器,甚至是那些被优先级更高的选择器覆盖的选择器,那就真的太好了。
  • 交互式 CSS 样式表。你可以编写 CSS 规则并让它实时应用。这不仅有助于找到正确的选择器,还可以直接使用 GTK+ 而不是像 Inkscape 这样的工具来尝试绘制。能够快速迭代和尝试新事物会带来更好的设计。

如果这一切听起来很像现代浏览器提供的功能,那并非巧合。

CSS Nodes in the Inspector
检查器中的 CSS 节点
未来的改进

在修改 Adwaita 方面,使我们灵活性降低的一个主要因素是图形资源。我们仍然有一些东西必须使用图像资源。这些实际上是在一个大型的 SVG 资源表中,我们有一堆脚本来切割多种尺寸的图像(用于 HiDPI)。添加或更改某个特定部分仍然很麻烦。

为了使这不那么枯燥,这里有一个小型的网络演示,展示了我们如何可能避免使用图像资源来绘制 GtkScale 滑块,并使用简单的 CSS 盒子来代替

<div id="scale" class="scale">
  <div class="trough"></div>
  <div id="slider" class="slider run-animation"></div>
</div>

<style type="text/css">
.scale {
  position: relative;
  width: 100%;
  height: 64px;
}
.scale:hover { }
.trough {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  height: 8px;
  border-style: solid;
  border-width: 2px;
  border-radius: 4px;
  left: 0; right: 0;
  border-color: #a7a7a7;
  background-color: #b1b3b1;
  background-image: linear-gradient(to bottom, #a7a7a7, #bebebe);
  box-shadow: 0 1px 0 rgba(255,255,255,0.8);
}
.slider {
  position: absolute;
  width: 48px; height: 48px;
  top: 50%;
  left: 0%;
  transform: translateY(-50%) translateX(0%) rotate(0deg);
  border-style: solid;
  border-width: 2px;
  border-color: #a7a7a7;
  border-radius: 0;
  background-color: #e0e0e0;
  background-image: linear-gradient(135deg, #ededed, #d3d3d3);
  box-shadow: inset 0 0 0 2px rgba(255,255,255,0.2),
              2px 2px 2px rgba(0,0,0,0.1);
}
.slider.run-animation {
  animation-name: morph, progress;
  animation-delay: 6s,10s;
  animation-duration: 3s,3s;
  animation-iteration-count:1,infinite;
  animation-direction: normal, alternate;
  animation-timing-function: ease-in-out;
  animation-fill-mode: forwards;
}
.slider.run-animation:hover {
  /* the best way to reset CSS animations is switching between identical keyframes */
  animation-name: morphClone, progressClone;
}
@keyframes morph {
  0% {
    border-radius: 0;
    transform: translateY(-50%) translateX(0%) rotate(0deg);
  }
  90% {
    border-radius: 50% 50% 0 50%;
    transform: translateY(-50%) translateX(0%) rotate(0deg);
  }
  100% {
    border-radius: 50% 50% 0 50%;
    transform: translateY(-60%) translateX(0%) rotate(45deg);
  }
}
@keyframes morphClone {
  0% {
    border-radius: 0;
    transform: translateY(-50%) translateX(0%) rotate(0deg);
  }
  90% {
    border-radius: 50% 50% 0 50%;
    transform: translateY(-50%) translateX(0%) rotate(0deg);
  }
  100% {
    border-radius: 50% 50% 0 50%;
    transform: translateY(-60%) translateX(0%) rotate(45deg);
  }
}
@keyframes progress {
  0% {
    left: 0%;
    transform: translateY(-60%) translateX(0%) rotate(45deg);
  }
  100% {
    left: 100%;
    transform: translateY(-60%) translateX(-100%) rotate(45deg);
  }
}
@keyframes progressClone {
  0% {
    left: 0%;
    transform: translateY(-60%) translateX(0%) rotate(45deg);
  }
  100% {
    left: 100%;
    transform: translateY(-60%) translateX(-100%) rotate(45deg);
  }
}
</style>

本质上,滑块是一个具有 3 个圆角的盒子,旋转了 45 度。我们只需要盒子。

    /* transforms-based scale slider on the web */
.slider {
  position: absolute;
  width: 48px; height: 48px;
  top: 50%;
  left: 0%;
  /* move up slightly after rotation, thus not 50% */
  transform: translateY(-60%) rotate(45deg);
  border-style: solid;
  border-width: 2px;
  border-color: #a7a7a7;
  border-radius: 50% 50% 0 50%;
  background-color: #e0e0e0;
  background-image: linear-gradient(135deg, #ededed, #d3d3d3);
  box-shadow: inset 0 0 0 2px rgba(255,255,255,0.2),
              2px 2px 2px rgba(0,0,0,0.1);
}