]> git.tdb.fi Git - builder.git/blob - source/lib/target.cpp
Refactor transitive dependencies to work on all targets
[builder.git] / source / lib / target.cpp
1 #include <msp/core/algorithm.h>
2 #include <msp/fs/stat.h>
3 #include <msp/fs/utils.h>
4 #include <msp/strings/utils.h>
5 #include "builder.h"
6 #include "filetarget.h"
7 #include "sourcepackage.h"
8 #include "target.h"
9 #include "task.h"
10 #include "tool.h"
11
12 using namespace std;
13 using namespace Msp;
14
15 vector<Target *> Target::prepare_stack;
16
17 Target::Target(Builder &b, const string &n):
18         builder(b),
19         name(n)
20 {
21         builder.get_build_graph().add_target(this);
22 }
23
24 void Target::add_dependency(Target &dep)
25 {
26         if(&dep==this)
27                 throw invalid_argument("Target::add_depend");
28         depends.push_back(&dep);
29         if(state>PREPARING)
30                 dep.signal_bubble_rebuild.connect(sigc::mem_fun(this, &Target::check_rebuild));
31 }
32
33 void Target::add_transitive_dependency(Target &dep)
34 {
35         if(&dep==this)
36                 throw invalid_argument("Target::add_transitive_dependency");
37         trans_depends.push_back(&dep);
38 }
39
40 void Target::add_side_effect(Target &se)
41 {
42         side_effects.push_back(&se);
43         if(tool)
44                 se.set_tool(*tool);
45         se.primary_target = this;
46         /* Side effects are checked for rebuild after the primary target.  Recheck
47         the primary if a side effect is marked for rebuild. */
48         se.signal_bubble_rebuild.connect(sigc::mem_fun(this, &Target::check_rebuild));
49 }
50
51 bool Target::find_transitive_dependencies()
52 {
53         vector<Target *> found_deps;
54         for(Target *d: depends)
55                 if(!d->is_buildable())
56                         find_transitive_dependencies(*d, found_deps);
57
58         bool any_added = false;
59         for(Target *d: found_deps)
60                 if(!any_equals(depends, d))
61                 {
62                         any_added = true;
63                         add_dependency(*d);
64                         if(d->get_real_target()->is_buildable())
65                                 d->signal_modified.connect([this]{ rescan_trans_deps = true; });
66                 }
67
68         return any_added;
69 }
70
71 void Target::find_transitive_dependencies(Target &tgt, vector<Target *> &found_deps) const
72 {
73         tgt.prepare();
74
75         Target *rtgt = tgt.get_real_target();
76         Dependencies deps_to_add = rtgt->get_transitive_dependencies();
77         if(rtgt!=&tgt)
78         {
79                 for(Target *&d: deps_to_add)
80                         d = resolve_transitive_dependency(tgt, *d);
81         }
82
83         for(Target *d: deps_to_add)
84                 if(d)
85                 {
86                         auto i = lower_bound(found_deps, d);
87                         if(i==found_deps.end() || *i!=d)
88                         {
89                                 found_deps.insert(i, d);
90                                 find_transitive_dependencies(*d, found_deps);
91                         }
92                 }
93 }
94
95 Target *Target::get_buildable_target()
96 {
97         if(primary_target)
98                 return primary_target->get_buildable_target();
99         if(!needs_rebuild())
100                 return 0;
101
102         bool self_ok = state!=BUILDING;
103         for(Target *d: depends)
104         {
105                 // Avoid infinite recursion if a target depends on its own side effect
106                 if(any_equals(side_effects, d))
107                         continue;
108
109                 Target *tgt = d->get_buildable_target();
110                 if(tgt)
111                         return tgt;
112                 else if(d->needs_rebuild())
113                         self_ok = false;
114         }
115
116         if(self_ok)
117         {
118                 if(rescan_trans_deps)
119                 {
120                         rescan_trans_deps = false;
121                         if(find_transitive_dependencies())
122                                 return get_buildable_target();
123                 }
124
125                 return this;
126         }
127
128         return 0;
129 }
130
131 void Target::set_tool(Tool &t)
132 {
133         tool = &t;
134         for(Target *s: side_effects)
135                 s->set_tool(t);
136 }
137
138 void Target::collect_build_info(BuildInfo &binfo) const
139 {
140         if(tool)
141                 binfo.update_from(tool->get_build_info());
142         if(component)
143                 binfo.update_from(component->get_build_info());
144         else if(package)
145                 binfo.update_from(package->get_build_info());
146 }
147
148 void Target::force_rebuild()
149 {
150         if(!is_buildable())
151                 throw logic_error("Target::force_rebuild");
152         mark_rebuild("Forced rebuild");
153 }
154
155 void Target::mark_rebuild(const string &reason)
156 {
157         if(reason.empty())
158                 throw invalid_argument("No reason given for rebuilding "+name);
159
160         state = REBUILD;
161         rebuild_reason = reason;
162
163         builder.get_logger().log("rebuild", "Rebuilding %s: %s", name, reason);
164
165         signal_bubble_rebuild.emit();
166 }
167
168 void Target::prepare()
169 {
170         if(primary_target && primary_target->state<PREPARING)
171                 primary_target->prepare();
172
173         if(state>PREPARING)
174                 return;
175         if(state==PREPARING)
176         {
177                 auto i = find(prepare_stack, this);
178                 if(i!=prepare_stack.end())
179                 {
180                         string cycle;
181                         for(; i!=prepare_stack.end(); ++i)
182                                 append(cycle, " -> ", (*i)->name);
183                         append(cycle, " -> ", name);
184                         builder.get_logger().log("problems", "Dependency cycle detected: %s", cycle);
185                         problems.push_back(format("Dependency cycle detected: %s", cycle));
186                 }
187                 else
188                 {
189                         builder.get_logger().log("problems", "Dependency cycle detected at %s", name);
190                         problems.push_back("Dependency cycle detected");
191                 }
192                 state = BROKEN;
193                 return;
194         }
195
196         PushPrepare _push(this);
197         state = PREPARING;
198         /* Prepare existing dependencies early, because their information may be
199         needed to find other dependencies. */
200         for(Target *d: depends)
201                 d->prepare();
202         if(tool)
203                 tool->prepare();
204
205         find_dependencies();
206         find_transitive_dependencies();
207         bool broken = !problems.empty();
208
209         if(tool)
210         {
211                 if(FileTarget *tool_exe = tool->get_executable())
212                         add_dependency(*tool_exe);
213                 broken |= tool->is_broken();
214
215                 // Only check package and component brokenness for buildable targets
216                 broken |= (package && package->is_broken());
217                 broken |= (component && component->is_broken());
218         }
219
220         /* Now that all dependencies are known, prepare them again.  This will do
221         nothing to already prepared targets. */
222         for(Target *d: depends)
223         {
224                 d->prepare();
225                 broken |= d->is_broken();
226         }
227         for(Target *d: trans_depends)
228                 d->prepare();
229
230         check_rebuild();
231         if(broken)
232                 state = BROKEN;
233         else if(state==PREPARING)
234                 state = UPTODATE;
235
236         for(Target *d: depends)
237                 d->signal_bubble_rebuild.connect(sigc::mem_fun(this, &Target::check_rebuild));
238 }
239
240 Task *Target::build()
241 {
242         if(primary_target)
243                 return primary_target->build();
244
245         Task *task = tool->run(*this);
246         task->signal_finished.connect(sigc::mem_fun(this, &Target::build_finished));
247         state = BUILDING;
248
249         build(*task);
250         for(Target *s: side_effects)
251                 s->build(*task);
252
253         return task;
254 }
255
256 void Target::build_finished(bool success)
257 {
258         state = UPTODATE;
259         if(success)
260         {
261                 modified();
262                 for(Target *s: side_effects)
263                         s->build_finished(success);
264                 signal_modified.emit();
265         }
266 }