| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668 |
- -module('rebar.config').
- -export([do/2]).
- do(Dir, CONFIG) ->
- ok = assert_otp(),
- ok = warn_profile_env(),
- case iolist_to_binary(Dir) of
- <<".">> ->
- C1 = deps(CONFIG),
- Config = dialyzer(C1),
- maybe_dump(Config ++ [{overrides, overrides()}] ++ coveralls() ++ config());
- _ ->
- CONFIG
- end.
- assert_otp() ->
- Oldest = 24,
- Latest = 25,
- OtpRelease = list_to_integer(erlang:system_info(otp_release)),
- case OtpRelease < Oldest orelse OtpRelease > Latest of
- true ->
- io:format(
- standard_error,
- "ERROR: Erlang/OTP version ~p found. min=~p, recommended=~p~n",
- [OtpRelease, Oldest, Latest]
- ),
- halt(1);
- false when OtpRelease =/= Latest ->
- io:format(
- "WARNING: Erlang/OTP version ~p found, recommended==~p~n",
- [OtpRelease, Latest]
- );
- false ->
- ok
- end.
- bcrypt() ->
- {bcrypt, {git, "https://github.com/emqx/erlang-bcrypt.git", {tag, "0.6.0"}}}.
- quicer() ->
- {quicer, {git, "https://github.com/emqx/quic.git", {tag, "0.0.114"}}}.
- jq() ->
- {jq, {git, "https://github.com/emqx/jq", {tag, "v0.3.10"}}}.
- deps(Config) ->
- {deps, OldDeps} = lists:keyfind(deps, 1, Config),
- MoreDeps =
- [bcrypt() || provide_bcrypt_dep()] ++
- [jq() || is_jq_supported()] ++
- [quicer() || is_quicer_supported()],
- lists:keystore(deps, 1, Config, {deps, OldDeps ++ MoreDeps}).
- overrides() ->
- [{add, [{extra_src_dirs, [{"etc", [{recursive, true}]}]}]}] ++ snabbkaffe_overrides().
- %% Temporary workaround for a rebar3 erl_opts duplication
- %% bug. Ideally, we want to set this define globally
- snabbkaffe_overrides() ->
- Apps = [snabbkaffe, ekka, mria, gen_rpc],
- [{add, App, [{erl_opts, [{d, snk_kind, msg}]}]} || App <- Apps].
- config() ->
- [
- {cover_enabled, is_cover_enabled()},
- {profiles, profiles()},
- {plugins, plugins()}
- ].
- is_cover_enabled() ->
- case os:getenv("ENABLE_COVER_COMPILE") of
- "1" -> true;
- "true" -> true;
- _ -> false
- end.
- is_enterprise(ce) -> false;
- is_enterprise(ee) -> true.
- is_community_umbrella_app("apps/emqx_bridge_kafka") -> false;
- is_community_umbrella_app("apps/emqx_bridge_gcp_pubsub") -> false;
- is_community_umbrella_app("apps/emqx_bridge_cassandra") -> false;
- is_community_umbrella_app("apps/emqx_bridge_opents") -> false;
- is_community_umbrella_app("apps/emqx_bridge_clickhouse") -> false;
- is_community_umbrella_app("apps/emqx_bridge_dynamo") -> false;
- is_community_umbrella_app("apps/emqx_bridge_hstreamdb") -> false;
- is_community_umbrella_app("apps/emqx_bridge_influxdb") -> false;
- is_community_umbrella_app("apps/emqx_bridge_iotdb") -> false;
- is_community_umbrella_app("apps/emqx_bridge_matrix") -> false;
- is_community_umbrella_app("apps/emqx_bridge_mongodb") -> false;
- is_community_umbrella_app("apps/emqx_bridge_mysql") -> false;
- is_community_umbrella_app("apps/emqx_bridge_pgsql") -> false;
- is_community_umbrella_app("apps/emqx_bridge_pulsar") -> false;
- is_community_umbrella_app("apps/emqx_bridge_redis") -> false;
- is_community_umbrella_app("apps/emqx_bridge_rocketmq") -> false;
- is_community_umbrella_app("apps/emqx_bridge_tdengine") -> false;
- is_community_umbrella_app("apps/emqx_bridge_timescale") -> false;
- is_community_umbrella_app("apps/emqx_bridge_oracle") -> false;
- is_community_umbrella_app("apps/emqx_bridge_sqlserver") -> false;
- is_community_umbrella_app("apps/emqx_oracle") -> false;
- is_community_umbrella_app("apps/emqx_bridge_rabbitmq") -> false;
- is_community_umbrella_app("apps/emqx_ft") -> false;
- is_community_umbrella_app("apps/emqx_s3") -> false;
- is_community_umbrella_app("apps/emqx_schema_registry") -> false;
- is_community_umbrella_app("apps/emqx_enterprise") -> false;
- is_community_umbrella_app("apps/emqx_bridge_kinesis") -> false;
- is_community_umbrella_app(_) -> true.
- is_jq_supported() ->
- not (false =/= os:getenv("BUILD_WITHOUT_JQ") orelse
- is_win32()) orelse
- "1" == os:getenv("BUILD_WITH_JQ").
- is_quicer_supported() ->
- not (false =/= os:getenv("BUILD_WITHOUT_QUIC") orelse
- is_macos() orelse
- is_win32() orelse is_centos_6()) orelse
- "1" == os:getenv("BUILD_WITH_QUIC").
- is_rocksdb_supported() ->
- not (false =/= os:getenv("BUILD_WITHOUT_ROCKSDB") orelse
- is_raspbian()) orelse
- "1" == os:getenv("BUILD_WITH_ROCKSDB").
- is_macos() ->
- {unix, darwin} =:= os:type().
- is_centos_6() ->
- %% reason:
- %% glibc is too old
- case file:read_file("/etc/centos-release") of
- {ok, <<"CentOS release 6", _/binary>>} ->
- true;
- _ ->
- false
- end.
- is_raspbian() ->
- case os_cmd("./scripts/get-distro.sh") of
- "raspbian" ++ _ ->
- true;
- _ ->
- false
- end.
- is_win32() ->
- win32 =:= element(1, os:type()).
- project_app_dirs() ->
- project_app_dirs(get_edition_from_profile_env()).
- project_app_dirs(Edition) ->
- IsEnterprise = is_enterprise(Edition),
- UmbrellaApps = [
- Path
- || Path <- filelib:wildcard("apps/*"),
- is_community_umbrella_app(Path) orelse IsEnterprise
- ],
- UmbrellaApps ++
- case IsEnterprise of
- true -> ["lib-ee/*"];
- false -> []
- end.
- plugins() ->
- [
- %{relup_helper, {git, "https://github.com/emqx/relup_helper", {tag, "2.1.0"}}},
- %% emqx main project does not require port-compiler
- %% pin at root level for deterministic
- {pc, "v1.14.0"}
- ] ++
- %% test plugins are concatenated to default profile plugins
- %% otherwise rebar3 test profile runs are super slow
- test_plugins().
- test_plugins() ->
- [
- {rebar3_proper, "0.12.1"},
- {coveralls, {git, "https://github.com/emqx/coveralls-erl", {tag, "v2.2.0-emqx-1"}}}
- ].
- test_deps() ->
- [
- {bbmustache, "1.10.0"},
- {meck, "0.9.2"},
- {proper, "1.4.0"},
- {er_coap_client, {git, "https://github.com/emqx/er_coap_client", {tag, "v1.0.5"}}},
- {erl_csv, "0.2.0"}
- ].
- common_compile_opts() ->
- common_compile_opts(get_edition_from_profile_env(), undefined).
- common_compile_opts(Edition, Vsn) ->
- % always include debug_info
- [
- debug_info,
- {compile_info, [{emqx_vsn, Vsn} || Vsn /= undefined]},
- {d, 'EMQX_RELEASE_EDITION', Edition}
- ] ++
- [{d, 'EMQX_BENCHMARK'} || os:getenv("EMQX_BENCHMARK") =:= "1"] ++
- [{d, 'BUILD_WITHOUT_QUIC'} || not is_quicer_supported()].
- warn_profile_env() ->
- case os:getenv("PROFILE") of
- false ->
- io:format(
- standard_error,
- "WARN: environment variable PROFILE is not set, using 'emqx-enterprise'~n",
- []
- );
- _ ->
- ok
- end.
- %% this function is only used for test/check profiles
- get_edition_from_profile_env() ->
- case os:getenv("PROFILE") of
- "emqx-enterprise" ++ _ ->
- ee;
- "emqx" ++ _ ->
- ce;
- false ->
- ee;
- V ->
- io:format(standard_error, "ERROR: bad_PROFILE ~p~n", [V]),
- exit(bad_PROFILE)
- end.
- prod_compile_opts(Edition, Vsn) ->
- [
- compressed,
- deterministic,
- warnings_as_errors
- | common_compile_opts(Edition, Vsn)
- ].
- prod_overrides() ->
- [{add, [{erl_opts, [deterministic]}]}].
- profiles() ->
- case get_edition_from_profile_env() of
- ee ->
- profiles_ee();
- ce ->
- profiles_ce()
- end ++ profiles_dev().
- profiles_ce() ->
- Vsn = get_vsn(emqx),
- [
- {'emqx', [
- {erl_opts, prod_compile_opts(ce, Vsn)},
- {relx, relx(Vsn, cloud, bin, ce)},
- {overrides, prod_overrides()},
- {project_app_dirs, project_app_dirs(ce)}
- ]},
- {'emqx-pkg', [
- {erl_opts, prod_compile_opts(ce, Vsn)},
- {relx, relx(Vsn, cloud, pkg, ce)},
- {overrides, prod_overrides()},
- {project_app_dirs, project_app_dirs(ce)}
- ]}
- ].
- profiles_ee() ->
- Vsn = get_vsn('emqx-enterprise'),
- [
- {'emqx-enterprise', [
- {erl_opts, prod_compile_opts(ee, Vsn)},
- {relx, relx(Vsn, cloud, bin, ee)},
- {overrides, prod_overrides()},
- {project_app_dirs, project_app_dirs(ee)}
- ]},
- {'emqx-enterprise-pkg', [
- {erl_opts, prod_compile_opts(ee, Vsn)},
- {relx, relx(Vsn, cloud, pkg, ee)},
- {overrides, prod_overrides()},
- {project_app_dirs, project_app_dirs(ee)}
- ]}
- ].
- %% EE has more files than CE, always test/check with EE options.
- profiles_dev() ->
- [
- {check, [
- {erl_opts, common_compile_opts()},
- {project_app_dirs, project_app_dirs()}
- ]},
- {test, [
- {deps, test_deps()},
- {erl_opts, common_compile_opts() ++ erl_opts_i()},
- {extra_src_dirs, [{"test", [{recursive, true}]}]},
- {project_app_dirs, project_app_dirs()}
- ]}
- ].
- %% RelType: cloud (full size)
- %% PkgType: bin | pkg
- %% Edition: ce (opensource) | ee (enterprise)
- relx(Vsn, RelType, PkgType, Edition) ->
- [
- {include_src, false},
- {include_erts, true},
- {extended_start_script, false},
- {generate_start_script, false},
- {sys_config, false},
- {vm_args, false},
- {release, {emqx, Vsn}, relx_apps(RelType, Edition)},
- {overlay, relx_overlay(RelType, Edition)},
- {overlay_vars_values,
- build_info() ++
- [
- {emqx_description, emqx_description(RelType, Edition)}
- | overlay_vars(RelType, PkgType, Edition)
- ]}
- ].
- %% Make a HOCON compatible format
- build_info() ->
- Os = os_cmd("./scripts/get-distro.sh"),
- [
- {build_info_arch, erlang:system_info(system_architecture)},
- {build_info_wordsize, rebar_utils:wordsize()},
- {build_info_os, Os},
- {build_info_erlang, rebar_utils:otp_release()},
- {build_info_elixir, none},
- {build_info_relform, relform()}
- ].
- relform() ->
- case os:getenv("EMQX_REL_FORM") of
- false -> "tgz";
- Other -> Other
- end.
- emqx_description(cloud, ee) -> "EMQX Enterprise";
- emqx_description(cloud, ce) -> "EMQX".
- overlay_vars(cloud, PkgType, Edition) ->
- [
- {emqx_default_erlang_cookie, "emqxsecretcookie"}
- ] ++
- overlay_vars_pkg(PkgType) ++
- overlay_vars_edition(Edition).
- overlay_vars_edition(ce) ->
- [
- {emqx_schema_mod, emqx_conf_schema},
- {emqx_configuration_doc,
- "https://www.emqx.io/docs/en/v5.0/configuration/configuration.html"},
- {is_enterprise, "no"}
- ];
- overlay_vars_edition(ee) ->
- [
- {emqx_schema_mod, emqx_enterprise_schema},
- {emqx_configuration_doc,
- "https://docs.emqx.com/en/enterprise/v5.0/configuration/configuration.html"},
- {is_enterprise, "yes"}
- ].
- %% vars per packaging type, bin(zip/tar.gz/docker) or pkg(rpm/deb)
- overlay_vars_pkg(bin) ->
- [
- {platform_data_dir, "data"},
- {platform_etc_dir, "etc"},
- {platform_plugins_dir, "plugins"},
- {runner_bin_dir, "$RUNNER_ROOT_DIR/bin"},
- {emqx_etc_dir, "$RUNNER_ROOT_DIR/etc"},
- {runner_lib_dir, "$RUNNER_ROOT_DIR/lib"},
- {runner_log_dir, "$RUNNER_ROOT_DIR/log"},
- {runner_user, ""},
- {is_elixir, "no"}
- ];
- overlay_vars_pkg(pkg) ->
- [
- {platform_data_dir, "/var/lib/emqx"},
- {platform_etc_dir, "/etc/emqx"},
- {platform_plugins_dir, "/var/lib/emqx/plugins"},
- {runner_bin_dir, "/usr/bin"},
- {emqx_etc_dir, "/etc/emqx"},
- {runner_lib_dir, "$RUNNER_ROOT_DIR/lib"},
- {runner_log_dir, "/var/log/emqx"},
- {runner_user, "emqx"},
- {is_elixir, "no"}
- ].
- relx_apps(ReleaseType, Edition) ->
- [
- kernel,
- sasl,
- crypto,
- public_key,
- asn1,
- syntax_tools,
- ssl,
- os_mon,
- inets,
- compiler,
- runtime_tools,
- redbug,
- xmerl,
- {hocon, load},
- telemetry,
- % started by emqx_machine
- {emqx, load},
- {emqx_conf, load},
- emqx_machine
- ] ++
- [{mnesia_rocksdb, load} || is_rocksdb_supported()] ++
- [
- {mnesia, load},
- {ekka, load},
- {esasl, load},
- observer_cli,
- tools,
- {covertool, load},
- % started by emqx_machine
- {system_monitor, load},
- {emqx_utils, load},
- emqx_http_lib,
- emqx_resource,
- emqx_connector,
- emqx_authn,
- emqx_authz,
- emqx_auto_subscribe,
- emqx_gateway,
- emqx_gateway_stomp,
- emqx_gateway_mqttsn,
- emqx_gateway_coap,
- emqx_gateway_lwm2m,
- emqx_gateway_exproto,
- emqx_exhook,
- emqx_bridge,
- emqx_bridge_mqtt,
- emqx_bridge_http,
- emqx_rule_engine,
- emqx_modules,
- emqx_management,
- emqx_dashboard,
- emqx_retainer,
- emqx_prometheus,
- emqx_psk,
- emqx_slow_subs,
- emqx_mongodb,
- emqx_redis,
- emqx_mysql,
- emqx_plugins
- ] ++
- [quicer || is_quicer_supported()] ++
- [bcrypt || provide_bcrypt_release(ReleaseType)] ++
- %% Started automatically when needed (only needs to be started when the
- %% port implementation is used)
- [{jq, load} || is_jq_supported()] ++
- [{observer, load} || is_app(observer)] ++
- relx_apps_per_edition(Edition).
- is_app(Name) ->
- case application:load(Name) of
- ok -> true;
- {error, {already_loaded, _}} -> true;
- _ -> false
- end.
- relx_apps_per_edition(ee) ->
- [
- emqx_license,
- {emqx_enterprise, load},
- emqx_bridge_kafka,
- emqx_bridge_pulsar,
- emqx_bridge_gcp_pubsub,
- emqx_bridge_cassandra,
- emqx_bridge_opents,
- emqx_bridge_clickhouse,
- emqx_bridge_dynamo,
- emqx_bridge_hstreamdb,
- emqx_bridge_influxdb,
- emqx_bridge_iotdb,
- emqx_bridge_matrix,
- emqx_bridge_mongodb,
- emqx_bridge_mysql,
- emqx_bridge_pgsql,
- emqx_bridge_redis,
- emqx_bridge_rocketmq,
- emqx_bridge_tdengine,
- emqx_bridge_timescale,
- emqx_bridge_sqlserver,
- emqx_oracle,
- emqx_bridge_oracle,
- emqx_bridge_rabbitmq,
- emqx_schema_registry,
- emqx_eviction_agent,
- emqx_node_rebalance,
- emqx_ft,
- emqx_bridge_kinesis
- ];
- relx_apps_per_edition(ce) ->
- [emqx_telemetry].
- relx_overlay(ReleaseType, Edition) ->
- [
- {mkdir, "log/"},
- {mkdir, "data/"},
- {mkdir, "plugins"},
- {mkdir, "data/mnesia"},
- {mkdir, "data/configs"},
- {mkdir, "data/patches"},
- {mkdir, "data/scripts"},
- {template, "rel/emqx_vars", "releases/emqx_vars"},
- {template, "rel/BUILD_INFO", "releases/{{release_version}}/BUILD_INFO"},
- {copy, "bin/emqx", "bin/emqx"},
- {copy, "bin/emqx_ctl", "bin/emqx_ctl"},
- {copy, "bin/emqx_cluster_rescue", "bin/emqx_cluster_rescue"},
- {copy, "bin/node_dump", "bin/node_dump"},
- {copy, "bin/install_upgrade.escript", "bin/install_upgrade.escript"},
- {copy, "bin/emqx", "bin/emqx-{{release_version}}"},
- {copy, "bin/emqx_ctl", "bin/emqx_ctl-{{release_version}}"},
- {copy, "bin/install_upgrade.escript", "bin/install_upgrade.escript-{{release_version}}"},
- {copy, "apps/emqx_gateway_lwm2m/lwm2m_xml", "etc/lwm2m_xml"},
- {copy, "apps/emqx_authz/etc/acl.conf", "etc/acl.conf"},
- {template, "bin/emqx.cmd", "bin/emqx.cmd"},
- {template, "bin/emqx_ctl.cmd", "bin/emqx_ctl.cmd"},
- {copy, "bin/nodetool", "bin/nodetool"},
- {copy, "bin/nodetool", "bin/nodetool-{{release_version}}"}
- ] ++ etc_overlay(ReleaseType, Edition).
- etc_overlay(ReleaseType, Edition) ->
- Templates = emqx_etc_overlay(ReleaseType),
- [
- {mkdir, "etc/"},
- {copy, "{{base_dir}}/lib/emqx/etc/certs", "etc/"}
- | copy_examples(Edition)
- ] ++
- lists:map(
- fun
- ({From, To}) -> {template, From, To};
- (FromTo) -> {template, FromTo, FromTo}
- end,
- Templates
- ).
- copy_examples(ce) ->
- [{copy, "rel/config/examples", "etc/"}];
- copy_examples(ee) ->
- [
- {copy, "rel/config/examples", "etc/"},
- {copy, "rel/config/ee-examples/*", "etc/examples/"}
- ].
- emqx_etc_overlay(ReleaseType) ->
- emqx_etc_overlay_per_rel(ReleaseType) ++
- emqx_etc_overlay().
- emqx_etc_overlay_per_rel(cloud) ->
- [{"{{base_dir}}/lib/emqx/etc/vm.args.cloud", "etc/vm.args"}].
- emqx_etc_overlay() ->
- [
- {"{{base_dir}}/lib/emqx/etc/ssl_dist.conf", "etc/ssl_dist.conf"},
- {"{{base_dir}}/lib/emqx_conf/etc/emqx.conf.all", "etc/emqx.conf"}
- ].
- get_vsn(Profile) ->
- case os:getenv("PKG_VSN") of
- false ->
- os_cmd("pkg-vsn.sh " ++ atom_to_list(Profile));
- Vsn ->
- Vsn
- end.
- %% to make it compatible to Linux and Windows,
- %% we must use bash to execute the bash file
- %% because "./" will not be recognized as an internal or external command
- os_cmd(Cmd) ->
- Output = os:cmd("bash " ++ Cmd),
- re:replace(Output, "\n", "", [{return, list}]).
- maybe_dump(Config) ->
- is_debug() andalso
- file:write_file("rebar.config.rendered", [io_lib:format("~p.\n", [I]) || I <- Config]),
- Config.
- is_debug() -> is_debug("DEBUG") orelse is_debug("DIAGNOSTIC").
- is_debug(VarName) ->
- case os:getenv(VarName) of
- false -> false;
- "" -> false;
- _ -> true
- end.
- provide_bcrypt_dep() ->
- not is_win32().
- provide_bcrypt_release(ReleaseType) ->
- provide_bcrypt_dep() andalso ReleaseType =:= cloud.
- erl_opts_i() ->
- [{i, "apps"}] ++
- [{i, Dir} || Dir <- filelib:wildcard(filename:join(["apps", "*", "include"]))] ++
- [{i, Dir} || Dir <- filelib:wildcard(filename:join(["lib-ee", "*", "include"]))].
- dialyzer(Config) ->
- {dialyzer, OldDialyzerConfig} = lists:keyfind(dialyzer, 1, Config),
- AppsToAnalyse =
- case os:getenv("DIALYZER_ANALYSE_APP") of
- false ->
- [];
- Value ->
- [list_to_atom(App) || App <- string:tokens(Value, ",")]
- end,
- AppNames = app_names(),
- KnownApps = [Name || Name <- AppsToAnalyse, lists:member(Name, AppNames)],
- AppsToExclude = AppNames -- KnownApps,
- Extra =
- [bcrypt || provide_bcrypt_dep()] ++
- [jq || is_jq_supported()] ++
- [quicer || is_quicer_supported()],
- NewDialyzerConfig =
- OldDialyzerConfig ++
- [{exclude_apps, AppsToExclude} || length(AppsToAnalyse) > 0] ++
- [{plt_extra_apps, Extra} || length(Extra) > 0],
- lists:keystore(
- dialyzer,
- 1,
- Config,
- {dialyzer, NewDialyzerConfig}
- ).
- coveralls() ->
- case {os:getenv("GITHUB_ACTIONS"), os:getenv("GITHUB_TOKEN")} of
- {"true", Token} when is_list(Token) ->
- Cfgs = [
- {coveralls_repo_token, Token},
- {coveralls_service_job_id, os:getenv("GITHUB_RUN_ID")},
- {coveralls_commit_sha, os:getenv("GITHUB_SHA")},
- {coveralls_coverdata, "_build/test/cover/*.coverdata"},
- {coveralls_service_name, "github"}
- ],
- case
- os:getenv("GITHUB_EVENT_NAME") =:= "pull_request" andalso
- string:tokens(os:getenv("GITHUB_REF"), "/")
- of
- [_, "pull", PRNO, _] ->
- [{coveralls_service_pull_request, PRNO} | Cfgs];
- _ ->
- Cfgs
- end;
- _ ->
- []
- end.
- app_names() -> list_dir("apps") ++ list_dir("lib-ee").
- list_dir(Dir) ->
- case filelib:is_dir(Dir) of
- true ->
- {ok, Names} = file:list_dir(Dir),
- [list_to_atom(Name) || Name <- Names, filelib:is_dir(filename:join([Dir, Name]))];
- false ->
- []
- end.
|