Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
jachris committed Jul 27, 2017
0 parents commit 83da7bf
Show file tree
Hide file tree
Showing 93 changed files with 5,054 additions and 0 deletions.
22 changes: 22 additions & 0 deletions .gitignore
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/
10 changes: 10 additions & 0 deletions CHANGELOG.md
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.
21 changes: 21 additions & 0 deletions LICENSE.md
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.
74 changes: 74 additions & 0 deletions README.md
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.
1 change: 1 addition & 0 deletions cook/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .core import version
258 changes: 258 additions & 0 deletions cook/__main__.py
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)
Loading

0 comments on commit 83da7bf

Please sign in to comment.