Skip to content

Commit 31c3fe7

Browse files
committed
[IMP] runbot: allow to filter on dependencies
This commit adds the possibility to filter modules based on their dependencies and dependants in the build configuration. This allows to easily trigger tests on modules that are impacted by changes, even if they are not directly modified. To make it work the modules listing is now done only in the git repository, without exporting sources, this should help to easily to have faster builds when the only task is to create builds based on modified modules. This should also and mostly help to test a dynamic config without running it.
1 parent 336b66f commit 31c3fe7

8 files changed

Lines changed: 374 additions & 103 deletions

File tree

runbot/controllers/frontend.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,8 @@ def build(self, build_id, search=None, from_batch=None, **post):
323323
@route([
324324
'/runbot/build/search',
325325
], website=True, auth='public', type='http', sitemap=False)
326-
def builds(self, **kwargs):
326+
def builds(self, limit=100, **kwargs):
327+
limit = min(int(limit), 1000)
327328
domain = []
328329
for key in ('config_id', 'version_id', 'project_id', 'trigger_id', 'create_batch_id.bundle_id', 'create_batch_id'): # allowed params
329330
value = kwargs.get(key)
@@ -337,10 +338,12 @@ def builds(self, **kwargs):
337338

338339
for key in ('description',):
339340
if key in kwargs:
340-
domain.append((f'{key}', 'ilike', kwargs.get(key)))
341+
value = kwargs.get(key)
342+
operator = 'ilike' if '%' in value else '='
343+
domain.append((f'{key}', operator, value))
341344

342345
context = {
343-
'builds': request.env['runbot.build'].search(domain, limit=100),
346+
'builds': request.env['runbot.build'].search(domain, limit=limit),
344347
}
345348

346349
return request.render('runbot.build_search', context)

runbot/models/build.py

Lines changed: 63 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# -*- coding: utf-8 -*-
2-
2+
import ast
33
import datetime
44
import getpass
55
import hashlib
@@ -17,7 +17,7 @@
1717
from psycopg2 import sql
1818
from psycopg2.extensions import TransactionRollbackError
1919

20-
from ..common import dt2time, now, grep, local_pgadmin_cursor, dest_reg, os, list_local_dbs, pseudo_markdown, RunbotException, findall, sanitize, markdown_escape, tail
20+
from ..common import dt2time, now, grep, local_pgadmin_cursor, dest_reg, os, list_local_dbs, pseudo_markdown, RunbotException, findall, sanitize, markdown_escape, tail, transactioncache
2121
from ..container import docker_stop, docker_state, Command, docker_run, docker_pull
2222
from ..fields import JsonDictField
2323

@@ -61,7 +61,6 @@ def remove_readonly(func, path_str, exinfo):
6161
def make_selection(array):
6262
return [(elem, elem.replace('_', ' ').capitalize()) if isinstance(elem, str) else elem for elem in array]
6363

64-
6564
class BuildParameters(models.Model):
6665
_name = 'runbot.build.params'
6766
_description = "Build parameters"
@@ -1091,25 +1090,28 @@ def _checkout(self):
10911090

10921091
return exports
10931092

