Skip to content

Commit

Permalink
Move compilation options to the code server, closes #205.
Browse files Browse the repository at this point in the history
  • Loading branch information
José Valim committed Apr 10, 2012
1 parent 834d0c8 commit 393098f
Show file tree
Hide file tree
Showing 9 changed files with 75 additions and 68 deletions.
7 changes: 0 additions & 7 deletions include/elixir.hrl
Expand Up @@ -8,13 +8,6 @@


-define(ELIXIR_ATOM_CONCAT(Atoms), list_to_atom(lists:concat(Atoms))). -define(ELIXIR_ATOM_CONCAT(Atoms), list_to_atom(lists:concat(Atoms))).


-record(elixir_compile, {
docs=false, %% when true, attach docs to the defined module
internal=false, %% when true, skip features in order to compile internal modules
debug_info=false, %% when true, attach debug info to the defined module
ignore_module_conflict=false %% when true, module conflicts are ignored
}).

-record(elixir_scope, { -record(elixir_scope, {
assign=false, %% when true, new variables can be defined in that subtree assign=false, %% when true, new variables can be defined in that subtree
guard=false, %% when true, we are inside a guard guard=false, %% when true, we are inside a guard
Expand Down
35 changes: 23 additions & 12 deletions lib/code.ex
Expand Up @@ -93,23 +93,36 @@ defmodule Code do
end end


@doc """ @doc """
Compiles `file` and returns a list of tuples where Loads the compilation options from the code server.
the first element is the module name and the second Check compiler_options/1 for more information.
one is its binary. """
def compiler_options do
server_call :compiler_options
end


## Options @doc """
Sets compilation options. Those options are global
since they are stored by Elixir's Code Server.
Available options are: Available options are:
* docs - when true, retain documentation in the compiled module; * docs - when true, retain documentation in the compiled module;
* debug_info - when true, retain debug information in the compiled module. * debug_info - when true, retain debug information in the compiled module.
Notice debug information can be used to reconstruct the source code; Notice debug information can be used to reconstruct the source code;
* ignore_module_conflict - when true, override modules that were already defined;
""" """
def compile_file(file, opts // []) do def compiler_options(opts) do
Erlang.elixir_compiler.with_opts opts, fn -> server_call { :compiler_options, opts }
Erlang.elixir_compiler.file to_char_list(file) end
end
@doc """
Compiles `file` and returns a list of tuples where
the first element is the module name and the second
one is its binary.
"""
def compile_file(file) do
Erlang.elixir_compiler.file to_char_list(file)
end end


@doc """ @doc """
Expand All @@ -118,10 +131,8 @@ defmodule Code do
See compile_file/2 for available options. See compile_file/2 for available options.
""" """
def compile_file_to_dir(file, destination, opts // []) do def compile_file_to_dir(file, destination) do
Erlang.elixir_compiler.with_opts opts, fn -> Erlang.elixir_compiler.file_to_path to_char_list(file), to_char_list(destination)
Erlang.elixir_compiler.file_to_path to_char_list(file), to_char_list(destination)
end
end end


## Helpers ## Helpers
Expand Down
10 changes: 5 additions & 5 deletions lib/elixir/cli.ex
@@ -1,5 +1,5 @@
defrecord Elixir.CLI.Config, commands: [], close: [], defrecord Elixir.CLI.Config, commands: [], close: [],
output: '.', compile: false, halt: true, compile_options: [] output: '.', compile: false, halt: true, compiler_options: []


defmodule Elixir.CLI do defmodule Elixir.CLI do
import Exception, only: [format_stacktrace: 1] import Exception, only: [format_stacktrace: 1]
Expand Down Expand Up @@ -147,17 +147,17 @@ defmodule Elixir.CLI do
end end


defp process_compiler(['--docs'|t], config) do defp process_compiler(['--docs'|t], config) do
process_compiler t, config.merge_compile_options(docs: true) process_compiler t, config.merge_compiler_options(docs: true)
end end


defp process_compiler(['--debug-info'|t], config) do defp process_compiler(['--debug-info'|t], config) do
process_compiler t, config.merge_compile_options(debug_info: true) process_compiler t, config.merge_compiler_options(debug_info: true)
end end


# This option is used internally so we can compile # This option is used internally so we can compile
# Elixir with Elixir without raising module conflicts # Elixir with Elixir without raising module conflicts
defp process_compiler(['--ignore-module-conflict'|t], config) do defp process_compiler(['--ignore-module-conflict'|t], config) do
process_compiler t, config.merge_compile_options(ignore_module_conflict: true) process_compiler t, config.merge_compiler_options(ignore_module_conflict: true)
end end


defp process_compiler([h|t] = list, config) do defp process_compiler([h|t] = list, config) do
Expand Down Expand Up @@ -191,7 +191,7 @@ defmodule Elixir.CLI do
lines = Enum.map lines, File.wildcard(&1) lines = Enum.map lines, File.wildcard(&1)
concat = List.uniq(List.concat(lines)) concat = List.uniq(List.concat(lines))


Erlang.elixir_compiler.set_opts(config.compile_options) Code.compiler_options(config.compiler_options)


Enum.map concat, fn(file) -> Enum.map concat, fn(file) ->
IO.puts "Compiling #{list_to_binary(file)}" IO.puts "Compiling #{list_to_binary(file)}"
Expand Down
12 changes: 11 additions & 1 deletion lib/elixir/server.ex
@@ -1,6 +1,8 @@
defrecord Elixir.Server.Config, argv: [], loaded: [], at_exit: [] defrecord Elixir.Server.Config, argv: [], loaded: [], at_exit: [], compiler_options: []


defmodule Elixir.Server do defmodule Elixir.Server do
@moduledoc false

use GenServer.Behavior use GenServer.Behavior


def start_link do def start_link do
Expand All @@ -23,6 +25,10 @@ defmodule Elixir.Server do
{ :reply, :ok, config.argv(argv) } { :reply, :ok, config.argv(argv) }
end end


def handle_call({:compiler_options, record}, _from, config) do
{ :reply, :ok, config.compiler_options(record) }
end

def handle_call(:loaded, _from, config) do def handle_call(:loaded, _from, config) do
{ :reply, config.loaded, config } { :reply, config.loaded, config }
end end
Expand All @@ -35,6 +41,10 @@ defmodule Elixir.Server do
{ :reply, config.argv, config } { :reply, config.argv, config }
end end


def handle_call(:compiler_options, _from, config) do
{ :reply, config.compiler_options, config }
end

def handle_call(request, from, config) do def handle_call(request, from, config) do
super(request, from, config) super(request, from, config)
end end
Expand Down
40 changes: 13 additions & 27 deletions src/elixir_compiler.erl
@@ -1,32 +1,25 @@
-module(elixir_compiler). -module(elixir_compiler).
-export([with_opts/2, set_opts/1, get_opts/0, file/1, file_to_path/2]). -export([get_opts/0, get_opt/1, get_opt/2, file/1, file_to_path/2]).
-export([core/0, module/3, eval_forms/4]). -export([core/0, module/3, eval_forms/4]).
-include("elixir.hrl"). -include("elixir.hrl").


%% Public API %% Public API


%% Set and get compilation options. %% Get compilation options.


with_opts(Opts, Function) -> get_opt(Key) -> get_opt(Key, get_opts()).
Previous = get(elixir_compiler_opts),
try
set_opts(Opts),
Function()
after
put(elixir_compiler_opts, Previous)
end.


set_opts(Opts) -> get_opt(Key, Dict) ->
put(elixir_compiler_opts, #elixir_compile { case orddict:find(Key, Dict) of
docs=get_value(Opts, docs, false), { ok, Value } -> Value;
debug_info=get_value(Opts, debug_info, false), error -> false
ignore_module_conflict=get_value(Opts, ignore_module_conflict, false) end.
}).


get_opts() -> get_opts() ->
case get(elixir_compiler_opts) of try
undefined -> #elixir_compile{}; gen_server:call(elixir_code_server, compiler_options)
Else -> Else catch
exit:{ noproc, _ } -> [{internal,true}]
end. end.


%% Compile a file, return a tuple of module names and binaries. %% Compile a file, return a tuple of module names and binaries.
Expand Down Expand Up @@ -80,7 +73,7 @@ eval_forms(Forms, Line, RawModule, Value, S) ->
%% executes the callback in case of success. This automatically %% executes the callback in case of success. This automatically
%% handles errors and warnings. Used by this module and elixir_module. %% handles errors and warnings. Used by this module and elixir_module.
module(Forms, S, Callback) -> module(Forms, S, Callback) ->
Options = case (get_opts())#elixir_compile.debug_info of Options = case get_opt(debug_info) of
true -> [debug_info]; true -> [debug_info];
_ -> [] _ -> []
end, end,
Expand All @@ -101,20 +94,13 @@ module(Forms, Filename, Options, Callback) ->
%% Invoked from the Makefile. %% Invoked from the Makefile.


core() -> core() ->
put(elixir_compiler_opts, #elixir_compile{internal=true}),
[core_file(File) || File <- core_main()], [core_file(File) || File <- core_main()],
AllLists = [filelib:wildcard(Wildcard) || Wildcard <- core_list()], AllLists = [filelib:wildcard(Wildcard) || Wildcard <- core_list()],
Files = lists:append(AllLists) -- core_main(), Files = lists:append(AllLists) -- core_main(),
[core_file(File) || File <- '__MAIN__.List':uniq(Files)]. [core_file(File) || File <- '__MAIN__.List':uniq(Files)].


%% HELPERS %% HELPERS


get_value(Keyword, Key, Default) ->
case orddict:find(Key, Keyword) of
{ ok, Value } -> Value;
error -> Default
end.

no_auto_import() -> no_auto_import() ->
{ attribute, 0, compile, { { attribute, 0, compile, {
no_auto_import, [ no_auto_import, [
Expand Down
2 changes: 1 addition & 1 deletion src/elixir_def.erl
Expand Up @@ -139,7 +139,7 @@ compile_super(Module, #elixir_scope{function=Function, super=true}) ->
compile_super(_Module, _S) -> []. compile_super(_Module, _S) -> [].


compile_docs(Kind, Line, Module, Name, Arity, S) -> compile_docs(Kind, Line, Module, Name, Arity, S) ->
case (elixir_compiler:get_opts())#elixir_compile.internal of case elixir_compiler:get_opt(internal) of
true -> []; true -> [];
_ -> _ ->
case '__MAIN__.Module':compile_doc(Module, Line, Kind, { Name, Arity }) of case '__MAIN__.Module':compile_doc(Module, Line, Kind, { Name, Arity }) of
Expand Down
2 changes: 1 addition & 1 deletion src/elixir_macros.erl
Expand Up @@ -28,7 +28,7 @@ translate_macro({ Op, Line, Exprs }, S) when is_list(Exprs),
translate_macro({'@', Line, [{ Name, _, Args }]}, S) -> translate_macro({'@', Line, [{ Name, _, Args }]}, S) ->
assert_module_scope(Line, '@', S), assert_module_scope(Line, '@', S),
assert_no_function_scope(Line, '@', S), assert_no_function_scope(Line, '@', S),
case is_reserved_data(Name) andalso (elixir_compiler:get_opts())#elixir_compile.internal of case is_reserved_data(Name) andalso elixir_compiler:get_opt(internal) of
true -> true ->
{ { nil, Line }, S }; { { nil, Line }, S };
_ -> _ ->
Expand Down
27 changes: 15 additions & 12 deletions src/elixir_module.erl
Expand Up @@ -143,14 +143,16 @@ load_form(Forms, S) ->
end end
end). end).


check_module_availability(Line, Filename, Module, #elixir_compile{ignore_module_conflict=false}) -> check_module_availability(Line, Filename, Module, Compiler) ->
case code:ensure_loaded(Module) of case elixir_compiler:get_opt(ignore_module_conflict, Compiler) of
{ module, _ } -> elixir_errors:form_error(Line, Filename, ?MODULE, { module_defined, Module }); false ->
{ error, _ } -> [] case code:ensure_loaded(Module) of
end; { module, _ } -> elixir_errors:form_error(Line, Filename, ?MODULE, { module_defined, Module });

{ error, _ } -> []
check_module_availability(_Line, _Filename, _Module, _C) -> end;
[]. true ->
[]
end.


% EXTRA FUNCTIONS % EXTRA FUNCTIONS


Expand All @@ -159,11 +161,12 @@ add_info_function(Line, Filename, Module, Export, Functions, Macros, C) ->
case lists:member(Pair, Export) of case lists:member(Pair, Export) of
true -> elixir_errors:form_error(Line, Filename, ?MODULE, {internal_function_overridden, Pair}); true -> elixir_errors:form_error(Line, Filename, ?MODULE, {internal_function_overridden, Pair});
false -> false ->
Docs = elixir_compiler:get_opt(docs, C),
Contents = { function, Line, '__info__', 1, [ Contents = { function, Line, '__info__', 1, [
macros_clause(Line, Macros), macros_clause(Line, Macros),
data_clause(Line, Module), data_clause(Line, Module),
docs_clause(Line, Module, C), docs_clause(Line, Module, Docs),
moduledoc_clause(Line, Module, C), moduledoc_clause(Line, Module, Docs),
else_clause(Line) else_clause(Line)
] }, ] },
{ [Pair|Export], [Contents|Functions] } { [Pair|Export], [Contents|Functions] }
Expand All @@ -173,14 +176,14 @@ macros_clause(Line, Macros) ->
Sorted = lists:sort(Macros), Sorted = lists:sort(Macros),
{ clause, Line, [{ atom, Line, macros }], [], [elixir_tree_helpers:abstract_syntax(Sorted)] }. { clause, Line, [{ atom, Line, macros }], [], [elixir_tree_helpers:abstract_syntax(Sorted)] }.


docs_clause(Line, Module, #elixir_compile { docs = true }) -> docs_clause(Line, Module, true) ->
Docs = ets:tab2list(docs_table(Module)), Docs = ets:tab2list(docs_table(Module)),
{ clause, Line, [{ atom, Line, docs }], [], [elixir_tree_helpers:abstract_syntax(Docs)] }; { clause, Line, [{ atom, Line, docs }], [], [elixir_tree_helpers:abstract_syntax(Docs)] };


docs_clause(Line, _Module, _) -> docs_clause(Line, _Module, _) ->
{ clause, Line, [{ atom, Line, docs }], [], [{ atom, Line, nil }] }. { clause, Line, [{ atom, Line, docs }], [], [{ atom, Line, nil }] }.


moduledoc_clause(Line, Module, #elixir_compile { docs = true }) -> moduledoc_clause(Line, Module, true) ->
Docs = '__MAIN__.Module':read_data(Module, moduledoc), Docs = '__MAIN__.Module':read_data(Module, moduledoc),
{ clause, Line, [{ atom, Line, moduledoc }], [], [elixir_tree_helpers:abstract_syntax({ Line, Docs })] }; { clause, Line, [{ atom, Line, moduledoc }], [], [elixir_tree_helpers:abstract_syntax({ Line, Docs })] };


Expand Down
8 changes: 6 additions & 2 deletions test/elixir/kernel/doc_test.exs
@@ -1,22 +1,26 @@
Code.require_file "../../test_helper", __FILE__ Code.require_file "../../test_helper", __FILE__


defmodule Kernel.DocTest do defmodule Kernel.DocTest do
use ExUnit.Case # Since this module is changing the code
# server state, we need to run it in sync.
use ExUnit.Case, sync: true


test :compiled_docs do test :compiled_docs do
tmp = File.expand_path("../../tmp", __FILE__) tmp = File.expand_path("../../tmp", __FILE__)
path = File.expand_path("../../fixtures/compiled_with_docs.ex", __FILE__) path = File.expand_path("../../fixtures/compiled_with_docs.ex", __FILE__)


try do try do
:file.make_dir(tmp) :file.make_dir(tmp)
Code.compile_file_to_dir(path, tmp, docs: true) Code.compiler_options(docs: true)
Code.compile_file_to_dir(path, tmp)
Code.prepend_path(tmp) Code.prepend_path(tmp)


assert_equal [], CompiledWithDocs.__info__(:data) assert_equal [], CompiledWithDocs.__info__(:data)
expected = [{{:example,1},5,:def,"Some example"},{{:nodoc,0},8,:def,nil}] expected = [{{:example,1},5,:def,"Some example"},{{:nodoc,0},8,:def,nil}]
assert_equal expected, CompiledWithDocs.__info__(:docs) assert_equal expected, CompiledWithDocs.__info__(:docs)
assert_equal { 1, "moduledoc" }, CompiledWithDocs.__info__(:moduledoc) assert_equal { 1, "moduledoc" }, CompiledWithDocs.__info__(:moduledoc)
after: after:
Code.compiler_options(docs: false)
:os.cmd('rm -rf #{tmp}') :os.cmd('rm -rf #{tmp}')
end end
end end
Expand Down

0 comments on commit 393098f

Please sign in to comment.