-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmodules.py
361 lines (291 loc) · 10.2 KB
/
modules.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
#This file is part of tryton-task. The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms.
import configparser
import logging
import os
import hgapi
import git
import yaml
from invoke import Collection, task
from blessings import Terminal
from multiprocessing import Process
from sql import Table
from .scm import hg_clone, hg_update, git_clone, git_update
from .tools import wait_processes, set_context
from . import patches
try:
from trytond.transaction import Transaction
except:
pass
t = Terminal()
logger = logging.getLogger(__name__)
MAX_PROCESSES = 25
__all__ = ['info', 'clone', 'branches']
class bcolors:
HEADER = '\033[95m'
OKBLUE = '\033[94m'
OKGREEN = '\033[92m'
WARN = '\033[93m'
FAIL = '\033[91m'
ENDC = '\033[0m'
BOLD = "\033[1m"
def read_config_file(config_file=None, type='repos', unstable=True):
assert type in ('repos', 'servers', 'patches', 'all'), "Invalid 'type' param"
Config = configparser.ConfigParser()
if config_file is not None:
Config.readfp(open('./config/'+config_file))
else:
for r, d, f in os.walk("./config"):
for files in f:
if not files.endswith(".cfg"):
continue
if not unstable and files.endswith("-unstable.cfg"):
continue
if 'templates' in r:
continue
Config.readfp(open(os.path.join(r, files)))
if type == 'all':
return Config
for section in Config.sections():
is_patch = (Config.has_option(section, 'patch')
and Config.getboolean(section, 'patch'))
is_server = (Config.has_option(section, 'server')
and Config.get(section, 'server'))
if type == 'repos' and (is_patch or is_server):
Config.remove_section(section)
elif type == 'patches' and not is_patch:
Config.remove_section(section)
elif type == 'servers' and not is_server:
Config.remove_section(section)
return Config
@task
def info(ctx, config=None):
'Info config modules'
Config = read_config_file(config)
modules = Config.sections()
modules.sort()
total = len(modules)
logger.info(t.bold(str(total) + ' modules'))
for module in modules:
message = t.green(module)+' %s %s %s %s' % (
Config.get(module, 'repo'),
Config.get(module, 'url'),
Config.get(module, 'path'),
Config.get(module, 'branch'),
)
print(message)
def _hg_branches(module, path, config_branch=None):
client = hgapi.Repo(path)
branches = client.get_branch_names()
active = client.hg_branch()
b = []
branches.sort()
branches.reverse()
for branch in branches:
br = branch
if branch == active:
br = "*" + br
if branch == config_branch:
br = "[" + br + "]"
b.append(br)
msg = str.ljust(module, 40, ' ') + "\t".join(b)
if "[*" in msg:
msg = bcolors.OKGREEN + msg + bcolors.ENDC
elif "\t[" in msg or '\t*' in msg:
msg = bcolors.FAIL + msg + bcolors.ENDC
else:
msg = bcolors.WARN + msg + bcolors.ENDC
logger.info(msg)
def _git_branches(module, path, config_branch=None):
repo = git.Repo(path)
branches = repo.git.branch('-a')
branches = [x.replace('remotes/origin/','').replace('*','').strip()
for x in branches.split('\n') if 'HEAD' not in x]
active = branches[0]
b = []
branches = list(set(branches))
branches.sort()
branches.reverse()
for branch in branches:
br = branch
if branch == active:
br = "*" + br
if branch == config_branch:
br = "[" + br + "]"
b.append(br)
msg = str.ljust(module, 40, ' ') + "\t".join(b)
if "[*" in msg:
msg = bcolors.OKGREEN + msg + bcolors.ENDC
elif "\t[" in msg or '\t*' in msg:
msg = bcolors.FAIL + msg + bcolors.ENDC
else:
msg = bcolors.WARN + msg + bcolors.ENDC
logger.info(msg)
@task(help={
'config': 'Configuration file: config.cfg',
'branch': 'Repo branch. Default is "default"',
'master': 'If not is true, source repo is from hg.zikzakmedia.com'
})
def clone(ctx, config=None, branch=None, master=False):
'''Clone trytond modules'''
Modules = read_config_file(config)
modules = Modules.sections()
modules.sort()
processes = []
for module in modules:
repo = Modules.get(module, 'repo')
url = Modules.get(module, 'url')
path = Modules.get(module, 'path')
mod_branch = branch or Modules.get(module, 'branch')
repo_path = os.path.join(path, module)
if os.path.exists(repo_path):
continue
if not os.path.exists('./trytond') and config != 'base.cfg':
logger.info(t.bold_red('Before clone all modules, please clone base.cfg modules'))
return
logger.info( "Adding Module " + t.bold(module) + " to clone")
func = hg_clone if repo == 'hg' else git_clone
p = Process(target=func, args=(url, repo_path, mod_branch, master))
p.start()
processes.append(p)
if processes:
wait_processes(processes)
@task
def update(ctx, config=None, module=None):
'''Update trytond modules'''
Modules = read_config_file(config)
modules = Modules.sections()
modules.sort()
if module:
if module in modules:
modules = [module]
else:
logger.error( "Not found " + t.bold(module))
return
processes = []
for module in modules:
repo = Modules.get(module, 'repo')
path = Modules.get(module, 'path')
repo_path = os.path.join(path, module)
if not os.path.exists(repo_path):
continue
logger.info( "Adding Module " + t.bold(module) + " to update")
func = hg_update if repo == 'hg' else git_update
p = Process(target=func, args=(repo_path,))
p.start()
processes.append(p)
if processes:
wait_processes(processes)
@task
def branches(ctx, config=None, module=None):
'''Show info module branches'''
Modules = read_config_file(config)
modules = Modules.sections()
modules.sort()
if module:
modules = [module] if (module and module in modules) else None
for module in modules:
repo = Modules.get(module, 'repo')
url = Modules.get(module, 'url')
path = Modules.get(module, 'path')
branch = Modules.get(module, 'branch')
repo_path = os.path.join(path, module)
if repo == 'hg':
_hg_branches(module, repo_path, branch)
else:
_git_branches(module, repo_path, branch)
@task
def forgotten(ctx, database, config_file=os.environ.get('TRYTOND_CONFIG')):
'''Remove modules in ir-modules that not found in cfg config'''
ir_module = Table('ir_module')
with set_context(database, config_file):
cursor = Transaction().connection.cursor()
cursor.execute(*ir_module.select(ir_module.name, ir_module.state))
db_module_list = [(r[0], r[1]) for r in cursor.fetchall()]
Modules = read_config_file()
modules = Modules.sections()
modules.append('tests')
modules.sort()
to_delete = []
for module, state in db_module_list:
if state == 'not activated' and module not in modules:
to_delete.append(module)
if to_delete:
cursor.execute(*ir_module.delete(where=ir_module.name.in_(to_delete)))
logger.info( "Deleted: " + ', '.join(to_delete))
@task
def tests(ctx, module=None):
'Tests'
if module:
modules = [module]
else:
Config = read_config_file()
modules = Config.sections()
modules.sort()
Base = read_config_file('base.cfg')
bases = Base.sections()
processes = []
for module in modules:
if module in bases:
continue
logger.info("Run Module " + t.bold(module) + " to test")
os.system('python %s/trytond/trytond/tests/run-tests.py -m %s' % (
os.path.dirname(os.path.realpath(__file__)).replace('/tasks', ''),
module))
@task
def fetch(ctx):
'''Fetch env (update trytond, all modules and patches)'''
logger.info(t.bold('Fetch (patches and update)'))
logger.info(t.bold('Pop patches...'))
patches._pop()
logger.info(t.bold('Pull Update...'))
update(ctx)
logger.info(t.bold('Cloning...'))
clone(ctx)
logger.info(t.bold('Push patches...'))
patches._push()
logger.info(t.bold('Updating requirements...'))
os.system('pip install -r config/requirements.txt')
if os.path.isfile('requirements.txt'):
os.system('pip install -r requirements.txt')
logger.info(t.bold('Fetched.'))
@task
def missing(ctx):
'''Missing installed modules (activated_modules.yml)'''
# activated_modules.yml:
# activated:
# - module name
if not os.path.isfile('activated_modules.yml'):
logger.error('Not found activated_modules.yml file')
return
# available modules
Config = read_config_file()
modules = Config.sections()
modules.sort()
# installed modules
activated = yaml.load(open('activated_modules.yml', 'r').read())
modules_activated = activated.get('activated')
# to unistall
upgrades = yaml.load(open('upgrades/config.yml', 'r').read())
modules_touninstall = upgrades.get('to_uninstall')
if os.path.isfile('upgrade.yml'):
override = yaml.load(open('upgrade.yml', 'r').read())
modules_touninstall += override.get('to_uninstall', [])
missing = []
for module in modules_activated:
if module in modules_touninstall:
continue
if module not in modules:
missing.append(module)
logger.info('Missing modules:')
logger.info(' '.join(missing))
# Add Invoke Collections
ModulesCollection = Collection('modules')
ModulesCollection.add_task(info)
ModulesCollection.add_task(clone)
ModulesCollection.add_task(update)
ModulesCollection.add_task(branches)
ModulesCollection.add_task(forgotten)
ModulesCollection.add_task(tests)
ModulesCollection.add_task(fetch)
ModulesCollection.add_task(missing)