1093+
def _list_available_modules(self):
1094+
for commit in self.env.context.get('defined_commit_ids') or self.params_id.commit_ids:
1095+
for (addons_path, module, manifest_file_name) in commit._list_available_modules():
1096+
yield commit, addons_path, module, manifest_file_name
1097+
10941098
def _get_available_modules(self):
10951099
all_modules = dict()
10961100
available_modules = defaultdict(list)
10971101
# repo_modules = []
1098-
for commit in self.env.context.get('defined_commit_ids') or self.params_id.commit_ids:
1099-
for (addons_path, module, manifest_file_name) in commit._get_available_modules():
1100-
if module in all_modules:
1101-
self._log(
1102-
'Building environment',
1103-
'%s is a duplicated modules (found in "%s", already defined in %s)' % (
1104-
module,
1105-
commit._source_path(addons_path, module, manifest_file_name),
1106-
all_modules[module]._source_path(addons_path, module, manifest_file_name)),
1107-
level='WARNING',
1108-
)
1109-
else:
1110-
available_modules[commit.repo_id].append(module)
1111-
all_modules[module] = commit
1112-
# return repo_modules, available_modules
1102+
for commit, addons_path, module, manifest_file_name in self._list_available_modules():
1103+
if module in all_modules:
1104+
self._log(
1105+
'Building environment',
1106+
'%s is a duplicated modules (found in "%s", already defined in %s)' % (
1107+
module,
1108+
commit._source_path(addons_path, module, manifest_file_name),
1109+
all_modules[module]._source_path(addons_path, module, manifest_file_name)),
1110+
level='WARNING',
1111+
)
1112+
else:
1113+
available_modules[commit.repo_id].append(module)
1114+
all_modules[module] = commit
11131115
return available_modules
11141116

11151117
def _get_modules_to_test(self, modules_patterns=''):
@@ -1120,6 +1122,43 @@ def _get_modules_to_test(self, modules_patterns=''):
11201122
modules_patterns = (modules_patterns or '').split(',')
11211123
return trigger._filter_modules_to_test(modules, params_patterns + modules_patterns) # we may switch params_patterns and modules_patterns order
11221124

1125+
@transactioncache
1126+
def _dependency_graph(self):
1127+
dependency_graph = defaultdict(set)
1128+
dependant_graph = defaultdict(set)
1129+
for commit, addons_path, module, manifest_file_name in self._list_available_modules():
1130+
manifest = commit._git_show_file(os.path.join(addons_path, module, manifest_file_name))
1131+
manifest_content = ast.literal_eval(manifest)
1132+
depends = manifest_content.get('depends', [])
1133+
if not depends and module != 'base':
1134+
depends = ['base']
1135+
for dep in depends:
1136+
dependency_graph[module].add(dep)
1137+
dependant_graph[dep].add(module)
1138+
return dependency_graph, dependant_graph
1139+
1140+
def search_modules_graph(self, modules, graph, depth=None):
1141+
def search(modules, depth=None, visited=None):
1142+
visited = visited or set()
1143+
modules = set(modules) - visited
1144+
visited |= modules
1145+
dependencies = set(modules)
1146+
if depth == 0 or not modules:
1147+
return dependencies
1148+
for module in modules:
1149+
dependencies |= search(graph[module], depth - 1 if depth is not None else None, visited)
1150+
return dependencies
1151+
return sorted(search(modules, depth))
1152+
1153+
def _get_modules_dependencies(self, modules, depth=None):
1154+
self.ensure_one()
1155+
dependency_graph, _ = self._dependency_graph()
1156+
return self.search_modules_graph(modules, dependency_graph, depth)
1157+
1158+
def _get_dependant_modules(self, modules, depth=None):
1159+
_, dependant_graph = self._dependency_graph()
1160+
return self.search_modules_graph(modules, dependant_graph, depth)
1161+
11231162
def _local_pg_dropdb(self, dbname):
11241163
msg = ''
11251164
try:
@@ -1249,13 +1288,17 @@ def _modified_files(self, commit_link_links=None):
12491288
modified_files[commit_link] = files
12501289
return modified_files
12511290

1252-
def _modified_modules(self, commit_link_links=None):
1291+
def _modified_modules(self, commit_link_links=None, defaults=None):
12531292
modified_files = self._modified_files(commit_link_links)
12541293
modified_modules = set()
12551294
for commit_link, files in modified_files.items():
12561295
commit = commit_link.commit_id
12571296
for file in files:
1258-
modified_modules.add(commit.repo_id._get_module(file))
1297+
module = commit.repo_id._get_module(file)
1298+
if module:
1299+
modified_modules.add(module)
1300+
elif defaults:
1301+
modified_modules |= set(defaults)
12591302
return modified_modules
12601303

12611304
def _get_upgrade_path(self):

0 commit comments

Comments
 (0)