From 9f1ac0fbb343a08157d617c8b95aa8c4e9eb7def Mon Sep 17 00:00:00 2001 From: Bryan Paxton <39971740+starbelly@users.noreply.github.com> Date: Tue, 21 Dec 2021 17:10:31 -0600 Subject: [PATCH] Add --doc-dir option for circumventing doc providers (#266) --- src/rebar3_hex_build.erl | 61 +++++++++++++++++++++------------ src/rebar3_hex_publish.erl | 53 ++++++++++++++-------------- test/rebar3_hex_build_SUITE.erl | 25 +++++++++++--- test/support/test_utils.erl | 3 +- 4 files changed, 90 insertions(+), 52 deletions(-) diff --git a/src/rebar3_hex_build.erl b/src/rebar3_hex_build.erl index 349010ba..d901507e 100644 --- a/src/rebar3_hex_build.erl +++ b/src/rebar3_hex_build.erl @@ -1,6 +1,6 @@ -module(rebar3_hex_build). --export([create_package/3, create_docs/3]). +-export([create_package/3, create_docs/3, create_docs/4]). -include("rebar3_hex.hrl"). @@ -153,19 +153,17 @@ known_exclude_file(Path, ExcludeRe) -> has_checkouts(State) -> filelib:is_dir(rebar_dir:checkouts_dir(State)). --dialyzer({nowarn_function, create_docs/3}). create_docs(State, Repo, App) -> - case maybe_gen_docs(State, Repo) of - {ok, _State1} -> - AppDir = rebar_app_info:dir(App), - AppOpts = rebar_app_info:opts(App), - EdocOpts = rebar_opts:get(AppOpts, edoc_opts, []), - AppDetails = rebar_app_info:app_details(App), - Dir = proplists:get_value(dir, EdocOpts, ?DEFAULT_DOC_DIR), - DocDir = proplists:get_value(doc, AppDetails, Dir), - IndexFile = filename:join(AppDir, DocDir) ++ "/index.html", - case filelib:is_file(IndexFile) of + create_docs(State, Repo, App, #{doc_dir => undefined}). + +-dialyzer({nowarn_function, create_docs/4}). +create_docs(State, Repo, App, Args) -> + case maybe_gen_docs(State, Repo, App, Args) of + {ok, DocDir} -> + case docs_detected(DocDir) of true -> + AppDir = rebar_app_info:dir(App), + AppDetails = rebar_app_info:app_details(App), Files = rebar3_hex_file:expand_paths([DocDir], AppDir), Name = rebar_utils:to_list(rebar_app_info:name(App)), PkgName = rebar_utils:to_list(proplists:get_value(pkg_name, AppDetails, Name)), @@ -194,32 +192,53 @@ create_docs(State, Repo, App) -> {error, Err} end. -maybe_gen_docs(State, Repo) -> +maybe_gen_docs(_State, _Repo, App, #{doc_dir := DocDir}) when is_list(DocDir) -> + AppDir = rebar_app_info:dir(App), + {ok, filename:absname(filename:join(AppDir, DocDir))}; +maybe_gen_docs(State, Repo, App, _Args) -> case doc_opts(State, Repo) of - {ok, #{provider := PrvName}} -> + {ok, PrvName} -> case providers:get_provider(PrvName, rebar_state:providers(State)) of not_found -> - {error, doc_provider_not_found}; + {error, {doc_provider_not_found, PrvName}}; Prv -> case providers:do(Prv, State) of - {ok, State1} -> - {ok, State1}; + {ok, _State1} -> + {ok, resolve_dir(App, PrvName)}; _ -> - {error, doc_provider_failed} + {error, {doc_provider_failed, PrvName}} end end; _ -> {error, no_doc_config} end. +resolve_dir(App, PrvName) -> + AppDir = rebar_app_info:dir(App), + AppOpts = rebar_app_info:opts(App), + DocOpts = + case PrvName of + edoc -> + rebar_opts:get(AppOpts, edoc_opts, []); + _ -> + rebar_opts:get(AppOpts, PrvName, []) + end, + DocDir = proplists:get_value(dir, DocOpts, ?DEFAULT_DOC_DIR), + filename:absname(filename:join(AppDir, DocDir)). + +docs_detected(DocDir) -> + filelib:is_file(DocDir ++ "/index.html"). + doc_opts(State, Repo) -> case Repo of - #{doc := DocOpts} when is_map(DocOpts) -> - {ok, DocOpts}; + #{doc := #{provider := PrvName}} when is_atom(PrvName) -> + {ok, PrvName}; _ -> Opts = rebar_state:opts(State), case proplists:get_value(doc, rebar_opts:get(Opts, hex, []), undefined) of - DocOpts when is_map(DocOpts) -> {ok, DocOpts}; + undefined -> undefined; + PrvName when is_atom(PrvName) -> {ok, PrvName}; + #{provider := PrvName} -> {ok, PrvName}; _ -> undefined end end. diff --git a/src/rebar3_hex_publish.erl b/src/rebar3_hex_publish.erl index 342bdfa1..ba247464 100644 --- a/src/rebar3_hex_publish.erl +++ b/src/rebar3_hex_publish.erl @@ -35,7 +35,8 @@ init(State) -> {opts, [rebar3_hex:repo_opt(), {yes, $y, "yes", {boolean, false}, help(yes)}, {app, $a, "app", {string, undefined}, help(app)}, - {dry_run, undefined, "dry-run", {boolean, false}, help(dry_run)}, + {doc_dir, undefined, "doc-dir", {string, undefined}, help(yes)}, + {dry_run, undefined, "dry-run", {boolean, false}, help(dry_run)}, {replace, undefined, "replace", {boolean, false}, help(replace)}, {revert, undefined, "revert", string, help(revert)}]}]), State1 = rebar_state:add_provider(State, Provider), @@ -80,7 +81,7 @@ format_error({invalid_licenses, Invalids, AppName}) -> format_error({invalid_semver, {AppName, Version}}) -> Err = "~ts.app.src : non-semantic version number \"~ts\" found", io_lib:format(Err, [AppName, Version]); -format_error({invalid_semver_arg, Vsn}) -> +format_error({invalid_semver_arg, Vsn}) -> io_lib:format("The version argument provided \"~s\" is not a valid semantic version.", [Vsn]); format_error({has_unstable_deps, Deps}) -> MainMsg = "The following pre-release dependencies were found : ", @@ -96,11 +97,11 @@ format_error({app_not_found, AppName}) -> io_lib:format("App ~s specified with --app switch not found in project", [AppName]); format_error(bad_command) -> "bad command"; -format_error({publish_package, app_switch_required}) -> +format_error({publish_package, app_switch_required}) -> "--app required when publishing with the package argument in a umbrella"; -format_error({publish_docs, app_switch_required}) -> +format_error({publish_docs, app_switch_required}) -> "--app required when publishing with the docs argument in a umbrella"; -format_error({revert, app_switch_required}) -> +format_error({revert, app_switch_required}) -> "--app required when reverting in a umbrella with multiple apps"; format_error({required, repo}) -> "publish requires a repo name argument to identify the repo to publish to"; @@ -116,6 +117,8 @@ format_error({publish, {error, #{<<"errors">> := Errors, <<"message">> := Messag io_lib:format("Failed to publish package: ~ts~n\t~ts", [Message, ErrorString]); format_error({publish, {error, #{<<"message">> := Message}}}) -> io_lib:format("Failed to publish package: ~ts", [Message]); +format_error({create_docs, {error, {doc_provider_not_found, PrvName}}}) -> + io_lib:format("The ~ts documentation provider could not be found", [PrvName]); format_error({non_hex_deps, Excluded}) -> Err = "Can not publish package because the following deps are not available" ++ " in hex: ~s", @@ -236,10 +239,10 @@ handle_task(#{args := #{app := AppName}, apps := Apps, multi_app := true} = Tas -dialyzer({nowarn_function, publish/4}). publish(State, Repo, App, Args) -> {ok, HexConfig} = write_config(Repo), - case publish_package(State, HexConfig, App, Args) of - abort -> + case publish_package(State, HexConfig, App, Args) of + abort -> {ok, State}; - _ -> + _ -> publish_docs(State, HexConfig, App, Args) end. @@ -260,7 +263,7 @@ publish_package(State, Repo, App, Args) -> proceed -> HexOpts = hex_opts(Args), rebar_api:info("package argument given, will not publish docs", []), - #{tarball := Tarball} = Package, + #{tarball := Tarball} = Package, case rebar3_hex_client:publish(Repo, Tarball, HexOpts) of {ok, _Res} -> #{name := Name, version := Version} = Package, @@ -274,11 +277,11 @@ publish_package(State, Repo, App, Args) -> abort end. -create_package(State, Repo, App) -> +create_package(State, Repo, App) -> case rebar3_hex_build:create_package(State, Repo, App) of - {ok, Package} -> + {ok, Package} -> Package; - Err -> + Err -> ?RAISE({create_package, Err}) end. @@ -338,7 +341,7 @@ maybe_prompt(_Args, Message) -> hex_opts(Opts) -> lists:filter(fun({replace, _}) -> true; - ({_,_}) -> false + ({_,_}) -> false end, maps:to_list(Opts)). @@ -347,7 +350,7 @@ hex_opts(Opts) -> %%% =================================================================== publish_docs(State, Repo, App, Args) -> - #{tarball := Tar, name := Name, vsn := Vsn} = create_docs(State, Repo, App), + #{tarball := Tar, name := Name, vsn := Vsn} = create_docs(State, Repo, App, Args), case Args of #{dry_run := true} -> rebar_api:info("--dry-run enabled : will not publish docs.", []), @@ -362,11 +365,11 @@ publish_docs(State, Repo, App, Args) -> end end. -create_docs(State, Repo, App) -> - case rebar3_hex_build:create_docs(State, Repo, App) of +create_docs(State, Repo, App, Args) -> + case rebar3_hex_build:create_docs(State, Repo, App, Args) of {ok, Docs} -> Docs; - Err -> + Err -> ?RAISE({create_docs, Err}) end. @@ -380,7 +383,7 @@ revert_package(State, Repo, AppName, Vsn) -> assert_valid_version_arg(BinVsn), {ok, HexConfig} = write_config(Repo), case rebar3_hex_client:delete_release(HexConfig, BinAppName, BinVsn) of - {ok, _} -> + {ok, _} -> rebar_api:info("Successfully deleted package ~ts ~ts", [AppName, Vsn]), Prompt = io_lib:format("Also delete tag v~ts?", [Vsn]), case rebar3_hex_io:ask(Prompt, boolean, "N") of @@ -429,19 +432,19 @@ assert_valid_app(State, App) -> case rebar3_hex_app:validate(AppData) of ok -> {ok, State}; - {error, #{warnings := Warnings, errors := Errors}} -> + {error, #{warnings := Warnings, errors := Errors}} -> lists:foreach(fun(W) -> rebar_log:log(warn, format_error(W), []) end, Warnings), - case Errors of - [] -> + case Errors of + [] -> {ok, State}; - Errs -> + Errs -> ?RAISE({validation_errors, Errs}) end end. -assert_valid_version_arg(Vsn) -> +assert_valid_version_arg(Vsn) -> case verl:parse(Vsn) of - {ok, _} -> + {ok, _} -> ok; _ -> ?RAISE({invalid_semver_arg, Vsn}) @@ -500,6 +503,6 @@ support() -> " - a valid repository, only required when multiple repositories are configured~n~n" " - a valid version string, currently only utilized with --revert switch~n~n". -write_config(Repo) -> +write_config(Repo) -> assert_has_write_key(Repo), rebar3_hex_config:hex_config_write(Repo). diff --git a/test/rebar3_hex_build_SUITE.erl b/test/rebar3_hex_build_SUITE.erl index e95671c0..cb2e9955 100644 --- a/test/rebar3_hex_build_SUITE.erl +++ b/test/rebar3_hex_build_SUITE.erl @@ -13,6 +13,8 @@ all() -> create_docs_test, create_docs_unknown_provider_test, create_docs_no_provider_test, + create_docs_doc_dir_test, + create_docs_doc_dir_missing_test, create_docs_provider_failure_test ]. @@ -68,19 +70,34 @@ create_docs_unknown_provider_test(Config) -> StubConfig = #{type => app, dir => data_dir(Config), name => "valid"}, {State, Repo, App} = test_utils:make_stub(StubConfig), Repo1 = Repo#{doc => #{provider => foo}}, - ?assertMatch({error, doc_provider_not_found}, rebar3_hex_build:create_docs(State, Repo1, App)). + ?assertMatch({error, {doc_provider_not_found, foo}}, rebar3_hex_build:create_docs(State, Repo1, App)). create_docs_no_provider_test(Config) -> - StubConfig = #{type => app, dir => data_dir(Config), name => "valid"}, + StubConfig = #{type => app, dir => data_dir(Config), name => "no_doc_config"}, {State, Repo, App} = test_utils:make_stub(StubConfig), - Repo1 = Repo#{doc => #{}}, + Repo1 = maps:remove(doc, Repo), ?assertMatch({error, no_doc_config}, rebar3_hex_build:create_docs(State, Repo1, App)). +create_docs_doc_dir_test(Config) -> + #{dir := RootDir} = StubConfig = #{type => app, dir => data_dir(Config), name => "doc_attr"}, + {State, Repo, App} = test_utils:make_stub(StubConfig), + DocDir = filename:join([RootDir, "doc_attr", "doc"]), + test_utils:mkdir_p(DocDir), + ok = file:write_file(filename:join([DocDir, "index.html"]), "eh?"), + Repo1 = Repo#{doc => #{}}, + ?assertMatch({ok, _Docs}, rebar3_hex_build:create_docs(State, Repo1, App, #{doc_dir => "doc"})). + +create_docs_doc_dir_missing_test(Config) -> + StubConfig = #{type => app, dir => data_dir(Config), name => "doc_attr"}, + {State, Repo, App} = test_utils:make_stub(StubConfig), + Repo1 = Repo#{doc => #{}}, + ?assertMatch({ok, _Docs}, rebar3_hex_build:create_docs(State, Repo1, App, #{doc_dir => "doc"})). + create_docs_provider_failure_test(Config) -> StubConfig = #{type => app, dir => data_dir(Config), name => "valid"}, {State, Repo, App} = test_utils:make_stub(StubConfig), Repo1 = Repo#{doc => #{provider => bad_doc}}, {ok, State1} = bad_doc_provider:init(State), - ?assertMatch({error, doc_provider_failed}, rebar3_hex_build:create_docs(State1, Repo1, App)). + ?assertMatch({error, {doc_provider_failed, bad_doc}}, rebar3_hex_build:create_docs(State1, Repo1, App)). data_dir(Config) -> ?config(priv_dir, Config). diff --git a/test/support/test_utils.erl b/test/support/test_utils.erl index 155f0d3a..cf22acab 100644 --- a/test/support/test_utils.erl +++ b/test/support/test_utils.erl @@ -1,6 +1,6 @@ -module(test_utils). --export([make_stub/1, stub_app/1, mock_command/4, repo_config/0, repo_config/1]). +-export([mkdir_p/1, make_stub/1, stub_app/1, mock_command/4, repo_config/0, repo_config/1]). -define(REPO_CONFIG, maps:merge(hex_core:default_config(), #{ name => <<"hexpm">>, @@ -42,7 +42,6 @@ make_stub(#{type := app, name := Name, dir := Dir} = StubConfig) -> _ConfigFile = write_config_file(AppDir, StubConfig1), _LockFile = write_lock_file(AppDir, StubConfig1), #{repo := Repo} = StubConfig1, - %%Setup = stub_project(StubConfig1), State = init_state(AppDir, Repo, StubConfig1), {ok, App} = rebar3_hex_app:find(rebar_state:project_apps(State), Name), {ok, State1} = rebar_prv_edoc:init(State),