summaryrefslogtreecommitdiffstats
path: root/bitbake/lib
diff options
context:
space:
mode:
authorAlexandru DAMIAN <alexandru.damian@intel.com>2015-02-02 17:57:36 +0000
committerRichard Purdie <richard.purdie@linuxfoundation.org>2015-02-10 23:07:48 +0000
commit202d808f890b03958cd6873486e6a37f3f437098 (patch)
tree7b88e2cb5ab7b85648821bec4d2f4c16d290207f /bitbake/lib
parentb741c9a4b4047439c6c5428e36a72c22a784feda (diff)
downloadpoky-202d808f890b03958cd6873486e6a37f3f437098.tar.gz
bitbake: toastergui: improvements in layer selection logic
This patch clearers and bring fixes for the layer selection logic in order to enable information collected during build to be used in configuring projects, specifically targeting the recipes learned through the building process. The patch also adds tests to verify the layer selection logic. [YOCTO #7189] (Bitbake rev: f0faba8ef0f08c98ac4bddf5b3954d540820d215) Signed-off-by: Alexandru DAMIAN <alexandru.damian@intel.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'bitbake/lib')
-rw-r--r--bitbake/lib/toaster/bldcontrol/bbcontroller.py13
-rw-r--r--bitbake/lib/toaster/bldcontrol/localhostbecontroller.py54
-rw-r--r--bitbake/lib/toaster/bldcontrol/sshbecontroller.py2
-rw-r--r--bitbake/lib/toaster/orm/models.py23
-rw-r--r--bitbake/lib/toaster/orm/tests.py131
-rw-r--r--bitbake/lib/toaster/toastergui/templates/targets.html14
-rwxr-xr-xbitbake/lib/toaster/toastergui/views.py35
-rw-r--r--bitbake/lib/toaster/toastermain/urls.py5
8 files changed, 224 insertions, 53 deletions
diff --git a/bitbake/lib/toaster/bldcontrol/bbcontroller.py b/bitbake/lib/toaster/bldcontrol/bbcontroller.py
index cf3f1fde75..42675d3fc6 100644
--- a/bitbake/lib/toaster/bldcontrol/bbcontroller.py
+++ b/bitbake/lib/toaster/bldcontrol/bbcontroller.py
@@ -81,19 +81,6 @@ def getBuildEnvironmentController(**kwargs):
81 raise Exception("FIXME: Implement BEC for type %s" % str(be.betype)) 81 raise Exception("FIXME: Implement BEC for type %s" % str(be.betype))
82 82
83 83
84def _get_git_clonedirectory(url, branch):
85 """ Utility that returns the last component of a git path as directory
86 """
87 import re
88 components = re.split(r'[:\.\/]', url)
89 base = components[-2] if components[-1] == "git" else components[-1]
90
91 if branch != "HEAD":
92 return "_%s_%s.toaster_cloned" % (base, branch)
93
94 return base
95
96
97class BuildEnvironmentController(object): 84class BuildEnvironmentController(object):
98 """ BuildEnvironmentController (BEC) is the abstract class that defines the operations that MUST 85 """ BuildEnvironmentController (BEC) is the abstract class that defines the operations that MUST
99 or SHOULD be supported by a Build Environment. It is used to establish the framework, and must 86 or SHOULD be supported by a Build Environment. It is used to establish the framework, and must
diff --git a/bitbake/lib/toaster/bldcontrol/localhostbecontroller.py b/bitbake/lib/toaster/bldcontrol/localhostbecontroller.py
index 47708d169a..005c464314 100644
--- a/bitbake/lib/toaster/bldcontrol/localhostbecontroller.py
+++ b/bitbake/lib/toaster/bldcontrol/localhostbecontroller.py
@@ -30,7 +30,7 @@ import subprocess
30 30
31from toastermain import settings 31from toastermain import settings
32 32
33from bbcontroller import BuildEnvironmentController, ShellCmdException, BuildSetupException, _get_git_clonedirectory 33from bbcontroller import BuildEnvironmentController, ShellCmdException, BuildSetupException
34 34
35import logging 35import logging
36logger = logging.getLogger("toaster") 36logger = logging.getLogger("toaster")
@@ -54,6 +54,7 @@ class LocalhostBEController(BuildEnvironmentController):
54 if cwd is None: 54 if cwd is None:
55 cwd = self.be.sourcedir 55 cwd = self.be.sourcedir
56 56
57 #logger.debug("lbc_shellcmmd: (%s) %s" % (cwd, command))
57 p = subprocess.Popen(command, cwd = cwd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 58 p = subprocess.Popen(command, cwd = cwd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
58 (out,err) = p.communicate() 59 (out,err) = p.communicate()
59 p.wait() 60 p.wait()
@@ -62,7 +63,7 @@ class LocalhostBEController(BuildEnvironmentController):
62 err = "command: %s \n%s" % (command, out) 63 err = "command: %s \n%s" % (command, out)
63 else: 64 else:
64 err = "command: %s \n%s" % (command, err) 65 err = "command: %s \n%s" % (command, err)
65 #logger.debug("localhostbecontroller: shellcmd error %s" % err) 66 #logger.warn("localhostbecontroller: shellcmd error %s" % err)
66 raise ShellCmdException(err) 67 raise ShellCmdException(err)
67 else: 68 else:
68 #logger.debug("localhostbecontroller: shellcmd success") 69 #logger.debug("localhostbecontroller: shellcmd success")
@@ -106,19 +107,12 @@ class LocalhostBEController(BuildEnvironmentController):
106 107
107 logger.debug("localhostbecontroller: running the listener at %s" % own_bitbake) 108 logger.debug("localhostbecontroller: running the listener at %s" % own_bitbake)
108 109
109 try:
110 os.remove(os.path.join(self.be.builddir, "toaster_ui.log"))
111 except OSError as e:
112 import errno
113 if e.errno != errno.ENOENT:
114 raise
115
116 110
117 cmd = "bash -c \"source %s/oe-init-build-env %s && bitbake --read conf/toaster-pre.conf --postread conf/toaster.conf --server-only -t xmlrpc -B 0.0.0.0:0 && DATABASE_URL=%s BBSERVER=0.0.0.0:-1 daemon -d -i -D %s -o toaster_ui.log -- %s --observe-only -u toasterui &\"" % (self.pokydirname, self.be.builddir, 111 cmd = "bash -c \"source %s/oe-init-build-env %s && bitbake --read conf/toaster-pre.conf --postread conf/toaster.conf --server-only -t xmlrpc -B 0.0.0.0:0 && DATABASE_URL=%s BBSERVER=0.0.0.0:-1 daemon -d -i -D %s -o toaster_ui.log -- %s --observe-only -u toasterui &\"" % (self.pokydirname, self.be.builddir,
118 self.dburl, self.be.builddir, own_bitbake) 112 self.dburl, self.be.builddir, own_bitbake)
119 logger.debug("fullcommand |%s| " % cmd)
120 port = "-1" 113 port = "-1"
121 for i in self._shellcmd(cmd).split("\n"): 114 cmdoutput = self._shellcmd(cmd)
115 for i in cmdoutput.split("\n"):
122 if i.startswith("Bitbake server address"): 116 if i.startswith("Bitbake server address"):
123 port = i.split(" ")[-1] 117 port = i.split(" ")[-1]
124 logger.debug("localhostbecontroller: Found bitbake server port %s" % port) 118 logger.debug("localhostbecontroller: Found bitbake server port %s" % port)
@@ -132,10 +126,17 @@ class LocalhostBEController(BuildEnvironmentController):
132 return True 126 return True
133 return False 127 return False
134 128
135 while not _toaster_ui_started(os.path.join(self.be.builddir, "toaster_ui.log")): 129 retries = 0
130 started = False
131 while not started and retries < 10:
132 started = _toaster_ui_started(os.path.join(self.be.builddir, "toaster_ui.log"))
136 import time 133 import time
137 logger.debug("localhostbecontroller: Waiting bitbake server to start") 134 logger.debug("localhostbecontroller: Waiting bitbake server to start")
138 time.sleep(0.5) 135 time.sleep(0.5)
136 retries += 1
137
138 if not started:
139 raise BuildSetupException("localhostbecontroller: Bitbake server did not start in 5 seconds, aborting (Error: '%s')" % (cmdoutput))
139 140
140 logger.debug("localhostbecontroller: Started bitbake server") 141 logger.debug("localhostbecontroller: Started bitbake server")
141 142
@@ -163,6 +164,25 @@ class LocalhostBEController(BuildEnvironmentController):
163 self.be.save() 164 self.be.save()
164 logger.debug("localhostbecontroller: Stopped bitbake server") 165 logger.debug("localhostbecontroller: Stopped bitbake server")
165 166
167 def getGitCloneDirectory(self, url, branch):
168 """ Utility that returns the last component of a git path as directory
169 """
170 import re
171 components = re.split(r'[:\.\/]', url)
172 base = components[-2] if components[-1] == "git" else components[-1]
173
174 if branch != "HEAD":
175 return "_%s_%s.toaster_cloned" % (base, branch)
176
177
178 # word of attention; this is a localhost-specific issue; only on the localhost we expect to have "HEAD" releases
179 # which _ALWAYS_ means the current poky checkout
180 from os.path import dirname as DN
181 local_checkout_path = DN(DN(DN(DN(DN(os.path.abspath(__file__))))))
182 #logger.debug("localhostbecontroller: using HEAD checkout in %s" % local_checkout_path)
183 return local_checkout_path
184
185
166 def setLayers(self, bitbakes, layers): 186 def setLayers(self, bitbakes, layers):
167 """ a word of attention: by convention, the first layer for any build will be poky! """ 187 """ a word of attention: by convention, the first layer for any build will be poky! """
168 188
@@ -208,15 +228,17 @@ class LocalhostBEController(BuildEnvironmentController):
208 228
209 layerlist = [] 229 layerlist = []
210 230
231
211 # 3. checkout the repositories 232 # 3. checkout the repositories
212 for giturl, commit in gitrepos.keys(): 233 for giturl, commit in gitrepos.keys():
213 localdirname = os.path.join(self.be.sourcedir, _get_git_clonedirectory(giturl, commit)) 234 localdirname = os.path.join(self.be.sourcedir, self.getGitCloneDirectory(giturl, commit))
214 logger.debug("localhostbecontroller: giturl %s:%s checking out in current directory %s" % (giturl, commit, localdirname)) 235 logger.debug("localhostbecontroller: giturl %s:%s checking out in current directory %s" % (giturl, commit, localdirname))
215 236
216 # make sure our directory is a git repository 237 # make sure our directory is a git repository
217 if os.path.exists(localdirname): 238 if os.path.exists(localdirname):
218 if not giturl in self._shellcmd("git remote -v", localdirname): 239 localremotes = self._shellcmd("git remote -v", localdirname)
219 raise BuildSetupException("Existing git repository at %s, but with different remotes (not '%s'). Aborting." % (localdirname, giturl)) 240 if not giturl in localremotes:
241 raise BuildSetupException("Existing git repository at %s, but with different remotes ('%s', expected '%s'). Toaster will not continue out of fear of damaging something." % (localdirname, ", ".join(localremotes.split("\n")), giturl))
220 else: 242 else:
221 if giturl in cached_layers: 243 if giturl in cached_layers:
222 logger.debug("localhostbecontroller git-copying %s to %s" % (cached_layers[giturl], localdirname)) 244 logger.debug("localhostbecontroller git-copying %s to %s" % (cached_layers[giturl], localdirname))
@@ -230,7 +252,7 @@ class LocalhostBEController(BuildEnvironmentController):
230 # branch magic name "HEAD" will inhibit checkout 252 # branch magic name "HEAD" will inhibit checkout
231 if commit != "HEAD": 253 if commit != "HEAD":
232 logger.debug("localhostbecontroller: checking out commit %s to %s " % (commit, localdirname)) 254 logger.debug("localhostbecontroller: checking out commit %s to %s " % (commit, localdirname))
233 self._shellcmd("git fetch --all && git checkout \"%s\"" % commit , localdirname) 255 self._shellcmd("git fetch --all && git checkout \"%s\" && git pull --rebase" % (commit) , localdirname)
234 256
235 # take the localdirname as poky dir if we can find the oe-init-build-env 257 # take the localdirname as poky dir if we can find the oe-init-build-env
236 if self.pokydirname is None and os.path.exists(os.path.join(localdirname, "oe-init-build-env")): 258 if self.pokydirname is None and os.path.exists(os.path.join(localdirname, "oe-init-build-env")):
diff --git a/bitbake/lib/toaster/bldcontrol/sshbecontroller.py b/bitbake/lib/toaster/bldcontrol/sshbecontroller.py
index be797c9486..11ad08d440 100644
--- a/bitbake/lib/toaster/bldcontrol/sshbecontroller.py
+++ b/bitbake/lib/toaster/bldcontrol/sshbecontroller.py
@@ -29,7 +29,7 @@ import subprocess
29 29
30from toastermain import settings 30from toastermain import settings
31 31
32from bbcontroller import BuildEnvironmentController, ShellCmdException, BuildSetupException, _get_git_clonedirectory 32from bbcontroller import BuildEnvironmentController, ShellCmdException, BuildSetupException
33 33
34def DN(path): 34def DN(path):
35 return "/".join(path.split("/")[0:-1]) 35 return "/".join(path.split("/")[0:-1])
diff --git a/bitbake/lib/toaster/orm/models.py b/bitbake/lib/toaster/orm/models.py
index 5eff955453..454f3692be 100644
--- a/bitbake/lib/toaster/orm/models.py
+++ b/bitbake/lib/toaster/orm/models.py
@@ -103,7 +103,7 @@ class Project(models.Model):
103 if release == None: 103 if release == None:
104 release = self.release 104 release = self.release
105 # layers on the same branch or layers specifically set for this project 105 # layers on the same branch or layers specifically set for this project
106 queryset = Layer_Version.objects.filter((Q(up_branch__name = release.branch_name) & Q(project = None)) | Q(project = self)) 106 queryset = Layer_Version.objects.filter((Q(up_branch__name = release.branch_name) & Q(project = None)) | Q(project = self) | Q(build__project = self))
107 if layer_name is not None: 107 if layer_name is not None:
108 # we select only a layer name 108 # we select only a layer name
109 queryset = queryset.filter(layer__name = layer_name) 109 queryset = queryset.filter(layer__name = layer_name)
@@ -952,11 +952,24 @@ class Layer_Version(models.Model):
952 """ Returns an ordered layerversion list that satisfies a LayerVersionDependency using the layer name and the current Project Releases' LayerSource priority """ 952 """ Returns an ordered layerversion list that satisfies a LayerVersionDependency using the layer name and the current Project Releases' LayerSource priority """
953 def _get_ls_priority(ls): 953 def _get_ls_priority(ls):
954 try: 954 try:
955 # if there is no layer source, we have minus infinite priority, as we don't want this layer selected
956 if ls == None:
957 return -10000
955 return ls.releaselayersourcepriority_set.get(release=project.release).priority 958 return ls.releaselayersourcepriority_set.get(release=project.release).priority
956 except ReleaseLayerSourcePriority.DoesNotExist: 959 except ReleaseLayerSourcePriority.DoesNotExist:
957 raise 960 raise
961
962 # layers created for this project, or coming from a build inthe project
963 query = Q(project = project) | Q(build__project = project)
964 if self.up_branch is not None:
965 # the same up_branch name
966 query |= Q(up_branch__name=self.up_branch.name)
967 else:
968 # or we have a layer in the project that's similar to mine (See the layer.name constraint below)
969 query |= Q(projectlayer__project=project)
970
958 return sorted( 971 return sorted(
959 Layer_Version.objects.filter( layer__name = self.layer.name, up_branch__name = self.up_branch.name ), 972 Layer_Version.objects.filter(layer__name = self.layer.name).filter(query).select_related('layer_source', 'layer'),
960 key = lambda x: _get_ls_priority(x.layer_source), 973 key = lambda x: _get_ls_priority(x.layer_source),
961 reverse = True) 974 reverse = True)
962 975
@@ -965,10 +978,12 @@ class Layer_Version(models.Model):
965 return self.commit 978 return self.commit
966 if self.branch is not None and len(self.branch) > 0: 979 if self.branch is not None and len(self.branch) > 0:
967 return self.branch 980 return self.branch
968 return self.up_branch.name 981 if self.up_branch is not None:
982 return self.up_branch.name
983 raise Exception("Cannot determine the vcs_reference for layer version %s" % vars(self))
969 984
970 def __unicode__(self): 985 def __unicode__(self):
971 return str(self.layer) + " (" + self.commit +")" 986 return str(self.layer) + "(%s,%s)" % (self.get_vcs_reference(), self.build.project if self.build is not None else "None")
972 987
973 class Meta: 988 class Meta:
974 unique_together = ("layer_source", "up_id") 989 unique_together = ("layer_source", "up_id")
diff --git a/bitbake/lib/toaster/orm/tests.py b/bitbake/lib/toaster/orm/tests.py
index b965d8e50e..7b1b9633f9 100644
--- a/bitbake/lib/toaster/orm/tests.py
+++ b/bitbake/lib/toaster/orm/tests.py
@@ -2,6 +2,12 @@ from django.test import TestCase
2from orm.models import LocalLayerSource, LayerIndexLayerSource, ImportedLayerSource, LayerSource 2from orm.models import LocalLayerSource, LayerIndexLayerSource, ImportedLayerSource, LayerSource
3from orm.models import Branch 3from orm.models import Branch
4 4
5from orm.models import Project, Build, Layer, Layer_Version, Branch, ProjectLayer
6from orm.models import Release, ReleaseLayerSourcePriority, BitbakeVersion
7
8from django.utils import timezone
9
10# tests to verify inheritance for the LayerSource proxy-inheritance classes
5class LayerSourceVerifyInheritanceSaveLoad(TestCase): 11class LayerSourceVerifyInheritanceSaveLoad(TestCase):
6 def test_object_creation(self): 12 def test_object_creation(self):
7 lls = LayerSource.objects.create(name = "a1", sourcetype = LayerSource.TYPE_LOCAL, apiurl = "") 13 lls = LayerSource.objects.create(name = "a1", sourcetype = LayerSource.TYPE_LOCAL, apiurl = "")
@@ -23,7 +29,7 @@ class LayerSourceVerifyInheritanceSaveLoad(TestCase):
23 self.assertRaises(Exception, duplicate) 29 self.assertRaises(Exception, duplicate)
24 30
25 31
26 32# test to verify the layer source update functionality for layerindex. edit to pass the URL to a layerindex application
27class LILSUpdateTestCase(TestCase): 33class LILSUpdateTestCase(TestCase):
28 def test_update(self): 34 def test_update(self):
29 lils = LayerSource.objects.create(name = "b1", sourcetype = LayerSource.TYPE_LAYERINDEX, apiurl = "http://adamian-desk.local:8080/layerindex/api/") 35 lils = LayerSource.objects.create(name = "b1", sourcetype = LayerSource.TYPE_LAYERINDEX, apiurl = "http://adamian-desk.local:8080/layerindex/api/")
@@ -34,3 +40,126 @@ class LILSUpdateTestCase(TestCase):
34 40
35 # print vars(lils) 41 # print vars(lils)
36 #print map(lambda x: vars(x), Branch.objects.all()) 42 #print map(lambda x: vars(x), Branch.objects.all())
43
44 # run asserts
45 self.assertTrue(lils.branch_set.all().count() > 0, "update() needs to fetch some branches")
46
47
48
49# tests to verify layer_version priority selection
50class LayerVersionEquivalenceTestCase(TestCase):
51 def setUp(self):
52 # create layer sources
53 ls = LayerSource.objects.create(name = "dummy-layersource", sourcetype = LayerSource.TYPE_LOCAL)
54
55 # create bitbake version
56 bbv = BitbakeVersion.objects.create(name="master", giturl="git://git.openembedded.org/bitbake")
57 # create release
58 release = Release.objects.create(name="default-release", bitbake_version = bbv, branch_name = "master")
59 # attach layer source to release
60 ReleaseLayerSourcePriority.objects.create(release = release, layer_source = ls, priority = 1)
61
62 # create layer attach
63 self.layer = Layer.objects.create(name="meta-testlayer", layer_source = ls)
64 # create branch
65 self.branch = Branch.objects.create(name="master", layer_source = ls)
66
67 # set a layer version for the layer on the specified branch
68 self.layerversion = Layer_Version.objects.create(layer = self.layer, layer_source = ls, up_branch = self.branch)
69
70 # create spoof layer that should not appear in the search results
71 Layer_Version.objects.create(layer = Layer.objects.create(name="meta-notvalid", layer_source = ls), layer_source = ls, up_branch = self.branch)
72
73
74 # create a project ...
75 self.project = Project.objects.create_project(name="test-project", release = release)
76 # ... and set it up with a single layer version
77 ProjectLayer.objects.create(project= self.project, layercommit = self.layerversion)
78
79 def test_single_layersource(self):
80 # when we have a single layer version, get_equivalents_wpriority() should return a list with just this layer_version
81 equivalent_list = self.layerversion.get_equivalents_wpriority(self.project)
82 self.assertTrue(len(equivalent_list) == 1)
83 self.assertTrue(equivalent_list[0] == self.layerversion)
84
85 def test_dual_layersource(self):
86 # if we have two layers with the same name, from different layer sources, we expect both layers in, in increasing priority of the layer source
87 ls2 = LayerSource.objects.create(name = "dummy-layersource2", sourcetype = LayerSource.TYPE_LOCAL)
88
89 # assign a lower priority for the second layer source
90 Release.objects.get(name="default-release").releaselayersourcepriority_set.create(layer_source = ls2, priority = 2)
91
92 # create a new layer_version for a layer with the same name coming from the second layer source
93 self.layer2 = Layer.objects.create(name="meta-testlayer", layer_source = ls2)
94 self.layerversion2 = Layer_Version.objects.create(layer = self.layer2, layer_source = ls2, up_branch = self.branch)
95
96 # expect two layer versions, in the priority order
97 equivalent_list = self.layerversion.get_equivalents_wpriority(self.project)
98 self.assertTrue(len(equivalent_list) == 2)
99 self.assertTrue(equivalent_list[0] == self.layerversion2)
100 self.assertTrue(equivalent_list[1] == self.layerversion)
101
102 def test_build_layerversion(self):
103 # any layer version coming from the build should show up before any layer version coming from upstream
104 build = Build.objects.create(project = self.project, started_on = timezone.now(), completed_on = timezone.now())
105 self.layerversion_build = Layer_Version.objects.create(layer = self.layer, build = build, commit = "deadbeef")
106
107 # a build layerversion must be in the equivalence list for the original layerversion
108 equivalent_list = self.layerversion.get_equivalents_wpriority(self.project)
109 self.assertTrue(len(equivalent_list) == 2)
110 self.assertTrue(equivalent_list[0] == self.layerversion)
111 self.assertTrue(equivalent_list[1] == self.layerversion_build)
112
113 # getting the build layerversion equivalent list must return the same list as the original layer
114 build_equivalent_list = self.layerversion_build.get_equivalents_wpriority(self.project)
115
116 self.assertTrue(equivalent_list == build_equivalent_list, "%s is not %s" % (equivalent_list, build_equivalent_list))
117
118class ProjectLVSelectionTestCase(TestCase):
119 def setUp(self):
120 # create layer sources
121 ls = LayerSource.objects.create(name = "dummy-layersource", sourcetype = LayerSource.TYPE_LOCAL)
122
123 # create bitbake version
124 bbv = BitbakeVersion.objects.create(name="master", giturl="git://git.openembedded.org/bitbake")
125 # create release
126 release = Release.objects.create(name="default-release", bitbake_version = bbv, branch_name="master")
127 # attach layer source to release
128 ReleaseLayerSourcePriority.objects.create(release = release, layer_source = ls, priority = 1)
129
130 # create layer attach
131 self.layer = Layer.objects.create(name="meta-testlayer", layer_source = ls)
132 # create branch
133 self.branch = Branch.objects.create(name="master", layer_source = ls)
134
135 # set a layer version for the layer on the specified branch
136 self.layerversion = Layer_Version.objects.create(layer = self.layer, layer_source = ls, up_branch = self.branch)
137
138
139 # create a project ...
140 self.project = Project.objects.create_project(name="test-project", release = release)
141 # ... and set it up with a single layer version
142 ProjectLayer.objects.create(project= self.project, layercommit = self.layerversion)
143
144 def test_single_layersource(self):
145 compatible_layerversions = self.project.compatible_layerversions()
146 self.assertTrue(len(compatible_layerversions) == 1)
147 self.assertTrue(compatible_layerversions[0] == self.layerversion)
148
149
150 def test_dual_layersource(self):
151 # if we have two layers with the same name, from different layer sources, we expect both layers in, in increasing priority of the layer source
152 ls2 = LayerSource.objects.create(name = "dummy-layersource2", sourcetype = LayerSource.TYPE_LOCAL)
153
154 # assign a lower priority for the second layer source
155 Release.objects.get(name="default-release").releaselayersourcepriority_set.create(layer_source = ls2, priority = 2)
156
157 # create a new layer_version for a layer with the same name coming from the second layer source
158 self.layer2 = Layer.objects.create(name="meta-testlayer", layer_source = ls2)
159 self.layerversion2 = Layer_Version.objects.create(layer = self.layer2, layer_source = ls2, up_branch = self.branch)
160
161 # expect two layer versions, in the priority order
162 equivalent_list = self.project.compatible_layerversions()
163 self.assertTrue(len(equivalent_list) == 2)
164 self.assertTrue(equivalent_list[0] == self.layerversion2)
165 self.assertTrue(equivalent_list[1] == self.layerversion)
diff --git a/bitbake/lib/toaster/toastergui/templates/targets.html b/bitbake/lib/toaster/toastergui/templates/targets.html
index 590ecb9a0e..3038649303 100644
--- a/bitbake/lib/toaster/toastergui/templates/targets.html
+++ b/bitbake/lib/toaster/toastergui/templates/targets.html
@@ -52,11 +52,11 @@
52 </td> 52 </td>
53 <td class="target-section">{{o.section}}</td> 53 <td class="target-section">{{o.section}}</td>
54 <td class="license">{{o.license}}</td> 54 <td class="license">{{o.license}}</td>
55 <td class="layer"><a href="{% url 'layerdetails' o.layer_version.id%}">{{o.layer_version.layer.name}}</a></td> 55 <td class="layer"><a href="{% url 'layerdetails' o.preffered_layerversion.id%}">{{o.preffered_layerversion.layer.name}}</a></td>
56 <td class="source">{{o.layer_source.name}}</td> 56 <td class="source">{{o.preffered_layerversion.layer_source.name}}</td>
57 <td class="branch"> 57 <td class="branch">
58 {% if o.layer_version.up_branch %} 58 {% if o.preffered_layerversion.up_branch %}
59 {{o.layer_version.up_branch.name}} 59 {{o.preffered_layerversion.up_branch.name}}
60 {% else %} 60 {% else %}
61 <a class="btn" 61 <a class="btn"
62 data-content="<ul class='unstyled'> 62 data-content="<ul class='unstyled'>
@@ -66,15 +66,15 @@
66 </a> 66 </a>
67 {% endif %} 67 {% endif %}
68 </td> 68 </td>
69 <td class="add-layer" value="{{o.pk}}" layerversion_id="{{o.layer_version.pk}}"> 69 <td class="add-layer" value="{{o.pk}}" layerversion_id="{{o.preffered_layerversion.pk}}">
70 <div id="layer-tooltip-{{o.pk}}" style="display: none; font-size: 11px; line-height: 1.3;" class="tooltip-inner">layer was modified</div> 70 <div id="layer-tooltip-{{o.pk}}" style="display: none; font-size: 11px; line-height: 1.3;" class="tooltip-inner">layer was modified</div>
71 <a href="{% url 'project' project.id %}#/targetbuild={{o.name}}" id="target-build-{{o.pk}}" class="btn btn-block remove-layer" style="display:none;" > 71 <a href="{% url 'project' project.id %}#/targetbuild={{o.name}}" id="target-build-{{o.pk}}" class="btn btn-block remove-layer" style="display:none;" >
72 Build target 72 Build target
73 </a> 73 </a>
74 <a id="layer-add-{{o.pk}}" class="btn btn-block" style="display:none;" href="javascript:layerAdd({{o.layer_version.pk}}, '{{o.layer_version.layer.name}}', '{%url 'layerdetails' o.layer_version.pk%}', {{o.pk}})" > 74 <a id="layer-add-{{o.pk}}" class="btn btn-block" style="display:none;" href="javascript:layerAdd({{o.preffered_layerversion.pk}}, '{{o.preffered_layerversion.layer.name}}', '{%url 'layerdetails' o.preffered_layerversion.pk%}', {{o.pk}})" >
75 <i class="icon-plus"></i> 75 <i class="icon-plus"></i>
76 Add layer 76 Add layer
77 <i title="" class="icon-question-sign get-help" data-original-title="To build this target, you must first add the {{o.layer_version.layer.name}} layer to your project"></i> 77 <i title="" class="icon-question-sign get-help" data-original-title="To build this target, you must first add the {{o.preffered_layerversion.layer.name}} layer to your project"></i>
78 </a> 78 </a>
79 </td> 79 </td>
80 </tr> 80 </tr>
diff --git a/bitbake/lib/toaster/toastergui/views.py b/bitbake/lib/toaster/toastergui/views.py
index 6ccbf5452d..7353844bf1 100755
--- a/bitbake/lib/toaster/toastergui/views.py
+++ b/bitbake/lib/toaster/toastergui/views.py
@@ -22,7 +22,7 @@
22import operator,re 22import operator,re
23import HTMLParser 23import HTMLParser
24 24
25from django.db.models import Q, Sum, Count 25from django.db.models import Q, Sum, Count, Max
26from django.db import IntegrityError 26from django.db import IntegrityError
27from django.shortcuts import render, redirect 27from django.shortcuts import render, redirect
28from orm.models import Build, Target, Task, Layer, Layer_Version, Recipe, LogMessage, Variable 28from orm.models import Build, Target, Task, Layer, Layer_Version, Recipe, LogMessage, Variable
@@ -236,7 +236,7 @@ def _get_queryset(model, queryset, filter_string, search_term, ordering_string,
236 if search_term: 236 if search_term:
237 queryset = _get_search_results(search_term, queryset, model) 237 queryset = _get_search_results(search_term, queryset, model)
238 238
239 if ordering_string and queryset: 239 if ordering_string:
240 column, order = ordering_string.split(':') 240 column, order = ordering_string.split(':')
241 if column == re.sub('-','',ordering_secondary): 241 if column == re.sub('-','',ordering_secondary):
242 ordering_secondary='' 242 ordering_secondary=''
@@ -2046,7 +2046,7 @@ if toastermain.settings.MANAGED:
2046 "url": x.layercommit.layer.layer_index_url, 2046 "url": x.layercommit.layer.layer_index_url,
2047 "layerdetailurl": reverse("layerdetails", args=(x.layercommit.pk,)), 2047 "layerdetailurl": reverse("layerdetails", args=(x.layercommit.pk,)),
2048 # This branch name is actually the release 2048 # This branch name is actually the release
2049 "branch" : { "name" : x.layercommit.commit, "layersource" : x.layercommit.up_branch.layer_source.name}}, 2049 "branch" : { "name" : x.layercommit.commit, "layersource" : x.layercommit.up_branch.layer_source.name if x.layercommit.up_branch != None else None}},
2050 prj.projectlayer_set.all().order_by("id")), 2050 prj.projectlayer_set.all().order_by("id")),
2051 "targets" : map(lambda x: {"target" : x.target, "task" : x.task, "pk": x.pk}, prj.projecttarget_set.all()), 2051 "targets" : map(lambda x: {"target" : x.target, "task" : x.task, "pk": x.pk}, prj.projecttarget_set.all()),
2052 "freqtargets": freqtargets, 2052 "freqtargets": freqtargets,
@@ -2243,11 +2243,11 @@ if toastermain.settings.MANAGED:
2243 2243
2244 # returns layer versions that provide the named targets 2244 # returns layer versions that provide the named targets
2245 if request.GET['type'] == "layers4target": 2245 if request.GET['type'] == "layers4target":
2246 # we returnd ata only if the recipe can't be provided by the current project layer set 2246 # we return data only if the recipe can't be provided by the current project layer set
2247 if reduce(lambda x, y: x + y, [x.recipe_layer_version.filter(name="anki").count() for x in prj.projectlayer_equivalent_set()], 0): 2247 if reduce(lambda x, y: x + y, [x.recipe_layer_version.filter(name=request.GET['value']).count() for x in prj.projectlayer_equivalent_set()], 0):
2248 final_list = [] 2248 final_list = []
2249 else: 2249 else:
2250 queryset_all = prj.compatible_layerversions().filter(recipe_layer_version__name = request.GET.get('value', '__none__')) 2250 queryset_all = prj.compatible_layerversions().filter(recipe_layer_version__name = request.GET['value'])
2251 2251
2252 # exclude layers in the project 2252 # exclude layers in the project
2253 queryset_all = queryset_all.exclude(pk__in = [x.id for x in prj.projectlayer_equivalent_set()]) 2253 queryset_all = queryset_all.exclude(pk__in = [x.id for x in prj.projectlayer_equivalent_set()])
@@ -2259,14 +2259,20 @@ if toastermain.settings.MANAGED:
2259 2259
2260 # returns targets provided by current project layers 2260 # returns targets provided by current project layers
2261 if request.GET['type'] == "targets": 2261 if request.GET['type'] == "targets":
2262 queryset_all = Recipe.objects.all() 2262 queryset_all = Recipe.objects.filter(name__icontains=request.GET.get('value',''))
2263 layer_equivalent_set = [] 2263 layer_equivalent_set = []
2264 for i in prj.projectlayer_set.all(): 2264 for i in prj.projectlayer_set.all():
2265 layer_equivalent_set += i.layercommit.get_equivalents_wpriority(prj) 2265 layer_equivalent_set += i.layercommit.get_equivalents_wpriority(prj)
2266 queryset_all = queryset_all.filter(layer_version__in = layer_equivalent_set) 2266 queryset_all = queryset_all.filter(layer_version__in = layer_equivalent_set)
2267
2268 # if we have more than one hit here (for distinct name and version), max the id it out
2269 queryset_all_maxids = queryset_all.values('name').distinct().annotate(max_id=Max('id')).values_list('max_id')
2270 queryset_all = queryset_all.filter(id__in = queryset_all_maxids)
2271
2272
2267 return HttpResponse(jsonfilter({ "error":"ok", 2273 return HttpResponse(jsonfilter({ "error":"ok",
2268 "list" : map ( lambda x: {"id": x.pk, "name": x.name, "detail":"[" + x.layer_version.layer.name+ (" | " + x.layer_version.up_branch.name + "]" if x.layer_version.up_branch is not None else "]")}, 2274 "list" : map ( lambda x: {"id": x.pk, "name": x.name, "detail":"[" + x.layer_version.layer.name + (" | " + x.layer_version.up_branch.name + "]" if x.layer_version.up_branch is not None else "]")},
2269 queryset_all.filter(name__icontains=request.GET.get('value',''))[:8]), 2275 queryset_all[:8]),
2270 2276
2271 }), content_type = "application/json") 2277 }), content_type = "application/json")
2272 2278
@@ -2663,10 +2669,17 @@ if toastermain.settings.MANAGED:
2663 2669
2664 queryset_with_search = _get_queryset(Recipe, queryset_all, None, search_term, ordering_string, '-name') 2670 queryset_with_search = _get_queryset(Recipe, queryset_all, None, search_term, ordering_string, '-name')
2665 2671
2666 queryset_with_search.prefetch_related("layer_source") 2672 # get unique values for 'name' and 'version', and select the maximum ID for each entry (the max id is the newest one)
2673 queryset_with_search_maxids = queryset_with_search.values('name').distinct().annotate(max_id=Max('id')).values_list('max_id')
2674
2675 queryset_with_search = queryset_with_search.filter(id__in=queryset_with_search_maxids).select_related('layer_version', 'layer_version__layer')
2676
2677 objects = list(queryset_with_search)
2678 for e in objects:
2679 e.preffered_layerversion = e.layer_version.get_equivalents_wpriority(prj)[0]
2667 2680
2668 # retrieve the objects that will be displayed in the table; targets a paginator and gets a page range to display 2681 # retrieve the objects that will be displayed in the table; targets a paginator and gets a page range to display
2669 target_info = _build_page_range(Paginator(queryset_with_search, request.GET.get('count', 10)),request.GET.get('page', 1)) 2682 target_info = _build_page_range(Paginator(objects, request.GET.get('count', 10)),request.GET.get('page', 1))
2670 2683
2671 2684
2672 context = { 2685 context = {
diff --git a/bitbake/lib/toaster/toastermain/urls.py b/bitbake/lib/toaster/toastermain/urls.py
index a2916e2dd7..6112067579 100644
--- a/bitbake/lib/toaster/toastermain/urls.py
+++ b/bitbake/lib/toaster/toastermain/urls.py
@@ -48,6 +48,11 @@ import toastermain.settings
48if toastermain.settings.FRESH_ENABLED: 48if toastermain.settings.FRESH_ENABLED:
49 urlpatterns.insert(1, url(r'', include('fresh.urls'))) 49 urlpatterns.insert(1, url(r'', include('fresh.urls')))
50 50
51if toastermain.settings.DEBUG_PANEL_ENABLED:
52 import debug_toolbar
53 urlpatterns.insert(1, url(r'', include(debug_toolbar.urls)))
54
55
51if toastermain.settings.MANAGED: 56if toastermain.settings.MANAGED:
52 urlpatterns = [ 57 urlpatterns = [
53 # Uncomment the next line to enable the admin: 58 # Uncomment the next line to enable the admin: