自定义依赖资源
与 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_state
、rebar_app_info
、rebar_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
配置值中的类型(git
、hg
等),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.config
和 rebar.lock
的 deps
信息。无法获取与项目配置相关的任何信息,这基本上限制了每个资源可以执行的操作。
这些自定义资源在高于 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_resource、rebar_hg_resource 和 rebar_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 提供可接受的(即使是降级的)用户体验时有用。