blob: 966ed5883b068cbe694a91414f98bd28bd32bdd5 [file] [log] [blame]
%%=============================================================================
%% Copyright 2010-2017 Adam Lindberg, 2010-2011 Erlang Solutions Ltd
%%
%% 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
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%=============================================================================
%% @hidden
%% @author Adam Lindberg <eproxus@gmail.com>
%% @copyright 2010-2017 Adam Lindberg, 2010-2011 Erlang Solutions Ltd
%% @doc Module wrangling helper functions.
-module(meck_code).
%% Interface exports
-export([abstract_code/1]).
-export([add_exports/2]).
-export([beam_file/1]).
-export([compile_and_load_forms/1]).
-export([compile_and_load_forms/2]).
-export([compile_options/1]).
-export([enable_on_load/2]).
-export([rename_module/3]).
%% Types
-type erlang_form() :: term().
-type compile_options() :: [term()].
-type export() :: {atom(), byte()}.
%%=============================================================================
%% Interface exports
%%=============================================================================
-spec abstract_code(binary()) -> erlang_form().
abstract_code(BeamFile) ->
case beam_lib:chunks(BeamFile, [abstract_code]) of
{ok, {_, [{abstract_code, {raw_abstract_v1, Forms}}]}} ->
Forms;
{ok, {_, [{abstract_code, no_abstract_code}]}} ->
throw(no_abstract_code)
end.
-spec add_exports([export()], erlang_form()) -> erlang_form().
add_exports(Exports, AbsCode) ->
{attribute, Line, export, OrigExports} = lists:keyfind(export, 3, AbsCode),
Attr = {attribute, Line, export, OrigExports ++ Exports},
lists:keyreplace(export, 3, AbsCode, Attr).
-spec beam_file(module()) -> binary().
beam_file(Module) ->
% code:which/1 cannot be used for cover_compiled modules
case code:get_object_code(Module) of
{_, Binary, _Filename} -> Binary;
error -> throw({object_code_not_found, Module})
end.
-spec compile_and_load_forms(erlang_form()) -> binary().
compile_and_load_forms(AbsCode) -> compile_and_load_forms(AbsCode, []).
-spec compile_and_load_forms(erlang_form(), compile_options()) -> binary().
compile_and_load_forms(AbsCode, Opts) ->
case compile:forms(AbsCode, [return_errors|Opts]) of
{ok, ModName, Binary} ->
load_binary(ModName, Binary),
Binary;
{ok, ModName, Binary, _Warnings} ->
load_binary(ModName, Binary),
Binary;
Error ->
exit({compile_forms, Error})
end.
-spec compile_options(binary() | module()) -> compile_options().
compile_options(BeamFile) when is_binary(BeamFile) ->
case beam_lib:chunks(BeamFile, [compile_info]) of
{ok, {_, [{compile_info, Info}]}} ->
filter_options(proplists:get_value(options, Info));
_ ->
[]
end;
compile_options(Module) ->
filter_options(proplists:get_value(options, Module:module_info(compile))).
enable_on_load(Forms, false) ->
Map = fun({attribute,L,on_load,{F,A}}) -> {attribute,L,export,[{F,A}]};
(Other) -> Other
end,
lists:map(Map, Forms);
enable_on_load(Forms, _) ->
Forms.
-spec rename_module(erlang_form(), module(), module()) -> erlang_form().
rename_module(Forms, Old, New) ->
lists:map(fun(F) -> rename_module_in_form(F, Old, New) end, Forms).
%%=============================================================================
%% Internal functions
%%=============================================================================
load_binary(Name, Binary) ->
case code:load_binary(Name, "", Binary) of
{module, Name} -> ok;
{error, Reason} -> exit({error_loading_module, Name, Reason})
end.
% parse transforms have already been applied to the abstract code in the
% module, and often are not always available when compiling the forms, so
% filter them out of the options.
%
% Furthermore, since Erlang/OTP 20, a code may be compiled from core but
% still have abstract code, so we make sure to remove the from_core option
% as we always compile it as a form.
%
% The -MMD option (makedep_side_effect) needs to be removed, otherwise
% the compiler will attempt to generate a dependency file.
filter_options (Options) ->
case Options of
undefined -> [];
_ -> lists:filter(
fun({parse_transform,_}) -> false;
(makedep_side_effect) -> false;
(from_core) -> false;
(_) -> true
end, Options)
end.
rename_module_in_form({attribute, Line, AttrName, AttrData}, Old, New) ->
{attribute, Line, AttrName,
rename_module_in_attribute(AttrName, AttrData, Old, New)
};
rename_module_in_form(Form, _Old, _New) ->
Form.
rename_module_in_attribute(module, Old, Old, New) ->
New;
rename_module_in_attribute(spec, {{Old, Fun, Arity}, Spec}, Old, New) ->
{{New, Fun, Arity}, Spec};
rename_module_in_attribute(_AttrName, AttrData, _Old, _New) ->
AttrData.