1 /**
2    Necessary boilerplate for pyd.
3 
4    To wrap all functions/return/parameter types and  struct/class definitions from
5    a list of modules, write this in a "main" module and generate mylib.{so,dll}:
6 
7    ------
8    mixin wrapAll(LibraryName("mylib"), Modules("module1", "module2", ...));
9    ------
10  */
11 module autowrap.python.boilerplate;
12 
13 /**
14    The name of the dynamic library, i.e. the file name with the .so/.dll extension
15  */
16 struct LibraryName {
17     string value;
18 }
19 
20 
21 /**
22    The list of modules to automatically wrap for Python consumption
23  */
24 struct Modules {
25     import autowrap.reflection: Module;
26     import std.traits: Unqual;
27     import std.meta: allSatisfy;
28 
29     Module[] value;
30 
31     this(A...)(auto ref A modules) {
32 
33         foreach(module_; modules) {
34             static if(is(Unqual!(typeof(module_)) == Module))
35                 value ~= module_;
36             else static if(is(Unqual!(typeof(module_)) == string))
37                 value ~= Module(module_);
38             else
39                 static assert(false, "Modules must either be `string` or `Module`");
40         }
41     }
42 }
43 
44 
45 /**
46    Code to be inserted before the call to module_init
47  */
48 struct PreModuleInitCode {
49     string value;
50 }
51 
52 /**
53    Code to be inserted after the call to module_init
54  */
55 struct PostModuleInitCode {
56     string value;
57 }
58 
59 
60 /**
61    A string to be mixed in that defines all the necessary runtime pyd boilerplate.
62  */
63 string wrapAll(in LibraryName libraryName,
64                in Modules modules,
65                in PreModuleInitCode preModuleInitCode = PreModuleInitCode(),
66                in PostModuleInitCode postModuleInitCode = PostModuleInitCode())
67     @safe pure
68 {
69     if(!__ctfe) return null;
70 
71     string ret =
72         pydMainMixin(modules, preModuleInitCode, postModuleInitCode) ~
73         pydInitMixin(libraryName.value);
74 
75     version(Have_excel_d) {
76         ret ~=
77         // this is needed because of the excel-d dependency
78         q{
79             import xlld.wrap.worksheet: WorksheetFunction;
80             extern(C) WorksheetFunction[] getWorksheetFunctions() @safe pure nothrow { return []; }
81         };
82     } else version(Windows) {
83         ret ~= dllMainMixinStr;
84     }
85 
86     return ret;
87 }
88 
89 /**
90    A string to be mixed in that defines PydMain, automatically registering all
91    functions and structs in the passed in modules.
92  */
93 string pydMainMixin(in Modules modules,
94                     in PreModuleInitCode preModuleInitCode = PreModuleInitCode(),
95                     in PostModuleInitCode postModuleInitCode = PostModuleInitCode())
96     @safe pure
97 {
98     import std.format: format;
99     import std.algorithm: map;
100     import std.array: join;
101 
102     if(!__ctfe) return null;
103 
104     const modulesList = modules.value.map!(a => a.toString).join(", ");
105 
106     return q{
107         extern(C) void PydMain() {
108             import std.typecons: Yes, No;
109             import pyd.pyd: module_init;
110             import autowrap.python.wrap: wrapAllFunctions, wrapAllAggregates;
111 
112             // this must go before module_init
113             wrapAllFunctions!(%s);
114 
115             %s
116 
117             module_init;
118 
119             // this must go after module_init
120             wrapAllAggregates!(%s);
121 
122             %s
123         }
124     }.format(modulesList, preModuleInitCode.value, modulesList, postModuleInitCode.value);
125 }
126 
127 /**
128    A string to be mixed in that defines the PyInit function for a library.
129  */
130 string pydInitMixin(in string libraryName) @safe pure {
131     import std.format: format;
132 
133     if(!__ctfe) return null;
134 
135     version(Python_3_0_Or_Later) {
136         return q{
137             import deimos.python.object: PyObject;
138             extern(C) export PyObject* PyInit_%s() {
139                 import pyd.def: pyd_module_name, pyd_modules;
140                 import pyd.exception: exception_catcher;
141                 import pyd.thread: ensureAttached;
142 
143                 return exception_catcher(delegate PyObject*() {
144                         ensureAttached();
145                         pyd_module_name = "%s";
146                         PydMain();
147                         return pyd_modules[""];
148                     });
149             }
150         }.format(libraryName, libraryName);
151     } else {
152         return q{
153             import pyd.exception: exception_catcher;
154             import pyd.thread: ensureAttached;
155             import pyd.def: pyd_module_name;
156             extern(C) export void init%s() {
157                 exception_catcher(delegate void() {
158                         ensureAttached();
159                         pyd_module_name = "%s";
160                         PydMain();
161                     });
162 
163             }
164         }.format(libraryName, libraryName);
165     }
166 }
167 
168 /**
169    Returns a string to be mixed in that defines DllMain, needed on Windows.
170  */
171 private string dllMainMixinStr() @safe pure {
172     return q{
173 
174         import core.sys.windows.windows: HINSTANCE, BOOL, ULONG, LPVOID;
175 
176         __gshared HINSTANCE g_hInst;
177 
178         extern (Windows) BOOL DllMain(HINSTANCE hInstance, ULONG ulReason, LPVOID pvReserved) {
179 
180             import core.sys.windows.windows;
181             import core.sys.windows.dll;
182 
183             switch (ulReason)  {
184 
185                 case DLL_PROCESS_ATTACH:
186                     g_hInst = hInstance;
187                     dll_process_attach(hInstance, true);
188                 break;
189 
190                 case DLL_PROCESS_DETACH:
191                     dll_process_detach(hInstance, true);
192                     break;
193 
194                 case DLL_THREAD_ATTACH:
195                     dll_thread_attach(true, true);
196                     break;
197 
198                 case DLL_THREAD_DETACH:
199                     dll_thread_detach(true, true);
200                     break;
201 
202                 default:
203             }
204 
205             return true;
206         }
207     };
208 }