diff options
author | Alexandru DAMIAN <alexandru.damian@intel.com> | 2015-02-02 17:57:36 +0000 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2015-02-10 23:07:48 +0000 |
commit | 202d808f890b03958cd6873486e6a37f3f437098 (patch) | |
tree | 7b88e2cb5ab7b85648821bec4d2f4c16d290207f | |
parent | b741c9a4b4047439c6c5428e36a72c22a784feda (diff) | |
download | poky-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>
-rw-r--r-- | bitbake/lib/toaster/bldcontrol/bbcontroller.py | 13 | ||||
-rw-r--r-- | bitbake/lib/toaster/bldcontrol/localhostbecontroller.py | 54 | ||||
-rw-r--r-- | bitbake/lib/toaster/bldcontrol/sshbecontroller.py | 2 | ||||
-rw-r--r-- | bitbake/lib/toaster/orm/models.py | 23 | ||||
-rw-r--r-- | bitbake/lib/toaster/orm/tests.py | 131 | ||||
-rw-r--r-- | bitbake/lib/toaster/toastergui/templates/targets.html | 14 | ||||
-rwxr-xr-x | bitbake/lib/toaster/toastergui/views.py | 35 | ||||
-rw-r--r-- | bitbake/lib/toaster/toastermain/urls.py | 5 |
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 | ||
84 | def _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 | |||
97 | class BuildEnvironmentController(object): | 84 | class 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 | ||
31 | from toastermain import settings | 31 | from toastermain import settings |
32 | 32 | ||
33 | from bbcontroller import BuildEnvironmentController, ShellCmdException, BuildSetupException, _get_git_clonedirectory | 33 | from bbcontroller import BuildEnvironmentController, ShellCmdException, BuildSetupException |
34 | 34 | ||
35 | import logging | 35 | import logging |
36 | logger = logging.getLogger("toaster") | 36 | logger = 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 | ||
30 | from toastermain import settings | 30 | from toastermain import settings |
31 | 31 | ||
32 | from bbcontroller import BuildEnvironmentController, ShellCmdException, BuildSetupException, _get_git_clonedirectory | 32 | from bbcontroller import BuildEnvironmentController, ShellCmdException, BuildSetupException |
33 | 33 | ||
34 | def DN(path): | 34 | def 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 | |||
2 | from orm.models import LocalLayerSource, LayerIndexLayerSource, ImportedLayerSource, LayerSource | 2 | from orm.models import LocalLayerSource, LayerIndexLayerSource, ImportedLayerSource, LayerSource |
3 | from orm.models import Branch | 3 | from orm.models import Branch |
4 | 4 | ||
5 | from orm.models import Project, Build, Layer, Layer_Version, Branch, ProjectLayer | ||
6 | from orm.models import Release, ReleaseLayerSourcePriority, BitbakeVersion | ||
7 | |||
8 | from django.utils import timezone | ||
9 | |||
10 | # tests to verify inheritance for the LayerSource proxy-inheritance classes | ||
5 | class LayerSourceVerifyInheritanceSaveLoad(TestCase): | 11 | class 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 | |
27 | class LILSUpdateTestCase(TestCase): | 33 | class 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 | ||
50 | class 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 | |||
118 | class 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 @@ | |||
22 | import operator,re | 22 | import operator,re |
23 | import HTMLParser | 23 | import HTMLParser |
24 | 24 | ||
25 | from django.db.models import Q, Sum, Count | 25 | from django.db.models import Q, Sum, Count, Max |
26 | from django.db import IntegrityError | 26 | from django.db import IntegrityError |
27 | from django.shortcuts import render, redirect | 27 | from django.shortcuts import render, redirect |
28 | from orm.models import Build, Target, Task, Layer, Layer_Version, Recipe, LogMessage, Variable | 28 | from 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 | |||
48 | if toastermain.settings.FRESH_ENABLED: | 48 | if toastermain.settings.FRESH_ENABLED: |
49 | urlpatterns.insert(1, url(r'', include('fresh.urls'))) | 49 | urlpatterns.insert(1, url(r'', include('fresh.urls'))) |
50 | 50 | ||
51 | if toastermain.settings.DEBUG_PANEL_ENABLED: | ||
52 | import debug_toolbar | ||
53 | urlpatterns.insert(1, url(r'', include(debug_toolbar.urls))) | ||
54 | |||
55 | |||
51 | if toastermain.settings.MANAGED: | 56 | if 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: |