diff options
| author | Dave Lerner <dave.lerner@windriver.com> | 2014-03-12 16:54:09 -0500 |
|---|---|---|
| committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2014-03-21 14:47:53 +0000 |
| commit | 4cdd56fff3471669ff2372f0d8bf15f9a287374a (patch) | |
| tree | 76b81b2cb87e3b3df9751d8487351b5dfb2c59a3 /bitbake/lib/toaster/toastergui/views.py | |
| parent | e94f0b4e8c2cbea92688975b1a45a061af48911e (diff) | |
| download | poky-4cdd56fff3471669ff2372f0d8bf15f9a287374a.tar.gz | |
bitbake: toaster: image information views
[YOCTO # 4346]
When a target image is selected, this commit adds to the toaster
project a two-tabbed page that shows
1) 'packages included' a table of packages included in the image
(see target.html), and
2) 'directory structure', the target image's file system directory
and detailed information showing the source of each file in the
directory table (see dirinfo.html).
The directory structure tab relies on the open source jQuery plugin
jtreetable which provides hierarchical table expansions and contractions
of the directory entry tables as the user drills down into directories.
A file of jtreetable styles that are compatible with other toaster styles
is provided included as css/jquery.treetable.theme.toaster.css. The
complete unaltered jtreetable plugin is added via a separate commit.
This work was developed base on the bugzilla specification number 4346
and the document "Design 1.1 Image information" attached to that report.
Whitespace and typo fixes from Alex Damian.
(Bitbake rev: 1ba9f310a8b4fd0952a95be86ab43ae27fe6d983)
Signed-off-by: Dave Lerner <dave.lerner@windriver.com>
Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'bitbake/lib/toaster/toastergui/views.py')
| -rw-r--r-- | bitbake/lib/toaster/toastergui/views.py | 258 |
1 files changed, 241 insertions, 17 deletions
diff --git a/bitbake/lib/toaster/toastergui/views.py b/bitbake/lib/toaster/toastergui/views.py index 9740ef38d1..97514cc0f6 100644 --- a/bitbake/lib/toaster/toastergui/views.py +++ b/bitbake/lib/toaster/toastergui/views.py | |||
| @@ -21,17 +21,18 @@ | |||
| 21 | 21 | ||
| 22 | import operator | 22 | import operator |
| 23 | 23 | ||
| 24 | from django.db.models import Q | 24 | from django.db.models import Q, Sum |
| 25 | from django.shortcuts import render, redirect | 25 | from django.shortcuts import render, redirect |
| 26 | from orm.models import Build, Target, Task, Layer, Layer_Version, Recipe, LogMessage, Variable | 26 | from orm.models import Build, Target, Task, Layer, Layer_Version, Recipe, LogMessage, Variable |
| 27 | from orm.models import Task_Dependency, Recipe_Dependency, Package, Package_File, Package_Dependency | 27 | from orm.models import Task_Dependency, Recipe_Dependency, Package, Package_File, Package_Dependency |
| 28 | from orm.models import Target_Installed_Package | 28 | from orm.models import Target_Installed_Package, Target_File |
| 29 | from django.views.decorators.cache import cache_control | 29 | from django.views.decorators.cache import cache_control |
| 30 | from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger | 30 | from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger |
| 31 | from django.http import HttpResponseBadRequest | 31 | from django.http import HttpResponseBadRequest |
| 32 | from django.utils import timezone | 32 | from django.utils import timezone |
| 33 | from datetime import timedelta | 33 | from datetime import timedelta |
| 34 | from django.utils import formats | 34 | from django.utils import formats |
| 35 | import json | ||
| 35 | 36 | ||
| 36 | def _build_page_range(paginator, index = 1): | 37 | def _build_page_range(paginator, index = 1): |
| 37 | try: | 38 | try: |
| @@ -163,7 +164,7 @@ def _get_search_results(search_term, queryset, model): | |||
| 163 | def _search_tuple(request, model): | 164 | def _search_tuple(request, model): |
| 164 | ordering_string, invalid = _validate_input(request.GET.get('orderby', ''), model) | 165 | ordering_string, invalid = _validate_input(request.GET.get('orderby', ''), model) |
| 165 | if invalid: | 166 | if invalid: |
| 166 | raise BaseException("Invalid ordering " + str(invalid)) | 167 | raise BaseException("Invalid ordering model:" + str(model) + str(invalid)) |
| 167 | 168 | ||
| 168 | filter_string, invalid = _validate_input(request.GET.get('filter', ''), model) | 169 | filter_string, invalid = _validate_input(request.GET.get('filter', ''), model) |
| 169 | if invalid: | 170 | if invalid: |
| @@ -284,8 +285,8 @@ def builds(request): | |||
| 284 | 'qhelp': "The date and time the build finished", | 285 | 'qhelp': "The date and time the build finished", |
| 285 | 'orderfield': _get_toggle_order(request, "completed_on", True), | 286 | 'orderfield': _get_toggle_order(request, "completed_on", True), |
| 286 | 'ordericon':_get_toggle_order_icon(request, "completed_on"), | 287 | 'ordericon':_get_toggle_order_icon(request, "completed_on"), |
| 287 | 'filter' : {'class' : 'completed_on', | 288 | 'filter' : {'class' : 'completed_on', |
| 288 | 'label': 'Show:', | 289 | 'label': 'Show:', |
| 289 | 'options' : [ | 290 | 'options' : [ |
| 290 | ("Today's builds", 'completed_on__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_with_search.filter(completed_on__gte=timezone.now().strftime("%Y-%m-%d")).count()), | 291 | ("Today's builds", 'completed_on__gte:'+timezone.now().strftime("%Y-%m-%d"), queryset_with_search.filter(completed_on__gte=timezone.now().strftime("%Y-%m-%d")).count()), |
| 291 | ("Yesterday's builds", 'completed_on__gte:'+(timezone.now()-timedelta(hours=24)).strftime("%Y-%m-%d"), queryset_with_search.filter(completed_on__gte=(timezone.now()-timedelta(hours=24)).strftime("%Y-%m-%d")).count()), | 292 | ("Yesterday's builds", 'completed_on__gte:'+(timezone.now()-timedelta(hours=24)).strftime("%Y-%m-%d"), queryset_with_search.filter(completed_on__gte=(timezone.now()-timedelta(hours=24)).strftime("%Y-%m-%d")).count()), |
| @@ -307,8 +308,8 @@ def builds(request): | |||
| 307 | 'qhelp': "How many errors were encountered during the build (if any)", | 308 | 'qhelp': "How many errors were encountered during the build (if any)", |
| 308 | 'orderfield': _get_toggle_order(request, "errors_no", True), | 309 | 'orderfield': _get_toggle_order(request, "errors_no", True), |
| 309 | 'ordericon':_get_toggle_order_icon(request, "errors_no"), | 310 | 'ordericon':_get_toggle_order_icon(request, "errors_no"), |
| 310 | 'filter' : {'class' : 'errors_no', | 311 | 'filter' : {'class' : 'errors_no', |
| 311 | 'label': 'Show:', | 312 | 'label': 'Show:', |
| 312 | 'options' : [ | 313 | 'options' : [ |
| 313 | ('Builds with errors', 'errors_no__gte:1', queryset_with_search.filter(errors_no__gte=1).count()), | 314 | ('Builds with errors', 'errors_no__gte:1', queryset_with_search.filter(errors_no__gte=1).count()), |
| 314 | ('Builds without errors', 'errors_no:0', queryset_with_search.filter(errors_no=0).count()), | 315 | ('Builds without errors', 'errors_no:0', queryset_with_search.filter(errors_no=0).count()), |
| @@ -319,8 +320,8 @@ def builds(request): | |||
| 319 | 'qhelp': "How many warnigns were encountered during the build (if any)", | 320 | 'qhelp': "How many warnigns were encountered during the build (if any)", |
| 320 | 'orderfield': _get_toggle_order(request, "warnings_no", True), | 321 | 'orderfield': _get_toggle_order(request, "warnings_no", True), |
| 321 | 'ordericon':_get_toggle_order_icon(request, "warnings_no"), | 322 | 'ordericon':_get_toggle_order_icon(request, "warnings_no"), |
| 322 | 'filter' : {'class' : 'warnings_no', | 323 | 'filter' : {'class' : 'warnings_no', |
| 323 | 'label': 'Show:', | 324 | 'label': 'Show:', |
| 324 | 'options' : [ | 325 | 'options' : [ |
| 325 | ('Builds with warnings','warnings_no__gte:1', queryset_with_search.filter(warnings_no__gte=1).count()), | 326 | ('Builds with warnings','warnings_no__gte:1', queryset_with_search.filter(warnings_no__gte=1).count()), |
| 326 | ('Builds without warnings','warnings_no:0', queryset_with_search.filter(warnings_no=0).count()), | 327 | ('Builds without warnings','warnings_no:0', queryset_with_search.filter(warnings_no=0).count()), |
| @@ -417,13 +418,236 @@ def recipe(request, build_id, recipe_id): | |||
| 417 | 418 | ||
| 418 | def target(request, build_id, target_id): | 419 | def target(request, build_id, target_id): |
| 419 | template = "target.html" | 420 | template = "target.html" |
| 420 | if Build.objects.filter(pk=build_id).count() == 0 : | 421 | mandatory_parameters = { 'count': 25, 'page' : 1, 'orderby':'name:+'}; |
| 421 | return redirect(builds) | 422 | retval = _verify_parameters( request.GET, mandatory_parameters ) |
| 422 | context = { | 423 | if retval: |
| 423 | 'build' : Build.objects.filter(pk=build_id)[0], | 424 | return _redirect_parameters( 'target', request.GET, mandatory_parameters, build_id = build_id, target_id = target_id) |
| 424 | } | 425 | (filter_string, search_term, ordering_string) = _search_tuple(request, Package) |
| 426 | |||
| 427 | # FUTURE: get rid of nested sub-queries replacing with ManyToMany field | ||
| 428 | queryset = Package.objects.filter(id__in=Target_Installed_Package.objects.filter(target_id=target_id).values('package_id')) | ||
| 429 | packages_sum = queryset.aggregate(Sum('installed_size')) | ||
| 430 | queryset = _get_queryset(Package, queryset, filter_string, search_term, ordering_string) | ||
| 431 | packages = _build_page_range(Paginator(queryset, request.GET.get('count', 25)),request.GET.get('page', 1)) | ||
| 432 | context = { 'build': Build.objects.filter(pk=build_id)[0], | ||
| 433 | 'target': Target.objects.filter(pk=target_id)[0], | ||
| 434 | 'objects': packages, | ||
| 435 | 'packages_sum' : packages_sum['installed_size__sum'], | ||
| 436 | 'object_search_display': "packages included", | ||
| 437 | 'tablecols':[ | ||
| 438 | { | ||
| 439 | 'name':'Package', | ||
| 440 | 'qhelp':'Packaged output resulting from building a recipe and included in this image', | ||
| 441 | 'orderfield': _get_toggle_order(request, "name"), | ||
| 442 | 'ordericon':_get_toggle_order_icon(request, "name"), | ||
| 443 | }, | ||
| 444 | { | ||
| 445 | 'name':'Package version', | ||
| 446 | 'qhelp':'The package version and revision', | ||
| 447 | }, | ||
| 448 | { | ||
| 449 | 'name':'Size', | ||
| 450 | 'qhelp':'The size of the package', | ||
| 451 | 'orderfield': _get_toggle_order(request, "size"), | ||
| 452 | 'ordericon':_get_toggle_order_icon(request, "size"), | ||
| 453 | 'clclass': 'package_size', | ||
| 454 | 'hidden' : 0, | ||
| 455 | }, | ||
| 456 | { | ||
| 457 | 'name':'Size over total (%)', | ||
| 458 | 'qhelp':'Proportion of the overall included package size represented by this package', | ||
| 459 | 'orderfield': _get_toggle_order(request, "size"), | ||
| 460 | 'ordericon':_get_toggle_order_icon(request, "size"), | ||
| 461 | 'clclass': 'size_over_total', | ||
| 462 | 'hidden' : 1, | ||
| 463 | }, | ||
| 464 | { | ||
| 465 | 'name':'License', | ||
| 466 | 'qhelp':'The license under which the package is distributed. Separate license names using | (pipe) means there is a choice between licenses. Separate license names using & (ampersand) means multiple licenses exist that cover different parts of the source', | ||
| 467 | 'orderfield': _get_toggle_order(request, "license"), | ||
| 468 | 'ordericon':_get_toggle_order_icon(request, "license"), | ||
| 469 | 'clclass': 'license', | ||
| 470 | 'hidden' : 1, | ||
| 471 | }, | ||
| 472 | { | ||
| 473 | 'name':'Dependencies', | ||
| 474 | 'qhelp':"Package runtime dependencies (other packages)", | ||
| 475 | 'clclass': 'depends', | ||
| 476 | 'hidden' : 0, | ||
| 477 | }, | ||
| 478 | { | ||
| 479 | 'name':'Reverse dependencies', | ||
| 480 | 'qhelp':'Package run-time reverse dependencies (i.e. which other packages depend on this package', | ||
| 481 | 'clclass': 'brought_in_by', | ||
| 482 | 'hidden' : 0, | ||
| 483 | }, | ||
| 484 | { | ||
| 485 | 'name':'Recipe', | ||
| 486 | 'qhelp':'The name of the recipe building the package', | ||
| 487 | 'orderfield': _get_toggle_order(request, "recipe__name"), | ||
| 488 | 'ordericon':_get_toggle_order_icon(request, "recipe__name"), | ||
| 489 | 'clclass': 'recipe_name', | ||
| 490 | 'hidden' : 0, | ||
| 491 | }, | ||
| 492 | { | ||
| 493 | 'name':'Recipe version', | ||
| 494 | 'qhelp':'Version and revision of the recipe building the package', | ||
| 495 | 'clclass': 'recipe_version', | ||
| 496 | 'hidden' : 1, | ||
| 497 | }, | ||
| 498 | { | ||
| 499 | 'name':'Layer', | ||
| 500 | 'qhelp':'The name of the layer providing the recipe that builds the package', | ||
| 501 | 'orderfield': _get_toggle_order(request, "recipe__layer_version__layer__name"), | ||
| 502 | 'ordericon':_get_toggle_order_icon(request, "recipe__layer_version__layer__name"), | ||
| 503 | 'clclass': 'layer_name', | ||
| 504 | 'hidden' : 1, | ||
| 505 | }, | ||
| 506 | { | ||
| 507 | 'name':'Layer branch', | ||
| 508 | 'qhelp':'The Git branch of the layer providing the recipe that builds the package', | ||
| 509 | 'orderfield': _get_toggle_order(request, "recipe__layer_version__branch"), | ||
| 510 | 'ordericon':_get_toggle_order_icon(request, "recipe__layer_version__branch"), | ||
| 511 | 'clclass': 'layer_branch', | ||
| 512 | 'hidden' : 1, | ||
| 513 | }, | ||
| 514 | { | ||
| 515 | 'name':'Layer commit', | ||
| 516 | 'qhelp':'The Git commit of the layer providing the recipe that builds the package', | ||
| 517 | 'clclass': 'layer_commit', | ||
| 518 | 'hidden' : 1, | ||
| 519 | }, | ||
| 520 | { | ||
| 521 | 'name':'Layer directory', | ||
| 522 | 'qhelp':'Location in disk of the layer providing the recipe that builds the package', | ||
| 523 | 'orderfield': _get_toggle_order(request, "recipe__layer_version__layer__local_path"), | ||
| 524 | 'ordericon':_get_toggle_order_icon(request, "recipe__layer_version__layer__local_path"), | ||
| 525 | 'clclass': 'layer_directory', | ||
| 526 | 'hidden' : 1, | ||
| 527 | }, | ||
| 528 | ] | ||
| 529 | } | ||
| 530 | |||
| 425 | return render(request, template, context) | 531 | return render(request, template, context) |
| 426 | 532 | ||
| 533 | from django.core.serializers.json import DjangoJSONEncoder | ||
| 534 | from django.http import HttpResponse | ||
| 535 | def dirinfo_ajax(request, build_id, target_id): | ||
| 536 | top = request.GET.get('start', '/') | ||
| 537 | return HttpResponse(_get_dir_entries(build_id, target_id, top)) | ||
| 538 | |||
| 539 | from django.utils.functional import Promise | ||
| 540 | from django.utils.encoding import force_text | ||
| 541 | class LazyEncoder(json.JSONEncoder): | ||
| 542 | def default(self, obj): | ||
| 543 | if isinstance(obj, Promise): | ||
| 544 | return force_text(obj) | ||
| 545 | return super(LazyEncoder, self).default(obj) | ||
| 546 | |||
| 547 | from toastergui.templatetags.projecttags import filtered_filesizeformat | ||
| 548 | from django import template | ||
| 549 | import os | ||
| 550 | def _get_dir_entries(build_id, target_id, start): | ||
| 551 | node_str = { | ||
| 552 | Target_File.ITYPE_REGULAR : '-', | ||
| 553 | Target_File.ITYPE_DIRECTORY : 'd', | ||
| 554 | Target_File.ITYPE_SYMLINK : 'l', | ||
| 555 | Target_File.ITYPE_SOCKET : 's', | ||
| 556 | Target_File.ITYPE_FIFO : 'p', | ||
| 557 | Target_File.ITYPE_CHARACTER : 'c', | ||
| 558 | Target_File.ITYPE_BLOCK : 'b', | ||
| 559 | } | ||
| 560 | response = [] | ||
| 561 | objects = Target_File.objects.filter(target__exact=target_id, directory__path=start) | ||
| 562 | target_packages = Target_Installed_Package.objects.filter(target__exact=target_id).values_list('package_id', flat=True) | ||
| 563 | for o in objects: | ||
| 564 | # exclude root inode '/' | ||
| 565 | if o.path == '/': | ||
| 566 | continue | ||
| 567 | try: | ||
| 568 | entry = {} | ||
| 569 | entry['parent'] = start | ||
| 570 | entry['name'] = os.path.basename(o.path) | ||
| 571 | entry['fullpath'] = o.path | ||
| 572 | |||
| 573 | # set defaults, not all dentries have packages | ||
| 574 | entry['installed_package'] = None | ||
| 575 | entry['package_id'] = None | ||
| 576 | entry['package'] = None | ||
| 577 | entry['link_to'] = None | ||
| 578 | if o.inodetype == Target_File.ITYPE_DIRECTORY: | ||
| 579 | entry['isdir'] = 1 | ||
| 580 | # is there content in directory | ||
| 581 | entry['childcount'] = Target_File.objects.filter(directory__path=o.path).all().count() | ||
| 582 | else: | ||
| 583 | entry['isdir'] = 0 | ||
| 584 | |||
| 585 | # resolve the file to get the package from the resolved file | ||
| 586 | resolved_id = o.sym_target_id | ||
| 587 | resolved_path = o.path | ||
| 588 | if target_packages.count(): | ||
| 589 | while resolved_id != "" and resolved_id != None: | ||
| 590 | tf = Target_File.objects.get(pk=resolved_id) | ||
| 591 | resolved_path = tf.path | ||
| 592 | resolved_id = tf.sym_target_id | ||
| 593 | |||
| 594 | thisfile=Package_File.objects.all().filter(path__exact=resolved_path, package_id__in=target_packages) | ||
| 595 | if thisfile.count(): | ||
| 596 | p = Package.objects.get(pk=thisfile[0].package_id) | ||
| 597 | entry['installed_package'] = p.installed_name | ||
| 598 | entry['package_id'] = str(p.id) | ||
| 599 | entry['package'] = p.name | ||
| 600 | # don't use resolved path from above, show immediate link-to | ||
| 601 | if o.sym_target_id != "" and o.sym_target_id != None: | ||
| 602 | entry['link_to'] = Target_File.objects.get(pk=o.sym_target_id).path | ||
| 603 | t = template.Template('{% load projecttags %} {{ size|filtered_filesizeformat }}') | ||
| 604 | c = template.Context({'size': o.size}) | ||
| 605 | entry['size'] = str(t.render(c)) | ||
| 606 | if entry['link_to'] != None: | ||
| 607 | entry['permission'] = node_str[o.inodetype] + o.permission | ||
| 608 | else: | ||
| 609 | entry['permission'] = node_str[o.inodetype] + o.permission | ||
| 610 | entry['owner'] = o.owner | ||
| 611 | entry['group'] = o.group | ||
| 612 | response.append(entry) | ||
| 613 | |||
| 614 | except: | ||
| 615 | pass | ||
| 616 | |||
| 617 | # sort by directories first, then by name | ||
| 618 | rsorted = sorted(response, key=lambda entry : entry['name']) | ||
| 619 | rsorted = sorted(rsorted, key=lambda entry : entry['isdir'], reverse=True) | ||
| 620 | return json.dumps(rsorted, cls=LazyEncoder) | ||
| 621 | |||
| 622 | def dirinfo(request, build_id, target_id, file_path=None): | ||
| 623 | template = "dirinfo.html" | ||
| 624 | objects = _get_dir_entries(build_id, target_id, '/') | ||
| 625 | packages_sum = Package.objects.filter(id__in=Target_Installed_Package.objects.filter(target_id=target_id).values('package_id')).aggregate(Sum('installed_size')) | ||
| 626 | dir_list = None | ||
| 627 | if file_path != None: | ||
| 628 | """ | ||
| 629 | Link from the included package detail file list page and is | ||
| 630 | requesting opening the dir info to a specific file path. | ||
| 631 | Provide the list of directories to expand and the full path to | ||
| 632 | highlight in the page. | ||
| 633 | """ | ||
| 634 | # Aassume target's path separator matches host's, that is, os.sep | ||
| 635 | sep = os.sep | ||
| 636 | dir_list = [] | ||
| 637 | head = file_path | ||
| 638 | while head != sep: | ||
| 639 | (head,tail) = os.path.split(head) | ||
| 640 | if head != sep: | ||
| 641 | dir_list.insert(0, head) | ||
| 642 | |||
| 643 | context = { 'build': Build.objects.filter(pk=build_id)[0], | ||
| 644 | 'target': Target.objects.filter(pk=target_id)[0], | ||
| 645 | 'packages_sum': packages_sum['installed_size__sum'], | ||
| 646 | 'objects': objects, | ||
| 647 | 'dir_list': dir_list, | ||
| 648 | 'file_path': file_path, | ||
| 649 | } | ||
| 650 | return render(request, template, context) | ||
| 427 | 651 | ||
| 428 | def _find_task_dep(task): | 652 | def _find_task_dep(task): |
| 429 | tp = [] | 653 | tp = [] |
| @@ -593,7 +817,7 @@ def tasks_common(request, build_id, variant): | |||
| 593 | } | 817 | } |
| 594 | 818 | ||
| 595 | } | 819 | } |
| 596 | #if 'tasks' == variant: tc_cache['hidden']='0'; | 820 | #if 'tasks' == variant: tc_cache['hidden']='0'; |
| 597 | tc_time={ | 821 | tc_time={ |
| 598 | 'name':'Time (secs)', | 822 | 'name':'Time (secs)', |
| 599 | 'qhelp':'How long it took the task to finish, expressed in seconds', | 823 | 'qhelp':'How long it took the task to finish, expressed in seconds', |
| @@ -796,7 +1020,7 @@ def configvars(request, build_id): | |||
| 796 | # remove duplicate records from multiple search hits in the VariableHistory table | 1020 | # remove duplicate records from multiple search hits in the VariableHistory table |
| 797 | queryset = queryset.distinct() | 1021 | queryset = queryset.distinct() |
| 798 | # remove records where the value is empty AND there are no history files | 1022 | # remove records where the value is empty AND there are no history files |
| 799 | queryset = queryset.exclude(variable_value='',vhistory__file_name__isnull=True) | 1023 | queryset = queryset.exclude(variable_value='',vhistory__file_name__isnull=True) |
| 800 | 1024 | ||
| 801 | variables = _build_page_range(Paginator(queryset, request.GET.get('count', 50)), request.GET.get('page', 1)) | 1025 | variables = _build_page_range(Paginator(queryset, request.GET.get('count', 50)), request.GET.get('page', 1)) |
| 802 | 1026 | ||
| @@ -811,7 +1035,7 @@ def configvars(request, build_id): | |||
| 811 | file_filter += 'conf/distro/' | 1035 | file_filter += 'conf/distro/' |
| 812 | if filter_string.find('/bitbake.conf') > 0: | 1036 | if filter_string.find('/bitbake.conf') > 0: |
| 813 | file_filter += '/bitbake.conf' | 1037 | file_filter += '/bitbake.conf' |
| 814 | 1038 | ||
| 815 | context = { | 1039 | context = { |
| 816 | 'objectname': 'configvars', | 1040 | 'objectname': 'configvars', |
| 817 | 'object_search_display':'variables', | 1041 | 'object_search_display':'variables', |
