diff --git a/gazelle/BUILD.bazel b/gazelle/BUILD.bazel index bda3d5de..f4b6c0ee 100644 --- a/gazelle/BUILD.bazel +++ b/gazelle/BUILD.bazel @@ -4,6 +4,7 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library") exports_files([ "dot_app_to_json.sh", "erl_attrs_to_json.sh", + "rebar_config_to_json.sh", ]) go_library( diff --git a/gazelle/generate.go b/gazelle/generate.go index a8cd9c55..9f2b5e5f 100644 --- a/gazelle/generate.go +++ b/gazelle/generate.go @@ -166,8 +166,10 @@ func importRebar(args language.GenerateArgs, erlangApp *ErlangApp) error { } if rebarConfig.ErlOpts != nil { - for _, o := range *rebarConfig.ErlOpts { - erlangApp.ErlcOpts.Add("+" + o) + for _, opt := range *rebarConfig.ErlOpts { + if opt.Kind == "erlc" { + erlangApp.ErlcOpts.Add("+" + opt.Value) + } } } diff --git a/gazelle/rebar_config_parser.go b/gazelle/rebar_config_parser.go index 39ed0400..ed2eb5ad 100644 --- a/gazelle/rebar_config_parser.go +++ b/gazelle/rebar_config_parser.go @@ -110,9 +110,14 @@ func (p *rebarConfigParser) parseRebarConfig(configFilename string) (*rebarConfi return &metadata, nil } +type rebarConfigErlOpt struct { + Kind string `json:"kind"` + Value string `json:"value"` +} + type rebarConfig struct { - Deps []map[string]string `json:"deps"` - ErlOpts *[]string `json:"erl_opts"` + Deps []map[string]string `json:"deps"` + ErlOpts *[]rebarConfigErlOpt `json:"erl_opts"` } func (p *rebarConfigParser) parseRebarLock(lockFilename string) (*rebarLock, error) { diff --git a/gazelle/rebar_config_to_json.sh b/gazelle/rebar_config_to_json.sh index 5792c561..16d91cb1 100755 --- a/gazelle/rebar_config_to_json.sh +++ b/gazelle/rebar_config_to_json.sh @@ -5,6 +5,10 @@ -export([main/1]). +-ifdef(TEST). +-export([parse/1]). +-endif. + main(Args) -> case io:get_line("") of eof -> @@ -27,21 +31,61 @@ parse_json_string("\"" ++ Tail) -> mapify_dep(Name) when is_atom(Name) -> #{name => Name, kind => hex}; -mapify_dep({Name, _, {git = Kind, Remote, Ref}}) -> +mapify_dep({Name, Version}) when is_list(Version) -> + #{name => Name, + kind => hex, + version => Version}; +mapify_dep({Name, Version, {git = Kind, Remote, Ref}}) -> #{name => Name, kind => Kind, + version => Version, remote => Remote, ref => Ref}; -mapify_dep({Name, Version}) -> +mapify_dep({Name, {git = Kind, Remote, Ref}}) -> #{name => Name, - kind => hex, - version => Version}. + kind => Kind, + remote => Remote, + ref => Ref}; +mapify_dep({Name, {hg = Kind, Remote, Ref}}) -> + #{name => Name, + kind => Kind, + remote => Remote, + ref => Ref}; +%% legacy formats +mapify_dep({Name, {git = Kind, Remote}}) -> + #{name => Name, + kind => Kind, + remote => Remote}; +mapify_dep({Name, Version, {git = Kind, Remote}}) -> + #{name => Name, + kind => Kind, + version => Version, + remote => Remote}; +mapify_dep({Name, Version, {git = Kind, Remote, Ref}, [raw]}) -> + #{name => Name, + kind => Kind, + version => Version, + remote => Remote, + ref => Ref}. + +conformErlOpt(Opt) when is_atom(Opt)-> + #{kind => erlc, + value => Opt}; +conformErlOpt({i, Include}) -> + #{kind => include, + value => Include}; +conformErlOpt({platform_define, _Platform, _Key}) -> + #{kind => platform_define, + value => ignored}; +conformErlOpt({platform_define, _Platform, _Key, _Value}) -> + #{kind => platform_define, + value => ignored}. conformConfig(List) -> maps:map( fun (erl_opts, Opts) -> - Opts; + [conformErlOpt(O) || O <- Opts]; (deps, Deps) -> [mapify_dep(D) || D <- Deps]; (_, _) -> diff --git a/private/erlang_bytecode2.bzl b/private/erlang_bytecode2.bzl index 8419f7e5..22fc7eee 100644 --- a/private/erlang_bytecode2.bzl +++ b/private/erlang_bytecode2.bzl @@ -94,10 +94,7 @@ fi include_args = " ".join(include_args), pa_args = " ".join(pa_args), out_dir = out_dir, - erlc_opts = " ".join([ - "'{}'".format(opt) - for opt in ctx.attr.erlc_opts[ErlcOptsInfo].values - ]), + erlc_opts = " ".join(ctx.attr.erlc_opts[ErlcOptsInfo].values), ) inputs = depset( diff --git a/test/dot_app_to_json/BUILD.thoas b/test/BUILD.thoas similarity index 100% rename from test/dot_app_to_json/BUILD.thoas rename to test/BUILD.thoas diff --git a/test/MODULE.bazel b/test/MODULE.bazel index 206abe5b..83be0375 100644 --- a/test/MODULE.bazel +++ b/test/MODULE.bazel @@ -66,7 +66,7 @@ erlang_package = use_extension( erlang_package.hex_package( name = "thoas", - build_file = "@//dot_app_to_json:BUILD.thoas", + build_file = "@//:BUILD.thoas", sha256 = "442296847aca11db8d25180693d7ca3073d6d7179f66952f07b16415306513b6", version = "0.4.0", ) diff --git a/test/WORKSPACE.bazel b/test/WORKSPACE.bazel index dc7dd0f1..65c11bc0 100644 --- a/test/WORKSPACE.bazel +++ b/test/WORKSPACE.bazel @@ -44,9 +44,6 @@ http_archive( ], ) -load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies") -load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies") - http_archive( name = "io_buildbuddy_buildbuddy_toolchain", sha256 = "a2a5cccec251211e2221b1587af2ce43c36d32a42f5d881737db3b546a536510", @@ -110,7 +107,7 @@ load("@rules_erlang//:hex_pm.bzl", "hex_pm_erlang_app") hex_pm_erlang_app( name = "thoas", - build_file = "@//dot_app_to_json:BUILD.thoas", + build_file = "@//:BUILD.thoas", sha256 = "442296847aca11db8d25180693d7ca3073d6d7179f66952f07b16415306513b6", version = "0.4.0", ) diff --git a/test/rebar_config_to_json/BUILD.bazel b/test/rebar_config_to_json/BUILD.bazel new file mode 100644 index 00000000..a22e8b32 --- /dev/null +++ b/test/rebar_config_to_json/BUILD.bazel @@ -0,0 +1,111 @@ +load( + "@rules_erlang//:app_file.bzl", + "app_file", +) +load( + "@rules_erlang//:erlang_bytecode.bzl", + "erlang_bytecode", +) +load( + "@rules_erlang//:erlang_app_info.bzl", + "erlang_app_info", +) +load( + "@rules_erlang//:erlang_app.bzl", + "DEFAULT_ERLC_OPTS", + "DEFAULT_TEST_ERLC_OPTS", +) +load( + "@rules_erlang//:xref2.bzl", + "xref", +) +load( + "@rules_erlang//:dialyze.bzl", + "DEFAULT_PLT_APPS", + "dialyze", + "plt", +) +load( + "@rules_erlang//:ct.bzl", + "assert_suites2", + "ct_suite", +) + +genrule( + name = "src", + srcs = ["@rules_erlang//gazelle:rebar_config_to_json.sh"], + outs = ["src/rebar_config_to_json.erl"], + cmd = """\ +echo "-module(rebar_config_to_json)." > $@ +tail -n +4 $< >> $@ +""", +) + +APP_NAME = "rebar_config_to_json" + +APP_VERSION = "1.0.0" + +erlang_bytecode( + name = "beam_files", + srcs = ["src/rebar_config_to_json.erl"], + dest = "ebin", + erlc_opts = DEFAULT_ERLC_OPTS, +) + +erlang_bytecode( + name = "test_beam_files", + testonly = True, + srcs = ["src/rebar_config_to_json.erl"], + dest = "test", + erlc_opts = DEFAULT_TEST_ERLC_OPTS + [ + "+nowarn_export_all", + ], +) + +app_file( + name = "app_file", + app_name = APP_NAME, + app_version = APP_VERSION, +) + +erlang_app_info( + name = "erlang_app", + srcs = ["src/rebar_config_to_json.erl"], + app = ":app_file", + app_name = APP_NAME, + beam = [":beam_files"], +) + +erlang_app_info( + name = "test_erlang_app", + testonly = True, + srcs = ["src/rebar_config_to_json.erl"], + app = ":app_file", + app_name = APP_NAME, + beam = [":test_beam_files"], +) + +xref() + +plt( + name = "base_plt", + apps = DEFAULT_PLT_APPS, +) + +dialyze( + plt = ":base_plt", +) + +ct_suite( + name = "rebar_config_to_json_SUITE", + size = "small", + data = [ + # https://github.com/erlang/rebar3/blob/main/rebar.config.sample + "test/rebar.config", + ], + runtime_deps = [ + "@thoas//:erlang_app", + ], +) + +assert_suites2() diff --git a/test/rebar_config_to_json/test/rebar.config b/test/rebar_config_to_json/test/rebar.config new file mode 100644 index 00000000..c78603b8 --- /dev/null +++ b/test/rebar_config_to_json/test/rebar.config @@ -0,0 +1,279 @@ +%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ts=4 sw=4 ft=erlang et +%% This is a sample rebar.conf file that shows examples of some of rebar's +%% options. + +%% == Artifacts == + +%% Artifacts are files generated by other tools (or plugins) and whose +%% presence is necessary for a build to be considered successful +{artifacts, ["priv/mycode.so"]}. + + +%% == Erlang Compiler == + +%% Erlang files to compile before the rest. Rebar automatically compiles +%% parse_transforms and custom behaviours before anything other than the files +%% in this list. +{erl_first_files, ["src/mymib1.erl", "src/mymib2.erl"]}. + +%% Erlang compiler options +{erl_opts, [no_debug_info, + {i, "myinclude"}, + {platform_define, + "(linux|solaris|freebsd|darwin)", 'HAVE_SENDFILE'}, + {platform_define, "(linux|freebsd)", 'BACKLOG', 128}, + {platform_define, "R13", 'old_inets'}]}. + +{minimum_otp_vsn, "21.0"}. + +%% MIB Options? +{mib_opts, []}. +%% SNMP mibs to compile first? +{mib_first_files, []}. + +%% leex options +{xrl_opts, []}. +%% leex files to compile first +{xrl_first_files, []}. + +%% yecc options +{yrl_opts, []}. +%% yecc files to compile first +{yrl_first_files, []}. + +%% == Application Discovery == + +%% Extensions for discovering application resource files. Rebar3 discovers +%% applications by searching for these files. When building the application +%% information, rebar will use the first discovered files in the list. +%% This configuration is intended to let profiles compose with special app +%% files by prefixing extensions. +{application_resource_extensions, [ + ".app.src.script", ".app.src" +]}. + +%% == Common Test == + +%% {erl_opts, [...]}, but for CT runs +{ct_compile_opts, []}. +%% {erl_first_files, ...} but for CT runs +{ct_first_files, []}. +%% Same options as for ct:run_test(Opts) +{ct_opts, []}. + + +%% == Cover == + +%% Whether to enable coverage reporting where commands support cover. Default +%% is `false' +{cover_enabled, false}. + +%% Modules to exclude from cover +{cover_excl_mods, []}. + +%% Options to pass to cover provider +{cover_opts, [verbose]}. + + +%% == Dependencies == + +%% What dependencies we have, dependencies can be of 3 forms, an application +%% name as an atom, eg. mochiweb, a name and a version (from the .app file), or +%% an application name, a version and the SCM details on how to fetch it (SCM +%% type, location and revision). +%% Rebar3 currently supports git and hg +{deps, [ + app_name, % latest version of package + {rebar, "1.0.0"}, % version 1.0.0 of a package + {rebar, % git, main branch of app, explicit + {git, "git://github.com/rebar/rebar.git", {branch, "main"}}}, + {rebar, % git, over HTTPS + {git, "https://github.com/rebar/rebar.git", {branch, "main"}}}, + {rebar, % git, tag + {git, "https://github.com/rebar/rebar.git", {tag, "1.0.0"}}}, + {rebar, % git, specific reference/hash + {git, "https://github.com/rebar/rebar.git", {ref, "7f73b8d6"}}}, + {rebar, % mercurial is also supported + {hg, "https://github.com/rebar/rebar.git", {tag, "1.0.0"}}}, + %% Alternative formats, backwards compatible declarations + {rebar, % default branch, will warn recommending explicit branch + {git, "git://github.com/rebar/rebar.git"}}, + {rebar, "1.0.*", % regex version check, ignored + {git, "git://github.com/rebar/rebar.git"}}, + {rebar, "1.0.*", % literal branch/ref/tag, will warn for explicit reference + {git, "git://github.com/rebar/rebar.git", "Rev"}}, + {rebar, ".*", % 'raw' dependency, argument ignored + {git, "git://github.com/rebar/rebar.git", {branch, "main"}}, + [raw]} +]}. + + +%% == Dialyzer == + +{dialyzer, [ + {warnings, [underspecs, no_return]}, + {get_warnings, true}, + {plt_apps, top_level_deps}, % top_level_deps | all_deps + {plt_extra_apps, []}, + {plt_location, local}, % local | "/my/file/name" + {plt_prefix, "rebar3"}, + {base_plt_apps, [stdlib, kernel, crypto]}, + {base_plt_location, global}, % global | "/my/file/name" + {base_plt_prefix, "rebar3"} +]}. + + +%% == Directories == + +%% directory for artifacts produced by rebar3 +{base_dir, "_build"}. +%% directory in '//' where deps go +{deps_dir, "lib"}. +%% where rebar3 operates from; defaults to the current working directory +{root_dir, "."}. +%% where checkout dependencies are to be located +{checkouts_dir, "_checkouts"}. +%% where, under / checkout dependencies are to be built +{checkouts_out_dir, "checkouts"}. +%% directory in '//' where plugins go +{plugins_dir, "plugins"}. +%% directories where OTP applications for the project can be located +{project_app_dirs, ["apps/*", "lib/*", "."]}. +%% Directories where source files for an OTP application can be found +{src_dirs, ["src"]}. +%% Paths to miscellaneous Erlang files to compile for an app +%% without including them in its modules list +{extra_src_dirs, []}. +%% Path where custom rebar3 templates could be found +{template_dir, []}. + +%% == EDoc == + +%% EDoc options, same as those passed to the edoc compiler +%% when called by hand. +{edoc_opts, []}. + + +%% == Escript == + +%% name of the main OTP application to boot +{escript_main_app, application}. +%% Name of the resulting escript executable +{escript_name, "application"}. +%% Wrapper type(s) for escript executable on windows +{escript_wrappers_windows, ["cmd","powershell"]}. +%% apps (other than main and deps) to be included +{escript_incl_apps, []}. +%% Executable escript lines +{escript_shebang, "#!/usr/bin/env escript\n"}. +{escript_comment, "%%\n"}. +{escript_emu_args, "%%! -escript main ~s -pa ~s/~s/ebin\n"}. + + +%% == EUnit == + +%% eunit:test(Tests) +{eunit_tests, [{application, rebar3}]}. +%% Options for eunit:test(Tests, Opts) +{eunit_opts, [verbose]}. +%% Additional compile options for eunit. erl_opts is also used +{eunit_compile_opts, [{d, some_define}]}. +%% {erl_first_files, ...} but for Eunit +{eunit_first_files, ["test/test_behaviour.erl"]}. + + +%% == Overrides == + +{overrides, [ + %% Add options to mydep's configurations for each element + {add, mydep, [{erl_opts, []}]}, + %% replace mydep's configuration option + {override, mydep, [{erl_opts, []}]}, + %% replace all dependencies' configuration options + {override, [{erl_opts, []}]} +]}. + +%% == Pre/Post Shell Hooks == + +%% Running shell commands before or after a given rebar3 command + +{pre_hooks, [{clean, "./prepare_package_files.sh"}, + {"linux", compile, "c_src/build_linux.sh"}, + {compile, "escript generate_headers"}, + {compile, "escript check_headers"}]}. + +{post_hooks, [{clean, "touch file1.out"}, + {"freebsd", compile, "c_src/freebsd_tweaks.sh"}, + {eunit, "touch file2.out"}, + {compile, "touch postcompile.out"}]}. + +%% == Provider Hooks == + +%% Run a rebar3 command before or after another one. +%% Only clean, ct, compile, eunit, release, and tar can be hooked around + +%% runs 'clean' before 'compile' +{provider_hooks, [{pre, [{compile, clean}]}]}. + + +%% == Releases == + +{relx, [{release, {my_release, "0.0.1"}, + [myapp]}, + + {dev_mode, true}, + {include_erts, false}, + + {extended_start_script, true}]}. + +%% == Shell == + +%% apps to auto-boot with `rebar3 shell'; defaults to apps +%% specified in a `relx' tuple, if any. +{shell, [{apps, [app1, app2]}, {script_file, "shell.escript"}]}. + +%% == xref == + +{xref_warnings, false}. + +%% optional extra paths to include in xref:set_library_path/2. +%% specified relative location of rebar.config. +%% e.g. {xref_extra_paths,["../gtknode/src"]} +{xref_extra_paths,[]}. + +%% xref checks to run +{xref_checks, [undefined_function_calls, undefined_functions, + locals_not_used, exports_not_used, + deprecated_function_calls, deprecated_functions]}. + +%% Optional custom xref queries (xref manual has details) specified as +%% {xref_queries, [{query_string(), expected_query_result()},...]} +%% The following for example removes all references to mod:*foo/4 +%% functions from undefined external function calls as those are in a +%% generated module +{xref_queries, + [{"(XC - UC) || (XU - X - B" + " - (\"mod\":\".*foo\"/\"4\"))",[]}]}. + +%% You might want to exclude certains functions or modules from your +%% rebar3 xref analysis. +%% You can do so with the following option, that takes as list items +%% one or more of a combination of: +%% * module(), +%% * {module(), function()}, +%% * {module(), function(), arity()} +{xref_ignores, []}. + +%% == alias == + +%% When you want a certain combination of operations to be done +%% as if you were using a rebar3 command. In the example below: +%% > rebar3 check +%% (equivalent to rebar3 do xref, dialyzer) +%% or +%% > rebar3 test +%% (equivalent to rebar3 do eunit, ct --suite=rebar_alias_SUITE, cover) + +{alias, [{check, [xref, dialyzer]}, + {test, [eunit, {ct, "--suite=rebar_alias_SUITE"}, cover]}]}. \ No newline at end of file diff --git a/test/rebar_config_to_json/test/rebar_config_to_json_SUITE.erl b/test/rebar_config_to_json/test/rebar_config_to_json_SUITE.erl new file mode 100644 index 00000000..66c9c472 --- /dev/null +++ b/test/rebar_config_to_json/test/rebar_config_to_json_SUITE.erl @@ -0,0 +1,81 @@ +-module(rebar_config_to_json_SUITE). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("eunit/include/eunit.hrl"). + +-compile(export_all). + +all() -> [ + basic + ]. + +basic(_) -> + Json = rebar_config_to_json:parse(fixture_path("test/rebar.config")), + ct:pal(?LOW_IMPORTANCE, "Json: ~p", [Json]), + {ok, Parsed} = thoas:decode(Json), + ?assertMatch( + #{<<"deps">> := Deps, + <<"erl_opts">> := Opts} when is_list(Deps) andalso is_list(Opts), + Parsed), + ?assertMatch( + [#{<<"kind">> := <<"hex">>, + <<"name">> := <<"app_name">>}, + #{<<"kind">> := <<"hex">>, + <<"name">> := <<"rebar">>, + <<"version">> := <<"1.0.0">>}, + #{<<"kind">> := <<"git">>, + <<"name">> := <<"rebar">>, + <<"ref">> := <<"{branch,\"main\"}">>, + <<"remote">> := + <<"git://github.com/rebar/rebar.git">>}, + #{<<"kind">> := <<"git">>, + <<"name">> := <<"rebar">>, + <<"ref">> := <<"{branch,\"main\"}">>, + <<"remote">> := + <<"https://github.com/rebar/rebar.git">>}, + #{<<"kind">> := <<"git">>, + <<"name">> := <<"rebar">>, + <<"ref">> := <<"{tag,\"1.0.0\"}">>, + <<"remote">> := + <<"https://github.com/rebar/rebar.git">>}, + #{<<"kind">> := <<"git">>, + <<"name">> := <<"rebar">>, + <<"ref">> := <<"{ref,\"7f73b8d6\"}">>, + <<"remote">> := + <<"https://github.com/rebar/rebar.git">>}, + #{<<"kind">> := <<"hg">>,<<"name">> := <<"rebar">>, + <<"ref">> := <<"{tag,\"1.0.0\"}">>, + <<"remote">> := + <<"https://github.com/rebar/rebar.git">>}, + #{<<"kind">> := <<"git">>, + <<"name">> := <<"rebar">>, + <<"remote">> := + <<"git://github.com/rebar/rebar.git">>}, + #{<<"kind">> := <<"git">>, + <<"name">> := <<"rebar">>, + <<"remote">> := + <<"git://github.com/rebar/rebar.git">>, + <<"version">> := <<"1.0.*">>}, + #{<<"kind">> := <<"git">>, + <<"name">> := <<"rebar">>,<<"ref">> := <<"Rev">>, + <<"remote">> := + <<"git://github.com/rebar/rebar.git">>, + <<"version">> := <<"1.0.*">>}, + #{<<"kind">> := <<"git">>, + <<"name">> := <<"rebar">>, + <<"ref">> := <<"{branch,\"main\"}">>, + <<"remote">> := + <<"git://github.com/rebar/rebar.git">>, + <<"version">> := <<".*">>}], + maps:get(<<"deps">>, Parsed)), + ?assertMatch( + [#{<<"kind">> := <<"erlc">>, + <<"value">> := <<"no_debug_info">>} | _], + maps:get(<<"erl_opts">>, Parsed)), + ok. + +fixture_path(File) -> + filename:join([os:getenv("TEST_SRCDIR"), + os:getenv("TEST_WORKSPACE"), + "rebar_config_to_json", + File]).