1 module mage.target;
2 import mage;
3 import std.variant;
4 import std.typecons : tuple;
5 public import std.typecons : Tuple;
6 
7 
8 //debug = PragmaMsg;
9 
10 
11 /// Base class for all targets.
12 abstract class Target
13 {
14   Properties _properties;
15   MagicContext context;
16 
17   this()
18   {
19     properties.name = typeid(this).toString();
20 
21     // Useful for targets that only do some global config.
22     properties["type"] = "none";
23     // TODO Source file properties.
24   }
25 
26   @property ref inout(Properties) properties() inout {
27     return _properties;
28   }
29 
30   mixin PropertiesOperators!_properties;
31 
32   @property bool isExternal() const { return false; }
33 
34   void configure() {}
35 
36   override string toString() const
37   {
38     auto var = this["name"];
39     if(var.hasValue())
40     {
41       return (cast()var).get!string;
42     }
43     import std.conv : to;
44     return typeid(this).to!string();
45   }
46 }
47 
48 
49 abstract class Executable : Target
50 {
51   this() {
52     properties["type"] = "executable";
53   }
54 }
55 
56 
57 enum LibraryType
58 {
59   Static,
60   Shared
61 }
62 
63 abstract class Library : Target
64 {
65   this(LibraryType libType)
66   {
67     this["type"] = "library";
68     this["libType"] = libType;
69   }
70 }
71 
72 abstract class StaticLibrary : Library
73 {
74   this() { super(LibraryType.Static); }
75 }
76 
77 abstract class SharedLibrary : Library
78 {
79   this() { super(LibraryType.Shared); }
80 }
81 
82 
83 /// Used for third-party inclusion.
84 abstract class ExternalTarget : Target
85 {
86   final override @property bool isExternal() const { return true; }
87 }
88 
89 
90 /// Helper to add link targets.
91 void addLinkTarget(Target target, Target linkTarget)
92 {
93   if(linkTarget.tryGet("libType") is null)
94   {
95     log.error("Missing `libType' property; Cannot add `%s' as link target.", linkTarget);
96     return;
97   }
98 
99   Target[] targets;
100   if(auto pValue = target.tryGet("linkTargets"))
101   {
102     targets = pValue.get!(Target[]);
103   }
104   targets ~= linkTarget;
105   log.trace("Link targets of `%s': %s", target, targets);
106   target["linkTargets"] = targets;
107 }
108 
109 struct Config
110 {
111   auto properties = Properties("<anonymousConfig>");
112 
113   this(string name) {
114     this.name = name;
115   }
116 
117   this(string name, string architecture)
118   {
119     this.name = name;
120     this.architecture = architecture;
121   }
122 
123   @property string name() const { return (cast()this["name"]).get!string; }
124   @property void name(string theName) { this["name"] = theName; }
125 
126   @property string architecture() const { return (cast()this["architecture"]).get!string; }
127   @property void architecture(string theArch) { this["architecture"] = theArch; }
128 
129   mixin PropertiesOperators!properties;
130 
131   string toString() const {
132     return "Config(`%s', `%s')".format(this.name, this.architecture);
133   }
134 }
135 
136 bool isMatch(ref in Config cfg1, ref in Config cfg2)
137 {
138   return cfg1.name == cfg2.name && cfg1.architecture == cfg2.architecture;
139 }
140 
141 auto matchingConfigurations(Config[] configs1, Config[] configs2)
142 {
143   Tuple!(Config*, Config*)[] result;
144   foreach(ref cfg; configs1)
145   {
146     foreach(ref otherCfg; configs2)
147     {
148       if(cfg.isMatch(otherCfg)) {
149         result ~= tuple(&cfg, &otherCfg);
150       }
151     }
152   }
153   return result;
154 }
155 
156 auto matchingConfigurations(ref Environment env1, ref Environment env2)
157 {
158   auto configs1 = env1.first("configurations");
159   auto configs2 = env2.first("configurations");
160   return matchingConfigurations(configs1.get!(Config[]), configs2.get!(Config[]));
161 }
162 
163 auto matchingConfigurations(Target t1, Target t2)
164 {
165   auto env1 = Environment("matchingConfigurations_t1", t1.properties);
166   auto env2 = Environment("matchingConfigurations_t2", t2.properties);
167   return matchingConfigurations(env1, env2);
168 }
169 
170 unittest
171 {
172   import std.range;
173 
174   Config[] l;
175   l.length = 3;
176   l[0].name = "a";
177   l[0].architecture = "x86";
178   l[1].name = "b";
179   l[1].architecture = "x86";
180   l[2].name = "c";
181   l[2].architecture = "x86";
182 
183   Config[] r;
184   r.length = 3;
185   r[0].name = "c";
186   r[0].architecture = "x86";
187   r[1].name = "z";
188   r[1].architecture = "x86";
189   r[2].name = "a";
190   r[2].architecture = "x86";
191 
192   auto matches = matchingConfigurations(l, r);
193   assert(matches.length == 2);
194   assert(matches.front[0].name == "a");
195   assert(matches.front[1].name == "a");
196   matches.popFront();
197   assert(matches.front[0].name == "c");
198   assert(matches.front[1].name == "c");
199 }
200 
201 
202 /// User-defined Attribute (UDA) to decorate a dependency field with.
203 struct Dependency
204 {
205 }
206 
207 
208 interface ITargetWrapper
209 {
210   abstract @property Path filePath() const;
211   abstract @property string targetName() const;
212   abstract @property const(TypeInfo) wrappedTypeInfo() const;
213   abstract @property const(TypeInfo)[] dependencies() const;
214   abstract void setDependencyInstance(Target target, Target dependency);
215   abstract Target create();
216 }
217 
218 __gshared ITargetWrapper[] wrappedTargets;
219 
220 class TargetWrapper(TargetType) : ITargetWrapper
221 {
222   import std.stdio;
223 private:
224   alias DependencySetter = void function(Target, Target);
225 
226   Path _filePath;
227   DependencySetter[TypeInfo] _dependencies;
228 
229 public:
230   override @property Path filePath() const { return _filePath; }
231   override @property string targetName() const { return TargetType.stringof; }
232   override @property const(TypeInfo) wrappedTypeInfo() const { return typeid(TargetType); }
233   override @property const(TypeInfo)[] dependencies() const { return _dependencies.keys; }
234   override void setDependencyInstance(Target target, Target dependency)
235   {
236     log.info("Finding %s...", typeid(dependency));
237     _dependencies[typeid(dependency)](target, dependency);
238   }
239 
240   this(Path filePath) {
241     import mage.util.reflection;
242     _filePath = filePath;
243     debug(PragmaMsg) { pragma(msg, `[mage] Scanning Target "` ~ TargetType.stringof ~ `" members for dependencies.`); }
244     foreach(m; __traits(allMembers, TargetType))
245     {
246       debug(PragmaMsg) { pragma(msg, "[mage]   Member: " ~ m); }
247       alias Member = Resolve!(__traits(getMember, TargetType, m));
248       static if(__traits(compiles, typeof(Member)))
249       {
250         foreach(uda; __traits(getAttributes, Member))
251         {
252           debug(PragmaMsg) { pragma(msg, "[mage]     UDA: " ~ uda.stringof); }
253           if(is(uda == Dependency))
254           {
255             static assert(is(typeof(Member) : Target), `Only "Target" types may be decorated with "@Dependency".`);
256             debug(PragmaMsg) { pragma(msg, "[mage] +++ Collecting dependency: " ~ typeof(Member).stringof); }
257             this._dependencies[typeid(typeof(Member))] = (t, d) // Setter for the member dependency instances
258             {
259               mixin("(cast(TargetType)t)." ~ m ~ " = cast(typeof(TargetType." ~ m ~ "))d;");
260             };
261           }
262         }
263       }
264     }
265   }
266 
267   override Target create() {
268     return new TargetType();
269   }
270 }
271 
272 mixin template registerMageFile(alias T, alias filePath)
273 {
274   shared static this()
275   {
276     import mage.util.reflection;
277     debug(PragmaMsg) { pragma(msg, "[mage] Reflecting module: " ~ T.stringof); }
278     foreach(m; __traits(allMembers, T))
279     {
280       debug(PragmaMsg) { pragma(msg, "[mage]   Checking member: " ~ m); }
281       static if(!__traits(compiles, typeof(__traits(getMember, T, m))))
282       {
283         debug(PragmaMsg) { pragma(msg, "[mage]     Got one further!"); }
284         static if(__traits(compiles, ResolveType!(__traits(getMember, T, m))))
285         {
286           debug(PragmaMsg) { pragma(msg, "[mage]       One more down!"); }
287           alias Type = ResolveType!(__traits(getMember, T, m));
288           static if(is(Type : Target))
289           {
290             debug(PragmaMsg) { pragma(msg, "[mage]   Found a target! " ~ Type.stringof); }
291             static if(!__traits(compiles, new Type())) {
292               debug(PragmaMsg) { pragma(msg, "[mage]   WARNING: Target is not instantiable with `new`."); }
293             }
294             log.info("Wrapping " ~ Type.stringof);
295             wrappedTargets ~= new TargetWrapper!Type(Path(filePath));
296           }
297         }
298       }
299     }
300   }
301 }
302 
303 mixin template M_MageFileMixin()
304 {
305   class MageFileInstance{}
306 
307   // The parent of MageFileInstance is the module.
308   mixin registerMageFile!(__traits(parent, MageFileInstance), M_mageFilePath);
309 }