VSoft Technologies博客

rss

VSoft Technologies博客——关于我们的产品和软件开发的文章。

Delphi包管理器RFC

Delphi/Rad Studio迫切需要一个合适的包/库/组件管理器。包管理器提供了使用第三方库的标准化方法。目前,第三方库的使用是非常特别的(adhoc),在许多情况下,这使得在机器之间移动项目,或者让新雇员快速开始运行变得非常困难。

其他开发环境,如Ruby, .net 和Javascript eco系统,在许多年前就认识到并解决了这个问题。让一个.net 或javascript项目在新的工作文件夹或新机器上运行是很简单的。

在Delphi/Rad Studio中,这比应有的难度还要困难得多。在咨询工作中,我特别关注客户是如何处理第三方代码的,每个客户都有不同的处理方式。常见的方法是……将其描述为adhoc(要安装的第三方产品列表可能还有一个readme文件)。在CI服务器上编译那些代码是一场噩梦。

现有的包管理器

Embarcadero通过XE8引入了他们的GetIt包管理器,GetIt基础设施无疑使RAD Studio本身的安装更加方便。但是作为针对第三方库的包管理器,它在许多方面存在不足。

还有Delphinus,这是一项令人钦佩的成果,但没有得到太多的关注,可能是因为它与github紧密相连(你真的需要有github帐户才能使用它,否则会出现api速率限制错误)。

我不想对GetIt或Delphinus吹毛求疵,而是想概述一下我对Delphi包管理器的想法。我花了很多时间使用.net(nuget)和javascript(npm、yarn),所以它们对我下面的要讲的内容有很大影响。

我恢复了一个老项目(从2013年),当宣布发布GetIt时,我搁置了这个项目。我花了大量的时间思考包管理(不仅仅是Delphi中的),但我相信我还没有考虑周全,我很想听听有兴趣参与这个项目的人或只是潜在用户的反馈。

项目的理想设计

这是2013年我刚开始研究这个的时候写的一些笔记,我试图把它们整理成一定的顺序,以便在这里进行展示,但它们只是我的想法的粗略概述。

开源

这个项目应该是开源的。当然,我们应该欢迎来自商业实体的贡献,但项目的方向将由社区(即,用户)控制。该项目将在GitHub上进行,参与者将通过Pull Requests进行贡献,贡献将由指导委员会(将予宣布)进行审核。

公共包注册中心

将会有一个公共网站/包服务器,在那里用户可以浏览可用的包,包的作者可以上传包。这将是该项目的第二阶段,初始阶段的重点是获得一个工作的客户端/包架构,以及一个针对包源的本地或网络共享文件夹。

包注册中心不应该变成一个存储处。一旦一个公共包注册中心/服务器可用,就可以允许使用评估包,可能需要付费(网络托管不是免费的)。商业供应商当然能够将商业包直接分发给他们的客户,因为包管理器将支持在共享网络或本地目录中托管包。包元数据将包括标记,以表明包是商业的、评估的、还是免费/开源的。用户将能够决定在他们的搜索中显示哪些包类型。

包的提交

将包提交到公共注册中心应该是一个简单的过程,不用填写、签署和传真表格!在这方面,我们将遵循nuget、npm、ruby等的做法。应该有一个针对对包名称、版权侵权等的争议解决程序。还应该能够分配一个包的所有权,例如当项目所有权发生变化时。

包作者将能够保留包前缀,以防止其他作者侵犯其名称或版权。例如,Embarcadero可能会保留Emb.作为他们的前缀,TMS可以保留TMS.作为他们的前缀。(当然,我希望两者都参与)。此项目将提供针对包前缀和名称的争议解决程序。

Delphi的特定挑战

与.net或者nodejs/javascript方面相比,Delphi呈现出了许多挑战。

兼容性

在npm中,包(package)包含了纯javascript的源代码(通常是小化的和模糊的)。兼容性非常高。

使用Nuget,包(package)包含了编译(到.NET IL)的程序集。一个包可能包含几个不同的版本,以针对不同版本的.net框架。同样,兼容性也很好。在net 2.0上编译的程序集也可以在net 4.7上运行(.net core打破了这一点,但它有一个新的兼容性模型,netstandard)。

