最近需要在好几个Unity项目之间共享一个自己写的自定义Package。之前一直都是用PackageManager的Git源安装的。虽然用起来很方便,但它不支持把自己作为别的包的依赖。

我自己写过一个运行时的包,里面有很多好用的扩展类和实用类型,可以很大程度上简化代码。另外我还有一个编辑器扩展的包,可以打打扩展编辑器的能力,做一些图形化啊,自动化的工作。

其中这个编辑器包它就依赖前面的运行时包,因为很多好用的方法和类编辑器包也要用的,所以就必须要设置一个依赖。

如果要设置依赖,就必须要自己搭建私有的PackageManager软件包仓库。Unity使用NPM来管理自己包,所以自己搭建私有包仓库的流程就和发布NPM包大同小异了。

原先我的Unity包是存放在Gitea上的,Gitea自带npm的软件包仓库的支持,所以我直接用Gitea来作为软件包仓库,就不用另外单独安装了。

在开始时首先要说明一下Gitea的软件包,软件包始终是属于一个用户或者组织的,无法归属于某个具体的仓库。顶多只能链接某个软件包到仓库上。

准备AccessToken

AccessToken也叫访问令牌

Gitea的npm软件包仓库不需要手动开启,只需要准备好对应的访问令牌就能直接使用。所以第一步是生成一个自己的访问令牌。安全起见,最好是单独创建一个低权限Gitea小号来专门发布新版的Unity包。因为用自己的管理员账号直接创建访问令牌权限太大了,不太安全。

创建方法很简单,登录小号,打开个人设置,点击应用,在管理Access Token这里新增。

令牌名称可以自己随便打,好记就行。

create-access-token

在下面的选择范围这里,只勾选Package这一项权限,其它选项不勾选。

detailed-access

生成之后复制一下令牌,因为这个令牌它只显示一次,你刷新之后就没了,再也显示不了。

为小号设置权限

要把Unity包上传到Gitea软件包仓库,首先要决定上传到哪个用户/组织的名下。我这里是单独创建了一个名为unity-registry的组织来单独存放我的Unity包

org

接着需要新增一个组织团队。Gitea的团队和GitHub的团队不太一样,GitHub上你可以直接隶属于某个组织,而团队不过只是个权限的合集罢了,并非一个实实在在的实体。而Gitea的团队是一个实打实存在的权限实体,每个组织成员必须要隶属到某个团队下面,无法直接隶属到组织名下。所以在Gitea的组织管理页面找不到“添加新用户”这个按钮实属正常!因为这个按钮在团队的页面下。

因为默认只有一个管理员团队,所以要我们要单独创建一个普通的团队。

团队的名字和描述随意。仓库权限选择所有仓库,权限选择常规权限。

下面的权限表里,将软件包设置为写入,其它全部设置为无权访问。最后创建这个团队即可。

team-settings

团队创建(我这里叫bot团队)好之后,把小号拉到这个普通团队里来。这样组织下面就有个两个团队了,每个团队只有一个人。

org

设置仓库的NPM Registry

电脑上默认的NPM软件包仓库是官方的https://registry.npmjs.org。我们当然不希望把自己的Unity包推送到官方仓库上去了。同时又不希望影响电脑上别的前端项目的NPM正常使用,此时就需要单独为这个仓库配置软件包仓库地址。

接着教程会分为两个版本,一个版本是用npm推送,一个版本是用yarn推送,按照你的个人喜好选择即可。

使用npm推送

首先在Unity包的package.json旁边创建一个.npmrc文件,用来保存仓库等级的npm配置。然后打开这个文件进行编辑。

添加这些两行:

registry=https://your-gitea.domain:443/api/packages/unity-registry/npm
//your-gitea.domain:443/api/packages/unity-registry/:_authToken=ffffffffffffffffffffffffffffff

第一行registry是为这npm包(也是Unity包)单独配置Package Level的软件包仓库地址,这样就不会影响电脑上别的npm项目正常使用了。后面的值就是你Gitea的npm软件包仓库api。

这个地址的格式模板是这样https://gitea.example.com/api/packages/{owner}/npm/。里面的owner就是这个包要推送到哪个用户/组织名下

第二行以//开头的可不是注释!这是npmrc文件特有的格式,而是叫host scoped configuration也就是说这些配置仅在匹配某些主机地址的时候才会有效。

host scoped configuration固定格式为//{host}:{key}={value}。其中两个下划线和冒号是固定格式,或者说是分隔符,这是npm自创的格式,并无其它衍生意义。

