diff options
Diffstat (limited to 'bitbake/lib/toaster/orm/models.py')
-rw-r--r-- | bitbake/lib/toaster/orm/models.py | 775 |
1 files changed, 775 insertions, 0 deletions
diff --git a/bitbake/lib/toaster/orm/models.py b/bitbake/lib/toaster/orm/models.py new file mode 100644 index 0000000000..77afe35861 --- /dev/null +++ b/bitbake/lib/toaster/orm/models.py | |||
@@ -0,0 +1,775 @@ | |||
1 | # | ||
2 | # ex:ts=4:sw=4:sts=4:et | ||
3 | # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- | ||
4 | # | ||
5 | # BitBake Toaster Implementation | ||
6 | # | ||
7 | # Copyright (C) 2013 Intel Corporation | ||
8 | # | ||
9 | # This program is free software; you can redistribute it and/or modify | ||
10 | # it under the terms of the GNU General Public License version 2 as | ||
11 | # published by the Free Software Foundation. | ||
12 | # | ||
13 | # This program is distributed in the hope that it will be useful, | ||
14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
16 | # GNU General Public License for more details. | ||
17 | # | ||
18 | # You should have received a copy of the GNU General Public License along | ||
19 | # with this program; if not, write to the Free Software Foundation, Inc., | ||
20 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
21 | |||
22 | from django.db import models | ||
23 | from django.db.models import F | ||
24 | from django.utils.encoding import python_2_unicode_compatible | ||
25 | from django.utils import timezone | ||
26 | |||
27 | class ToasterSetting(models.Model): | ||
28 | name = models.CharField(max_length=63) | ||
29 | helptext = models.TextField() | ||
30 | value = models.CharField(max_length=255) | ||
31 | |||
32 | class ToasterSettingDefaultLayer(models.Model): | ||
33 | layer_version = models.ForeignKey('Layer_Version') | ||
34 | |||
35 | class ProjectManager(models.Manager): | ||
36 | def create_project(self, name, release): | ||
37 | prj = self.model(name = name, bitbake_version = release.bitbake_version, release = release) | ||
38 | prj.save() | ||
39 | |||
40 | for defaultconf in ToasterSetting.objects.filter(name__startswith="DEFCONF_"): | ||
41 | name = defaultconf.name[8:] | ||
42 | ProjectVariable.objects.create( project = prj, | ||
43 | name = name, | ||
44 | value = defaultconf.value) | ||
45 | |||
46 | for layer in map(lambda x: x.layer, ReleaseDefaultLayer.objects.filter(release = release)): | ||
47 | for branches in Branch.objects.filter(name = release.branch): | ||
48 | for lv in Layer_Version.objects.filter(layer = layer, up_branch = branches ): | ||
49 | ProjectLayer.objects.create( project = prj, | ||
50 | layercommit = lv, | ||
51 | optional = False ) | ||
52 | |||
53 | return prj | ||
54 | |||
55 | def create(self, *args, **kwargs): | ||
56 | raise Exception("Invalid call to Project.objects.create. Use Project.objects.create_project() to create a project") | ||
57 | |||
58 | def get_or_create(self, *args, **kwargs): | ||
59 | raise Exception("Invalid call to Project.objects.get_or_create. Use Project.objects.create_project() to create a project") | ||
60 | |||
61 | class Project(models.Model): | ||
62 | name = models.CharField(max_length=100) | ||
63 | short_description = models.CharField(max_length=50, blank=True) | ||
64 | bitbake_version = models.ForeignKey('BitbakeVersion') | ||
65 | release = models.ForeignKey("Release") | ||
66 | created = models.DateTimeField(auto_now_add = True) | ||
67 | updated = models.DateTimeField(auto_now = True) | ||
68 | # This is a horrible hack; since Toaster has no "User" model available when | ||
69 | # running in interactive mode, we can't reference the field here directly | ||
70 | # Instead, we keep a possible null reference to the User id, as not to force | ||
71 | # hard links to possibly missing models | ||
72 | user_id = models.IntegerField(null = True) | ||
73 | objects = ProjectManager() | ||
74 | |||
75 | |||
76 | def schedule_build(self): | ||
77 | from bldcontrol.models import BuildRequest, BRTarget, BRLayer, BRVariable, BRBitbake | ||
78 | br = BuildRequest.objects.create(project = self) | ||
79 | |||
80 | BRBitbake.objects.create(req = br, | ||
81 | giturl = self.bitbake_version.giturl, | ||
82 | commit = self.bitbake_version.branch, | ||
83 | dirpath = self.bitbake_version.dirpath) | ||
84 | |||
85 | for l in self.projectlayer_set.all(): | ||
86 | BRLayer.objects.create(req = br, name = l.layercommit.layer.name, giturl = l.layercommit.layer.vcs_url, commit = l.layercommit.commit, dirpath = l.layercommit.dirpath) | ||
87 | for t in self.projecttarget_set.all(): | ||
88 | BRTarget.objects.create(req = br, target = t.target, task = t.task) | ||
89 | for v in self.projectvariable_set.all(): | ||
90 | BRVariable.objects.create(req = br, name = v.name, value = v.value) | ||
91 | |||
92 | br.state = BuildRequest.REQ_QUEUED | ||
93 | br.save() | ||
94 | return br | ||
95 | |||
96 | class Build(models.Model): | ||
97 | SUCCEEDED = 0 | ||
98 | FAILED = 1 | ||
99 | IN_PROGRESS = 2 | ||
100 | |||
101 | BUILD_OUTCOME = ( | ||
102 | (SUCCEEDED, 'Succeeded'), | ||
103 | (FAILED, 'Failed'), | ||
104 | (IN_PROGRESS, 'In Progress'), | ||
105 | ) | ||
106 | |||
107 | search_allowed_fields = ['machine', 'cooker_log_path', "target__target", "target__target_image_file__file_name"] | ||
108 | |||
109 | project = models.ForeignKey(Project, null = True) | ||
110 | machine = models.CharField(max_length=100) | ||
111 | distro = models.CharField(max_length=100) | ||
112 | distro_version = models.CharField(max_length=100) | ||
113 | started_on = models.DateTimeField() | ||
114 | completed_on = models.DateTimeField() | ||
115 | timespent = models.IntegerField(default=0) | ||
116 | outcome = models.IntegerField(choices=BUILD_OUTCOME, default=IN_PROGRESS) | ||
117 | errors_no = models.IntegerField(default=0) | ||
118 | warnings_no = models.IntegerField(default=0) | ||
119 | cooker_log_path = models.CharField(max_length=500) | ||
120 | build_name = models.CharField(max_length=100) | ||
121 | bitbake_version = models.CharField(max_length=50) | ||
122 | |||
123 | def completeper(self): | ||
124 | tf = Task.objects.filter(build = self) | ||
125 | tfc = tf.count() | ||
126 | if tfc > 0: | ||
127 | completeper = tf.exclude(order__isnull=True).count()*100/tf.count() | ||
128 | else: | ||
129 | completeper = 0 | ||
130 | return completeper | ||
131 | |||
132 | def eta(self): | ||
133 | from django.utils import timezone | ||
134 | eta = 0 | ||
135 | completeper = self.completeper() | ||
136 | if self.completeper() > 0: | ||
137 | eta = timezone.now() + ((timezone.now() - self.started_on)*(100-completeper)/completeper) | ||
138 | return eta | ||
139 | |||
140 | |||
141 | def get_sorted_target_list(self): | ||
142 | tgts = Target.objects.filter(build_id = self.id).order_by( 'target' ); | ||
143 | return( tgts ); | ||
144 | |||
145 | class ProjectTarget(models.Model): | ||
146 | project = models.ForeignKey(Project) | ||
147 | target = models.CharField(max_length=100) | ||
148 | task = models.CharField(max_length=100, null=True) | ||
149 | |||
150 | @python_2_unicode_compatible | ||
151 | class Target(models.Model): | ||
152 | search_allowed_fields = ['target', 'file_name'] | ||
153 | build = models.ForeignKey(Build) | ||
154 | target = models.CharField(max_length=100) | ||
155 | is_image = models.BooleanField(default = False) | ||
156 | image_size = models.IntegerField(default=0) | ||
157 | license_manifest_path = models.CharField(max_length=500, null=True) | ||
158 | |||
159 | def package_count(self): | ||
160 | return Target_Installed_Package.objects.filter(target_id__exact=self.id).count() | ||
161 | |||
162 | def __str__(self): | ||
163 | return self.target | ||
164 | |||
165 | class Target_Image_File(models.Model): | ||
166 | target = models.ForeignKey(Target) | ||
167 | file_name = models.FilePathField(max_length=254) | ||
168 | file_size = models.IntegerField() | ||
169 | |||
170 | class Target_File(models.Model): | ||
171 | ITYPE_REGULAR = 1 | ||
172 | ITYPE_DIRECTORY = 2 | ||
173 | ITYPE_SYMLINK = 3 | ||
174 | ITYPE_SOCKET = 4 | ||
175 | ITYPE_FIFO = 5 | ||
176 | ITYPE_CHARACTER = 6 | ||
177 | ITYPE_BLOCK = 7 | ||
178 | ITYPES = ( (ITYPE_REGULAR ,'regular'), | ||
179 | ( ITYPE_DIRECTORY ,'directory'), | ||
180 | ( ITYPE_SYMLINK ,'symlink'), | ||
181 | ( ITYPE_SOCKET ,'socket'), | ||
182 | ( ITYPE_FIFO ,'fifo'), | ||
183 | ( ITYPE_CHARACTER ,'character'), | ||
184 | ( ITYPE_BLOCK ,'block'), | ||
185 | ) | ||
186 | |||
187 | target = models.ForeignKey(Target) | ||
188 | path = models.FilePathField() | ||
189 | size = models.IntegerField() | ||
190 | inodetype = models.IntegerField(choices = ITYPES) | ||
191 | permission = models.CharField(max_length=16) | ||
192 | owner = models.CharField(max_length=128) | ||
193 | group = models.CharField(max_length=128) | ||
194 | directory = models.ForeignKey('Target_File', related_name="directory_set", null=True) | ||
195 | sym_target = models.ForeignKey('Target_File', related_name="symlink_set", null=True) | ||
196 | |||
197 | |||
198 | class TaskManager(models.Manager): | ||
199 | def related_setscene(self, task_object): | ||
200 | return Task.objects.filter(task_executed=True, build = task_object.build, recipe = task_object.recipe, task_name=task_object.task_name+"_setscene") | ||
201 | |||
202 | class Task(models.Model): | ||
203 | |||
204 | SSTATE_NA = 0 | ||
205 | SSTATE_MISS = 1 | ||
206 | SSTATE_FAILED = 2 | ||
207 | SSTATE_RESTORED = 3 | ||
208 | |||
209 | SSTATE_RESULT = ( | ||
210 | (SSTATE_NA, 'Not Applicable'), # For rest of tasks, but they still need checking. | ||
211 | (SSTATE_MISS, 'File not in cache'), # the sstate object was not found | ||
212 | (SSTATE_FAILED, 'Failed'), # there was a pkg, but the script failed | ||
213 | (SSTATE_RESTORED, 'Succeeded'), # successfully restored | ||
214 | ) | ||
215 | |||
216 | CODING_NA = 0 | ||
217 | CODING_PYTHON = 2 | ||
218 | CODING_SHELL = 3 | ||
219 | |||
220 | TASK_CODING = ( | ||
221 | (CODING_NA, 'N/A'), | ||
222 | (CODING_PYTHON, 'Python'), | ||
223 | (CODING_SHELL, 'Shell'), | ||
224 | ) | ||
225 | |||
226 | OUTCOME_NA = -1 | ||
227 | OUTCOME_SUCCESS = 0 | ||
228 | OUTCOME_COVERED = 1 | ||
229 | OUTCOME_CACHED = 2 | ||
230 | OUTCOME_PREBUILT = 3 | ||
231 | OUTCOME_FAILED = 4 | ||
232 | OUTCOME_EMPTY = 5 | ||
233 | |||
234 | TASK_OUTCOME = ( | ||
235 | (OUTCOME_NA, 'Not Available'), | ||
236 | (OUTCOME_SUCCESS, 'Succeeded'), | ||
237 | (OUTCOME_COVERED, 'Covered'), | ||
238 | (OUTCOME_CACHED, 'Cached'), | ||
239 | (OUTCOME_PREBUILT, 'Prebuilt'), | ||
240 | (OUTCOME_FAILED, 'Failed'), | ||
241 | (OUTCOME_EMPTY, 'Empty'), | ||
242 | ) | ||
243 | |||
244 | TASK_OUTCOME_HELP = ( | ||
245 | (OUTCOME_SUCCESS, 'This task successfully completed'), | ||
246 | (OUTCOME_COVERED, 'This task did not run because its output is provided by another task'), | ||
247 | (OUTCOME_CACHED, 'This task restored output from the sstate-cache directory or mirrors'), | ||
248 | (OUTCOME_PREBUILT, 'This task did not run because its outcome was reused from a previous build'), | ||
249 | (OUTCOME_FAILED, 'This task did not complete'), | ||
250 | (OUTCOME_EMPTY, 'This task has no executable content'), | ||
251 | (OUTCOME_NA, ''), | ||
252 | ) | ||
253 | |||
254 | search_allowed_fields = [ "recipe__name", "recipe__version", "task_name", "logfile" ] | ||
255 | |||
256 | objects = TaskManager() | ||
257 | |||
258 | def get_related_setscene(self): | ||
259 | return Task.objects.related_setscene(self) | ||
260 | |||
261 | def get_outcome_text(self): | ||
262 | return Task.TASK_OUTCOME[self.outcome + 1][1] | ||
263 | |||
264 | def get_outcome_help(self): | ||
265 | return Task.TASK_OUTCOME_HELP[self.outcome][1] | ||
266 | |||
267 | def get_sstate_text(self): | ||
268 | if self.sstate_result==Task.SSTATE_NA: | ||
269 | return '' | ||
270 | else: | ||
271 | return Task.SSTATE_RESULT[self.sstate_result][1] | ||
272 | |||
273 | def get_executed_display(self): | ||
274 | if self.task_executed: | ||
275 | return "Executed" | ||
276 | return "Not Executed" | ||
277 | |||
278 | def get_description(self): | ||
279 | helptext = HelpText.objects.filter(key=self.task_name, area=HelpText.VARIABLE, build=self.build) | ||
280 | try: | ||
281 | return helptext[0].text | ||
282 | except IndexError: | ||
283 | return '' | ||
284 | |||
285 | build = models.ForeignKey(Build, related_name='task_build') | ||
286 | order = models.IntegerField(null=True) | ||
287 | task_executed = models.BooleanField(default=False) # True means Executed, False means Not/Executed | ||
288 | outcome = models.IntegerField(choices=TASK_OUTCOME, default=OUTCOME_NA) | ||
289 | sstate_checksum = models.CharField(max_length=100, blank=True) | ||
290 | path_to_sstate_obj = models.FilePathField(max_length=500, blank=True) | ||
291 | recipe = models.ForeignKey('Recipe', related_name='build_recipe') | ||
292 | task_name = models.CharField(max_length=100) | ||
293 | source_url = models.FilePathField(max_length=255, blank=True) | ||
294 | work_directory = models.FilePathField(max_length=255, blank=True) | ||
295 | script_type = models.IntegerField(choices=TASK_CODING, default=CODING_NA) | ||
296 | line_number = models.IntegerField(default=0) | ||
297 | disk_io = models.IntegerField(null=True) | ||
298 | cpu_usage = models.DecimalField(max_digits=6, decimal_places=2, null=True) | ||
299 | elapsed_time = models.DecimalField(max_digits=6, decimal_places=2, null=True) | ||
300 | sstate_result = models.IntegerField(choices=SSTATE_RESULT, default=SSTATE_NA) | ||
301 | message = models.CharField(max_length=240) | ||
302 | logfile = models.FilePathField(max_length=255, blank=True) | ||
303 | |||
304 | outcome_text = property(get_outcome_text) | ||
305 | sstate_text = property(get_sstate_text) | ||
306 | |||
307 | class Meta: | ||
308 | ordering = ('order', 'recipe' ,) | ||
309 | unique_together = ('build', 'recipe', 'task_name', ) | ||
310 | |||
311 | |||
312 | class Task_Dependency(models.Model): | ||
313 | task = models.ForeignKey(Task, related_name='task_dependencies_task') | ||
314 | depends_on = models.ForeignKey(Task, related_name='task_dependencies_depends') | ||
315 | |||
316 | class Package(models.Model): | ||
317 | search_allowed_fields = ['name', 'version', 'revision', 'recipe__name', 'recipe__version', 'recipe__license', 'recipe__layer_version__layer__name', 'recipe__layer_version__branch', 'recipe__layer_version__commit', 'recipe__layer_version__layer__local_path', 'installed_name'] | ||
318 | build = models.ForeignKey('Build') | ||
319 | recipe = models.ForeignKey('Recipe', null=True) | ||
320 | name = models.CharField(max_length=100) | ||
321 | installed_name = models.CharField(max_length=100, default='') | ||
322 | version = models.CharField(max_length=100, blank=True) | ||
323 | revision = models.CharField(max_length=32, blank=True) | ||
324 | summary = models.CharField(max_length=200, blank=True) | ||
325 | description = models.TextField(blank=True) | ||
326 | size = models.IntegerField(default=0) | ||
327 | installed_size = models.IntegerField(default=0) | ||
328 | section = models.CharField(max_length=80, blank=True) | ||
329 | license = models.CharField(max_length=80, blank=True) | ||
330 | |||
331 | class Package_DependencyManager(models.Manager): | ||
332 | use_for_related_fields = True | ||
333 | |||
334 | def get_query_set(self): | ||
335 | return super(Package_DependencyManager, self).get_query_set().exclude(package_id = F('depends_on__id')) | ||
336 | |||
337 | class Package_Dependency(models.Model): | ||
338 | TYPE_RDEPENDS = 0 | ||
339 | TYPE_TRDEPENDS = 1 | ||
340 | TYPE_RRECOMMENDS = 2 | ||
341 | TYPE_TRECOMMENDS = 3 | ||
342 | TYPE_RSUGGESTS = 4 | ||
343 | TYPE_RPROVIDES = 5 | ||
344 | TYPE_RREPLACES = 6 | ||
345 | TYPE_RCONFLICTS = 7 | ||
346 | ' TODO: bpackage should be changed to remove the DEPENDS_TYPE access ' | ||
347 | DEPENDS_TYPE = ( | ||
348 | (TYPE_RDEPENDS, "depends"), | ||
349 | (TYPE_TRDEPENDS, "depends"), | ||
350 | (TYPE_TRECOMMENDS, "recommends"), | ||
351 | (TYPE_RRECOMMENDS, "recommends"), | ||
352 | (TYPE_RSUGGESTS, "suggests"), | ||
353 | (TYPE_RPROVIDES, "provides"), | ||
354 | (TYPE_RREPLACES, "replaces"), | ||
355 | (TYPE_RCONFLICTS, "conflicts"), | ||
356 | ) | ||
357 | ''' Indexed by dep_type, in view order, key for short name and help | ||
358 | description which when viewed will be printf'd with the | ||
359 | package name. | ||
360 | ''' | ||
361 | DEPENDS_DICT = { | ||
362 | TYPE_RDEPENDS : ("depends", "%s is required to run %s"), | ||
363 | TYPE_TRDEPENDS : ("depends", "%s is required to run %s"), | ||
364 | TYPE_TRECOMMENDS : ("recommends", "%s extends the usability of %s"), | ||
365 | TYPE_RRECOMMENDS : ("recommends", "%s extends the usability of %s"), | ||
366 | TYPE_RSUGGESTS : ("suggests", "%s is suggested for installation with %s"), | ||
367 | TYPE_RPROVIDES : ("provides", "%s is provided by %s"), | ||
368 | TYPE_RREPLACES : ("replaces", "%s is replaced by %s"), | ||
369 | TYPE_RCONFLICTS : ("conflicts", "%s conflicts with %s, which will not be installed if this package is not first removed"), | ||
370 | } | ||
371 | |||
372 | package = models.ForeignKey(Package, related_name='package_dependencies_source') | ||
373 | depends_on = models.ForeignKey(Package, related_name='package_dependencies_target') # soft dependency | ||
374 | dep_type = models.IntegerField(choices=DEPENDS_TYPE) | ||
375 | target = models.ForeignKey(Target, null=True) | ||
376 | objects = Package_DependencyManager() | ||
377 | |||
378 | class Target_Installed_Package(models.Model): | ||
379 | target = models.ForeignKey(Target) | ||
380 | package = models.ForeignKey(Package, related_name='buildtargetlist_package') | ||
381 | |||
382 | class Package_File(models.Model): | ||
383 | package = models.ForeignKey(Package, related_name='buildfilelist_package') | ||
384 | path = models.FilePathField(max_length=255, blank=True) | ||
385 | size = models.IntegerField() | ||
386 | |||
387 | class Recipe(models.Model): | ||
388 | search_allowed_fields = ['name', 'version', 'file_path', 'section', 'license', 'layer_version__layer__name', 'layer_version__branch', 'layer_version__commit', 'layer_version__layer__local_path'] | ||
389 | |||
390 | layer_source = models.ForeignKey('LayerSource', default = None, null = True) # from where did we get this recipe | ||
391 | up_id = models.IntegerField(null = True, default = None) # id of entry in the source | ||
392 | up_date = models.DateTimeField(null = True, default = None) | ||
393 | |||
394 | name = models.CharField(max_length=100, blank=True) # pn | ||
395 | version = models.CharField(max_length=100, blank=True) # pv | ||
396 | layer_version = models.ForeignKey('Layer_Version', related_name='recipe_layer_version') | ||
397 | summary = models.CharField(max_length=100, blank=True) | ||
398 | description = models.TextField(blank=True) | ||
399 | section = models.CharField(max_length=100, blank=True) | ||
400 | license = models.CharField(max_length=200, blank=True) | ||
401 | homepage = models.URLField(blank=True) | ||
402 | bugtracker = models.URLField(blank=True) | ||
403 | file_path = models.FilePathField(max_length=255) | ||
404 | |||
405 | def get_vcs_link_url(self): | ||
406 | if self.layer_version.layer.vcs_web_file_base_url is None: | ||
407 | return "" | ||
408 | return self.layer_version.layer.vcs_web_file_base_url.replace('%path%', self.file_path).replace('%branch%', self.layer_version.up_branch.name) | ||
409 | |||
410 | def get_layersource_view_url(self): | ||
411 | if self.layer_source is None: | ||
412 | return "" | ||
413 | |||
414 | url = self.layer_source.get_object_view(self.layer_version.up_branch, "recipes", self.name) | ||
415 | return url | ||
416 | |||
417 | def __unicode__(self): | ||
418 | return "Recipe " + self.name + ":" + self.version | ||
419 | |||
420 | class Recipe_DependencyManager(models.Manager): | ||
421 | use_for_related_fields = True | ||
422 | |||
423 | def get_query_set(self): | ||
424 | return super(Recipe_DependencyManager, self).get_query_set().exclude(recipe_id = F('depends_on__id')) | ||
425 | |||
426 | class Recipe_Dependency(models.Model): | ||
427 | TYPE_DEPENDS = 0 | ||
428 | TYPE_RDEPENDS = 1 | ||
429 | |||
430 | DEPENDS_TYPE = ( | ||
431 | (TYPE_DEPENDS, "depends"), | ||
432 | (TYPE_RDEPENDS, "rdepends"), | ||
433 | ) | ||
434 | recipe = models.ForeignKey(Recipe, related_name='r_dependencies_recipe') | ||
435 | depends_on = models.ForeignKey(Recipe, related_name='r_dependencies_depends') | ||
436 | dep_type = models.IntegerField(choices=DEPENDS_TYPE) | ||
437 | objects = Recipe_DependencyManager() | ||
438 | |||
439 | |||
440 | class Machine(models.Model): | ||
441 | layer_source = models.ForeignKey('LayerSource', default = None, null = True) # from where did we get this machine | ||
442 | up_id = models.IntegerField(null = True, default = None) # id of entry in the source | ||
443 | up_date = models.DateTimeField(null = True, default = None) | ||
444 | |||
445 | layer_version = models.ForeignKey('Layer_Version') | ||
446 | name = models.CharField(max_length=255) | ||
447 | description = models.CharField(max_length=255) | ||
448 | |||
449 | def __unicode__(self): | ||
450 | return "Machine " + self.name + "(" + self.description + ")" | ||
451 | |||
452 | class Meta: | ||
453 | unique_together = ("layer_source", "up_id") | ||
454 | |||
455 | |||
456 | from django.db.models.base import ModelBase | ||
457 | |||
458 | class InheritanceMetaclass(ModelBase): | ||
459 | def __call__(cls, *args, **kwargs): | ||
460 | obj = super(InheritanceMetaclass, cls).__call__(*args, **kwargs) | ||
461 | return obj.get_object() | ||
462 | |||
463 | |||
464 | class LayerSource(models.Model): | ||
465 | __metaclass__ = InheritanceMetaclass | ||
466 | |||
467 | class Meta: | ||
468 | unique_together = (('sourcetype', 'apiurl'), ) | ||
469 | |||
470 | TYPE_LOCAL = 0 | ||
471 | TYPE_LAYERINDEX = 1 | ||
472 | SOURCE_TYPE = ( | ||
473 | (TYPE_LOCAL, "local"), | ||
474 | (TYPE_LAYERINDEX, "layerindex"), | ||
475 | ) | ||
476 | |||
477 | name = models.CharField(max_length=63) | ||
478 | sourcetype = models.IntegerField(choices=SOURCE_TYPE) | ||
479 | apiurl = models.CharField(max_length=255, null=True, default=None) | ||
480 | |||
481 | def save(self, *args, **kwargs): | ||
482 | if isinstance(self, LocalLayerSource): | ||
483 | self.sourcetype = LayerSource.TYPE_LOCAL | ||
484 | elif isinstance(self, LayerIndexLayerSource): | ||
485 | self.sourcetype = LayerSource.TYPE_LAYERINDEX | ||
486 | elif self.sourcetype == None: | ||
487 | raise Exception("Invalid LayerSource type") | ||
488 | return super(LayerSource, self).save(*args, **kwargs) | ||
489 | |||
490 | def get_object(self): | ||
491 | if self.sourcetype is not None: | ||
492 | if self.sourcetype == LayerSource.TYPE_LOCAL: | ||
493 | self.__class__ = LocalLayerSource | ||
494 | if self.sourcetype == LayerSource.TYPE_LAYERINDEX: | ||
495 | self.__class__ = LayerIndexLayerSource | ||
496 | return self | ||
497 | |||
498 | return "LS " + self.sourcetype + " " + self.name | ||
499 | |||
500 | |||
501 | class LocalLayerSource(LayerSource): | ||
502 | class Meta(LayerSource._meta.__class__): | ||
503 | proxy = True | ||
504 | |||
505 | def __init__(self, *args, **kwargs): | ||
506 | super(LocalLayerSource, self).__init__(args, kwargs) | ||
507 | self.sourcetype = LayerSource.TYPE_LOCAL | ||
508 | |||
509 | def update(self): | ||
510 | ''' | ||
511 | Fetches layer, recipe and machine information from local repository | ||
512 | ''' | ||
513 | pass | ||
514 | |||
515 | class LayerIndexLayerSource(LayerSource): | ||
516 | class Meta(LayerSource._meta.__class__): | ||
517 | proxy = True | ||
518 | |||
519 | def __init__(self, *args, **kwargs): | ||
520 | super(LayerIndexLayerSource, self).__init__(args, kwargs) | ||
521 | self.sourcetype = LayerSource.TYPE_LAYERINDEX | ||
522 | |||
523 | def get_object_view(self, branch, objectype, upid): | ||
524 | if self != branch.layer_source: | ||
525 | raise Exception("Invalid branch specification") | ||
526 | return self.apiurl + "../branch/" + branch.name + "/" + objectype + "/?q=" + str(upid) | ||
527 | |||
528 | def update(self): | ||
529 | ''' | ||
530 | Fetches layer, recipe and machine information from remote repository | ||
531 | ''' | ||
532 | assert self.apiurl is not None | ||
533 | |||
534 | def _get_json_response(apiurl = self.apiurl): | ||
535 | import httplib, urlparse, json | ||
536 | parsedurl = urlparse.urlparse(apiurl) | ||
537 | (host, port) = parsedurl.netloc.split(":") | ||
538 | if port is None: | ||
539 | port = 80 | ||
540 | else: | ||
541 | port = int(port) | ||
542 | #print "-- connect to: http://%s:%s%s?%s" % (host, port, parsedurl.path, parsedurl.query) | ||
543 | conn = httplib.HTTPConnection(host, port) | ||
544 | conn.request("GET", parsedurl.path + "?" + parsedurl.query) | ||
545 | r = conn.getresponse() | ||
546 | if r.status != 200: | ||
547 | raise Exception("Failed to read " + parsedurl.path + ": %d %s" % (r.status, r.reason)) | ||
548 | return json.loads(r.read()) | ||
549 | |||
550 | # verify we can get the basic api | ||
551 | try: | ||
552 | apilinks = _get_json_response() | ||
553 | except: | ||
554 | print "EE: could not connect to %s, skipping update" % self.apiurl | ||
555 | return | ||
556 | |||
557 | # update branches; only those that we already have names listed in the database | ||
558 | whitelist_branch_names = map(lambda x: x.name, Branch.objects.all()) | ||
559 | |||
560 | branches_info = _get_json_response(apilinks['branches'] | ||
561 | + "?filter=name:%s" % "OR".join(whitelist_branch_names)) | ||
562 | for bi in branches_info: | ||
563 | b, created = Branch.objects.get_or_create(layer_source = self, name = bi['name']) | ||
564 | b.up_id = bi['id'] | ||
565 | b.up_date = bi['updated'] | ||
566 | b.name = bi['name'] | ||
567 | b.bitbake_branch = bi['bitbake_branch'] | ||
568 | b.short_description = bi['short_description'] | ||
569 | b.save() | ||
570 | |||
571 | # update layers | ||
572 | layers_info = _get_json_response(apilinks['layerItems']) | ||
573 | for li in layers_info: | ||
574 | l, created = Layer.objects.get_or_create(layer_source = self, up_id = li['id']) | ||
575 | l.up_date = li['updated'] | ||
576 | l.name = li['name'] | ||
577 | l.vcs_url = li['vcs_url'] | ||
578 | l.vcs_web_file_base_url = li['vcs_web_file_base_url'] | ||
579 | l.summary = li['summary'] | ||
580 | l.description = li['description'] | ||
581 | l.save() | ||
582 | |||
583 | # update layerbranches/layer_versions | ||
584 | layerbranches_info = _get_json_response(apilinks['layerBranches'] | ||
585 | + "?filter=branch:%s" % "OR".join(map(lambda x: str(x.up_id), Branch.objects.filter(layer_source = self))) | ||
586 | ) | ||
587 | for lbi in layerbranches_info: | ||
588 | lv, created = Layer_Version.objects.get_or_create(layer_source = self, up_id = lbi['id']) | ||
589 | |||
590 | lv.up_date = lbi['updated'] | ||
591 | lv.layer = Layer.objects.get(layer_source = self, up_id = lbi['layer']) | ||
592 | lv.up_branch = Branch.objects.get(layer_source = self, up_id = lbi['branch']) | ||
593 | lv.branch = lbi['actual_branch'] | ||
594 | lv.commit = lbi['vcs_last_rev'] | ||
595 | lv.dirpath = lbi['vcs_subdir'] | ||
596 | lv.save() | ||
597 | |||
598 | |||
599 | # update machines | ||
600 | machines_info = _get_json_response(apilinks['machines'] | ||
601 | + "?filter=layerbranch:%s" % "OR".join(map(lambda x: str(x.up_id), Layer_Version.objects.filter(layer_source = self))) | ||
602 | ) | ||
603 | for mi in machines_info: | ||
604 | mo, created = Machine.objects.get_or_create(layer_source = self, up_id = mi['id']) | ||
605 | mo.up_date = mi['updated'] | ||
606 | mo.layer_version = Layer_Version.objects.get(layer_source = self, up_id = mi['layerbranch']) | ||
607 | mo.name = mi['name'] | ||
608 | mo.description = mi['description'] | ||
609 | mo.save() | ||
610 | |||
611 | # update recipes; paginate by layer version / layer branch | ||
612 | recipes_info = _get_json_response(apilinks['recipes'] | ||
613 | + "?filter=layerbranch:%s" % "OR".join(map(lambda x: str(x.up_id), Layer_Version.objects.filter(layer_source = self))) | ||
614 | ) | ||
615 | for ri in recipes_info: | ||
616 | ro, created = Recipe.objects.get_or_create(layer_source = self, up_id = ri['id']) | ||
617 | |||
618 | ro.up_date = ri['updated'] | ||
619 | ro.layer_version = Layer_Version.objects.get(layer_source = self, up_id = mi['layerbranch']) | ||
620 | |||
621 | ro.name = ri['pn'] | ||
622 | ro.version = ri['pv'] | ||
623 | ro.summary = ri['summary'] | ||
624 | ro.description = ri['description'] | ||
625 | ro.section = ri['section'] | ||
626 | ro.license = ri['license'] | ||
627 | ro.homepage = ri['homepage'] | ||
628 | ro.bugtracker = ri['bugtracker'] | ||
629 | ro.file_path = ri['filepath'] + ri['filename'] | ||
630 | ro.save() | ||
631 | |||
632 | pass | ||
633 | |||
634 | class BitbakeVersion(models.Model): | ||
635 | name = models.CharField(max_length=32, unique = True) | ||
636 | giturl = models.URLField() | ||
637 | branch = models.CharField(max_length=32) | ||
638 | dirpath = models.CharField(max_length=255) | ||
639 | |||
640 | |||
641 | class Release(models.Model): | ||
642 | name = models.CharField(max_length=32, unique = True) | ||
643 | description = models.CharField(max_length=255) | ||
644 | bitbake_version = models.ForeignKey(BitbakeVersion) | ||
645 | branch = models.CharField(max_length=32) | ||
646 | |||
647 | |||
648 | class ReleaseDefaultLayer(models.Model): | ||
649 | release = models.ForeignKey(Release) | ||
650 | layer = models.ForeignKey('Layer') | ||
651 | |||
652 | |||
653 | # Branch class is synced with layerindex.Branch, branches can only come from remote layer indexes | ||
654 | class Branch(models.Model): | ||
655 | layer_source = models.ForeignKey('LayerSource', null = True, default = True) | ||
656 | up_id = models.IntegerField(null = True, default = None) # id of branch in the source | ||
657 | up_date = models.DateTimeField(null = True, default = None) | ||
658 | |||
659 | name = models.CharField(max_length=50) | ||
660 | bitbake_branch = models.CharField(max_length=50, blank=True) | ||
661 | short_description = models.CharField(max_length=50, blank=True) | ||
662 | |||
663 | class Meta: | ||
664 | verbose_name_plural = "Branches" | ||
665 | unique_together = (('layer_source', 'name'),('layer_source', 'up_id')) | ||
666 | |||
667 | def __unicode__(self): | ||
668 | return self.name | ||
669 | |||
670 | |||
671 | # Layer class synced with layerindex.LayerItem | ||
672 | class Layer(models.Model): | ||
673 | layer_source = models.ForeignKey(LayerSource, null = True, default = None) # from where did we got this layer | ||
674 | up_id = models.IntegerField(null = True, default = None) # id of layer in the remote source | ||
675 | up_date = models.DateTimeField(null = True, default = None) | ||
676 | |||
677 | name = models.CharField(max_length=100) | ||
678 | local_path = models.FilePathField(max_length=255, null = True, default = None) | ||
679 | layer_index_url = models.URLField() | ||
680 | vcs_url = models.URLField(default = None, null = True) | ||
681 | vcs_web_file_base_url = models.URLField(null = True, default = None) | ||
682 | |||
683 | summary = models.CharField(max_length=200, help_text='One-line description of the layer', null = True, default = None) | ||
684 | description = models.TextField(null = True, default = None) | ||
685 | |||
686 | def __unicode__(self): | ||
687 | return "L " + self.name | ||
688 | |||
689 | class Meta: | ||
690 | unique_together = (("layer_source", "up_id"), ("layer_source", "name")) | ||
691 | |||
692 | |||
693 | # LayerCommit class is synced with layerindex.LayerBranch | ||
694 | class Layer_Version(models.Model): | ||
695 | search_allowed_fields = ["layer__name", "layer__summary",] | ||
696 | build = models.ForeignKey(Build, related_name='layer_version_build', default = None, null = True) | ||
697 | layer = models.ForeignKey(Layer, related_name='layer_version_layer') | ||
698 | |||
699 | layer_source = models.ForeignKey(LayerSource, null = True, default = None) # from where did we get this Layer Version | ||
700 | up_id = models.IntegerField(null = True, default = None) # id of layerbranch in the remote source | ||
701 | up_date = models.DateTimeField(null = True, default = None) | ||
702 | up_branch = models.ForeignKey(Branch, null = True, default = None) | ||
703 | |||
704 | branch = models.CharField(max_length=80) # LayerBranch.actual_branch | ||
705 | commit = models.CharField(max_length=100) # LayerBranch.vcs_last_rev | ||
706 | dirpath = models.CharField(max_length=255, null = True, default = None) # LayerBranch.vcs_subdir | ||
707 | priority = models.IntegerField(default = 0) # if -1, this is a default layer | ||
708 | |||
709 | def __unicode__(self): | ||
710 | return "LV " + str(self.layer) + " " + self.commit | ||
711 | |||
712 | class Meta: | ||
713 | unique_together = ("layer_source", "up_id") | ||
714 | |||
715 | class LayerVersionDependency(models.Model): | ||
716 | layer_source = models.ForeignKey(LayerSource, null = True, default = None) # from where did we got this layer | ||
717 | up_id = models.IntegerField(null = True, default = None) # id of layerbranch in the remote source | ||
718 | |||
719 | layer_version = models.ForeignKey(Layer_Version, related_name="dependencies") | ||
720 | depends_on = models.ForeignKey(Layer_Version, related_name="dependees") | ||
721 | |||
722 | class Meta: | ||
723 | unique_together = ("layer_source", "up_id") | ||
724 | |||
725 | class ProjectLayer(models.Model): | ||
726 | project = models.ForeignKey(Project) | ||
727 | layercommit = models.ForeignKey(Layer_Version, null=True) | ||
728 | optional = models.BooleanField(default = True) | ||
729 | |||
730 | class ProjectVariable(models.Model): | ||
731 | project = models.ForeignKey(Project) | ||
732 | name = models.CharField(max_length=100) | ||
733 | value = models.TextField(blank = True) | ||
734 | |||
735 | class Variable(models.Model): | ||
736 | search_allowed_fields = ['variable_name', 'variable_value', | ||
737 | 'vhistory__file_name', "description"] | ||
738 | build = models.ForeignKey(Build, related_name='variable_build') | ||
739 | variable_name = models.CharField(max_length=100) | ||
740 | variable_value = models.TextField(blank=True) | ||
741 | changed = models.BooleanField(default=False) | ||
742 | human_readable_name = models.CharField(max_length=200) | ||
743 | description = models.TextField(blank=True) | ||
744 | |||
745 | class VariableHistory(models.Model): | ||
746 | variable = models.ForeignKey(Variable, related_name='vhistory') | ||
747 | value = models.TextField(blank=True) | ||
748 | file_name = models.FilePathField(max_length=255) | ||
749 | line_number = models.IntegerField(null=True) | ||
750 | operation = models.CharField(max_length=64) | ||
751 | |||
752 | class HelpText(models.Model): | ||
753 | VARIABLE = 0 | ||
754 | HELPTEXT_AREA = ((VARIABLE, 'variable'), ) | ||
755 | |||
756 | build = models.ForeignKey(Build, related_name='helptext_build') | ||
757 | area = models.IntegerField(choices=HELPTEXT_AREA) | ||
758 | key = models.CharField(max_length=100) | ||
759 | text = models.TextField() | ||
760 | |||
761 | class LogMessage(models.Model): | ||
762 | INFO = 0 | ||
763 | WARNING = 1 | ||
764 | ERROR = 2 | ||
765 | |||
766 | LOG_LEVEL = ( (INFO, "info"), | ||
767 | (WARNING, "warn"), | ||
768 | (ERROR, "error") ) | ||
769 | |||
770 | build = models.ForeignKey(Build) | ||
771 | task = models.ForeignKey(Task, blank = True, null=True) | ||
772 | level = models.IntegerField(choices=LOG_LEVEL, default=INFO) | ||
773 | message=models.CharField(max_length=240) | ||
774 | pathname = models.FilePathField(max_length=255, blank=True) | ||
775 | lineno = models.IntegerField(null=True) | ||