返回来看Delphi,Delphi编译器版本之间的二进制兼容性几乎是不存在的(是的,我知道2006/7等的情况)。dcu、dcp和bpl文件通常只与它们被编译时使用的版本兼容。它们是针对某些平台生成的,也只与这些平台兼容(所以你不能在32位和64位windows之间,或者在iOS和Android之间共享dcu's)。因此,我们需要为我们希望我们的库支持的每个Delphi版本包含二进制文件。这对库依赖项(dependencies)也有重大影响。当npm和nuget将依赖项定义为一系列版本时,Delphi中的二进制依赖项将被固定到那个特定的版本。假如接口(interfaces)不改变,有一种方法可以维护版本之间的二进制兼容性,但是很难知道具体的规则是什么,所以现在我们将忽略这种可能性。

这限制了将库更新到新版本的范围,但也可以通过在包中包含源代码和在安装期间提供库的动态编译来克服。我倾向于使用预编译的库,因为可以加快构建过程(当然,因为这是我特别感兴趣的领域)。在持续集成(Continuous Integration)环境中,您希望快速且频繁地构建,在每次CI构建时重新构建库代码将是令人不快的(这里依经验而谈,构建FinalBuilder,50%的时间是在构建第三方库)。

还要考虑到调试和发布的问题——所以如果我们要包含二进制文件,为发布而进行编译是必需的,但是,调试是可选进行的吗?包文件的大小可能会有问题。如果包包含针对多个编译器版本的预编译的二进制文件,那么它可能会变得相当大。所以,是不是允许支持单个编译器版本的包,还是允许支持多个编译器版本的包?所支持的编译器将在包元数据中公开,可能还会在包文件名中公开。欢迎向我们提供有关这方面的反馈和想法。

包文件应该是(像使用其他包管理器一样)一个简单的zip文件,它包括一个描述包的内容的元数据(xml)文件,以及包含二进制文件、源代码(source)、资源等的文件夹。包将不包含任何脚本(即,在安装期间进行构建),这是出于安全原因(我不想运行随机脚本)。我们需要提供一种方法来在安装期间进行编译(使用简单的dsl来描述需要做什么),这仍然需要大量的思考(并且很大程度上涉及到依赖项)。

库路径/搜索路径

告别IDE的库(Library)路径。1995年的时候情况很好,当我们有一些第三方库和一些项目时,我们只需升级这些项目来处理库的版本控制(只是获取新近情况)。这与现在使用相同库的多个版本的概念是不相符的。

在我的产品的主要发行版的生命周期中,我很少更改一个库的主要版本,但是我可能会进行一些小的更新来修复错误或提高性能。处理这个问题的方法就是使用项目搜索(Project Search)路径。项目A可以使用一个库的版本1,项目2可以使用版本9,所有这些都非常安全(设计时组件design time components 确实会使这一点复杂化)。

如果一个项目以多个平台为目标,安装一个包,应该为它所支持的所有平台进行安装,但是用户应该可以指定需要为哪些平台安装此包。

设计时组件(Design time Component)的安装

Rad Studio IDE一次只允许安装一个版本的设计时包(design time package)。因此,当切换项目时,这些项目使用的可能是一个组件库的不同版本,我们将需要一个能够感知组件版本的系统,并且能够在项目加载时动态地卸载/安装组件。

我怀疑这将是需要克服的一个非常大的项目障碍,它要求一个具备非常好的开放工具api知识的人(也就是说,我不是合适人选)。

依赖项

依赖于其他库的库需要在元数据文件中指定这些依赖项(dependencies),以便在安装期间解析它们。正如我在上面提到的,二进制兼容性问题使依赖项的解析更加复杂,但不是不可克服的。解析算法将需要考虑编译器的版本和平台。该算法还需要处理包是从源码编译的情况,例如,不允许仅二进制包(binary only packages)依赖于仅源码包(source only packages)(以确保兼容性)。如果我们最终进行了安装时包(install time package)的编译,那么需要对依赖树算法进行一些重要的工作,以确定在安装期间还需要做什么(例如,是否存在哪些依赖项需要重新编译?)

这当然比其他平台要复杂得多,而且需要做大量的工作才能做好(ps,如果您认为不是这样的,那么您还没有从所有角度进行考虑!)