其中key/value仅仅在推送主机匹配时才会生效,否则不可见。这样设计是为了保证在配置了多个Registry的情况下,你的用户名密码或者令牌不会被发送到错误的主机上去,是为了安全目的。

如果你这样写token而不带主机部分,npm会直接拒绝执行,因为这是很危险的操作。

_autoToken=fffffffffffffffff

所以npmrc的第二行是这样写的:

//your-gitea.domain:443/api/packages/unity-registry/:_authToken=ffffffffffffffffffffffffffffff

细心的朋友已经发现了,第二行比第一行少了一个npm。不知道为什么,如果加上这个npm,保持第一行和第二行一致,执行npm publish的时候反而会出错。一定要去掉这个npm才会成功。不清楚是不是npm的bug,至少我在使用的过程中遇到了这个问题,这里顺便提一下。

配置好令牌之后,就可以在Unity包的根目录,也就是package.json所在目录下打开终端了。接着就要进行推送了。

命令很简单,直接npm publish就行了,因为我们提前配置好了令牌,此时就无需再登录账号密码了,会直接推送成功。

push-successfully

使用yarn推送

这里的yarn基于yarn 3.6.1版本,对yarn 1.x版本并不保证适用。

yarn和npm类似。首先在Unity包的package.json旁边创建一个.yarnrc.yml文件,用来保存仓库等级的yarn配置。然后打开这个文件进行编辑。

粘贴进这些配置:

npmRegistryServer: https://your-gitea.domain:443/api/packages/unity-registry/npm
npmRegistries:
  https://your-gitea.domain:443/api/packages/unity-registry/npm:
    npmAuthToken: ffffffffffffffffffffffff

第一行npmRegistryServer是为这npm包(也是Unity包)单独配置Package Level的软件包仓库地址,这样就不会影响电脑上别的npm项目正常使用了。后面的值就是你Gitea的npm软件包仓库api。

这个地址的格式模板是这样https://your-gitea.domain:443/api/packages/{owner}/npm/。里面的owner就是这个包要推送到哪个用户/组织名下

后面的npmRegistries是在为每个host或者url在定义验证方式,其key就是要匹配的主机或者url,其value是一个yaml object,可以有这些选项:【npmAlwaysAuth、npmAuthIdent、npmAuthToken】,我们只需要用到npmAuthToken就好了,账号密码验证那个选项不用管。

# On top of the global configuration, registries can be configured on a per-scope basis (for example to instruct Yarn to use your private registry when accessing packages from a given scope). The following properties are supported:
npmRegistries:

# This key represents the registry that's covered by the settings defined in the nested object. The protocol is optional (using https://npm.pkg.github.com would work just as well).
  //npm.pkg.github.com:
    npmAlwaysAuth: false
    npmAuthIdent: "username:password"
    npmAuthToken: "ffffffff-ffff-ffff-ffff-ffffffffffff"

配置好之后保存关闭.yarnrc.yml文件。

这里有一个小细节就是yarn的npmRegistryServer后面填写的地址和npmRegistries下面的地址是一样的,而npm那边则需要把后面的地址里面的npm三个字去掉,否则无法推送成功。

这样令牌和软件包仓库地址就配置好了。接着大家Unity包的根目录,也就是package.json所在目录下打开终端,就可以进行推送相关的工作了。

在推送之前需要先执行一下yarn install来生成yarn.lock文件。这是yarn的强制要求,因为如果找不到yarn.lock文件,yarn会直接拒绝推送。没办法,用yarn就得按着yarn的规矩来。

生成lock文件之后,推送就很简单了,在终端里执行yarn npm publish就行了,因为我们提前配置好了令牌,此时就无需再登录账号密码了,会直接推送成功。最终的截图大概是这样:

yarn-publish

Unity添加Registry

在publish好了自己的包之后,就要在unity里面添加这个软件包仓库的地址了。

首先我们发布的这些包的时候用到了访问令牌,那么此时从软件包仓库下载的时候,也是需要令牌的。这个令牌需要写到Unity自己的包管理器配置文件中,才能被Unity读取。在拉取包的时候才不会报错。

这个包管理器配置文件存在两个地方,一个是全局配置文件,一个是用户配置文件。

全局配置文件对整台机器上的所有用户生效,而用户配置文件只对当前用户生效。两个配置文件的位置如下:

全局配置文件位置:

Environment: Location:
Windows %ALLUSERSPROFILE%\Unity\config\upmconfig.toml (for example, C:\ProgramData\Unity\config\upmconfig.toml)
macOS and Linux /etc/upmconfig.toml

用户配置文件位置:

