自定义依赖资源

与 3.7.0 及更高版本兼容的资源

从 3.7.0 版本开始,Rebar3 发布了一个新的自定义资源 API,它可以访问项目的本地配置,从而支持更强大的自定义依赖格式。它们可以使用当前构建的上下文信息来自定义如何获取依赖项。

🚧

新接口不兼容旧版本

此新接口在 3.7.0 之前的版本中未知且不受支持。如果您正在编写应该与所有 Rebar3 版本兼容的库,请跳到下一节,其中记录了与所有 Rebar3 版本兼容的资源。但是,旧接口仍然与所有版本兼容,并且在添加新 API 时,对现有项目的支持没有中断。

新的回调 API 定义如下

%% Type declarations
-type resource() :: #resource{}. % an opaque record generated by an API call described below
-type source() :: {type(), location(), ref()} | {type(), location(), ref(), binary()}.
-type type() :: atom().
-type location() :: string().
-type ref() :: any().
-type resource_state() :: term().

%% and the following callbacks
-callback init(type(), rebar_state:t()) -> {ok, resource()}.
-callback lock(rebar_app_info:t(), resource_state()) -> source().
-callback download(file:filename_all(), rebar_app_info:t(), rebar_state:t(), resource_state()) ->
    ok | {error, any()}.
-callback needs_update(rebar_app_info:t(), resource_state()) -> boolean().
-callback make_vsn(rebar_app_info:t(), resource_state()) ->
    {plain, string()} | {error, string()}.

回调允许资源插件访问 rebar_state:t() 数据结构,这使您可以访问和操作Rebar3 状态,查找应用程序状态,以及通常使用rebar_staterebar_app_inforebar_dir和新的rebar_paths模块。

一个使用此功能的插件示例是rebar3_path_deps。Rebar3 自身的hex 包资源使用了此 API。

资源插件的初始化方式与其他任何插件相同

-module(my_rebar_plugin).

-export([init/1]).

-spec init(rebar_state:t()) -> {ok, rebar_state:t()}.
init(State) ->
    {ok, rebar_state:add_resource(State, {Tag, Module})}.

其中 Tag 代表 deps 配置值中的类型(githg 等),Module 是回调模块。

回调模块可能如下所示

-module(my_rebar_plugin_resource).

-export([init/2,
         lock/2,
         download/4,
         needs_update/2,
         make_vsn/1]).

%% Initialize the custom dep resource plugin
init(Type, _RebarState) ->
   CustomState = #{},
   Resource = rebar_resource_v2:new(
       Type,         % type tag such as 'git' or 'hg'
       ?MODULE,      % this callback module
       CustomState   % anything you want to carry around for next calls
   ),
   {ok, Resource}.

lock(AppInfo, CustomState) ->
  %% Extract info such as {Type, ResourcePath, ...} as declared
  %% in rebar.config
  SourceTuple = rebar_app_info:source(AppInfo),
  %% Annotate and modify the source tuple to make it absolutely
  %% and indeniably unambiguous (for example, with git this means
  %% transforming a branch name into an immutable ref)
  ...
  %% Return the unambiguous source tuple
  ModifiedSource.

download(TmpDir, AppInfo, RebarState, CustomState) ->
  %% Extract info such as {Type, ResourcePath, ...} as declared
  %% in rebar.config
  SourceTuple = rebar_app_info:source(AppInfo)),
  %% Download the resource defined by SourceTuple, which should be
  %% an OTP application or library, into TmpDir
  ...
  ok.

make_vsn(Dir, CustomState) ->
  %% Extract a version number from the application. This is useful
  %% when defining the version in the .app.src file as `{version, Type}',
  %% which means it should be derived from the build information. For
  %% the `git' resource, this means looking for the last tag and adding
  %% commit-specific information
  ...
  {plain, "0.1.2"}.


needs_update(AppInfo, CustomState) ->
  %% Extract the Source tuple if needed
  SourceTuple = rebar_app_info:source(AppInfo),
  %% Base version in the current file
  OriginalVsn = rebar_app_info:original_vsn(AppInfo)
  %% Check if the copy in the current install matches
  %% the defined value in the source tuple. On a conflict,
  %% return `true', otherwise `false'
  ...,
    Bool.

与所有版本兼容的资源

