Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:Ledest:erlang:24
erlang
6132-dialyzer-Use-argparse-to-parse-command-lin...
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File 6132-dialyzer-Use-argparse-to-parse-command-line.patch of Package erlang
From 0190550ecfe1bb03370b9c711ddab9b5f4afa625 Mon Sep 17 00:00:00 2001 From: Maxim Fedorov <maximfca@gmail.com> Date: Sat, 18 Feb 2023 08:50:36 -0800 Subject: [PATCH] [dialyzer] Use argparse to parse command line --- lib/dialyzer/src/dialyzer.hrl | 5 + lib/dialyzer/src/dialyzer_cl_parse.erl | 821 +++++++++---------------- lib/dialyzer/src/dialyzer_options.erl | 23 +- lib/dialyzer/src/typer.erl | 18 +- lib/stdlib/src/argparse.erl | 2 +- 5 files changed, 318 insertions(+), 551 deletions(-) diff --git a/lib/dialyzer/src/dialyzer.hrl b/lib/dialyzer/src/dialyzer.hrl index f34acb3410..26a4b0b0a5 100644 --- a/lib/dialyzer/src/dialyzer.hrl +++ b/lib/dialyzer/src/dialyzer.hrl @@ -157,6 +157,11 @@ 'incremental'} | {'warnings', [warn_option()]} | {'get_warnings', boolean()} + | {'use_spec', boolean()} + | {'filename_opt', filename_opt()} + | {'callgraph_file', file:filename()} + | {'mod_deps_file', file:filename()} + | {'warning_files_rec', [DirName :: file:filename()]} | {'error_location', error_location()}. -type dial_options() :: [dial_option()]. -type filename_opt() :: 'basename' | 'fullpath'. diff --git a/lib/dialyzer/src/dialyzer_cl_parse.erl b/lib/dialyzer/src/dialyzer_cl_parse.erl index bb792bb15a..fe7ad2af01 100644 --- a/lib/dialyzer/src/dialyzer_cl_parse.erl +++ b/lib/dialyzer/src/dialyzer_cl_parse.erl @@ -1,5 +1,3 @@ -%% -*- erlang-indent-level: 2 -*- -%% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at @@ -14,8 +12,7 @@ -module(dialyzer_cl_parse). --export([start/0, get_lib_dir/1]). --export([collect_args/1]). % used also by typer +-export([start/0]). -include("dialyzer.hrl"). @@ -27,550 +24,228 @@ | {'gui', #options{}} | {'error', string()}. --type deep_string() :: string() | [deep_string()]. - -%%----------------------------------------------------------------------- - -spec start() -> dial_cl_parse_ret(). - start() -> - init(), - Args = init:get_plain_arguments(), - try - Ret = cl(Args), - Ret - catch - throw:{dialyzer_cl_parse_error, Msg} -> {error, Msg}; - _:R:S -> - Msg = io_lib:format("~tp\n~tp\n", [R, S]), - {error, lists:flatten(Msg)} - end. - -cl(["--add_to_plt"|T]) -> - put(dialyzer_options_analysis_type, plt_add), - cl(T); -cl(["--apps"|T]) -> - T1 = get_lib_dir(T), - {Args, T2} = collect_args(T1), - append_var(dialyzer_options_files_rec, Args), - cl(T2); -cl(["--warning_apps"|T]) -> - T1 = get_lib_dir(T), - {Args, T2} = collect_args(T1), - append_var(dialyzer_options_warning_files_rec, Args), - cl(T2); -cl(["--build_plt"|T]) -> - put(dialyzer_options_analysis_type, plt_build), - cl(T); -cl(["--check_plt"|T]) -> - put(dialyzer_options_analysis_type, plt_check), - cl(T); -cl(["-n"|T]) -> - cl(["--no_check_plt"|T]); -cl(["--no_check_plt"|T]) -> - put(dialyzer_options_check_plt, false), - cl(T); -cl(["-nn"|T]) -> - %% Ignored since Erlang/OTP 24.0. - cl(T); -cl(["--no_native"|T]) -> - %% Ignored since Erlang/OTP 24.0. - cl(T); -cl(["--no_native_cache"|T]) -> - %% Ignored since Erlang/OTP 24.0. - cl(T); -cl(["--plt_info"|T]) -> - put(dialyzer_options_analysis_type, plt_info), - cl(T); -cl(["--get_warnings"|T]) -> - put(dialyzer_options_get_warnings, true), - cl(T); -cl(["-D"|_]) -> - cl_error("No defines specified after -D"); -cl(["-D"++Define|T]) -> - Def = re:split(Define, "=", [{return, list}, unicode]), - append_defines(Def), - cl(T); -cl(["-h"|_]) -> - help_message(); -cl(["--help"|_]) -> - help_message(); -cl(["-I"]) -> - cl_error("no include directory specified after -I"); -cl(["-I", Dir|T]) -> - append_include(Dir), - cl(T); -cl(["-I"++Dir|T]) -> - append_include(Dir), - cl(T); -cl(["--input_list_file"]) -> - cl_error("No input list file specified"); -cl(["--input_list_file",File|L]) -> - read_input_list_file(File), - cl(L); -cl(["-c"++_|T]) -> - NewTail = command_line(T), - cl(NewTail); -cl(["-r"++_|T0]) -> - {Args, T} = collect_args(T0), - append_var(dialyzer_options_files_rec, Args), - cl(T); -cl(["--remove_from_plt"|T]) -> - put(dialyzer_options_analysis_type, plt_remove), - cl(T); -cl(["--incremental"|T]) -> - put(dialyzer_options_analysis_type, incremental), - cl(T); -cl(["--com"++_|T]) -> - NewTail = command_line(T), - cl(NewTail); -cl(["--output"]) -> - cl_error("No outfile specified"); -cl(["-o"]) -> - cl_error("No outfile specified"); -cl(["--output",Output|T]) -> - put(dialyzer_output, Output), - cl(T); -cl(["--metrics_file",MetricsFile|T]) -> - put(dialyzer_metrics, MetricsFile), - cl(T); -cl(["--module_lookup_file",ModuleLookupFile|T]) -> - put(dialyzer_module_lookup, ModuleLookupFile), - cl(T); -cl(["--output_plt"]) -> - cl_error("No outfile specified for --output_plt"); -cl(["--output_plt",Output|T]) -> - put(dialyzer_output_plt, Output), - cl(T); -cl(["-o", Output|T]) -> - put(dialyzer_output, Output), - cl(T); -cl(["-o"++Output|T]) -> - put(dialyzer_output, Output), - cl(T); -cl(["--raw"|T]) -> - put(dialyzer_output_format, raw), - cl(T); -cl(["--fullpath"|T]) -> - put(dialyzer_filename_opt, fullpath), - cl(T); -cl(["--no_indentation"|T]) -> - put(dialyzer_indent_opt, false), - cl(T); -cl(["-pa", Path|T]) -> - case code:add_patha(Path) of - true -> cl(T); - {error, _} -> cl_error("Bad directory for -pa: " ++ Path) - end; -cl(["--plt"]) -> - error("No plt specified for --plt"); -cl(["--plt", PLT|T]) -> - put(dialyzer_init_plts, [PLT]), - cl(T); -cl(["--plts"]) -> - error("No plts specified for --plts"); -cl(["--plts"|T]) -> - {PLTs, NewT} = get_plts(T, []), - put(dialyzer_init_plts, PLTs), - cl(NewT); -cl(["-q"|T]) -> - put(dialyzer_options_report_mode, quiet), - cl(T); -cl(["--quiet"|T]) -> - put(dialyzer_options_report_mode, quiet), - cl(T); -cl(["--src"|T]) -> - put(dialyzer_options_from, src_code), - cl(T); -cl(["--no_spec"|T]) -> - put(dialyzer_options_use_contracts, false), - cl(T); -cl(["--statistics"|T]) -> - put(dialyzer_timing, true), - cl(T); -cl(["--resources"|T]) -> - put(dialyzer_options_report_mode, quiet), - put(dialyzer_timing, debug), - cl(T); -cl(["-v"|_]) -> - io:format("Dialyzer version "++?VSN++"\n"), - erlang:halt(?RET_NOTHING_SUSPICIOUS); -cl(["--version"|_]) -> - io:format("Dialyzer version "++?VSN++"\n"), - erlang:halt(?RET_NOTHING_SUSPICIOUS); -cl(["--verbose"|T]) -> - put(dialyzer_options_report_mode, verbose), - cl(T); -cl(["-W"|_]) -> - cl_error("-W given without warning"); -cl(["-Whelp"|_]) -> - help_warnings(); -cl(["-W"++Warn|T]) -> - append_var(dialyzer_warnings, [list_to_atom(Warn)]), - cl(T); -cl(["--dump_callgraph"]) -> - cl_error("No outfile specified for --dump_callgraph"); -cl(["--dump_callgraph", File|T]) -> - put(dialyzer_callgraph_file, File), - cl(T); -cl(["--dump_full_dependencies_graph"]) -> - cl_error("No outfile specified for --dump_full_dependencies_graph"); -cl(["--dump_full_dependencies_graph", File|T]) -> - put(dialyzer_mod_deps_file, File), - cl(T); -cl(["--gui"|T]) -> - put(dialyzer_options_mode, gui), - cl(T); -cl(["--error_location", LineOrColumn|T]) -> - put(dialyzer_error_location_opt, list_to_atom(LineOrColumn)), - cl(T); -cl(["--solver", Solver|T]) -> % not documented - append_var(dialyzer_solvers, [list_to_atom(Solver)]), - cl(T); -cl([H|_] = L) -> - case filelib:is_file(H) orelse filelib:is_dir(H) of - true -> - NewTail = command_line(L), - cl(NewTail); - false -> - cl_error("Unknown option: " ++ H) - end; -cl([]) -> - {RetTag, Opts} = - case get(dialyzer_options_analysis_type) =:= plt_info of - true -> - put(dialyzer_options_analysis_type, plt_check), - {plt_info, cl_options()}; - false -> - case get(dialyzer_options_mode) of - gui -> {gui, common_options()}; - cl -> - case get(dialyzer_options_analysis_type) =:= plt_check of - true -> {check_init, cl_options()}; - false -> {cl, cl_options()} - end - end - end, - case dialyzer_options:build(Opts) of - {error, Msg} -> cl_error(Msg); - OptsRecord -> {RetTag, OptsRecord} - end. - -%%----------------------------------------------------------------------- - -command_line(T0) -> - {Args, T} = collect_args(T0), - append_var(dialyzer_options_files, Args), - %% if all files specified are ".erl" files, set the 'src' flag automatically - case lists:all(fun(F) -> filename:extension(F) =:= ".erl" end, Args) of - true -> put(dialyzer_options_from, src_code); - false -> ok - end, - T. - -read_input_list_file(File) -> - case file:read_file(File) of - {ok,Bin} -> - Files = binary:split(Bin, <<"\n">>, [trim_all,global]), - NewFiles = [binary_to_list(string:trim(F)) || F <- Files], - append_var(dialyzer_options_files, NewFiles); - {error,Reason} -> - cl_error(io_lib:format("Reading of ~s failed: ~s", [File,file:format_error(Reason)])) - end. - --spec cl_error(deep_string()) -> no_return(). - -cl_error(Str) -> - Msg = lists:flatten(Str), - throw({dialyzer_cl_parse_error, Msg}). - -init() -> - %% By not initializing every option, the modified options can be - %% found. If every option were to be returned by cl_options() and - %% common_options(), then the environment variables (currently only - %% ERL_COMPILER_OPTIONS) would be overwritten by default values. - put(dialyzer_options_mode, cl), - put(dialyzer_options_files_rec, []), - put(dialyzer_options_warning_files_rec, []), - put(dialyzer_options_report_mode, normal), - put(dialyzer_warnings, []), - ok. - -append_defines([Def, Val]) -> - {ok, Tokens, _} = erl_scan:string(Val++"."), - {ok, ErlVal} = erl_parse:parse_term(Tokens), - append_var(dialyzer_options_defines, [{list_to_atom(Def), ErlVal}]); -append_defines([Def]) -> - append_var(dialyzer_options_defines, [{list_to_atom(Def), true}]). - -append_include(Dir) -> - append_var(dialyzer_include, [Dir]). - -append_var(Var, List) when is_list(List) -> - case get(Var) of - undefined -> - put(Var, List); - L -> - put(Var, L ++ List) - end, - ok. - -%%----------------------------------------------------------------------- - --spec collect_args([string()]) -> {[string()], [string()]}. - -collect_args(List) -> - collect_args_1(List, []). - -collect_args_1(["-"++_|_] = L, Acc) -> - {lists:reverse(Acc), L}; -collect_args_1([Arg|T], Acc) -> - collect_args_1(T, [Arg|Acc]); -collect_args_1([], Acc) -> - {lists:reverse(Acc), []}. + Args = init:get_plain_arguments(), + try argparse:parse(Args, cli(), #{progname => dialyzer}) of + {ok, ArgMap, _, _} -> + {Command, Opts} = postprocess_side_effects(ArgMap), + case dialyzer_options:build(maps:to_list(Opts)) of + {error, Msg2} -> + {error, Msg2}; + OptsRecord -> + {Command, OptsRecord} + end; + {error, Error} -> + {error, argparse:format_error(Error)} + catch + throw:{dialyzer_cl_parse_error, Msg} -> + {error, Msg}; + _:R:S -> + Msg = io_lib:format("~tp\n~tp\n", [R, S]), + {error, lists:flatten(Msg)} + end. %%----------------------------------------------------------------------- -cl_options() -> - OptsList = [{files, dialyzer_options_files}, - {files_rec, dialyzer_options_files_rec}, - {warning_files_rec, dialyzer_options_warning_files_rec}, - {output_file, dialyzer_output}, - {metrics_file, dialyzer_metrics}, - {module_lookup_file, dialyzer_module_lookup}, - {output_format, dialyzer_output_format}, - {filename_opt, dialyzer_filename_opt}, - {indent_opt, dialyzer_indent_opt}, - {analysis_type, dialyzer_options_analysis_type}, - {get_warnings, dialyzer_options_get_warnings}, - {timing, dialyzer_timing}, - {callgraph_file, dialyzer_callgraph_file}, - {mod_deps_file, dialyzer_mod_deps_file}], - get_options(OptsList) ++ common_options(). - -common_options() -> - OptsList = [{defines, dialyzer_options_defines}, - {from, dialyzer_options_from}, - {include_dirs, dialyzer_include}, - {plts, dialyzer_init_plts}, - {output_plt, dialyzer_output_plt}, - {report_mode, dialyzer_options_report_mode}, - {use_spec, dialyzer_options_use_contracts}, - {warnings, dialyzer_warnings}, - {check_plt, dialyzer_options_check_plt}, - {solvers, dialyzer_solvers}], - get_options(OptsList). - -get_options(TagOptionList) -> - lists:append([get_opt(Tag, Opt) || {Tag, Opt} <- TagOptionList]). - -get_opt(Tag, Opt) -> - case get(Opt) of - undefined -> - []; - V -> - [{Tag, V}] - end. - -%%----------------------------------------------------------------------- - --spec get_lib_dir([string()]) -> [string()]. - -get_lib_dir(Apps) -> - get_lib_dir(Apps, []). - -get_lib_dir([H|T], Acc) -> - NewElem = - case code:lib_dir(list_to_atom(H)) of - {error, bad_name} -> H; - LibDir when H =:= "erts" -> % hack for including erts in an un-installed system - EbinDir = filename:join([LibDir,"ebin"]), - case file:read_file_info(EbinDir) of - {error,enoent} -> - filename:join([LibDir,"preloaded","ebin"]); - _ -> - EbinDir - end; - LibDir -> filename:join(LibDir,"ebin") - end, - get_lib_dir(T, [NewElem|Acc]); -get_lib_dir([], Acc) -> - lists:reverse(Acc). - -%%----------------------------------------------------------------------- - -get_plts(["--"|T], Acc) -> {lists:reverse(Acc), T}; -get_plts(["-"++_Opt = H|T], Acc) -> {lists:reverse(Acc), [H|T]}; -get_plts([H|T], Acc) -> get_plts(T, [H|Acc]); -get_plts([], Acc) -> {lists:reverse(Acc), []}. - -%%----------------------------------------------------------------------- - --spec help_warnings() -> no_return(). - -help_warnings() -> - S = warning_options_msg(), - io:put_chars(S), - erlang:halt(?RET_NOTHING_SUSPICIOUS). - --spec help_message() -> no_return(). - -help_message() -> - S = "Usage: dialyzer [--add_to_plt] [--apps applications] [--build_plt] - [--check_plt] [-Ddefine]* [-Dname]* [--dump_callgraph file] - [--error_location flag] [files_or_dirs] [--fullpath] - [--get_warnings] [--gui] [--help] [-I include_dir]* - [--incremental] [--metrics_file] [--no_check_plt] [--no_indentation] [--no_spec] - [-o outfile] [--output_plt file] [-pa dir]* [--plt plt] [--plt_info] - [--plts plt*] [--quiet] [-r dirs] [--raw] [--remove_from_plt] - [--shell] [--src] [--statistics] [--verbose] [--version] - [--warning_apps] [-Wwarn]* - -Options: - files_or_dirs (for backwards compatibility also as: -c files_or_dirs) - Use Dialyzer from the command line to detect defects in the - specified files or directories containing .erl or .beam files, - depending on the type of the analysis. - -r dirs - Same as the previous but the specified directories are searched - recursively for subdirectories containing .erl or .beam files in - them, depending on the type of analysis. - --input_list_file file - Specify the name of a file that contains the names of the files - to be analyzed (one file name per line). - --apps applications - Option typically used when building or modifying a plt as in: - dialyzer --build_plt --apps erts kernel stdlib mnesia ... - to conveniently refer to library applications corresponding to the - Erlang/OTP installation. However, the option is general and can also - be used during analysis in order to refer to Erlang/OTP applications. - In addition, file or directory names can also be included, as in: - dialyzer --apps inets ssl ./ebin ../other_lib/ebin/my_module.beam - --warning_apps applications - By default, warnings will be reported to all applications given by - --apps. However, if --warning_apps is used, only those applications - given to --warning_apps will have warnings reported. All applications - given by --apps, but not --warning_apps, will be analysed to provide - context to the analysis, but warnings will not be reported for them. - For example, you may want to include libraries you depend on in the - analysis with --apps so discrepancies in their usage can be found, - but only include your own code with --warning_apps so that - discrepancies are only reported in code that you own. - -o outfile (or --output outfile) - When using Dialyzer from the command line, send the analysis - results to the specified outfile rather than to stdout. - --raw - When using Dialyzer from the command line, output the raw analysis - results (Erlang terms) instead of the formatted result. - The raw format is easier to post-process (for instance, to filter - warnings or to output HTML pages). - --src - Override the default, which is to analyze BEAM files, and - analyze starting from Erlang source code instead. - -Dname (or -Dname=value) - When analyzing from source, pass the define to Dialyzer. (**) - -I include_dir - When analyzing from source, pass the include_dir to Dialyzer. (**) - -pa dir - Include dir in the path for Erlang (useful when analyzing files - that have '-include_lib()' directives). - --output_plt file - Store the plt at the specified file after building it. - --plt plt - Use the specified plt as the initial plt (if the plt was built - during setup the files will be checked for consistency). - --plts plt* - Merge the specified plts to create the initial plt -- requires - that the plts are disjoint (i.e., do not have any module - appearing in more than one plt). - The plts are created in the usual way: - dialyzer --build_plt --output_plt plt_1 files_to_include - ... - dialyzer --build_plt --output_plt plt_n files_to_include - and then can be used in either of the following ways: - dialyzer files_to_analyze --plts plt_1 ... plt_n - or: - dialyzer --plts plt_1 ... plt_n -- files_to_analyze - (Note the -- delimiter in the second case) - -Wwarn - A family of options which selectively turn on/off warnings - (for help on the names of warnings use dialyzer -Whelp). - --shell - Do not disable the Erlang shell while running the GUI. - --version (or -v) - Print the Dialyzer version and some more information and exit. - --help (or -h) - Print this message and exit. - --quiet (or -q) - Make Dialyzer a bit more quiet. - --verbose - Make Dialyzer a bit more verbose. - --statistics - Prints information about the progress of execution (analysis phases, - time spent in each and size of the relative input). - --build_plt - The analysis starts from an empty plt and creates a new one from the - files specified with -c and -r. Only works for beam files. - Use --plt(s) or --output_plt to override the default plt location. - --add_to_plt - The plt is extended to also include the files specified with -c and -r. - Use --plt(s) to specify which plt to start from, and --output_plt to - specify where to put the plt. Note that the analysis might include - files from the plt if they depend on the new files. - This option only works with beam files. - --remove_from_plt - The information from the files specified with -c and -r is removed - from the plt. Note that this may cause a re-analysis of the remaining - dependent files. - --check_plt - Check the plt for consistency and rebuild it if it is not up-to-date. - Actually, this option is of rare use as it is on by default. - --no_check_plt (or -n) - Skip the plt check when running Dialyzer. Useful when working with - installed plts that never change. - --incremental - The analysis starts from an existing incremental PLT, or builds one from - scratch if one doesn't exist, and runs the minimal amount of additional - analysis to report all issues in the given set of apps. Notably, incremental - PLT files are not compatible with \"classic\" PLT files, and vice versa. - The initial incremental PLT will be updated unless an alternative output - incremental PLT is given. - --plt_info - Make Dialyzer print information about the plt and then quit. The plt - can be specified with --plt(s). - --get_warnings - Make Dialyzer emit warnings even when manipulating the plt. Warnings - are only emitted for files that are actually analyzed. - --dump_callgraph file - Dump the call graph into the specified file whose format is determined - by the file name extension. Supported extensions are: raw, dot, and ps. - If something else is used as file name extension, default format '.raw' - will be used. - --dump_full_dependencies_graph file - Dump the full dependency graph (i.e. dependencies induced by function - calls, usages of types in specs, behaviour implementations, etc.) into - the specified file whose format is determined by the file name - extension. Supported extensions are: dot and ps. - --metrics_file file - Write metrics about Dialyzer's incrementality (for example, total number of - modules considered, how many modules were changed since the PLT was - last updated, how many modules needed to be analyzed) to a file. This - can be useful for tracking and debugging Dialyzer's incrementality. - --error_location column | line - Use a pair {Line, Column} or an integer Line to pinpoint the location - of warnings. The default is to use a pair {Line, Column}. When - formatted, the line and the column are separated by a colon. - --fullpath - Display the full path names of files for which warnings are emitted. - --no_indentation - Do not indent contracts and success typings. Note that this option has - no effect when combined with the --raw option. - --no_spec - Ignore functions specs. This is useful for debugging when one suspects - that some specs are incorrect. - --gui - Use the GUI. - +parse_app(AppOrDir) -> + case code:lib_dir(list_to_atom(AppOrDir)) of + {error, bad_name} -> AppOrDir; + LibDir when AppOrDir =:= "erts" -> % hack for including erts in an un-installed system + EbinDir = filename:join([LibDir, "ebin"]), + case file:read_file_info(EbinDir) of + {error, enoent} -> + filename:join([LibDir, "preloaded", "ebin"]); + _ -> + EbinDir + end; + LibDir -> filename:join(LibDir, "ebin") + end. + +parse_input_list(File) -> + case file:read_file(File) of + {ok, Bin} -> + Files = binary:split(Bin, <<"\n">>, [trim_all, global]), + [binary_to_list(string:trim(F)) || F <- Files]; + {error, Reason} -> + cl_error(io_lib:format("Reading of ~s failed: ~s", [File, file:format_error(Reason)])) + end. + +parse_define(Arg) -> + case re:split(Arg, "=", [{return, list}, unicode]) of + [Def, Val] -> + {ok, Tokens, _} = erl_scan:string(Val++"."), + {ok, ErlVal} = erl_parse:parse_term(Tokens), + {list_to_atom(Def), ErlVal}; + [Def] -> + {list_to_atom(Def), true} + end. + +cli() -> + #{ + arguments => [ + #{name => files, action => extend, nargs => list, required => false, + help => <<"Use Dialyzer from the command line to detect defects in the " + "specified files or directories containing .erl or .beam files, " + "depending on the type of the analysis.">>}, + #{name => files, short => $c, long => "-com", action => extend, nargs => list, + help => <<"Same as files, specifies files to run the analysis on (left for compatibility)">>}, + #{name => files_rec, short => $r, action => extend, nargs => list, + help => <<"Search the specified directories " + "recursively for subdirectories containing .erl or .beam files in " + "them, depending on the type of analysis.">>}, + #{name => files, long => "-input_list_file", type => {custom, fun parse_input_list/1}, + action => extend, + help => <<"Specify the name of a file that contains the names of the files " + "to be analyzed (one file name per line).">>}, + #{name => files_rec, long => "-apps", type => {custom, fun parse_app/1}, + nargs => list, action => extend, + help => <<"Option typically used when building or modifying a plt as in: \n" + "dialyzer --build_plt --apps erts kernel stdlib mnesia ... \n" + "to conveniently refer to library applications corresponding to the " + "Erlang/OTP installation. However, the option is general and can also " + "be used during analysis in order to refer to Erlang/OTP applications. " + "In addition, file or directory names can also be included, as in: \n" + "dialyzer --apps inets ssl ./ebin ../other_lib/ebin/my_module.beam">>}, + + #{name => output_file, short => $o, long => "--output", + help => <<"When using Dialyzer from the command line, send the analysis " + "results to the specified outfile rather than to stdout.">>}, + #{name => output_format, long => "-raw", type => boolean, action => {store, raw}, + help => <<"When using Dialyzer from the command line, output the raw analysis " + "results (Erlang terms) instead of the formatted result. " + "The raw format is easier to post-process (for instance, to filter " + "warnings or to output HTML pages).">>}, + #{name => from, long => "-src", type => boolean, action => {store, src_code}, + help => <<"Override the default, which is to analyze BEAM files, and " + "analyze starting from Erlang source code instead.">>}, + #{name => defines, short=>$D, type => {custom, fun parse_define/1}, action => append, + help => <<"When analyzing from source, pass the define to Dialyzer. (**)">>}, + #{name => include_dirs, short=>$I, action => append, + help => <<"When analyzing from source, pass the include_dir to Dialyzer. (**)">>}, + #{name => pa, long => "pa", action => append, + help => <<"Include dir in the path for Erlang (useful when analyzing files " + "that have '-include_lib()' directives).">>}, + #{name => output_plt, long => "-output_plt", + help => <<"Store the plt at the specified file after building it.">>}, + #{name => plts, long => "-plt", nargs => 1, + help => <<"Use the specified plt as the initial plt (if the plt was built " + "during setup the files will be checked for consistency).">>}, + #{name => plts, long => "-plts", nargs => nonempty_list, + help => <<"Merge the specified plts to create the initial plt -- requires " + "that the plts are disjoint (i.e., do not have any module " + "appearing in more than one plt). " + "The plts are created in the usual way: \n" + " dialyzer --build_plt --output_plt plt_1 files_to_include " + " ... \n" + " dialyzer --build_plt --output_plt plt_n files_to_include " + "and then can be used in either of the following ways: \n" + " dialyzer files_to_analyze --plts plt_1 ... plt_n \n" + "or: \n" + " dialyzer --plts plt_1 ... plt_n -- files_to_analyze \n" + "(Note the -- delimiter in the second case)">>}, + #{name => warnings, short => $W, action => append, type => {atom, [error_handling, + no_behaviours, no_contracts, no_fail_call, no_fun_app, no_improper_lists, + no_match, no_missing_calls, no_opaque, no_return, no_undefined_callbacks, + no_underspecs, no_unknown, no_unused, underspecs, unknown, unmatched_returns, + overspecs, specdiffs, extra_return, no_extra_return, missing_return, no_missing_return]}, + help => {<<"[-Wwarn]*">>, [<<"A family of options which selectively turn on/off warnings">>]}}, + #{name => shell, long => "-shell", type => boolean, + help => <<"Do not disable the Erlang shell while running the GUI.">>}, + #{name => version, short => $v, long => "-version", type => boolean, + help => <<"Print the Dialyzer version and some more information and exit.">>}, + #{name => help, short => $h, long => "-help", type => boolean, + help => <<"Print this message and exit.">>}, + #{name => report_mode, short => $q, long => "-quiet", type => boolean, action => {store, quiet}, + default => normal, help => <<"Make Dialyzer a bit more quiet.">>}, + #{name => report_mode, long => "-verbose", type => boolean, action => {store, verbose}, + help => <<"Make Dialyzer a bit more verbose.">>}, + #{name => timing, long => "-statistics", type => boolean, + help => <<"Prints information about the progress of execution (analysis phases, " + "time spent in each and size of the relative input).">>}, + #{name => analysis_type, long => "-build_plt", type => boolean, action => {store, plt_build}, + help => <<"The analysis starts from an empty plt and creates a new one from the " + "files specified with -c and -r. Only works for beam files. " + "Use --plt(s) or --output_plt to override the default plt location.">>}, + #{name => analysis_type, long=> "-add_to_plt", type => boolean, action => {store, plt_add}, + help => <<"The plt is extended to also include the files specified with -c and -r. " + "Use --plt(s) to specify which plt to start from, and --output_plt to " + "specify where to put the plt. Note that the analysis might include " + "files from the plt if they depend on the new files. " + "This option only works with beam files.">>}, + #{name => analysis_type, long => "-remove_from_plt", type => boolean, action => {store, plt_remove}, + help => <<"The information from the files specified with -c and -r is removed " + "from the plt. Note that this may cause a re-analysis of the remaining " + "dependent files.">>}, + #{name => analysis_type, long => "-check_plt", type => boolean, action => {store, plt_check}, + help => <<"Check the plt for consistency and rebuild it if it is not up-to-date. " + "Actually, this option is of rare use as it is on by default.">>}, + #{name => check_plt, long => "-no_check_plt", short => $n, type => boolean, action => {store, false}, + help => <<"Skip the plt check when running Dialyzer. Useful when working with " + "installed plts that never change.">>}, + #{name => analysis_type, long => "-incremental", type => boolean, action => {store, incremental}, + help => <<"The analysis starts from an existing incremental PLT, or builds one from " + "scratch if one doesn't exist, and runs the minimal amount of additional " + "analysis to report all issues in the given set of apps. Notably, incremental " + "PLT files are not compatible with \"classic\" PLT files, and vice versa. " + "The initial incremental PLT will be updated unless an alternative output " + "incremental PLT is given.">>}, + #{name => analysis_type, long => "-plt_info", type => boolean, action => {store, plt_info}, + help => <<"Make Dialyzer print information about the plt and then quit. The plt " + "can be specified with --plt(s).">>}, + #{name => get_warnings, long => "-get_warnings", type => boolean, + help => <<"Make Dialyzer emit warnings even when manipulating the plt. Warnings " + "are only emitted for files that are actually analyzed.">>}, + #{name => callgraph_file, long => "-dump_callgraph", + help => <<"Dump the call graph into the specified file whose format is determined " + "by the file name extension. Supported extensions are: raw, dot, and ps. " + "If something else is used as file name extension, default format '.raw' " + "will be used.">>}, + #{name => mod_deps_file, long => "-dump_full_dependencies_graph", + help => <<"Dump the full dependency graph (i.e. dependencies induced by function " + "calls, usages of types in specs, behaviour implementations, etc.) into " + "the specified file whose format is determined by the file name " + "extension. Supported extensions are: dot and ps.">>}, + #{name => error_location, long => "-error_location", type => {atom, [column, line]}, + help => <<"Use a pair {Line, Column} or an integer Line to pinpoint the location " + "of warnings. The default is to use a pair {Line, Column}. When " + "formatted, the line and the column are separated by a colon.">>}, + #{name => filename_opt, long => "-fullpath", type => boolean, action => {store, fullpath}, + help => <<"Display the full path names of files for which warnings are emitted.">>}, + #{name => indent_opt, long => "-no_indentation", type => boolean, action => {store, false}, + help => <<"Do not indent contracts and success typings. Note that this option has " + "no effect when combined with the --raw option.">>}, + #{name => gui, long => "-gui", type => boolean, + help => <<"Use the GUI.">>}, + #{name => metrics_file, long => "-metrics_file", + help => <<"Write metrics about Dialyzer's incrementality (for example, total number of " + "modules considered, how many modules were changed since the PLT was " + "last updated, how many modules needed to be analyzed) to a file. This " + "can be useful for tracking and debugging Dialyzer's incrementality.">>}, + #{name => warning_files_rec, long => "-warning_apps", type => {custom, fun parse_app/1}, + nargs => list, action => extend, + help => <<"By default, warnings will be reported to all applications given by " + "--apps. However, if --warning_apps is used, only those applications " + "given to --warning_apps will have warnings reported. All applications " + "given by --apps, but not --warning_apps, will be analysed to provide " + "context to the analysis, but warnings will not be reported for them. " + "For example, you may want to include libraries you depend on in the " + "analysis with --apps so discrepancies in their usage can be found, " + "but only include your own code with --warning_apps so that " + "discrepancies are only reported in code that you own.">>}, + + %% Intentionally undocumented options + #{name => solvers, long => "-solver", type => {atom, [v1, v2]}, action => append, + help => hidden}, + #{name => timing, long => "-resources", type => boolean, action => {store, debug}, + help => hidden}, + + %% next definition is necessary to ignore '--' left for compatibility reasons + #{name => shell, short => $-, type => boolean, help => hidden} + ], + + help => [<<"Usage: ">>, usage, <<"\n\nOptions:\n">>, + arguments, options, " Note: * denotes that multiple occurrences of these options are possible. ** options -D and -I work both from command-line and in the Dialyzer GUI; @@ -582,9 +257,63 @@ The exit status of the command line version is: warnings were emitted. 1 - Problems were encountered during the analysis. 2 - No problems were encountered, but warnings were emitted. -", - io:put_chars(S), - erlang:halt(?RET_NOTHING_SUSPICIOUS). + +"] + }. + +postprocess_side_effects(ArgMap) when is_map_key(version, ArgMap) -> + %% Version handling + io:format("Dialyzer version " ++ ?VSN ++ "\n"), + erlang:halt(?RET_NOTHING_SUSPICIOUS); + +postprocess_side_effects(ArgMap) when is_map_key(help, ArgMap) -> + %% Help message + io:format(argparse:help(cli(), #{progname => dialyzer})), + erlang:halt(?RET_NOTHING_SUSPICIOUS); + +postprocess_side_effects(ArgMap) when is_map_key(pa, ArgMap) -> + %% Code path side effect + [code:add_patha(Path) =/= true andalso cl_error("Bad directory for -pa: " ++ Path) || + Path <- map_get(pa, ArgMap)], + postprocess_side_effects(maps:remove(pa, ArgMap)); + +postprocess_side_effects(ArgMap) when is_map_key(shell, ArgMap) -> + %% --shell option is processed by C executable (left here only for help/usage) + postprocess_side_effects(maps:remove(shell, ArgMap)); + +postprocess_side_effects(ArgMap) -> + %% if all files specified are ".erl" files, set the 'src' flag automatically + %% it is compatibility behaviour, potentially incorrect, because it does not take + %% directories (rec_files) into account + ArgMap1 = + case (is_map_key(files, ArgMap) andalso + lists:all(fun(F) -> filename:extension(F) =:= ".erl" end, maps:get(files, ArgMap))) of + true -> + ArgMap#{from => src_code}; + false -> + ArgMap + end, + + %% Run mode (command) is defined by the flag combination + case maps:get(analysis_type, ArgMap1, undefined) of + plt_info -> + %% plt_info is plt_check analysis type + {plt_info, ArgMap1#{analysis_type => plt_check}}; + plt_check -> + %% plt_check is a hidden "check_init" command + {check_init, ArgMap1}; + _ when map_get(gui, ArgMap1) -> + %% filter out command-line only arguments + Allowed = [defines, from, include_dirs, plts, output_plt, report_mode, + use_spec, warnings, check_plt, solvers], + {gui, maps:with(Allowed, ArgMap1)}; + _ -> + {cl, ArgMap1} + end. + +cl_error(Str) -> + Msg = lists:flatten(Str), + throw({dialyzer_cl_parse_error, Msg}). warning_options_msg() -> "Warning options: diff --git a/lib/dialyzer/src/dialyzer_options.erl b/lib/dialyzer/src/dialyzer_options.erl index 73a1bbb589..27da2f9c83 100644 --- a/lib/dialyzer/src/dialyzer_options.erl +++ b/lib/dialyzer/src/dialyzer_options.erl @@ -339,10 +339,31 @@ build_options([], Options) -> Options. get_app_dirs(Apps) when is_list(Apps) -> - dialyzer_cl_parse:get_lib_dir([atom_to_list(A) || A <- Apps]); + get_lib_dir([atom_to_list(A) || A <- Apps]); get_app_dirs(Apps) -> bad_option("Use a list of otp applications", Apps). +get_lib_dir(Apps) -> + get_lib_dir(Apps, []). + +get_lib_dir([H|T], Acc) -> + NewElem = + case code:lib_dir(list_to_atom(H)) of + {error, bad_name} -> H; + LibDir when H =:= "erts" -> % hack for including erts in an un-installed system + EbinDir = filename:join([LibDir,"ebin"]), + case file:read_file_info(EbinDir) of + {error,enoent} -> + filename:join([LibDir,"preloaded","ebin"]); + _ -> + EbinDir + end; + LibDir -> filename:join(LibDir,"ebin") + end, + get_lib_dir(T, [NewElem|Acc]); +get_lib_dir([], Acc) -> + lists:reverse(Acc). + assert_filenames(Term, Files) -> assert_filenames_form(Term, Files), assert_filenames_exist(Files). diff --git a/lib/dialyzer/src/typer.erl b/lib/dialyzer/src/typer.erl index 3b88ca6282..6467ae0093 100644 --- a/lib/dialyzer/src/typer.erl +++ b/lib/dialyzer/src/typer.erl @@ -93,21 +93,33 @@ cl(["-I",Dir|Opts]) -> {{inc, Dir}, Opts}; cl(["-I"|_Opts]) -> fatal_error("no include directory specified after -I"); cl(["-I"++Dir|Opts]) -> {{inc, Dir}, Opts}; cl(["-T"|Opts]) -> - {Files, RestOpts} = dialyzer_cl_parse:collect_args(Opts), + {Files, RestOpts} = collect_args(Opts), case Files of [] -> fatal_error("no file or directory specified after -T"); [_|_] -> {{trusted, Files}, RestOpts} end; cl(["-r"|Opts]) -> - {Files, RestOpts} = dialyzer_cl_parse:collect_args(Opts), + {Files, RestOpts} = collect_args(Opts), {{files_r, Files}, RestOpts}; cl(["-pa",Dir|Opts]) -> {{pa,Dir}, Opts}; cl(["-pz",Dir|Opts]) -> {{pz,Dir}, Opts}; cl(["-"++H|_]) -> fatal_error("unknown option -"++H); cl(Opts) -> - {Files, RestOpts} = dialyzer_cl_parse:collect_args(Opts), + {Files, RestOpts} = collect_args(Opts), {{files, Files}, RestOpts}. +-spec collect_args([string()]) -> {[string()], [string()]}. + +collect_args(List) -> + collect_args_1(List, []). + +collect_args_1(["-"++_|_] = L, Acc) -> + {lists:reverse(Acc), L}; +collect_args_1([Arg|T], Acc) -> + collect_args_1(T, [Arg|Acc]); +collect_args_1([], Acc) -> + {lists:reverse(Acc), []}. + process_def_list(L) -> case L of [Name, Value] -> diff --git a/lib/stdlib/src/argparse.erl b/lib/stdlib/src/argparse.erl index 7c7e14963e..a5fdd8d3d9 100644 --- a/lib/stdlib/src/argparse.erl +++ b/lib/stdlib/src/argparse.erl @@ -201,7 +201,7 @@ validate(Command, Options) -> is_list(Prog) orelse erlang:error(badarg, [Command, Options], [{error_info, #{cause => #{2 => <<"progname is not valid">>}}}]), Prefixes = maps:from_list([{P, true} || P <- maps:get(prefixes, Options, [$-])]), - validate_command([{Prog, Command}], Prefixes), + _ = validate_command([{Prog, Command}], Prefixes), Prog. %% @equiv parse(Args, Command, #{}) -- 2.35.3
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor