22% % -*- erlang -*-
33
44% % -----------------------------------------------------------------------------
5- % % Pre-compile generation of partisan OTP module SOURCES .
5+ % % Pre-compile generation of partisan OTP module BEAM files .
66% %
77% % Why a pre-compile (and not a post-compile) hook?
88% % ------------------------------------------------
1212% % had a chance to fire. plum_db then fails because
1313% % `partisan_gen_supervisor' does not yet exist.
1414% %
15- % % This pre-compile script writes `partisan_gen_server.erl',
16- % % `partisan_gen_supervisor.erl', etc. as real source files into
17- % % `src/'. They are then compiled by rebar3 as part of partisan's normal
18- % % compile phase — so by the time downstream deps start compiling,
19- % % the `partisan_gen_*' beams already exist.
15+ % % Why .beam (and not .erl source)?
16+ % % --------------------------------
17+ % % `erl_pp:form/1' silently strips the `erlang:' prefix from auto-
18+ % % imported BIF calls (e.g. `erlang:spawn_opt' becomes `spawn_opt'). In
19+ % % the rewritten `partisan_proc_lib' there are LOCAL functions named
20+ % % `spawn_opt/2,3,4,5', so a bare `spawn_opt(...)' call resolves to the
21+ % % local function — causing infinite recursion. Compiling forms
22+ % % directly to .beam preserves the AST exactly and avoids the issue.
2023% %
2124% % The three support modules (`partisan_otp_rewrite',
2225% % `partisan_otp_patches', `partisan_gen_transform') have no inter-
23- % % include dependencies, so we compile them in-memory here and load them
24- % % before invoking the generator.
26+ % % include dependencies, so we compile them in-memory here and load
27+ % % them before invoking the generator.
2528% % -----------------------------------------------------------------------------
2629
2730main (_Args ) ->
2831 SrcDir = " src" ,
32+ EbinDir = ebin_dir (),
2933
30- % % Sanity check: this script is meant to run from the partisan dep's
31- % % root directory, where `src/' exists.
3234 case filelib :is_dir (SrcDir ) of
3335 true -> ok ;
3436 false ->
@@ -39,10 +41,6 @@ main(_Args) ->
3941 halt (1 )
4042 end ,
4143
42- % % Compile the three support modules in dependency-call order.
43- % % Erlang modules can be compiled independently (calls are dynamic),
44- % % so the only constraint is that each is loaded into the BEAM
45- % % before `generate_sources/1' invokes it.
4644 SupportModules = [
4745 " partisan_otp_rewrite.erl" ,
4846 " partisan_otp_patches.erl" ,
@@ -52,42 +50,105 @@ main(_Args) ->
5250 lists :foreach (fun (File ) -> compile_and_load (File , SrcDir ) end ,
5351 SupportModules ),
5452
55- case partisan_gen_transform :generate_sources (SrcDir ) of
53+ ok = filelib :ensure_dir (filename :join (EbinDir , " dummy" )),
54+
55+ case partisan_gen_transform :generate_all (EbinDir ) of
5656 ok ->
5757 Modules = partisan_gen_transform :modules (),
5858 io :format (
59- " Generated ~p partisan OTP module sources into ~s /~n " ,
60- [length (Modules ), SrcDir ]
59+ " Generated ~p partisan OTP module beams into ~s /~n " ,
60+ [length (Modules ), EbinDir ]
6161 ),
62+ update_app_src (SrcDir , Modules ),
6263 ok ;
6364 {error , Errors } ->
6465 io :format (standard_error ,
65- " Failed to generate partisan OTP module sources :~n~p~n " ,
66+ " Failed to generate partisan OTP module beams :~n~p~n " ,
6667 [Errors ]),
6768 halt (1 )
6869 end .
6970
7071
72+ % % Locate the ebin directory for partisan. Honours rebar3's
73+ % % `REBAR_BUILD_DIR' env var when running as a dep, falling back to a
74+ % % sensible local path when running from partisan's own repo.
75+ ebin_dir () ->
76+ case os :getenv (" REBAR_BUILD_DIR" ) of
77+ false ->
78+ filename :absname (" ebin" );
79+ BuildDir ->
80+ CheckoutsDir = filename :join (
81+ [BuildDir , " checkouts" , " partisan" , " ebin" ]
82+ ),
83+ LibDir = filename :join (
84+ [BuildDir , " lib" , " partisan" , " ebin" ]
85+ ),
86+ case filelib :is_dir (CheckoutsDir ) of
87+ true -> CheckoutsDir ;
88+ false -> LibDir
89+ end
90+ end .
91+
92+
93+ % % Update partisan.app.src so the generated module names appear in the
94+ % % modules list. rebar3 reads .app.src to produce the .app file in
95+ % % ebin. Without this step, releases assembled in embedded mode cannot
96+ % % load the generated modules.
97+ update_app_src (SrcDir , OtpModules ) ->
98+ AppSrcFile = filename :join (SrcDir , " partisan.app.src" ),
99+ case file :consult (AppSrcFile ) of
100+ {ok , [{application , partisan , Props }]} ->
101+ ExistingModules = proplists :get_value (modules , Props , []),
102+ RenameMap = #{
103+ gen => partisan_gen ,
104+ proc_lib => partisan_proc_lib ,
105+ sys => partisan_sys ,
106+ gen_server => partisan_gen_server ,
107+ gen_event => partisan_gen_event ,
108+ gen_statem => partisan_gen_statem ,
109+ gen_fsm => partisan_gen_fsm ,
110+ supervisor => partisan_gen_supervisor
111+ },
112+ GenModules = [maps :get (M , RenameMap ) || M <- OtpModules ],
113+ AllModules = lists :usort (ExistingModules ++ GenModules ),
114+ case AllModules of
115+ ExistingModules ->
116+ % % No change — leave the file's mtime alone so
117+ % % rebar3's incremental build is happy.
118+ ok ;
119+ _ ->
120+ NewProps = lists :keystore (
121+ modules , 1 , Props , {modules , AllModules }
122+ ),
123+ Content = io_lib :format (
124+ " ~p .~n " , [{application , partisan , NewProps }]
125+ ),
126+ ok = file :write_file (AppSrcFile , Content )
127+ end ;
128+ _ ->
129+ ok
130+ end .
131+
132+
71133compile_and_load (File , SrcDir ) ->
72134 Path = filename :join (SrcDir , File ),
73- case compile :file (Path , [binary , return_errors ]) of
135+ case compile :file (Path , [binary , return_errors , debug_info ]) of
74136 {ok , Mod , Bin } ->
75- case code :load_binary (Mod , Path , Bin ) of
76- {module , Mod } ->
77- ok ;
78- {error , Reason } ->
79- fail (" loading" , File , Reason )
80- end ;
137+ load_or_fail (Mod , Path , Bin , File );
81138 {ok , Mod , Bin , _Warnings } ->
82- case code :load_binary (Mod , Path , Bin ) of
83- {module , Mod } -> ok ;
84- {error , Reason } -> fail (" loading" , File , Reason )
85- end ;
139+ load_or_fail (Mod , Path , Bin , File );
86140 {error , Errors , Warnings } ->
87141 fail (" compiling" , File , {Errors , Warnings })
88142 end .
89143
90144
145+ load_or_fail (Mod , Path , Bin , File ) ->
146+ case code :load_binary (Mod , Path , Bin ) of
147+ {module , Mod } -> ok ;
148+ {error , Reason } -> fail (" loading" , File , Reason )
149+ end .
150+
151+
91152fail (Action , File , Reason ) ->
92153 io :format (standard_error ,
93154 " generate_otp_sources.escript: error ~s ~s : ~p~n " ,
0 commit comments