在 3.7.0 版本之前,依赖资源框架的限制性更强一些。它必须基本上以无上下文的方式工作,只能使用来自 rebar.configrebar.lockdeps 信息。无法获取与项目配置相关的任何信息,这基本上限制了每个资源可以执行的操作。

这些自定义资源在高于 3.7.0 的 Rebar3 版本中仍然受支持,因此,如果您有使用旧版本构建的用户,我们建议您仅开发此类资源。

每个依赖资源都必须实现 rebar_resource 行为。

-module(rebar_resource).

-export_type([resource/0
             ,type/0
             ,location/0
             ,ref/0]).

-type resource() :: {type(), location(), ref()}.
-type type() :: atom().
-type location() :: string().
-type ref() :: any().

-callback lock(file:filename_all(), tuple()) ->
    rebar_resource:resource().
-callback download(file:filename_all(), tuple(), rebar_state:t()) ->
    {tarball, file:filename_all()} | {ok, any()} | {error, any()}.
-callback needs_update(file:filename_all(), tuple()) ->
    boolean().
-callback make_vsn(file:filename_all()) ->
    {plain, string()} | {error, string()}.

rebar3 中包含 rebar_git_resourcerebar_hg_resourcerebar_pkg_resource

自定义资源可以像插件一样包含。Kelly McLaughlin 的 rebar3_tidy_deps 资源 中可以看到一个示例

-module(rebar_tidy_deps).

-export([init/1]).

-spec init(rebar_state:t()) -> {ok, rebar_state:t()}.
init(State) ->
    {ok, rebar_state:add_resource(State, {github, rebar_github_resource})}.

此资源 rebar_github_resource 实现 rebar3 资源行为,并添加到 rebar_state 中可用的资源列表中。将存储库作为插件添加到 rebar.config 中,允许使用此资源。

{mydep, {github, "kellymclauglin/mydep.git", {tag, "1.0.1"}}}.

{plugins, [
    {rebar_tidy_deps, ".*", {git, "https://github.com/kellymclaughlin/rebar3-tidy-deps-plugin.git", {tag, "0.0.2"}}}
]}.

编写同时兼容两个版本的插件

如果您想编写一个同时兼容两个版本的自定义资源插件,您可以动态检测参数以提供向后兼容的功能。在下面的示例中,新的 API 忽略所有新信息,并将其自身重新插入旧的 API 中。

-module(my_rebar_plugin_resource).

-export([init/2,
         lock/2,
         download/4, download/3,
         needs_update/2,
         make_vsn/1]).

init(Type, _RebarState) ->
   CustomState = #{},
   Resource = rebar_resource_v2:new(Type, ?MODULE, CustomState),
   {ok, Resource}.

%% Old API
lock(Dir, Source) when is_tuple(Source) ->
  lock_(Dir, Source);
%% New API
lock(AppInfo, _ResourceState) ->
  %%      extract info for dir               extract info for source
  lock_(rebar_app_info:dir(AppInfo), rebar_app_info:source(AppInfo)).

%% Function handling normalized case
lock_(Dir, Path) ->
  ...

%% Old Version
download(TmpDir, SourceTuple, RebarState) ->
  download_(TmpDir, SourceTuple, State).

%% New Version
download(TmpDir, AppInfo, RebarState, _ResourceState) ->
  %%                            extract source tuple
  download_(TmpDir, rebar_app_info:source(AppInfo), RebarState).

%% Function handling normalized case
download_(TmpDir, {MyTag, ...}, _State) ->
  ...

%% Old version
make_vsn(Dir) ->
  ...
%% New version
make_vsn(Dir, _ResourceState) ->
  make_vsn(Dir).

%% Old Version
needs_update(Dir, {MyTag, Path, _}) ->
  needs_update_(Dir, {MyTag, Path});
%% New Version
needs_update(AppInfo, _) ->
  needs_update_(rebar_app_info:dir(AppInfo), rebar_app_info:source(AppInfo)).

%% Function handling normalized case
needs_update_(Dir, {Tag, Path}) ->
  ...

请注意,如果您的资源确实需要新的 API 才能工作,则向后兼容性将难以实现,因为无论何时调用它,它都不会拥有新 API 的所有信息。

这种方法主要在您可以使用旧 API 提供可接受的(即使是降级的)用户体验时有用。

上次修改时间:2022 年 12 月 9 日:修复损坏的源代码链接 (33b768f)