-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 83da7bf
Showing
93 changed files
with
5,054 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
__pycache__/ | ||
*.py[cod] | ||
.Python | ||
env/ | ||
build/ | ||
develop-eggs/ | ||
dist/ | ||
downloads/ | ||
eggs/ | ||
.eggs/ | ||
lib64/ | ||
parts/ | ||
sdist/ | ||
var/ | ||
*.egg-info/ | ||
.installed.cfg | ||
*.egg | ||
*.manifest | ||
*.spec | ||
pip-log.txt | ||
pip-delete-this-directory.txt | ||
_build/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# Changelog | ||
|
||
## [Unreleased](https://github.com/koehlja/cookpy/compare/...) | ||
|
||
Nothing yet. | ||
|
||
|
||
## 0.1.0 (2017-07-27) | ||
|
||
Initial version. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2017 Jannis Christopher Köhl | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
# [Cookpy — A Modern Build System](https://getcook.org/) | ||
|
||
⚠**️ This software is currently alpha and not ready for production use. | ||
Not everything mentioned below will work.** | ||
|
||
|
||
## Overview | ||
|
||
Cookpy is an extensible, dynamic, parallel and cross-platform | ||
build system. It is based on the concept that writing build definitions should | ||
be as powerful and easy as possible, which is why everything in Python. While | ||
many other systems may be slightly faster in benchmarks, we believe that, at | ||
least for most projects, these differences do not outweigh the advantages you | ||
get by using a more powerful system. | ||
|
||
|
||
## Example | ||
|
||
Using the built-in rules is straightforward. You just import the corresponding | ||
module and call the supplied rule. This is all you need to get a working build | ||
going. The following example will automatically work on any supported platform. | ||
|
||
```python | ||
from cookpy import cpp | ||
|
||
cpp.executable( | ||
sources=['main.cpp'], | ||
destination='main' | ||
) | ||
``` | ||
|
||
Executing this script will build an executable using the correct | ||
platform-specific details. For example, the output is either named `main` or | ||
`main.exe` depending on the operating system. | ||
|
||
``` | ||
$ ls | ||
BUILD.py main.cpp main.h | ||
$ cook | ||
[INFO] Evaluating build script... | ||
[ 0%] Compile ".cookpy/intermediate/0271de2df0c16df23a49aa4b52371a04.o" | ||
[ 50%] Link "main" | ||
[100%] Everything should be up-to-date. | ||
$ ls build/ | ||
main | ||
``` | ||
|
||
You can also easily create your own rules. Upon calling, they are executed | ||
until the required information is passed back to the system using | ||
`yield core.publish(...)`. Everything after that is executed shortly after if | ||
the system decides it is necessary to do so. | ||
|
||
```python | ||
from cookpy import core | ||
|
||
@core.rule | ||
def replace(source, destination, mapping): | ||
source = core.resolve(source) | ||
|
||
yield core.publish( | ||
inputs=[source], | ||
outputs=[destination], | ||
check=mapping | ||
) | ||
|
||
with open(source) as file: | ||
content = file.read() | ||
for key, value in mapping.items(): | ||
content.replace(key, value) | ||
with open(destination, 'w') as file: | ||
file.write(content) | ||
``` | ||
|
||
Please look at the documentation if you want to know more. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from .core import version |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,258 @@ | ||
import argparse | ||
import os | ||
import platform | ||
import sys | ||
import traceback | ||
import threading | ||
|
||
from .core import system, loader, events, builder | ||
from .core.misc import is_inside, relative | ||
|
||
windows = platform.system() == 'Windows' | ||
lock = threading.Lock() | ||
|
||
if windows: | ||
import ctypes | ||
import functools | ||
stdout_id = ctypes.c_ulong(0xfffffff5) | ||
ctypes.windll.Kernel32.GetStdHandle.restype = ctypes.c_ulong | ||
stdout_handle = ctypes.windll.Kernel32.GetStdHandle(stdout_id) | ||
set_color = functools.partial( | ||
ctypes.windll.Kernel32.SetConsoleTextAttribute, stdout_handle) | ||
|
||
def print_progress(percent, text): | ||
with lock: | ||
print('[', end='', flush=True) | ||
set_color(10) | ||
print('{:>3}%'.format(percent), end='', flush=True) | ||
set_color(7) | ||
print(']', text) | ||
|
||
def on_debug(msg): | ||
if verbose: | ||
with lock: | ||
print('[', end='', flush=True) | ||
set_color(11) | ||
print('DEBUG', end='', flush=True) | ||
set_color(7) | ||
print(']', msg) | ||
|
||
def on_info(msg): | ||
with lock: | ||
print('[', end='', flush=True) | ||
set_color(10) | ||
print('INFO', end='', flush=True) | ||
set_color(7) | ||
print(']', msg) | ||
|
||
def on_warning(msg): | ||
with lock: | ||
print('[', end='', flush=True) | ||
set_color(14) | ||
print('WARNING', end='', flush=True) | ||
set_color(7) | ||
print(']', msg) | ||
|
||
def on_error(msg): | ||
with lock: | ||
print('[', end='', flush=True) | ||
set_color(12) | ||
print('ERROR', end='', flush=True) | ||
set_color(7) | ||
print(']', msg) | ||
else: | ||
def print_progress(percent, text): | ||
print('[\033[32m{:>3}%\033[0m]'.format(percent), text) | ||
|
||
def on_debug(msg): | ||
if verbose: | ||
print('[\033[34mDEBUG\033[0m]', msg) | ||
|
||
def on_info(msg): | ||
print('[\033[32mINFO\033[0m]', msg) | ||
|
||
def on_warning(msg): | ||
print('[\033[33mWARNING\033[0m]', msg) | ||
|
||
def on_error(msg): | ||
print('[\033[31mERROR\033[0m]', msg) | ||
|
||
options = {} | ||
request = set() | ||
started = 0 | ||
outdated = 0 | ||
failed = 0 | ||
verbose = False | ||
|
||
|
||
def good_path(path): | ||
if is_inside(path, '.'): | ||
return relative(path) | ||
else: | ||
return path | ||
|
||
|
||
def on_option(name, type, default, help): | ||
if name in options: | ||
return options[name] | ||
else: | ||
return default | ||
|
||
|
||
def on_fail(task, exc): | ||
global failed | ||
failed += 1 | ||
|
||
on_error('Failed task: {}'.format(', '.join( | ||
good_path(file.path) for file in task.outputs))) | ||
|
||
if verbose: | ||
print('Saved traceback (most recent call last):') | ||
print(''.join(traceback.format_list(task.stack))) | ||
if hasattr(exc, 'command'): | ||
if verbose: | ||
print('Traceback (most recent call last):') | ||
print(''.join(traceback.format_exception( | ||
type(exc), exc, exc.__traceback__)))#[3:])) | ||
print('$', exc.scommand) | ||
print(exc.output, end='') | ||
else: | ||
print('Traceback (most recent call last):') | ||
print(''.join(traceback.format_exception( | ||
type(exc), exc, exc.__traceback__)), end='') #[3:] | ||
|
||
|
||
def on_start(job, task): | ||
global started | ||
percent = int(100 * started / outdated) | ||
started += 1 | ||
print_progress(percent, task.message) | ||
|
||
|
||
def on_outdated(count): | ||
global outdated | ||
outdated = count | ||
|
||
|
||
def main(): | ||
global verbose | ||
|
||
parser = argparse.ArgumentParser( | ||
usage='%(prog)s <args> [target] [option=value] ...', | ||
formatter_class=HelpFormatter | ||
) | ||
parser._optionals.title = 'Arguments' | ||
|
||
if hasattr(os, 'cpu_count'): | ||
jobs = round(1.5 * (os.cpu_count() or 4)) + 1 | ||
else: | ||
jobs = 6 | ||
|
||
arg = parser.add_argument | ||
arg('-b', '--build', metavar='PATH', default='.', | ||
help='location of BUILD.py') | ||
arg('-j', '--jobs', type=int, metavar='INT', | ||
help='number of jobs (default: {})'.format( | ||
jobs)) | ||
arg('-v', '--verbose', action='store_true', help='enable debug mode') | ||
arg('-o', '--output', metavar='PATH', | ||
help='override build directory') | ||
arg('rest', nargs='*', help=argparse.SUPPRESS) | ||
arg('-r', '--results', action='store_true') | ||
args = parser.parse_args() | ||
|
||
verbose = args.verbose | ||
events.on_debug = on_debug | ||
events.on_info = on_info | ||
events.on_warning = on_warning | ||
events.on_error = on_error | ||
events.on_option = on_option | ||
events.on_start = on_start | ||
events.on_fail = on_fail | ||
events.on_outdated = on_outdated | ||
|
||
for entry in args.rest: | ||
if '=' in entry: | ||
key, value = entry.split('=', 1) | ||
options[key.upper()] = value | ||
else: | ||
request.add(entry) | ||
|
||
if os.path.isfile(args.build): | ||
build = args.build | ||
else: | ||
build = os.path.join(args.build, 'BUILD.py') | ||
if not os.path.isfile(build): | ||
on_error('Could not find BUILD.py at {}'.format( | ||
os.path.abspath(args.build))) | ||
return 1 | ||
|
||
if args.output is None: | ||
output = os.path.join(os.path.dirname(build), 'build/') | ||
else: | ||
output = args.output | ||
|
||
system.initialize(output) | ||
try: | ||
loader.load(build) | ||
except Exception: | ||
on_error('Failed to load BUILD.py - see below') | ||
raise | ||
|
||
if not args.results: | ||
builder.start(args.jobs or jobs, request or None) | ||
else: | ||
import gc | ||
import json | ||
from .core import graph | ||
|
||
tasks = {} | ||
for obj in gc.get_objects(): | ||
if isinstance(obj, graph.Task): | ||
tasks[obj.primary] = obj.result.__dict__ | ||
|
||
def set_to_list(s): | ||
if isinstance(s, set): | ||
return list(s) | ||
else: | ||
return None | ||
# raise TypeError('could not serialize {}'.format(type(s))) | ||
|
||
with open('results.json', 'w') as file: | ||
json.dump(tasks, file, default=set_to_list) | ||
print('results dumped') | ||
|
||
if not outdated: | ||
on_info('No work to do.') | ||
elif not failed: | ||
print_progress(100, 'Done.') | ||
else: | ||
on_warning('Failed tasks: {}'.format(failed)) | ||
return 1 | ||
|
||
|
||
class HelpFormatter(argparse.HelpFormatter): | ||
def _format_action_invocation(self, action): | ||
if not action.option_strings: | ||
default = self._get_default_metavar_for_positional(action) | ||
metavar, = self._metavar_formatter(action, default)(1) | ||
return metavar | ||
else: | ||
parts = [] | ||
if action.nargs == 0: | ||
parts.extend(action.option_strings) | ||
else: | ||
default = self._get_default_metavar_for_optional(action) | ||
args_string = self._format_args(action, default) | ||
parts.extend(action.option_strings) | ||
parts[-1] += ' ' + args_string | ||
return ', '.join(parts) | ||
|
||
def _format_usage(self, usage, actions, groups, prefix): | ||
if prefix is None: | ||
prefix = 'Usage: ' | ||
return super()._format_usage(usage, actions, groups, prefix) | ||
|
||
|
||
if __name__ == '__main__': | ||
sys.exit(main() or 0) |
Oops, something went wrong.