vulcan-builder 0.2.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vulcan/__init__.py +0 -0
- vulcan/builder/__init__.py +18 -0
- vulcan/builder/_vb.py +384 -0
- vulcan/builder/classes.py +75 -0
- vulcan/builder/common.py +53 -0
- vulcan/meta_builder.py +5 -0
- vulcan_builder-0.2.4.dist-info/METADATA +315 -0
- vulcan_builder-0.2.4.dist-info/RECORD +12 -0
- vulcan_builder-0.2.4.dist-info/WHEEL +5 -0
- vulcan_builder-0.2.4.dist-info/entry_points.txt +2 -0
- vulcan_builder-0.2.4.dist-info/licenses/LICENSE.txt +19 -0
- vulcan_builder-0.2.4.dist-info/top_level.txt +1 -0
vulcan/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'''
|
|
2
|
+
Lightweight Python Build Tool
|
|
3
|
+
'''
|
|
4
|
+
|
|
5
|
+
from vulcan.builder.common import nsh, dump, dumps, safe_cd
|
|
6
|
+
from ._vb import task, async_task, main
|
|
7
|
+
import sh
|
|
8
|
+
import pkgutil
|
|
9
|
+
|
|
10
|
+
__path__ = pkgutil.extend_path(__path__, __name__)
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
'task', 'async_task',
|
|
14
|
+
'main',
|
|
15
|
+
'nsh', 'sh',
|
|
16
|
+
'dump', 'dumps',
|
|
17
|
+
'safe_cd'
|
|
18
|
+
]
|
vulcan/builder/_vb.py
ADDED
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Lightweight Python Build Tool
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import inspect
|
|
7
|
+
import argparse
|
|
8
|
+
import logging
|
|
9
|
+
import os
|
|
10
|
+
from os import path
|
|
11
|
+
import re
|
|
12
|
+
import importlib.util
|
|
13
|
+
import sys
|
|
14
|
+
import time
|
|
15
|
+
import concurrent.futures
|
|
16
|
+
from vulcan.meta_builder import __version__
|
|
17
|
+
from vulcan.builder.classes import CurrentThreadExecutor
|
|
18
|
+
from vulcan.builder.classes import Task
|
|
19
|
+
# from vulcan.builder.classes import LoggerWrapper
|
|
20
|
+
|
|
21
|
+
_CREDIT_LINE = ("Powered by vb %s "
|
|
22
|
+
"- A Lightweight Python Build Tool." % __version__)
|
|
23
|
+
_LOGGING_FORMAT = "[ %(name)s - %(message)s ]"
|
|
24
|
+
_TASK_PATTERN = re.compile("^([^\\[]+)(\\[([^\\]]*)\\])?$")
|
|
25
|
+
# "^([^\[]+)(\[([^\],=]*(,[^\],=]+)*(,[^\],=]+=[^\],=]+)*)\])?$"
|
|
26
|
+
|
|
27
|
+
thread_pool_executor = concurrent.futures.ThreadPoolExecutor(max_workers=20)
|
|
28
|
+
current_thread_executor = CurrentThreadExecutor()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def load_source(name, filepath):
|
|
32
|
+
"""
|
|
33
|
+
Load a build file as a module.
|
|
34
|
+
|
|
35
|
+
Replaces imp.load_source, gone in 3.12, including its handling of a name
|
|
36
|
+
already in sys.modules: the existing module is re-executed in place rather
|
|
37
|
+
than replaced, so references taken before the call stay valid.
|
|
38
|
+
"""
|
|
39
|
+
# A build file is free to import the rest of its project by name, and it
|
|
40
|
+
# is not run from a directory Python would have put on the path itself.
|
|
41
|
+
directory = path.dirname(path.abspath(filepath))
|
|
42
|
+
if directory not in sys.path:
|
|
43
|
+
sys.path.insert(0, directory)
|
|
44
|
+
|
|
45
|
+
spec = importlib.util.spec_from_file_location(name, filepath)
|
|
46
|
+
module = sys.modules.get(name)
|
|
47
|
+
if module is None:
|
|
48
|
+
module = importlib.util.module_from_spec(spec)
|
|
49
|
+
sys.modules[name] = module
|
|
50
|
+
spec.loader.exec_module(module)
|
|
51
|
+
return module
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def build(args):
|
|
55
|
+
"""
|
|
56
|
+
Build the specified module with specified arguments.
|
|
57
|
+
|
|
58
|
+
@type module: module
|
|
59
|
+
@type args: list of arguments
|
|
60
|
+
"""
|
|
61
|
+
# Build the command line.
|
|
62
|
+
parser = _create_parser()
|
|
63
|
+
|
|
64
|
+
# No args passed.
|
|
65
|
+
# if not args: #todo: execute default task.
|
|
66
|
+
# parser.print_help()
|
|
67
|
+
# print("\n\n"+_CREDIT_LINE)
|
|
68
|
+
# exit
|
|
69
|
+
# Parse arguments.
|
|
70
|
+
args = parser.parse_args(args)
|
|
71
|
+
|
|
72
|
+
if args.version:
|
|
73
|
+
print('vb %s' % __version__)
|
|
74
|
+
sys.exit(0)
|
|
75
|
+
|
|
76
|
+
# load build file as a module
|
|
77
|
+
if not path.isfile(args.file):
|
|
78
|
+
print("Build file '%s' does not exist. "
|
|
79
|
+
"Please specify a build file\n" % args.file)
|
|
80
|
+
parser.print_help()
|
|
81
|
+
sys.exit(1)
|
|
82
|
+
|
|
83
|
+
module = load_source(path.splitext(
|
|
84
|
+
path.basename(args.file))[0], args.file)
|
|
85
|
+
|
|
86
|
+
# Run task and all its dependencies.
|
|
87
|
+
if args.list_tasks:
|
|
88
|
+
print_tasks(module, args.file)
|
|
89
|
+
elif not args.tasks:
|
|
90
|
+
if not _run_default_task(module):
|
|
91
|
+
parser.print_help()
|
|
92
|
+
print("\n")
|
|
93
|
+
print_tasks(module, args.file)
|
|
94
|
+
else:
|
|
95
|
+
_run_from_task_names(module, args.tasks)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def print_tasks(module, file):
|
|
99
|
+
# Get all tasks.
|
|
100
|
+
tasks = _get_tasks(module)
|
|
101
|
+
|
|
102
|
+
# Build task_list to describe the tasks.
|
|
103
|
+
task_list = "Tasks in build file %s:" % file
|
|
104
|
+
name_width = _get_max_name_length(module)+4
|
|
105
|
+
task_help_format = "\n {0:<%s} {1: ^10} {2}" % name_width
|
|
106
|
+
default = _get_default_task(module)
|
|
107
|
+
for task in sorted(tasks, key=lambda task: task.name):
|
|
108
|
+
attributes = []
|
|
109
|
+
if task.ignored:
|
|
110
|
+
attributes.append('Ignored')
|
|
111
|
+
if task.async_task:
|
|
112
|
+
attributes.append('Async')
|
|
113
|
+
if default and task.name == default.name:
|
|
114
|
+
attributes.append('Default')
|
|
115
|
+
|
|
116
|
+
joined_attributes = ', '.join(attributes)
|
|
117
|
+
task_list += task_help_format.format(task.name,
|
|
118
|
+
('[{}]'.format(joined_attributes))
|
|
119
|
+
if attributes else '',
|
|
120
|
+
task.doc)
|
|
121
|
+
print(task_list + "\n\n"+_CREDIT_LINE)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _get_default_task(module):
|
|
125
|
+
matching_tasks = [
|
|
126
|
+
task for name, task in inspect.getmembers(module, Task.is_task)
|
|
127
|
+
if name == "__DEFAULT__"]
|
|
128
|
+
if matching_tasks:
|
|
129
|
+
return matching_tasks[0]
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _run_default_task(module):
|
|
133
|
+
default_task = _get_default_task(module)
|
|
134
|
+
if not default_task:
|
|
135
|
+
return False
|
|
136
|
+
|
|
137
|
+
completed_tasks = dict()
|
|
138
|
+
_run(module, _get_logger(module), default_task, completed_tasks)
|
|
139
|
+
for task in completed_tasks:
|
|
140
|
+
while completed_tasks.get(task).running():
|
|
141
|
+
time.sleep(0.5)
|
|
142
|
+
|
|
143
|
+
return True
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _run_from_task_names(module, task_names):
|
|
147
|
+
"""
|
|
148
|
+
@type module: module
|
|
149
|
+
@type task_name: string
|
|
150
|
+
@param task_name: Task name, exactly corresponds to function name.
|
|
151
|
+
"""
|
|
152
|
+
# Create logger.
|
|
153
|
+
logger = _get_logger(module)
|
|
154
|
+
all_tasks = _get_tasks(module)
|
|
155
|
+
completed_tasks = dict()
|
|
156
|
+
for task_name in task_names:
|
|
157
|
+
task, args, kwargs = _get_task(module, task_name, all_tasks)
|
|
158
|
+
_run(module, logger, task, completed_tasks, True, args, kwargs)
|
|
159
|
+
|
|
160
|
+
for task in completed_tasks:
|
|
161
|
+
while completed_tasks.get(task).running():
|
|
162
|
+
time.sleep(0.5)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _get_task(module, name, tasks):
|
|
166
|
+
# Get all tasks.
|
|
167
|
+
match = _TASK_PATTERN.match(name)
|
|
168
|
+
if not match:
|
|
169
|
+
raise Exception("Invalid task argument %s" % name)
|
|
170
|
+
task_name, _, args_str = match.groups()
|
|
171
|
+
|
|
172
|
+
args, kwargs = _parse_args(args_str)
|
|
173
|
+
if hasattr(module, task_name):
|
|
174
|
+
return getattr(module, task_name), args, kwargs
|
|
175
|
+
matching_tasks = [
|
|
176
|
+
task for task in tasks if task.name.startswith(task_name)]
|
|
177
|
+
|
|
178
|
+
if not matching_tasks:
|
|
179
|
+
raise Exception("Invalid task '%s'. Task should be one of %s" %
|
|
180
|
+
(name,
|
|
181
|
+
', '.join([task.name for task in tasks])))
|
|
182
|
+
if len(matching_tasks) == 1:
|
|
183
|
+
return matching_tasks[0], args, kwargs
|
|
184
|
+
raise Exception("Conflicting matches %s for task %s" % (
|
|
185
|
+
', '.join([task.name for task in matching_tasks]), task_name
|
|
186
|
+
))
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _parse_args(args_str):
|
|
190
|
+
args = []
|
|
191
|
+
kwargs = {}
|
|
192
|
+
if not args_str:
|
|
193
|
+
return args, kwargs
|
|
194
|
+
arg_parts = args_str.split(",")
|
|
195
|
+
|
|
196
|
+
for i, part in enumerate(arg_parts):
|
|
197
|
+
if "=" in part:
|
|
198
|
+
key, value = [_str.strip() for _str in part.split("=")]
|
|
199
|
+
if key in kwargs:
|
|
200
|
+
raise Exception("duplicate keyword argument %s" % part)
|
|
201
|
+
kwargs[key] = value
|
|
202
|
+
else:
|
|
203
|
+
if len(kwargs) > 0:
|
|
204
|
+
raise Exception("Non keyword arg %s "
|
|
205
|
+
"cannot follows a keyword arg %s"
|
|
206
|
+
% (part, arg_parts[i - 1]))
|
|
207
|
+
args.append(part.strip())
|
|
208
|
+
return args, kwargs
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _run(
|
|
212
|
+
module,
|
|
213
|
+
logger,
|
|
214
|
+
task,
|
|
215
|
+
completed_tasks,
|
|
216
|
+
from_command_line=False,
|
|
217
|
+
args=None,
|
|
218
|
+
kwargs=None):
|
|
219
|
+
"""
|
|
220
|
+
@type module: module
|
|
221
|
+
@type logging: Logger
|
|
222
|
+
@type task: Task
|
|
223
|
+
@type completed_tasts: set Task
|
|
224
|
+
@rtype: set Task
|
|
225
|
+
@return: Updated set of completed tasks after satisfying all dependencies.
|
|
226
|
+
"""
|
|
227
|
+
# Satsify dependencies recursively. Maintain set of completed tasks so each
|
|
228
|
+
# task is only performed once.
|
|
229
|
+
|
|
230
|
+
for dependency in task.dependencies:
|
|
231
|
+
_run(module, logger, dependency, completed_tasks)
|
|
232
|
+
|
|
233
|
+
for dependency in task.dependencies:
|
|
234
|
+
if not dependency.ignored:
|
|
235
|
+
while completed_tasks.get(dependency.name).running():
|
|
236
|
+
time.sleep(0.5)
|
|
237
|
+
|
|
238
|
+
# Perform current task, if need to.
|
|
239
|
+
if from_command_line or task.name not in completed_tasks:
|
|
240
|
+
|
|
241
|
+
if task.ignored:
|
|
242
|
+
logger.info("Ignoring task \"{}\"".format(task.name))
|
|
243
|
+
else:
|
|
244
|
+
try:
|
|
245
|
+
if task.async_task:
|
|
246
|
+
executor = thread_pool_executor
|
|
247
|
+
else:
|
|
248
|
+
executor = current_thread_executor
|
|
249
|
+
|
|
250
|
+
task.set_logger(logger)
|
|
251
|
+
running_task = executor.submit(
|
|
252
|
+
task,
|
|
253
|
+
*(args or []),
|
|
254
|
+
**(kwargs or {})
|
|
255
|
+
)
|
|
256
|
+
completed_tasks[task.name] = running_task
|
|
257
|
+
except Exception:
|
|
258
|
+
logger.critical("Error starting task \"{name}\"".format(
|
|
259
|
+
name=task.name))
|
|
260
|
+
logger.critical("Aborting build")
|
|
261
|
+
raise
|
|
262
|
+
|
|
263
|
+
return
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _create_parser():
|
|
267
|
+
"""
|
|
268
|
+
@rtype: argparse.ArgumentParser
|
|
269
|
+
"""
|
|
270
|
+
parser = argparse.ArgumentParser()
|
|
271
|
+
parser.add_argument(
|
|
272
|
+
"tasks", help="perform specified task and all its dependencies",
|
|
273
|
+
metavar="task", nargs='*')
|
|
274
|
+
|
|
275
|
+
parser.add_argument(
|
|
276
|
+
'-l', '--list-tasks', help="List the tasks",
|
|
277
|
+
action='store_true')
|
|
278
|
+
|
|
279
|
+
parser.add_argument(
|
|
280
|
+
'-v', '--version',
|
|
281
|
+
help="Display the version information",
|
|
282
|
+
action='store_true')
|
|
283
|
+
|
|
284
|
+
parser.add_argument(
|
|
285
|
+
'-f', '--file',
|
|
286
|
+
help=("Build file to read the tasks from. "
|
|
287
|
+
"'build.py' is default value assumed "
|
|
288
|
+
"if this argument is unspecified"),
|
|
289
|
+
metavar="file", default="build.py")
|
|
290
|
+
|
|
291
|
+
return parser
|
|
292
|
+
|
|
293
|
+
# Abbreviate for convenience.
|
|
294
|
+
# task = _TaskDecorator
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def task(*dependencies, **options):
|
|
298
|
+
for i, dependency in enumerate(dependencies):
|
|
299
|
+
if not Task.is_task(dependency):
|
|
300
|
+
if inspect.isfunction(dependency):
|
|
301
|
+
# Throw error specific to the most likely form of misuse.
|
|
302
|
+
if i == 0:
|
|
303
|
+
raise Exception("Replace use of @task with @task().")
|
|
304
|
+
else:
|
|
305
|
+
raise Exception("%s is not a task. "
|
|
306
|
+
"Each dependency should be a task."
|
|
307
|
+
% dependency)
|
|
308
|
+
else:
|
|
309
|
+
raise Exception("%s is not a task." % dependency)
|
|
310
|
+
|
|
311
|
+
def decorator(fn):
|
|
312
|
+
return Task(fn, dependencies, options)
|
|
313
|
+
return decorator
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def async_task(*dependencies, **options):
|
|
317
|
+
for i, dependency in enumerate(dependencies):
|
|
318
|
+
if not Task.is_task(dependency):
|
|
319
|
+
if inspect.isfunction(dependency):
|
|
320
|
+
# Throw error specific to the most likely form of misuse.
|
|
321
|
+
if i == 0:
|
|
322
|
+
raise Exception("Replace use of @task with @task().")
|
|
323
|
+
else:
|
|
324
|
+
raise Exception("%s is not a task. "
|
|
325
|
+
"Each dependency should be a task."
|
|
326
|
+
% dependency)
|
|
327
|
+
else:
|
|
328
|
+
raise Exception("%s is not a task." % dependency)
|
|
329
|
+
|
|
330
|
+
def decorator(fn):
|
|
331
|
+
return Task(fn, dependencies, options, async_task=True)
|
|
332
|
+
return decorator
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def _get_tasks(module):
|
|
336
|
+
"""
|
|
337
|
+
Returns all functions marked as tasks.
|
|
338
|
+
|
|
339
|
+
@type module: module
|
|
340
|
+
"""
|
|
341
|
+
# Get all functions that are marked as task and pull out the task object
|
|
342
|
+
# from each (name,value) pair.
|
|
343
|
+
return set(
|
|
344
|
+
member[1] for member in inspect.getmembers(module, Task.is_task)
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def _get_max_name_length(module):
|
|
349
|
+
"""
|
|
350
|
+
Returns the length of the longest task name.
|
|
351
|
+
|
|
352
|
+
@type module: module
|
|
353
|
+
"""
|
|
354
|
+
return max([len(task.name) for task in _get_tasks(module)])
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def _get_logger(module):
|
|
358
|
+
"""
|
|
359
|
+
@type module: module
|
|
360
|
+
@rtype: logging.Logger
|
|
361
|
+
"""
|
|
362
|
+
|
|
363
|
+
# Create Logger
|
|
364
|
+
logger = logging.getLogger(os.path.basename(module.__file__))
|
|
365
|
+
logger.setLevel(logging.DEBUG)
|
|
366
|
+
|
|
367
|
+
# Create console handler and set level to debug
|
|
368
|
+
ch = logging.StreamHandler()
|
|
369
|
+
ch.setLevel(logging.DEBUG)
|
|
370
|
+
|
|
371
|
+
# Create formatter
|
|
372
|
+
formatter = logging.Formatter(_LOGGING_FORMAT)
|
|
373
|
+
|
|
374
|
+
# Add formatter to ch
|
|
375
|
+
ch.setFormatter(formatter)
|
|
376
|
+
|
|
377
|
+
# Add ch to logger
|
|
378
|
+
logger.addHandler(ch)
|
|
379
|
+
|
|
380
|
+
return logger
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def main():
|
|
384
|
+
build(sys.argv[1:])
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import time
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
import logging
|
|
5
|
+
from threading import Lock
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Task(object):
|
|
9
|
+
|
|
10
|
+
def __init__(self, func, dependencies, options, **kwargs):
|
|
11
|
+
"""
|
|
12
|
+
@type func: 0-ary function
|
|
13
|
+
@type dependencies: list of Task objects
|
|
14
|
+
"""
|
|
15
|
+
self.func = func
|
|
16
|
+
self.name = func.__name__
|
|
17
|
+
self.doc = inspect.getdoc(func) or ''
|
|
18
|
+
self.dependencies = dependencies
|
|
19
|
+
self.ignored = bool(options.get('ignore', False))
|
|
20
|
+
self.async_task = kwargs.get('async_task', False)
|
|
21
|
+
self.logger = None
|
|
22
|
+
|
|
23
|
+
def __str__(self):
|
|
24
|
+
return self.name
|
|
25
|
+
|
|
26
|
+
def __call__(self, *args, **kwargs):
|
|
27
|
+
if self.logger:
|
|
28
|
+
if self.async_task:
|
|
29
|
+
self.logger.info("Starting async task \"{}\" in background".format(self.name))
|
|
30
|
+
else:
|
|
31
|
+
self.logger.info("Starting task \"{}\"".format(self.name))
|
|
32
|
+
|
|
33
|
+
t = datetime.now()
|
|
34
|
+
|
|
35
|
+
self.result = self.func.__call__(*args, **kwargs)
|
|
36
|
+
|
|
37
|
+
if self.logger:
|
|
38
|
+
self.logger.info("Completed task \"{task_name}\". Time: {run_time} sec".format(
|
|
39
|
+
task_name=self.name, run_time=(datetime.now() - t).seconds
|
|
40
|
+
)
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
return self.result
|
|
44
|
+
|
|
45
|
+
def set_future(self, future):
|
|
46
|
+
self.future = future
|
|
47
|
+
|
|
48
|
+
def set_logger(self, logger):
|
|
49
|
+
self.logger = logger
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def is_task(cls, obj):
|
|
53
|
+
"""
|
|
54
|
+
Returns true is an object is a build task.
|
|
55
|
+
"""
|
|
56
|
+
return isinstance(obj, cls)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class CurrentThreadExecutor(object):
|
|
60
|
+
|
|
61
|
+
def __init__(self):
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
def submit(self, task, *args, **kwargs):
|
|
65
|
+
task(*(args or []), **(kwargs or {}))
|
|
66
|
+
return PseudoFutureTask()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class PseudoFutureTask(object):
|
|
70
|
+
|
|
71
|
+
def __init__(self):
|
|
72
|
+
pass
|
|
73
|
+
|
|
74
|
+
def running(self):
|
|
75
|
+
return False
|
vulcan/builder/common.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from datetime import datetime, date
|
|
2
|
+
import json
|
|
3
|
+
import sys
|
|
4
|
+
import os
|
|
5
|
+
import sh
|
|
6
|
+
import contextlib
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def json_serial(obj):
|
|
10
|
+
"""JSON serializer for objects not serializable by default json code"""
|
|
11
|
+
|
|
12
|
+
if isinstance(obj, (datetime, date)):
|
|
13
|
+
return obj.isoformat()
|
|
14
|
+
raise TypeError("Type %s not serializable" % type(obj))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def dump(obj):
|
|
18
|
+
print('DUMP: {}'.format(json.dumps(obj, indent=1, default=json_serial)))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def dumps(obj):
|
|
22
|
+
return json.dumps(obj, indent=1, default=json_serial)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@contextlib.contextmanager
|
|
26
|
+
def safe_cd(path):
|
|
27
|
+
starting_directory = os.getcwd()
|
|
28
|
+
try:
|
|
29
|
+
os.chdir(path)
|
|
30
|
+
yield
|
|
31
|
+
finally:
|
|
32
|
+
os.chdir(starting_directory)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# Exr shell overriden methods
|
|
36
|
+
|
|
37
|
+
def print_out(line):
|
|
38
|
+
sys.stdout.write(line)
|
|
39
|
+
sys.stdout.write("\n")
|
|
40
|
+
sys.stdout.flush()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def print_err(line):
|
|
44
|
+
sys.stderr.write(line)
|
|
45
|
+
sys.stderr.write("\n")
|
|
46
|
+
sys.stderr.flush()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
nsh = None
|
|
50
|
+
if os.environ.get('TRAVIS', 'false') == 'true':
|
|
51
|
+
nsh = sh(_out=sys.stdout, _err_to_out=True)
|
|
52
|
+
else:
|
|
53
|
+
nsh = sh(_out=sys.stdout, _err_to_out=True, _tty_in=True)
|
vulcan/meta_builder.py
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vulcan-builder
|
|
3
|
+
Version: 0.2.4
|
|
4
|
+
Summary: Lightweight Python Build Tool.
|
|
5
|
+
Home-page: https://code.exrny.com/opensource/vulcan-builder/
|
|
6
|
+
Author: Peter Salnikov
|
|
7
|
+
Author-email: opensource@exrny.com
|
|
8
|
+
License: MIT License
|
|
9
|
+
Keywords: devops,build tool
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: System Administrators
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 2
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
18
|
+
License-File: LICENSE.txt
|
|
19
|
+
Requires-Dist: sh<2,>=1
|
|
20
|
+
Dynamic: author
|
|
21
|
+
Dynamic: author-email
|
|
22
|
+
Dynamic: classifier
|
|
23
|
+
Dynamic: description
|
|
24
|
+
Dynamic: home-page
|
|
25
|
+
Dynamic: keywords
|
|
26
|
+
Dynamic: license
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
Dynamic: requires-dist
|
|
29
|
+
Dynamic: summary
|
|
30
|
+
|
|
31
|
+
|Build Status|
|
|
32
|
+
|
|
33
|
+
Vulcan Builder
|
|
34
|
+
==============
|
|
35
|
+
|
|
36
|
+
This project is a fork of Pynt by `Raghunandan
|
|
37
|
+
Rao <https://github.com/rags/pynt>`__. We will contribute changes to the
|
|
38
|
+
original rags/pynt repo.
|
|
39
|
+
|
|
40
|
+
Vulcan Builder supports EXR’s applications via a lightweight, concise
|
|
41
|
+
Python DevOps tool. We will develop our own improvements on the initial
|
|
42
|
+
rags/pynt repo here and publish improvements to the original repo.
|
|
43
|
+
|
|
44
|
+
This is an EXR Open Source project.
|
|
45
|
+
|
|
46
|
+
A pynt of Python build.
|
|
47
|
+
=======================
|
|
48
|
+
|
|
49
|
+
Features
|
|
50
|
+
--------
|
|
51
|
+
|
|
52
|
+
- Easy to learn.
|
|
53
|
+
- Build tasks are just python funtions.
|
|
54
|
+
- Manages dependencies between tasks.
|
|
55
|
+
- Automatically generates a command line interface.
|
|
56
|
+
- Rake style param passing to tasks
|
|
57
|
+
- Supports python 2.7 and python 3.x
|
|
58
|
+
- Async tasks
|
|
59
|
+
|
|
60
|
+
Todo Features
|
|
61
|
+
-------------
|
|
62
|
+
|
|
63
|
+
- Additional tasks timing reporting
|
|
64
|
+
- Debug mode
|
|
65
|
+
|
|
66
|
+
Installation
|
|
67
|
+
------------
|
|
68
|
+
|
|
69
|
+
You can install vulcan-builder from the Python Package Index (PyPI) or
|
|
70
|
+
from source.
|
|
71
|
+
|
|
72
|
+
Using pip
|
|
73
|
+
|
|
74
|
+
.. code:: bash
|
|
75
|
+
|
|
76
|
+
$ pip install vulcan-builder
|
|
77
|
+
|
|
78
|
+
Using easy_install
|
|
79
|
+
|
|
80
|
+
.. code:: bash
|
|
81
|
+
|
|
82
|
+
$ easy_install vulcan-builder
|
|
83
|
+
|
|
84
|
+
Example
|
|
85
|
+
-------
|
|
86
|
+
|
|
87
|
+
The build script is written in pure Python and vulcan-builder takes care
|
|
88
|
+
of managing any dependencies between tasks and generating a command line
|
|
89
|
+
interface.
|
|
90
|
+
|
|
91
|
+
Writing build tasks is really simple, all you need to know is the @task
|
|
92
|
+
decorator. Tasks are just regular Python functions marked with the
|
|
93
|
+
``@task()`` decorator. Dependencies are specified with ``@task()`` too.
|
|
94
|
+
Tasks can be ignored with the ``@task(ignore=True)``. Disabling a task
|
|
95
|
+
is an useful feature to have in situations where you have one task that
|
|
96
|
+
a lot of other tasks depend on and you want to quickly remove it from
|
|
97
|
+
the dependency chains of all the dependent tasks.
|
|
98
|
+
|
|
99
|
+
**build.py**
|
|
100
|
+
------------
|
|
101
|
+
|
|
102
|
+
.. code:: python
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
#!/usr/bin/python
|
|
106
|
+
|
|
107
|
+
import sys
|
|
108
|
+
from vulcan.builder import task
|
|
109
|
+
|
|
110
|
+
@task()
|
|
111
|
+
def clean():
|
|
112
|
+
'''Clean build directory.'''
|
|
113
|
+
print 'Cleaning build directory...'
|
|
114
|
+
|
|
115
|
+
@task(clean)
|
|
116
|
+
def html(target='.'):
|
|
117
|
+
'''Generate HTML.'''
|
|
118
|
+
print 'Generating HTML in directory "%s"' % target
|
|
119
|
+
|
|
120
|
+
@task(clean, ignore=True)
|
|
121
|
+
def images():
|
|
122
|
+
'''Prepare images.'''
|
|
123
|
+
print 'Preparing images...'
|
|
124
|
+
|
|
125
|
+
@task(html,images)
|
|
126
|
+
def start_server(server='localhost', port = '80'):
|
|
127
|
+
'''Start the server'''
|
|
128
|
+
print 'Starting server at %s:%s' % (server, port)
|
|
129
|
+
|
|
130
|
+
@task(start_server) #Depends on task with all optional params
|
|
131
|
+
def stop_server():
|
|
132
|
+
print 'Stopping server....'
|
|
133
|
+
|
|
134
|
+
@task()
|
|
135
|
+
def copy_file(src, dest):
|
|
136
|
+
print 'Copying from %s to %s' % (src, dest)
|
|
137
|
+
|
|
138
|
+
@task()
|
|
139
|
+
def echo(*args,**kwargs):
|
|
140
|
+
print args
|
|
141
|
+
print kwargs
|
|
142
|
+
|
|
143
|
+
# Default task (if specified) is run when no task is specified in the command line
|
|
144
|
+
# make sure you define the variable __DEFAULT__ after the task is defined
|
|
145
|
+
# A good convention is to define it at the end of the module
|
|
146
|
+
# __DEFAULT__ is an optional member
|
|
147
|
+
|
|
148
|
+
__DEFAULT__=start_server
|
|
149
|
+
|
|
150
|
+
**Running vulcan-builder tasks**
|
|
151
|
+
--------------------------------
|
|
152
|
+
|
|
153
|
+
The command line interface and help is automatically generated. Task
|
|
154
|
+
descriptions are extracted from function docstrings.
|
|
155
|
+
|
|
156
|
+
.. code:: bash
|
|
157
|
+
|
|
158
|
+
$ vb -h
|
|
159
|
+
usage: vb [-h] [-l] [-v] [-f file] [task [task ...]]
|
|
160
|
+
|
|
161
|
+
positional arguments:
|
|
162
|
+
task perform specified task and all its dependencies
|
|
163
|
+
|
|
164
|
+
optional arguments:
|
|
165
|
+
-h, --help show this help message and exit
|
|
166
|
+
-l, --list-tasks List the tasks
|
|
167
|
+
-v, --version Display the version information
|
|
168
|
+
-f file, --file file Build file to read the tasks from. Default is
|
|
169
|
+
'build.py'
|
|
170
|
+
|
|
171
|
+
.. code:: bash
|
|
172
|
+
|
|
173
|
+
$ vb -l
|
|
174
|
+
Tasks in build file ./build.py:
|
|
175
|
+
clean Clean build directory.
|
|
176
|
+
copy_file
|
|
177
|
+
echo
|
|
178
|
+
html Generate HTML.
|
|
179
|
+
images [Ignored] Prepare images.
|
|
180
|
+
start_server [Default] Start the server
|
|
181
|
+
stop_server
|
|
182
|
+
|
|
183
|
+
Powered by vulcan-builder - A Lightweight Python Build Tool.
|
|
184
|
+
|
|
185
|
+
vulcan-builder takes care of dependencies between tasks. In the
|
|
186
|
+
following case start_server depends on clean, html and image generation
|
|
187
|
+
(image task is ignored).
|
|
188
|
+
|
|
189
|
+
.. code:: bash
|
|
190
|
+
|
|
191
|
+
$ vb #Runs the default task start_server. It does exactly what "vb start_server" would do.
|
|
192
|
+
[ example.py - Starting task "clean" ]
|
|
193
|
+
Cleaning build directory...
|
|
194
|
+
[ example.py - Completed task "clean" ]
|
|
195
|
+
[ example.py - Starting task "html" ]
|
|
196
|
+
Generating HTML in directory "."
|
|
197
|
+
[ example.py - Completed task "html" ]
|
|
198
|
+
[ example.py - Ignoring task "images" ]
|
|
199
|
+
[ example.py - Starting task "start_server" ]
|
|
200
|
+
Starting server at localhost:80
|
|
201
|
+
[ example.py - Completed task "start_server" ]
|
|
202
|
+
|
|
203
|
+
The first few characters of the task name is enough to execute the task,
|
|
204
|
+
as long as the partial name is unambigious. You can specify multiple
|
|
205
|
+
tasks to run in the commandline. Again the dependencies are taken taken
|
|
206
|
+
care of.
|
|
207
|
+
|
|
208
|
+
.. code:: bash
|
|
209
|
+
|
|
210
|
+
$ vb cle ht cl
|
|
211
|
+
[ example.py - Starting task "clean" ]
|
|
212
|
+
Cleaning build directory...
|
|
213
|
+
[ example.py - Completed task "clean" ]
|
|
214
|
+
[ example.py - Starting task "html" ]
|
|
215
|
+
Generating HTML in directory "."
|
|
216
|
+
[ example.py - Completed task "html" ]
|
|
217
|
+
[ example.py - Starting task "clean" ]
|
|
218
|
+
Cleaning build directory...
|
|
219
|
+
[ example.py - Completed task "clean" ]
|
|
220
|
+
|
|
221
|
+
The ‘html’ task dependency ‘clean’ is run only once. But clean can be
|
|
222
|
+
explicitly run again later.
|
|
223
|
+
|
|
224
|
+
vb tasks can accept parameters from commandline.
|
|
225
|
+
|
|
226
|
+
.. code:: bash
|
|
227
|
+
|
|
228
|
+
$ vb "copy_file[/path/to/foo, path_to_bar]"
|
|
229
|
+
[ example.py - Starting task "clean" ]
|
|
230
|
+
Cleaning build directory...
|
|
231
|
+
[ example.py - Completed task "clean" ]
|
|
232
|
+
[ example.py - Starting task "copy_file" ]
|
|
233
|
+
Copying from /path/to/foo to path_to_bar
|
|
234
|
+
[ example.py - Completed task "copy_file" ]
|
|
235
|
+
|
|
236
|
+
vb can also accept keyword arguments.
|
|
237
|
+
|
|
238
|
+
.. code:: bash
|
|
239
|
+
|
|
240
|
+
$ vb start[port=8888]
|
|
241
|
+
[ example.py - Starting task "clean" ]
|
|
242
|
+
Cleaning build directory...
|
|
243
|
+
[ example.py - Completed task "clean" ]
|
|
244
|
+
[ example.py - Starting task "html" ]
|
|
245
|
+
Generating HTML in directory "."
|
|
246
|
+
[ example.py - Completed task "html" ]
|
|
247
|
+
[ example.py - Ignoring task "images" ]
|
|
248
|
+
[ example.py - Starting task "start_server" ]
|
|
249
|
+
Starting server at localhost:8888
|
|
250
|
+
[ example.py - Completed task "start_server" ]
|
|
251
|
+
|
|
252
|
+
$ vb echo[hello,world,foo=bar,blah=123]
|
|
253
|
+
[ example.py - Starting task "echo" ]
|
|
254
|
+
('hello', 'world')
|
|
255
|
+
{'blah': '123', 'foo': 'bar'}
|
|
256
|
+
[ example.py - Completed task "echo" ]
|
|
257
|
+
|
|
258
|
+
**Organizing build scripts**
|
|
259
|
+
----------------------------
|
|
260
|
+
|
|
261
|
+
You can break up your build files into modules and simple import them
|
|
262
|
+
into your main build file.
|
|
263
|
+
|
|
264
|
+
.. code:: python
|
|
265
|
+
|
|
266
|
+
from deploy_tasks import *
|
|
267
|
+
from test_tasks import functional_tests, report_coverage
|
|
268
|
+
|
|
269
|
+
Contributors/Contributing
|
|
270
|
+
-------------------------
|
|
271
|
+
|
|
272
|
+
- Raghunandan Rao - vulcan-builder is preceded by and forked from
|
|
273
|
+
`pynt <https://github.com/rags/pynt>`__, which was created by
|
|
274
|
+
`Raghunandan Rao <https://github.com/rags/pynt>`__.
|
|
275
|
+
- Calum J. Eadie - pynt is preceded by and forked from
|
|
276
|
+
`microbuild <https://github.com/CalumJEadie/microbuild>`__, which was
|
|
277
|
+
created by `Calum J. Eadie <https://github.com/CalumJEadie>`__.
|
|
278
|
+
|
|
279
|
+
If you want to make changes the repo is at
|
|
280
|
+
https://github.com/exrny/vulcan-builder. You will need
|
|
281
|
+
`pytest <http://www.pytest.org>`__ to run the tests
|
|
282
|
+
|
|
283
|
+
.. code:: bash
|
|
284
|
+
|
|
285
|
+
$ ./vb t
|
|
286
|
+
|
|
287
|
+
It will be great if you can raise a `pull
|
|
288
|
+
request <https://help.github.com/articles/using-pull-requests>`__ once
|
|
289
|
+
you are done.
|
|
290
|
+
|
|
291
|
+
If you find any bugs or need new features please raise a ticket in the
|
|
292
|
+
`issues section <https://github.com/exrny/vulcan-builder/issues>`__ of
|
|
293
|
+
the github repo.
|
|
294
|
+
|
|
295
|
+
License
|
|
296
|
+
-------
|
|
297
|
+
|
|
298
|
+
vulcan-builder is licensed under a `MIT
|
|
299
|
+
license <http://opensource.org/licenses/MIT>`__
|
|
300
|
+
|
|
301
|
+
.. |Build Status| image:: https://travis-ci.org/exrny/vulcan-builder.png?branch=master
|
|
302
|
+
:target: https://travis-ci.org/exrny/vulcan-builder
|
|
303
|
+
|
|
304
|
+
Changes
|
|
305
|
+
=======
|
|
306
|
+
|
|
307
|
+
0.2.4 - 12/09/2026
|
|
308
|
+
------------------
|
|
309
|
+
|
|
310
|
+
- Load build files through importlib, as imp is gone in Python 3.12
|
|
311
|
+
|
|
312
|
+
0.1.0 - 04/02/2018
|
|
313
|
+
------------------
|
|
314
|
+
|
|
315
|
+
- Initial commit
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
vulcan/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
vulcan/meta_builder.py,sha256=vHDF6LBd-Xit7CjfBIQYhE1RcgGSAZInHrMnX_EBpDA,240
|
|
3
|
+
vulcan/builder/__init__.py,sha256=BiEddRUWhDjgUcIE7vqLG8N1vQKN2kJELn6b8vLPx74,323
|
|
4
|
+
vulcan/builder/_vb.py,sha256=uQTfvO20U2MHxEPAqKUC5-ixkEtXw4kK9hvXEfB5AUw,11528
|
|
5
|
+
vulcan/builder/classes.py,sha256=hYrrWv3zWghcIOOZvO9oZUmgIzWXypd_J5bu4vzi2bw,1864
|
|
6
|
+
vulcan/builder/common.py,sha256=lqzoZNsUMWfqNqj2Yba0jROjnb0T9lvCsyjYb4oTf8Y,1090
|
|
7
|
+
vulcan_builder-0.2.4.dist-info/licenses/LICENSE.txt,sha256=mEH0HA2l6nLLaVo9M63FBbmRy4poRUBc9Xgu5aCt7Zo,1093
|
|
8
|
+
vulcan_builder-0.2.4.dist-info/METADATA,sha256=xCQrdj5-Q8mWTxR79mK14rnfi3bPGDOfibFyV0KqZTA,9003
|
|
9
|
+
vulcan_builder-0.2.4.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
10
|
+
vulcan_builder-0.2.4.dist-info/entry_points.txt,sha256=aJV6R0DCgfoy9lU0DCCPBD5OVOY3AHpwotmzIX0z994,43
|
|
11
|
+
vulcan_builder-0.2.4.dist-info/top_level.txt,sha256=Wp8Ddga48cqQsqVTlPPjWVCX4e2R_XmTWdEbkQ4y4Hw,7
|
|
12
|
+
vulcan_builder-0.2.4.dist-info/RECORD,,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (C) 2012 Raghunandan Rao
|
|
2
|
+
Copyright (C) 2012 Calum J. Eadie
|
|
3
|
+
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
5
|
+
this software and associated documentation files (the "Software"), to deal in the
|
|
6
|
+
Software without restriction, including without limitation the rights to use, copy,
|
|
7
|
+
modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
|
8
|
+
and to permit persons to whom the Software is furnished to do so, subject to the
|
|
9
|
+
following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
15
|
+
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
16
|
+
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
17
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
18
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
19
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
vulcan
|