Environment: Location:
Windows (user account) %USERPROFILE%\.upmconfig.toml (for example, C:\Users\myusername\.upmconfig.toml)
Windows (system user account) %ALLUSERSPROFILE%\Unity\config\ServiceAccounts\.upmconfig.toml (for example, C:\Users\Public\Unity\config\ServiceAccounts\.upmconfig.toml)
macOS and Linux ~/.upmconfig.toml (for example, /Users/myusername/.upmconfig.toml)

我们这里使用的是Windows系统的用户配置文件,所以位置就在%USERPROFILE%\.upmconfig.toml,下,是个Toml格式的文件。(和Cargo包管理器所使用的配置文件格式一样)

如果没有这个文件就新建一个,如果有就打开编辑。粘贴以下内容进去,然后保存关闭。

[npmAuth."https://your-gitea.domain:443/api/packages/unity-registry/npm"]
token = "fffffffffffffffffffff"

重启Unity编辑器,然后打开包管理器,点击右上角搜索框旁边的Advanced Project Settings 打开包管理器设置。

upm

在包管理器设置页面的右边新增一个Registry设置。分别填入Name,URL,Scope(s)这三个选项。

  • Name:给这个软件包仓库取个好记的名字,可以随意
  • URL:软件包仓库的地址
  • Scope(s):软件包仓库的匹配范围。比如我这里填com.example就表示所有以com.example开头的包名的包,都会从这个软件包仓库里下载(比如com.example.tools和com.example.editor),而不匹配的话,就从官方仓库下载。如果填写com.example.tool就是以com.example.tool开头的软件包会匹配,而com.example.editor这样的包不会匹配

upm-settings

如果要给定多个Scope(s)选项,可以点击那个加号来多输入几个Scopes。

填写完成之后点击右下角Apply按钮保存。然后关掉这个Project Settings面板。

接着回到包管理器界面,点击左上角的下拉菜单切换到My Registies选项。然后点击左下角的更新按钮,就可以在下面的列表里看到自己上传的所有Unity包了。点击右下角的Install按钮即可。

upm-registries

一些小经验

虽然使用私有PackageManager服务器之后,Unity包里面就可以正常写依赖项了。但是依赖的版本号不支持范围语法,也就是~1.4.0^1.0.6这样的。必须要写具体的三段式版本号才行,否则没法正常Install。

CICD集成

前面的方法是手动发布的流程,有时候利用CICD系统做一个自动化发布会更加方便,也更加灵活。

自动化发布相比于手动发布有一些优势,比如手动发布时,软件包仓库地址必须要写在.npmrc文件中,这就带来一个问题:.npmrc文件应不应该加入Git?如果加入,会把令牌也一并发布出去,会造成不必要的麻烦。如果不加入Git,换一台电脑之后又要重新配置软件包仓库地址,很不方便。

此时就需要一个CICD服务器来解决这个问题,利用自动构建机制,可以把令牌和软件包仓库地址等信息通过CICD系统的配置文件,从环境变量传递给NPM,这样就避免了写在配置文件里的麻烦。

这里简单说一下环境变量应该怎么写,因为我自己并没有现成的CICD服务器,没法做实机演示。

这里以npm为例子,如果你是yarn那么大同小异,变通一下即可。

npm的环境变量(尤其是作为配置传递的环境变量)均以npm_config_打头,且整个环境变量不区分大小写。

环境变量有两个需要配置,分别对应上方NPM配置文件中软件包仓库地址和token,

第一个环境变量用来设置软件包仓库地址:

  • 键:npm_config_registry
  • 值:https://your-gitea.domain:443/api/packages/unity-registry/npm
  • Windows cmd.exe 示例:set npm_config_registry=https://your-gitea.domain:443/api/packages/unity-registry/npm
  • Linux bash 示例:npm_config_registry=https://your-gitea.domain:443/api/packages/unity-registry/npm

第二个环境变量用来设置软件包仓库令牌:

  • 键:npm_config_//your-gitea.domain:443/api/packages/unity-registry/:_authToken
  • 值:ffffffffffffffffffffffffffffffff
  • Windows cmd.exe 示例:set npm_config_//your-gitea.domain:443/api/packages/unity-registry/:_authToken=ffffffffffffffffffffffffffffffff
  • Linux bash 示例:npm_config_//your-gitea.domain:443/api/packages/unity-registry/:_authToken=ffffffffffffffffffffffffffffffff

当设置好环境变量之后,即使不没有.npmrc文件的情况下,也能通过npm publish命令正常推送Unity包

参考链接