├── .dockerignore ├── .git-nest ├── .gitattributes ├── .githooks ├── git.py ├── githooks.py ├── hooks.py ├── log.py ├── pre-commit ├── pre-push └── setup ├── .gitignore ├── .gitlab-ci.yml ├── .pylintrc ├── .travis.yml ├── Dockerfile ├── Dockerfile.ci ├── LICENSE ├── Makefile ├── README.md ├── TODO.org ├── docs ├── api.md ├── home.md ├── quickstarts │ ├── cxx.md │ ├── gnu.md │ ├── go.md │ ├── ocaml.md │ └── quickstart.md └── static_files │ ├── drake@2x.png │ └── drake_logotype@2x.png ├── drake.el ├── drakefile ├── etags ├── examples ├── gnu_builder │ ├── _build │ │ ├── linux64 │ │ │ └── drake │ │ └── macosx64 │ │ │ └── drake │ ├── drake-utils │ │ └── gnu_builder.py │ ├── drakefile │ ├── http_request.cc │ └── patches │ │ └── curl.patch ├── hello_world │ ├── _build │ │ ├── linux64 │ │ │ └── drake │ │ └── macosx64 │ │ │ └── drake │ ├── drakefile │ └── hello_world.cc └── user_libraries │ ├── _build │ ├── linux64 │ │ └── drake │ └── macosx64 │ │ └── drake │ ├── color │ ├── Color.cc │ └── Color.hh │ ├── drakefile │ ├── geometry │ ├── Shape.cc │ ├── Shape.hh │ ├── Square.cc │ ├── Square.hh │ └── drakefile │ └── main.cc ├── logo.png ├── requirements.txt ├── src ├── bin │ └── drake └── drake │ ├── __init__.py │ ├── cmake.py │ ├── command.py │ ├── cxx │ ├── __init__.py │ ├── bison.py │ ├── boost.py │ ├── curl.py │ ├── flex.py │ ├── ipp.py │ ├── opengl.py │ ├── qt.py │ ├── qt5.py │ ├── qt_headers.py │ ├── sdl.py │ └── sofia_sip.py │ ├── debian.py │ ├── debug.py │ ├── deprecation.py │ ├── docker.py │ ├── enumeration.py │ ├── gRPC │ └── __init__.py │ ├── git.py │ ├── gnu.py │ ├── go │ └── __init__.py │ ├── license_file.py │ ├── log.py │ ├── markdown.py │ ├── nsis.py │ ├── ocaml │ ├── __init__.py │ ├── menhir.py │ └── ocamllex.py │ ├── python │ └── __init__.py │ ├── redhat.py │ ├── sched.py │ ├── templating.py │ ├── threadpool.py │ ├── utils.py │ ├── valgrind.py │ ├── version │ └── __init__.py │ └── which.py ├── tests ├── .gitignore ├── HTTPDownload ├── base │ ├── builder │ │ ├── drake-build.py │ │ ├── drakefile.py │ │ └── iter │ ├── change-dynamic-dependency │ ├── command-line │ ├── copy │ │ ├── A │ │ │ ├── B │ │ │ │ ├── drakefile │ │ │ │ └── src.txt │ │ │ └── drakefile │ │ ├── drakefile │ │ └── test │ ├── cyclic-dependencies │ ├── dependency │ ├── deps-dyn │ │ ├── src │ │ └── test │ ├── deps │ │ ├── drake-build.py │ │ ├── drakefile.py │ │ └── iter │ ├── dynamic-termination │ ├── failure │ ├── failure-cmd │ ├── interrupt-dynamic-dependency │ ├── mtime │ ├── no-builder-to-make │ │ └── test │ ├── obsolete-path-cache │ ├── path │ │ ├── drakefile │ │ ├── sub │ │ │ └── drakefile │ │ └── test │ ├── range │ ├── runner-env │ ├── runner │ │ ├── drakefile │ │ ├── main.cc │ │ └── test │ ├── symlink │ ├── termination │ ├── termination-keep-successful │ ├── termination-runaway │ └── version ├── cxx │ ├── boost │ │ ├── deps │ │ │ ├── include-version │ │ │ │ └── include │ │ │ │ │ └── boost-1_53 │ │ │ │ │ └── boost │ │ │ │ │ └── version.hpp │ │ │ ├── no-include │ │ │ │ └── include │ │ │ │ │ └── version.hpp │ │ │ ├── no-version │ │ │ │ └── include │ │ │ │ │ └── boost │ │ │ │ │ └── version.hpp │ │ │ └── simplest │ │ │ │ └── include │ │ │ │ └── boost │ │ │ │ └── version.hpp │ │ └── test │ ├── chained-static-libraries │ ├── copied-libraries │ ├── cyclic-dependencies │ │ ├── branch-1.hh │ │ ├── branch-2.hh │ │ ├── one.hh │ │ ├── root-1.hh │ │ ├── root.hh │ │ ├── test │ │ ├── three.hh │ │ └── two.hh │ ├── dependency-directory-clash │ │ ├── drakefile │ │ ├── foo.cc │ │ └── test │ ├── find_library │ │ ├── _build │ │ │ └── .keep │ │ ├── deps │ │ │ └── include │ │ │ │ └── somelib │ │ │ │ └── somelib.hh │ │ ├── drakefile │ │ └── test │ ├── generated-headers-deps │ │ ├── drakefile │ │ ├── main.cc │ │ └── test │ ├── headers-deps │ │ ├── drakefile │ │ ├── main.cc │ │ ├── test │ │ ├── test.cc │ │ └── test.hh │ ├── pkg-config │ │ ├── drakefile │ │ ├── somelib.pc │ │ └── test │ └── qt │ │ └── moc │ │ ├── drakefile │ │ ├── main.cc │ │ ├── test │ │ ├── test.cc │ │ ├── test.hh │ │ ├── widget.cc │ │ └── widget.hh ├── doctest ├── git │ └── base ├── sched ├── threadpool └── utils.py └── zsh └── _drake /.dockerignore: -------------------------------------------------------------------------------- 1 | .dockerignore 2 | Dockerfile 3 | TAGS 4 | -------------------------------------------------------------------------------- /.git-nest: -------------------------------------------------------------------------------- 1 | {"wiki": {"remote": "wiki", "branch": "master"}} 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | .githooks/pre-* pylint-disable=C0103 2 | tests/base/* pylint-disable=C0103 3 | README.md lint=false 4 | -------------------------------------------------------------------------------- /.githooks/git.py: -------------------------------------------------------------------------------- 1 | '''Git commands helpers.''' 2 | 3 | import subprocess 4 | 5 | def pipe(cmd): 6 | '''Run a git command and return its pipable output.''' 7 | return subprocess.Popen(['git'] + cmd, stdout=subprocess.PIPE).stdout 8 | 9 | def git(cmd): 10 | '''Run a git command.''' 11 | return subprocess.check_output(['git'] + cmd).decode().strip() 12 | 13 | def head(): 14 | '''Current git HEAD.''' 15 | try: 16 | return git(['rev-parse', '--verify', 'HEAD']) 17 | except subprocess.CalledProcessError: 18 | # Initial commit: return an empty tree object 19 | return '4b825dc642cb6eb9a060e54bf8d69288fbee4904' 20 | -------------------------------------------------------------------------------- /.githooks/githooks.py: -------------------------------------------------------------------------------- 1 | '''Git hooks common helpers.''' 2 | 3 | import sys 4 | 5 | import git 6 | import hooks 7 | 8 | from log import log 9 | 10 | def print_exc(error, file): 11 | '''Recursively print exceptions messages.''' 12 | print(': {}'.format(error), file=file, end='') 13 | if error.__cause__ is not None: 14 | print_exc(error.__cause__, file) 15 | else: 16 | print('', file=file) 17 | if hasattr(error, 'output'): 18 | for line in error.output.split('\n'): 19 | print(' {}'.format(line), file=file) 20 | 21 | def main(entrypoint): 22 | '''Run hook, catching and printing errors.''' 23 | try: 24 | entrypoint() 25 | except Exception as error: 26 | print('fatal error', file=sys.stderr, end='') 27 | print_exc(error, sys.stderr) 28 | exit(1) 29 | 30 | def status_to_string(status): 31 | '''Convert git single-letter status to a human readable one.''' 32 | if status == 'A': 33 | return 'added' 34 | elif status == 'C': 35 | return 'copied' 36 | elif status == 'M': 37 | return 'modified' 38 | elif status.startswith('R'): 39 | return 'renamed' 40 | else: 41 | raise Exception('unrecognized git status {!r}'.format(status)) 42 | 43 | def run_regression_hooks(before, before_sha, after, after_sha): 44 | '''Run regressions hook on the given before/after file versions.''' 45 | for line in git.git(['diff', '--name-status', '--diff-filter=ACMR', '{}..{}'.format( 46 | before_sha, after_sha)]).split('\n'): 47 | if line == '': 48 | continue 49 | status, file = line.split('\t', maxsplit=1) 50 | if status == 'A': 51 | before_f = None 52 | after_f = '{}/{}'.format(before, file) 53 | elif status in ['C', 'M']: 54 | before_f = '{}/{}'.format(before, file) 55 | after_f = '{}/{}'.format(after, file) 56 | elif status.startswith('R'): 57 | before_f, after_f = file.split('\t') 58 | file = after_f 59 | before_f = '{}/{}'.format(before, file) 60 | after_f = '{}/{}'.format(after, file) 61 | log.info('validate {status} file {path!r}', status=status_to_string(status), path=file) 62 | for hook in hooks.REGRESSION_HOOKS: 63 | try: 64 | hook(before_f, after_f, file) 65 | except Exception as error: 66 | raise Exception('error on {}'.format(file)) from error 67 | -------------------------------------------------------------------------------- /.githooks/hooks.py: -------------------------------------------------------------------------------- 1 | '''List of hooks for the drake repository.''' 2 | 3 | import os 4 | import pipes 5 | import re 6 | import subprocess 7 | 8 | import git 9 | 10 | def run(*args, **kwargs): 11 | '''Run a command and return its output.''' 12 | return subprocess.check_output(*args, **kwargs).decode().strip() 13 | 14 | def is_python(path): 15 | '''Whether the given file is Python.''' 16 | if path is not None: 17 | return run(['file', path]).find('Python') >= 0 18 | else: 19 | return False 20 | 21 | PYLINT_RE = re.compile('^Your code has been rated at ([0-9.]+)/10') 22 | 23 | def pylint(path, err, args=None): 24 | '''Py-lint the given file.''' 25 | command = ['pylint', path] 26 | if args is not None: 27 | command += args 28 | if not err: 29 | command.append('--exit-zero') 30 | output = run(command, env=dict(os.environ, PYTHONPATH='src')) 31 | for line in output.split('\n'): 32 | match = re.search(PYLINT_RE, line) 33 | if match: 34 | return float(match.group(1)), output 35 | raise Exception('failed to parse pylint output ({})'.format( 36 | ' '.join(map(pipes.quote, command)))) 37 | command.append('--score=n') 38 | output = run(command, 39 | env=dict(os.environ, PYTHONPATH='src')).split('\n') 40 | return 10., output 41 | 42 | def no_pylint_regression(before, after, path): 43 | '''Check there are no pylint score regression between before and after. 44 | 45 | If there was no previous file, check the file lints perfectly.''' 46 | command = [] 47 | prefix_lint = '{}: lint: '.format(path) 48 | prefix_disable = '{}: pylint-disable: '.format(path) 49 | for line in git.git(['check-attr', '-a', path]).split('\n'): 50 | if line.startswith(prefix_lint): 51 | if line[len(prefix_lint):] == 'false': 52 | return 53 | if line.startswith(prefix_disable): 54 | command.append('--disable={}'.format(line[len(prefix_disable):])) 55 | if is_python(after): 56 | if is_python(before): 57 | (score_b, _), (score_a, output_a) = ( 58 | pylint(f, False, command) for f in [before, after]) 59 | if score_a < score_b: 60 | error = Exception('pylint regression: {} -> {}'.format(score_b, score_a)) 61 | error.output = output_a 62 | raise error 63 | else: 64 | try: 65 | pylint(after, True, command) 66 | except subprocess.CalledProcessError as process_error: 67 | error = Exception('pylint errors') 68 | error.output = process_error.output.decode() 69 | raise error from None 70 | 71 | REGRESSION_HOOKS = [ 72 | no_pylint_regression, 73 | ] 74 | -------------------------------------------------------------------------------- /.githooks/log.py: -------------------------------------------------------------------------------- 1 | import logbook 2 | import sys 3 | 4 | logbook.StreamHandler(sys.stderr).push_application() 5 | log = logbook.Logger('githooks') 6 | -------------------------------------------------------------------------------- /.githooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | '''Run all pre-commit hooks and reject commit if any exception is raised.''' 4 | 5 | import os 6 | import subprocess 7 | import tempfile 8 | 9 | import git 10 | import githooks 11 | import hooks 12 | 13 | HEAD = git.head() 14 | 15 | def main(): 16 | 17 | '''Main entrypoint.''' 18 | 19 | with tempfile.TemporaryDirectory() as root: 20 | try: 21 | params = { 22 | 'head': HEAD, 23 | 'root': root, 24 | } 25 | git.git(['checkout-index', '--prefix={root}/index/'.format(**params), '--all', '--force']) 26 | os.mkdir('{root}/prev'.format(**params)) 27 | subprocess.run(['tar', '-C', '{root}/prev'.format(**params), '-x'], 28 | stdin=git.pipe(['archive', HEAD, '--format=tar'])) 29 | for line in git.git(['diff', '--cached', '--name-status', '--diff-filter=ACMR']).split('\n'): 30 | if line == '': 31 | continue 32 | status, file = line.split('\t', maxsplit=1) 33 | if status == 'A': 34 | before = None 35 | after = '{root}/index/{f}'.format(f=file, **params) 36 | elif status in ['C', 'M']: 37 | before = '{root}/prev/{f}'.format(f=file, **params) 38 | after = '{root}/index/{f}'.format(f=file, **params) 39 | elif status.startswith('R'): 40 | before, after = file.split('\t') 41 | file = after 42 | before = '{root}/prev/{f}'.format(f=before, **params) 43 | after = '{root}/index/{f}'.format(f=after, **params) 44 | for hook in hooks.REGRESSION_HOOKS: 45 | try: 46 | hook(before, after, file) 47 | except Exception as error: 48 | raise Exception('error on {}'.format(file)) from error 49 | except Exception as error: 50 | def clean_exn(error): 51 | if hasattr(error, 'output'): 52 | error.output = error.output.replace('{}/index/'.format(root), '') 53 | if error.__cause__ is not None: 54 | clean_exn(error.__cause__) 55 | clean_exn(error) 56 | raise error 57 | 58 | githooks.main(main) 59 | -------------------------------------------------------------------------------- /.githooks/pre-push: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | '''Run all pre-push hooks and reject commit if any exception is raised.''' 4 | 5 | import os 6 | import subprocess 7 | import sys 8 | import tempfile 9 | 10 | import git 11 | import githooks 12 | 13 | REMOTE = sys.argv[1] 14 | URL = sys.argv[2] 15 | 16 | def main(): 17 | 18 | '''Main entrypoint.''' 19 | 20 | for line in sys.stdin: 21 | _, local_sha, _, remote_sha = line.strip().split(' ') 22 | 23 | if remote_sha == '0000000000000000000000000000000000000000': 24 | # FIXME: We should still check, but against what? 25 | continue 26 | 27 | # Only check commits we are adding. 28 | base_sha = git.git(['merge-base', local_sha, remote_sha]) 29 | 30 | with tempfile.TemporaryDirectory() as root: 31 | try: 32 | def checkout(path, ref): 33 | os.mkdir(path) 34 | subprocess.run(['tar', '-C', path, '-x'], 35 | stdin=git.pipe(['archive', ref, '--format=tar'])) 36 | before, after = (os.path.join(root, dir) for dir in ['before', 'after']) 37 | checkout(after, local_sha) 38 | checkout(before, base_sha) 39 | githooks.run_regression_hooks(before, base_sha, after, local_sha) 40 | except Exception as error: 41 | def clean_exn(error, root): 42 | if hasattr(error, 'output'): 43 | error.output = error.output.replace('{}/index/'.format(root), '') 44 | if error.__cause__ is not None: 45 | clean_exn(error.__cause__, root) 46 | clean_exn(error, root) 47 | raise error 48 | 49 | githooks.main(main) 50 | -------------------------------------------------------------------------------- /.githooks/setup: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | DIR="$(dirname $(readlink -f $0))" 4 | GIT="$(dirname "$DIR")/.git" 5 | 6 | if ! test -d "$GIT"; then 7 | echo "$0: expected the git directory to be located at $GIT" 1>&2 8 | exit 1 9 | fi 10 | 11 | for h in "$DIR"/*; do 12 | name="$(basename $h)" 13 | if test "$name" == "$(basename $0)"; then 14 | continue 15 | fi 16 | echo "$GIT/hooks/$name" 17 | ln -s "$DIR/$name" "$GIT/hooks/$name" 18 | done 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | TAGS 3 | _build/ 4 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - check 3 | - containerize 4 | 5 | variables: 6 | PYTHONUNBUFFERED: "1" 7 | 8 | before_script: 9 | - TAG="$(git describe --always)" 10 | 11 | check: 12 | stage: check 13 | image: registry.gitlab.gruntech.org/infinit/drake/trusty-ci 14 | script: 15 | - pip3 install -r requirements.txt coverage 16 | - src/bin/drake . --workdir _build --coverage=true 17 | - cd _build && coverage report 18 | coverage: '/TOTAL.+ ([0-9]{1,3}%)/' 19 | 20 | containerize: 21 | stage: containerize 22 | image: registry.gitlab.gruntech.org/infinit/drake/trusty-ci 23 | script: 24 | - docker build --tag "drake:$TAG" . 25 | - docker run --rm "drake:$TAG" 26 | dependencies: 27 | - check 28 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MESSAGES CONTROL] 2 | disable=abstract-method, broad-except, no-else-return, no-self-use, redefined-outer-name, expression-not-assigned, pointless-statement, too-few-public-methods, protected-access, too-many-arguments 3 | 4 | [FORMAT] 5 | indent-string=' ' 6 | indent-after-paren=2 7 | 8 | [MISCELLANEOUS] 9 | 10 | notes= 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.5" 4 | install: 5 | - "pip install -r requirements.txt coverage" 6 | - "git config --global user.email mefyl@gruntech.org" 7 | - "git config --global user.name mefyl" 8 | script: 9 | - src/bin/drake . --workdir _build --coverage=true 10 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest AS drake-build 2 | 3 | RUN apk add --no-cache gcc musl-dev python3 python3-dev py-pip 4 | ADD . /root/ 5 | RUN pip3 install -r /root/requirements.txt 6 | RUN /root/src/bin/drake /root --prefix=/usr //install 7 | 8 | FROM alpine:latest 9 | 10 | RUN apk add --no-cache python3 11 | COPY --from=drake-build /usr/bin/drake /usr/bin/drake 12 | COPY --from=drake-build /usr/lib/python3.6/site-packages/ /usr/lib/python3.6/site-packages/ 13 | ENTRYPOINT /usr/bin/drake 14 | -------------------------------------------------------------------------------- /Dockerfile.ci: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 AS trusty-ci 2 | 3 | RUN apt-get update && apt-get install -y curl git python3.5 python3-pip && rm -rf /var/lib/apt/lists/* 4 | RUN curl https://download.docker.com/linux/static/stable/x86_64/docker-19.03.1.tgz | tar -C /usr/bin -xz docker/docker --strip-components 1 5 | 6 | FROM trusty-ci AS trusty-check 7 | 8 | ADD requirements.txt . 9 | RUN pip3 install -r requirements.txt 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DOCKERIZE:=1 2 | 3 | all: check 4 | 5 | ifeq ($(DOCKERIZE),0) 6 | check: 7 | cd _build && ./drake //check 8 | else 9 | check: image/trusty-check 10 | USER=$$USER && docker run --rm --volume $$PWD:$$PWD --workdir $$PWD --env DRAKE_RAW mefyl/drake/trusty-check sh -e -c "useradd --uid $$(id -u) $$USER && chown $$USER:$$USER $$(getent passwd $$USER | cut -d : -f 6) && su $$USER -c 'make DOCKERIZE=0 check'" 11 | endif 12 | 13 | image/%: 14 | docker build . --target $(patsubst image/%,%,$@) --tag mefyl/drake/$(patsubst image/%,%,$@) 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Drake 2 | 3 | The well-formed build system. 4 | 5 | [![pipeline status](https://gitlab.gruntech.org/mefyl/drake/badges/master/pipeline.svg)](https://gitlab.gruntech.org/mefyl/drake/commits/master) [![coverage report](https://gitlab.gruntech.org/infinit/drake/badges/master/coverage.svg)](https://gitlab.gruntech.org/infinit/drake/commits/master) 6 | 7 | ## About drake 8 | 9 | Drake is a build system written in Python aiming at expressivity, 10 | consistency and extensibility. 11 | 12 | In a nutshell: 13 | * The declaration of the dependency graph is enforced and separated 14 | from the build command, creating a streamlined build process. 15 | * Build files are plain Python, enabling you to stay declarative or 16 | leverage the full power of the language if needed. 17 | * Support for new build processes can easily be added, no matter how 18 | complex. 19 | * Nested projects are merged in a single graph, enabling rich 20 | dependency exploration. 21 | 22 | ## Project status 23 | 24 | Drake is a ~~one man~~ few people project, initially used as an 25 | internal tool. The core itself is still designed in a clean and 26 | extensible well suited for collaboration. What can be lacking is 27 | language support, which need to be implemented. So far the main 28 | supported languages are: 29 | 30 | * C++ 31 | * OCaml 32 | * Python 33 | * Go 34 | * GNU autotools projects 35 | 36 | Several convenience tools also have good support, such as Mako 37 | templating, Debian packaging or Docker. 38 | 39 | ## Is drake for me ? 40 | 41 | If you want a streamlined experience with a widely used, well 42 | supported build system, then drake might not (yet) be for you. 43 | 44 | If existing solution do not satisfy you, and you are ready or need to 45 | implement the build steps you need, then drake might be the perfect 46 | base to build upon. More so if the technology you use is one of the 47 | already well supported one. 48 | 49 | ## Requirements 50 | 51 | Drake requires *Python3* along with the following modules: 52 | - [greenlet](https://pypi.python.org/pypi/greenlet) *(Micro-threads)*. 53 | - [orderedset](https://pypi.python.org/pypi/orderedset) *(Set that remembers original insertion order)*. 54 | 55 | ## Installation 56 | 57 | Drake is bootstrapped. From the root of the repository, simply run: 58 | 59 | ```bash 60 | src/bin/drake --prefix=/usr/local //install 61 | ``` 62 | 63 | ## Getting starting 64 | 65 | Refer to the [quickstart](docs/quickstarts/quickstart.md) for a fast 66 | functional setup of Drake. 67 | -------------------------------------------------------------------------------- /TODO.org: -------------------------------------------------------------------------------- 1 | * Remove Runner.execute boolean return in favor of exceptions only 2 | -------------------------------------------------------------------------------- /docs/api.md: -------------------------------------------------------------------------------- 1 | # Drake API 2 | 3 | This section explores the Drake API in details, and can be used as a 4 | guide to implement your own or maintain the existing language specific 5 | implementations. 6 | 7 | ## 8 | -------------------------------------------------------------------------------- /docs/home.md: -------------------------------------------------------------------------------- 1 | # Drake 2 | 3 | The well-formed build system. 4 | 5 | [![pipeline status](https://gitlab.gruntech.org/mefyl/drake/badges/master/pipeline.svg)](https://gitlab.gruntech.org/mefyl/drake/commits/master) [![coverage report](https://gitlab.gruntech.org/infinit/drake/badges/master/coverage.svg)](https://gitlab.gruntech.org/infinit/drake/commits/master) 6 | 7 | ## About drake 8 | 9 | Drake is a build system written in Python aiming at expressivity, 10 | consistency and extensibility. 11 | 12 | In a nutshell: 13 | * The declaration of the dependency graph is enforced and separated 14 | from the build command, creating a streamlined build process. 15 | * Build files are plain Python, enabling you to stay declarative or 16 | leverage the full power of the language if needed. 17 | * Support for new build processes can easily be added, no matter how 18 | complex. 19 | * Nested projects are merged in a single graph, enabling rich 20 | dependency exploration. 21 | 22 | ## Project status 23 | 24 | Drake is a ~~one man~~ few people project, initially used as an 25 | internal tool. The core itself is still designed in a clean and 26 | extensible well suited for collaboration. What can be lacking is 27 | language support, which need to be implemented. So far the main 28 | supported languages are: 29 | 30 | * C++ 31 | * OCaml 32 | * Python 33 | * Go 34 | * GNU autotools projects 35 | 36 | Several convenience tools also have good support, such as Mako 37 | templating, Debian packaging or Docker. 38 | 39 | ## Is drake for me ? 40 | 41 | If you want a streamlined experience with a widely used, well 42 | supported build system, then drake might not (yet) be for you. 43 | 44 | If existing solution do not satisfy you, and you are ready or need to 45 | implement the build steps you need, then drake might be the perfect 46 | base to build upon. More so if the technology you use is one of the 47 | already well supported one. 48 | 49 | ## Requirements 50 | 51 | Drake requires *Python3* along with the following modules: 52 | - [greenlet](https://pypi.python.org/pypi/greenlet) *(Micro-threads)*. 53 | - [orderedset](https://pypi.python.org/pypi/orderedset) *(Set that remembers original insertion order)*. 54 | 55 | ## Installation 56 | 57 | Drake is bootstrapped. From the root of the repository, simply run: 58 | 59 | ```bash 60 | src/bin/drake --prefix=/usr/local //install 61 | ``` 62 | 63 | ## Getting started 64 | 65 | The [quickstart](docs/quickstarts/quickstart.md) will give you 66 | the minimal operational understanding of Drake. 67 | -------------------------------------------------------------------------------- /docs/quickstarts/cxx.md: -------------------------------------------------------------------------------- 1 | ../missing.md -------------------------------------------------------------------------------- /docs/quickstarts/gnu.md: -------------------------------------------------------------------------------- 1 | ../missing.md -------------------------------------------------------------------------------- /docs/quickstarts/go.md: -------------------------------------------------------------------------------- 1 | ../missing.md -------------------------------------------------------------------------------- /docs/quickstarts/ocaml.md: -------------------------------------------------------------------------------- 1 | ../missing.md -------------------------------------------------------------------------------- /docs/static_files/drake@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mefyl/drake/9f1c3a3f0a96e9efa35afaa5066830731aefaaeb/docs/static_files/drake@2x.png -------------------------------------------------------------------------------- /docs/static_files/drake_logotype@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mefyl/drake/9f1c3a3f0a96e9efa35afaa5066830731aefaaeb/docs/static_files/drake_logotype@2x.png -------------------------------------------------------------------------------- /drake.el: -------------------------------------------------------------------------------- 1 | (c++-project '() "." "src/bin/drake . --workdir _build //check" "" '()) 2 | -------------------------------------------------------------------------------- /drakefile: -------------------------------------------------------------------------------- 1 | '''Drake bootstrap drakefile''' 2 | 3 | import os 4 | import shutil 5 | import sys 6 | 7 | import drake 8 | 9 | # Put ourselves in the path. 10 | PYTHONPATH = '' 11 | if 'PYTHONPATH' in os.environ: 12 | PYTHONPATH = os.environ['PYTHONPATH'] 13 | SOURCE = '%s/%s' % (os.getcwd(), drake.path_source('src')) 14 | PYTHONPATH = '%s:%s' % (SOURCE, PYTHONPATH) 15 | os.environ['PYTHONPATH'] = PYTHONPATH 16 | 17 | def configure(prefix='.', coverage: bool = False): 18 | 19 | """Configure drake. 20 | 21 | prefix -- The prefix where to install. 22 | coverage -- Whether to collect test suite coverage. 23 | """ 24 | 25 | prefix = drake.Path(prefix) 26 | 27 | # Sources 28 | sources = drake.nodes( 29 | 'src/drake/__init__.py', 30 | 'src/drake/command.py', 31 | 'src/drake/enumeration.py', 32 | 'src/drake/deprecation.py', 33 | 'src/drake/docker.py', 34 | 'src/drake/cxx/__init__.py', 35 | 'src/drake/cxx/bison.py', 36 | 'src/drake/cxx/boost.py', 37 | 'src/drake/cxx/curl.py', 38 | 'src/drake/cxx/flex.py', 39 | 'src/drake/cxx/ipp.py', 40 | 'src/drake/cxx/opengl.py', 41 | 'src/drake/cxx/qt.py', 42 | 'src/drake/cxx/qt5.py', 43 | 'src/drake/cxx/sdl.py', 44 | 'src/drake/cxx/sofia_sip.py', 45 | 'src/drake/debug.py', 46 | 'src/drake/git.py', 47 | 'src/drake/gnu.py', 48 | 'src/drake/go/__init__.py', 49 | 'src/drake/log.py', 50 | 'src/drake/ocaml/__init__.py', 51 | 'src/drake/ocaml/menhir.py', 52 | 'src/drake/ocaml/ocamllex.py', 53 | 'src/drake/python/__init__.py', 54 | 'src/drake/sched.py', 55 | 'src/drake/templating.py', 56 | 'src/drake/threadpool.py', 57 | 'src/drake/utils.py', 58 | 'src/drake/valgrind.py', 59 | 'src/drake/which.py', 60 | ) 61 | command = drake.node('src/bin/drake') 62 | 63 | # Install 64 | lib = drake.Path('lib/python%s.%s/site-packages' % \ 65 | (sys.version_info[0], sys.version_info[1])) 66 | install = drake.Rule('install') 67 | install << drake.copy(sources, prefix / lib, 'src') 68 | install << drake.copy(command, prefix/'bin', 'src/bin') 69 | 70 | 71 | ## ----- ## 72 | ## Tests ## 73 | ## ----- ## 74 | 75 | check = drake.Rule('check') 76 | 77 | tests = [ 78 | 'base/change-dynamic-dependency', 79 | 'base/command-line', 80 | 'base/cyclic-dependencies', 81 | 'base/dependency', 82 | 'base/dynamic-termination', 83 | 'base/failure', 84 | 'base/failure-cmd', 85 | 'base/interrupt-dynamic-dependency', 86 | 'base/mtime', 87 | 'base/obsolete-path-cache', 88 | 'base/range', 89 | 'base/runner-env', 90 | 'base/symlink', 91 | 'base/termination', 92 | 'base/termination-keep-successful', 93 | 'base/version', 94 | 'cxx/copied-libraries', 95 | 'cxx/chained-static-libraries', 96 | 'doctest', 97 | 'git/base', 98 | 'sched', 99 | 'threadpool', 100 | 'HTTPDownload', 101 | ] 102 | 103 | if coverage: 104 | coverage = ['coverage', 'run', '--source', str(drake.path_source('src')), '--append'] 105 | else: 106 | coverage = None 107 | 108 | for test in tests: 109 | test = drake.node('tests/%s' % test) 110 | test.dependencies_add(sources) 111 | runner = drake.Runner( 112 | test, 113 | env={'PYTHONPATH': '{}:{}'.format(PYTHONPATH, drake.path_source('tests'))}, 114 | prefix=coverage, 115 | ) 116 | runner.reporting = drake.Runner.Reporting.on_failure 117 | check << runner.status 118 | 119 | # Old style tests 120 | 121 | tests = [ 122 | 'base/copy', 123 | 'base/deps-dyn', 124 | 'base/no-builder-to-make', 125 | 'base/path', 126 | 'base/runner', 127 | 'cxx/boost', 128 | 'cxx/cyclic-dependencies', 129 | 'cxx/dependency-directory-clash', 130 | 'cxx/find_library', 131 | 'cxx/headers-deps', 132 | 'cxx/generated-headers-deps', 133 | # FIXME: not CI friendly 134 | # 'cxx/pkg-config', 135 | # 'cxx/qt/moc', 136 | ] 137 | 138 | class Tester(drake.Builder): 139 | 140 | '''Copy the whole test tree and run test script there.''' 141 | 142 | def __init__(self, name): 143 | self.__name = name 144 | path = drake.Path('tests') / drake.Path(name) 145 | self.__exe = drake.node(path / 'test') 146 | self.__target = drake.node(path.with_extension('tst')) 147 | drake.Builder.__init__(self, [self.__exe] + sources, [self.__target]) 148 | 149 | def execute(self): 150 | source = str(self.__exe.path().dirname()) 151 | dest = str(self.__exe.name().dirname()) 152 | try: 153 | shutil.rmtree(dest) 154 | except OSError as error: 155 | if error.errno == 2: 156 | pass 157 | else: 158 | raise 159 | shutil.copytree(source, dest) 160 | cmd = [str(drake.Path(os.getcwd()) / self.__exe.path())] 161 | if coverage is not None: 162 | cmd = coverage + cmd 163 | res = self.cmd('Test %s' % self.__name, 164 | # Use an absolute path, because we chdir, and 165 | # "./test" isn't very helpful in backtraces. 166 | cmd, 167 | cwd=dest) 168 | if res: 169 | self.__target.path().touch() 170 | return res 171 | 172 | def target(self): 173 | '''Test status output node''' 174 | return self.__target 175 | 176 | for test in tests: 177 | check << Tester(test).target() 178 | 179 | # Local Variables: 180 | # mode: python 181 | # End: 182 | -------------------------------------------------------------------------------- /etags: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | etags $(find . -name _build -prune -a -false -o -regex '.*\.py' -prune) 3 | -------------------------------------------------------------------------------- /examples/gnu_builder/_build/linux64/drake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import sys 5 | 6 | # Add drake to the Python path. 7 | # You could also use your PYTHONPATH to specify where drake is. 8 | sys.path.insert(0, os.path.realpath('../../../../src')) 9 | 10 | import drake 11 | 12 | # Instantiate drake to look for a drakefile at '../../drakefile'. 13 | with drake.Drake('../..') as d: 14 | d.run() 15 | -------------------------------------------------------------------------------- /examples/gnu_builder/_build/macosx64/drake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import sys 5 | 6 | # Add drake to the Python path. 7 | # You could also use your PYTHONPATH to specify where drake is. 8 | sys.path.insert(0, os.path.realpath('../../../../src')) 9 | 10 | import drake 11 | import drake.cxx 12 | 13 | # Set the C++ compiler to be clang++ and the C compiler to be clang. 14 | cxx_toolkit = drake.cxx.GccToolkit(compiler = 'clang++', compiler_c = 'clang') 15 | # Use a default C++ compiler configuration. 16 | cxx_config = drake.cxx.Config() 17 | 18 | # Specify that clang++ should use libc++. 19 | cxx_config.flag('-stdlib=libc++') 20 | 21 | # Instantiate drake to look for a drakefile at '../../drakefile'. 22 | # Pass the specified toolkit and compiler configuration. 23 | with drake.Drake('../..') as d: 24 | d.run(cxx_toolkit = cxx_toolkit, 25 | cxx_config = cxx_config) 26 | -------------------------------------------------------------------------------- /examples/gnu_builder/drake-utils/gnu_builder.py: -------------------------------------------------------------------------------- 1 | import drake 2 | import drake.cxx 3 | 4 | import subprocess 5 | 6 | def _default_make_binary(): 7 | from drake.which import which 8 | to_try = [ 9 | 'make', 10 | 'gmake', 11 | 'mingw32-make', 12 | 'mingw64-make', 13 | ] 14 | for binary in to_try: 15 | path = which(binary) 16 | if path is not None: 17 | return path 18 | 19 | _DEFAULT_MAKE_BINARY = _default_make_binary() 20 | 21 | # Define a builder that can build standard GNU packages. These require calling 22 | # `configure` with a list of arguments followed by `make` with a list of rules 23 | # to build. 24 | class GNUBuilder(drake.Builder): 25 | 26 | # Instantiation of our builder. 27 | # Here we pass the options that we need for configure and make along with 28 | # paths, environment, etc. 29 | # Note that we need to instantiate the super class with the sources and 30 | # targets. This is done towards the end of the function. 31 | def __init__( 32 | self, 33 | cxx_toolkit, 34 | targets = [], 35 | configure: """Configure script path (or None if no configure 36 | step is needed)""" = None, 37 | working_directory: "Deduced from configure" = None, 38 | configure_args: "Arguments of the configure script" = [], 39 | sources = None, 40 | make_binary: "Make binary" = _DEFAULT_MAKE_BINARY, 41 | makefile: "Makefile filename, used if not None" = None, 42 | build_args: "Additional arguments for the make command" = ['install'], 43 | additional_env: "Additional environment variables" = {}, 44 | configure_interpreter = None, 45 | patch = None, 46 | ): 47 | self.__toolkit = cxx_toolkit 48 | self.__configure = configure 49 | self.__configure_interpreter = configure_interpreter 50 | self.__configure_args = configure_args 51 | self.__targets = list(targets) 52 | self.__make_binary = make_binary 53 | self.__makefile = makefile 54 | self.__build_args = build_args 55 | self.__env = {} 56 | self.__env.update(additional_env) 57 | self.__patch = patch 58 | if make_binary is not None: 59 | self.__env.setdefault('MAKE', make_binary.replace('\\', '/')) 60 | if working_directory is not None: 61 | self.__working_directory = working_directory 62 | if not self.__working_directory.exists(): 63 | self.__working_directory.mkpath() 64 | else: 65 | if self.__configure is None: 66 | raise Exception( 67 | "Cannot deduce the working directory (no configure script)" 68 | ) 69 | self.__working_directory = self.__configure.path().dirname() 70 | # We instantiate the super class passing it the list of sources (srcs) and 71 | # targets (dsts). This allows drake to develop the the dependency map. 72 | if sources is None: 73 | sources = [] 74 | if isinstance(cxx_toolkit.patchelf, drake.BaseNode): 75 | sources.append(cxx_toolkit.patchelf) 76 | drake.Builder.__init__( 77 | self, 78 | srcs = (configure is not None and [configure] or []) + sources, 79 | dsts = self.__targets) 80 | 81 | # The `execute` function is what is actually run to make the targets. 82 | # This is only done once all the dependency chains have been resolved. 83 | def execute(self): 84 | env = dict(self.__env) 85 | import os 86 | env.update(os.environ) 87 | with drake.CWDPrinter(drake.path_root() / drake.path_build() / self.work_directory): 88 | # Patch 89 | if self.__patch is not None: 90 | patch_path = str(drake.path_root() / self.__patch.path()) 91 | patch_cmd = ['patch', '-N', '-p', '1', '-i', patch_path], 92 | if not self.cmd('Patch %s' % self.work_directory, 93 | patch_cmd, 94 | cwd = self.work_directory): 95 | return False 96 | # Configure step 97 | if self.__configure is not None: 98 | if not self.cmd('Configure %s' % self.work_directory, 99 | self.command_configure, 100 | cwd = self.work_directory, 101 | env = env, 102 | leave_stdout = False): 103 | return False 104 | # Build step 105 | if not self.cmd('Build %s' % self.work_directory, 106 | self.command_build, 107 | cwd = self.work_directory, 108 | env = env, 109 | leave_stdout = False): 110 | return False 111 | for target in self.__targets: 112 | path = target.path().without_prefix(self.work_directory) 113 | if isinstance(target, drake.cxx.DynLib): 114 | rpath = '.' 115 | elif isinstance(target, drake.cxx.Executable): 116 | rpath = '../lib' 117 | else: 118 | continue 119 | with drake.WritePermissions(target): 120 | cmd = self.__toolkit.rpath_set_command(target.path(), rpath) 121 | if self.__toolkit.os is not drake.os.windows: 122 | if not self.cmd('Fix rpath for %s' % target.path(), cmd): 123 | return False 124 | # On OS X, we ensure that the library id and paths to dependency 125 | # libraries are correct. 126 | if self.__toolkit.os is drake.os.macos: 127 | cmd = ['install_name_tool', 128 | '-id', '@rpath/%s' % target.name().basename(), 129 | str(target.path())] 130 | if not self.cmd('Fix rpath for %s' % target.path(), cmd): 131 | return False 132 | lib_dependecies = self.parse_otool_libraries(target.path()) 133 | for dep in lib_dependecies: 134 | if dep.basename() in (t.path().basename() for t in self.__targets): 135 | cmd = [ 136 | 'install_name_tool', 137 | '-change', 138 | str(dep), 139 | '@rpath/%s' % dep.basename(), 140 | str(target.path()), 141 | ] 142 | if not self.cmd('Fix dependency name for %s' % target.path(), cmd): 143 | return False 144 | return True 145 | 146 | def parse_otool_libraries(self, path): 147 | command = ['otool', '-L', str(path)] 148 | return [drake.Path(line[1:].split(' ')[0]) 149 | for line 150 | in subprocess.check_output(command).decode().split('\n') 151 | if line.startswith('\t')] 152 | 153 | @property 154 | def command_configure(self): 155 | if self.__configure is None: 156 | return None 157 | config = [str(drake.path_build(absolute = True) / self.__configure.path())] 158 | if self.__configure_interpreter is not None: 159 | config.insert(0, self.__configure_interpreter) 160 | return config + self.__configure_args 161 | 162 | @property 163 | def command_build(self): 164 | if self.__makefile is not None: 165 | return [self.__make_binary, '-f', self.__makefile, 'install'] + self.__build_args 166 | return [self.__make_binary] + self.__build_args 167 | 168 | @property 169 | def work_directory(self): 170 | return str(self.__working_directory) 171 | 172 | 173 | def hash(self): 174 | env = {} 175 | env.update(self.__env) 176 | env.pop('DRAKE_RAW', '1') 177 | return ''.join([ 178 | str(self.command_configure), 179 | str(self.command_build), 180 | str(tuple(sorted(env))), 181 | ]) 182 | 183 | def __str__(self): 184 | return '%s(%s)' % (self.__class__.__name__, self.__working_directory) 185 | -------------------------------------------------------------------------------- /examples/gnu_builder/http_request.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | int 6 | main(int argc, char** argv) 7 | { 8 | CURL* curl; 9 | CURLcode res; 10 | curl_global_init(CURL_GLOBAL_DEFAULT); 11 | curl = curl_easy_init(); 12 | if (curl) 13 | { 14 | curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/"); 15 | curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); 16 | // In order to verify the host, we need a set of trusted CA certificates. 17 | // curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); 18 | res = curl_easy_perform(curl); 19 | if (res != CURLE_OK) 20 | { 21 | std::cerr << "curl_easy_perform() failed: " << std::endl 22 | << curl_easy_strerror(res); 23 | } 24 | curl_easy_cleanup(curl); 25 | } 26 | curl_global_cleanup(); 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /examples/gnu_builder/patches/curl.patch: -------------------------------------------------------------------------------- 1 | --- a/src/tool_operate.c 2 | +++ b/src/tool_operate.c 3 | @@ -1795,7 +1795,11 @@ CURLcode operate(struct GlobalConfig *config, int argc, argv_item_t argv[]) 4 | tool_help(); 5 | /* Check if we were asked for the manual */ 6 | else if(res == PARAM_MANUAL_REQUESTED) 7 | +#ifdef USE_MANUAL 8 | hugehelp(); 9 | +#else 10 | + tool_help(); 11 | +#endif 12 | /* Check if we were asked for the version information */ 13 | else if(res == PARAM_VERSION_INFO_REQUESTED) 14 | tool_version_info(); 15 | -------------------------------------------------------------------------------- /examples/hello_world/_build/linux64/drake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import sys 5 | 6 | # Add drake to the Python path. 7 | # You could also use your PYTHONPATH to specify where drake is. 8 | sys.path.insert(0, os.path.realpath('../../../../src')) 9 | 10 | import drake 11 | 12 | # Instantiate drake to look for a drakefile at '../../drakefile'. 13 | with drake.Drake('../..') as d: 14 | d.run() 15 | -------------------------------------------------------------------------------- /examples/hello_world/_build/macosx64/drake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import sys 5 | 6 | # Add drake to the Python path. 7 | # You could also use your PYTHONPATH to specify where drake is. 8 | sys.path.insert(0, os.path.realpath('../../../../src')) 9 | 10 | import drake 11 | import drake.cxx 12 | 13 | # Set the C++ compiler to be clang++ and the C compiler to be clang. 14 | cxx_toolkit = drake.cxx.GccToolkit(compiler = 'clang++', compiler_c = 'clang') 15 | # Use a default C++ compiler configuration. 16 | cxx_config = drake.cxx.Config() 17 | 18 | # Specify that clang++ should use libc++. 19 | cxx_config.flag('-stdlib=libc++') 20 | 21 | # Instantiate drake to look for a drakefile at '../../drakefile'. 22 | # Pass the specified toolkit and compiler configuration. 23 | with drake.Drake('../..') as d: 24 | d.run(cxx_toolkit = cxx_toolkit, 25 | cxx_config = cxx_config) 26 | -------------------------------------------------------------------------------- /examples/hello_world/drakefile: -------------------------------------------------------------------------------- 1 | import drake 2 | import drake.cxx 3 | 4 | def configure(cxx_toolkit = None, 5 | cxx_config = drake.cxx.Config()): 6 | 7 | # Create a default C++ toolkit if none is provided. 8 | # This will use the default system compiler. 9 | cxx_toolkit = cxx_toolkit or drake.cxx.GccToolkit() 10 | 11 | # List the sources required. 12 | sources = drake.nodes( 13 | 'hello_world.cc', 14 | ) 15 | 16 | # Declare a builder to create our 'hello_world' executable. 17 | hello_world = drake.cxx.Executable( 18 | path = drake.Path('hello_world'), # Path in the build directory where the 19 | # executable will be output. 20 | sources = sources, # Sources on which the executable depends 21 | tk = cxx_toolkit, # C++ toolkit to use. 22 | cfg = cxx_config, # C++ compiler configuration to use. 23 | ) 24 | 25 | # Create a 'build' rule. 26 | # Invoking this rule using //build will build all the targets (and their 27 | # dependencies). 28 | build = drake.Rule('build') 29 | 30 | # Add the 'hello_world' executable to the rule's targets. 31 | build << hello_world 32 | -------------------------------------------------------------------------------- /examples/hello_world/hello_world.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int 4 | main(int argc, char** argv) 5 | { 6 | std::cout << "Hello world" << std::endl; 7 | return 0; 8 | } 9 | -------------------------------------------------------------------------------- /examples/user_libraries/_build/linux64/drake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import sys 5 | 6 | # Add drake to the Python path. 7 | # You could also use your PYTHONPATH to specify where drake is. 8 | sys.path.insert(0, os.path.realpath('../../../../src')) 9 | 10 | import drake 11 | 12 | # Instantiate drake to look for a drakefile at '../../drakefile'. 13 | with drake.Drake('../..') as d: 14 | d.run() 15 | -------------------------------------------------------------------------------- /examples/user_libraries/_build/macosx64/drake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import sys 5 | 6 | # Add drake to the Python path. 7 | # You could also use your PYTHONPATH to specify where drake is. 8 | sys.path.insert(0, os.path.realpath('../../../../src')) 9 | 10 | import drake 11 | import drake.cxx 12 | 13 | # Set the C++ compiler to be clang++ and the C compiler to be clang. 14 | cxx_toolkit = drake.cxx.GccToolkit(compiler = 'clang++', compiler_c = 'clang') 15 | # Use a default C++ compiler configuration. 16 | cxx_config = drake.cxx.Config() 17 | 18 | # Specify that clang++ should use libc++. 19 | cxx_config.flag('-stdlib=libc++') 20 | 21 | # Instantiate drake to look for a drakefile at '../../drakefile'. 22 | # Pass the specified toolkit and compiler configuration. 23 | with drake.Drake('../..') as d: 24 | d.run(cxx_toolkit = cxx_toolkit, 25 | cxx_config = cxx_config) 26 | -------------------------------------------------------------------------------- /examples/user_libraries/color/Color.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace color 4 | { 5 | Color::Color(Intensity r, 6 | Intensity g, 7 | Intensity b) 8 | : r(r) 9 | , g(g) 10 | , b(b) 11 | {} 12 | 13 | Color::~Color() 14 | {} 15 | } 16 | 17 | std::ostream& 18 | operator <<(std::ostream& out, 19 | color::Color const& color) 20 | { 21 | return out << "rgb(" 22 | << static_cast(color.r) << ", " 23 | << static_cast(color.g) << ", " 24 | << static_cast(color.b) 25 | << ")"; 26 | } 27 | -------------------------------------------------------------------------------- /examples/user_libraries/color/Color.hh: -------------------------------------------------------------------------------- 1 | #ifndef COLOR_COLOR_HH 2 | # define COLOR_COLOR_HH 3 | 4 | # include 5 | 6 | namespace color 7 | { 8 | struct Color 9 | { 10 | typedef uint8_t Intensity; 11 | Color(Intensity r = 0, 12 | Intensity g = 0, 13 | Intensity b = 0); 14 | 15 | Color(Color const&) = default; 16 | 17 | virtual 18 | ~Color(); 19 | 20 | Intensity r, g, b; 21 | }; 22 | } 23 | 24 | std::ostream& 25 | operator <<(std::ostream& out, 26 | color::Color const& color); 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /examples/user_libraries/drakefile: -------------------------------------------------------------------------------- 1 | import drake 2 | import drake.cxx 3 | 4 | def configure(cxx_toolkit = None, 5 | cxx_config = drake.cxx.Config()): 6 | 7 | # Create a default C++ toolkit if none is provided. 8 | # This will use the default system compiler. 9 | cxx_toolkit = cxx_toolkit or drake.cxx.GccToolkit() 10 | 11 | # Use c++17 standard. 12 | cxx_config.standard = drake.cxx.Config.cxx_17 13 | # Add a compiler flag. 14 | cxx_config.flag('-Werror') 15 | 16 | # Add the directory this drakefile appears in as an include path. 17 | cxx_config.add_local_include_path('.') 18 | 19 | # List sources of our color library. 20 | color = drake.nodes( 21 | 'color/Color.hh', # Source node paths are relative to the drakefile. 22 | 'color/Color.cc', 23 | ) 24 | 25 | # The actual application. 26 | sources = drake.nodes( 27 | 'main.cc', 28 | ) 29 | 30 | # Include the drakefile in geometry. 31 | # This drakefile describes how the geometry dynamic library is built. 32 | geometry = drake.include(path = 'geometry', 33 | cxx_toolkit = cxx_toolkit, 34 | cxx_config = cxx_config) 35 | 36 | # Copy the geometry library to the 'lib' directory. 37 | # Setting strip prefix to True removes the entire prefix of the file. If more 38 | # than one node is specified, the first's prefix is used. 39 | geometry_library = drake.copy(geometry.library, 'lib', strip_prefix = True) 40 | 41 | # Declare a builder for the color static library. 42 | color_library = drake.cxx.StaticLib( 43 | path = 'lib/color', # Path to output the library to in the build directory. 44 | sources = color, # Sources the library depends on. 45 | cfg = cxx_config, # C++ compiler configuration to use. 46 | tk = cxx_toolkit, # C++ toolkit to use. 47 | ) 48 | 49 | # Create a rule for building just the libraries. 50 | # This can be invoked using //libs. 51 | # Invoking the rule will build all its targets and their dependencies. 52 | libraries = drake.Rule('libs') 53 | libraries << [geometry_library, color_library] 54 | 55 | # Create a copy of the cxx_config for the executable. 56 | # This will allow you to specify different include paths, compiler flags, etc. 57 | # to those used in the global C++ compiler configuration. 58 | executable_cxx_config = drake.cxx.Config(cxx_config) 59 | 60 | # Add the runtime library path. 61 | executable_cxx_config.lib_path_runtime('../lib') 62 | 63 | # Declare a builder for our executable 'colored_shape'. 64 | # The executable is linked any libraries passed as part of the sources. 65 | colored_shape = drake.cxx.Executable( 66 | path = drake.Path('bin/colored_shape'), # Path to output the executable to 67 | # in the build directory. 68 | sources = sources # Nodes on which the executable depends. 69 | + [ # In this case, it's 'main.cc' along with the 70 | geometry_library, # libraries we defined earlier. 71 | color_library, 72 | ], 73 | tk = cxx_toolkit, # C++ toolkit to use. 74 | cfg = executable_cxx_config, # C++ compiler configuration to use. 75 | ) 76 | 77 | # Create a rule //build. 78 | build = drake.Rule('build') 79 | 80 | # Add the 'colored_shape' executable to the rule's targets. 81 | build << colored_shape 82 | -------------------------------------------------------------------------------- /examples/user_libraries/geometry/Shape.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace geometry 4 | { 5 | Shape::Shape() 6 | {} 7 | 8 | Shape::~Shape() 9 | {} 10 | }; 11 | 12 | std::ostream& 13 | operator <<(std::ostream& out, 14 | geometry::Shape const& color) 15 | { 16 | return out << "random shape"; 17 | } 18 | -------------------------------------------------------------------------------- /examples/user_libraries/geometry/Shape.hh: -------------------------------------------------------------------------------- 1 | #ifndef GEOMETRY_COLOR_HH 2 | # define GEOMETRY_COLOR_HH 3 | 4 | # include 5 | 6 | namespace geometry 7 | { 8 | struct Shape 9 | { 10 | Shape(); 11 | 12 | virtual 13 | ~Shape(); 14 | }; 15 | } 16 | 17 | std::ostream& 18 | operator <<(std::ostream& out, 19 | geometry::Shape const& color); 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /examples/user_libraries/geometry/Square.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace geometry 4 | { 5 | Square::Square(int width, 6 | int height) 7 | : width(width) 8 | , height(height) 9 | {} 10 | 11 | Square::~Square() 12 | {} 13 | } 14 | 15 | std::ostream& 16 | operator <<(std::ostream& out, 17 | geometry::Square const& square) 18 | { 19 | return out << square.width 20 | << " x " 21 | << square.height 22 | << " square"; 23 | } 24 | -------------------------------------------------------------------------------- /examples/user_libraries/geometry/Square.hh: -------------------------------------------------------------------------------- 1 | #ifndef GEOMETRY_SQUARE_HH 2 | # define GEOMETRY_SQUARE_HH 3 | 4 | # include 5 | 6 | # include 7 | 8 | namespace geometry 9 | { 10 | struct Square 11 | : public Shape 12 | { 13 | Square(int width = 1, 14 | int height = 1); 15 | 16 | Square(Square const&) = default; 17 | 18 | virtual 19 | ~Square(); 20 | 21 | int width, height; 22 | }; 23 | } 24 | 25 | std::ostream& 26 | operator <<(std::ostream& out, 27 | geometry::Square const& square); 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /examples/user_libraries/geometry/drakefile: -------------------------------------------------------------------------------- 1 | import drake 2 | import drake.cxx 3 | 4 | library = None 5 | 6 | def configure(cxx_toolkit, 7 | cxx_config): 8 | global library 9 | 10 | # List sources of our geometry library. 11 | sources = drake.nodes( 12 | 'Shape.hh', # Source node paths are relative to the drakefile. 13 | 'Shape.cc', 14 | 'Square.hh', 15 | 'Square.cc', 16 | ) 17 | 18 | # Declare a builder for the shape dynamic library. 19 | # This will create a .so, .dylib or .dll depending on the cxx_toolkit used. 20 | library = drake.cxx.DynLib( 21 | path = 'lib/shape', # Path to output the library to in the build directory. 22 | sources = sources, # Sources the library depends on. 23 | cfg = cxx_config, # C++ compiler configuration to use. 24 | tk = cxx_toolkit, # C++ toolkit to use. 25 | ) 26 | -------------------------------------------------------------------------------- /examples/user_libraries/main.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | struct ColoredSquare 7 | : public geometry::Square 8 | , public color::Color 9 | { 10 | ColoredSquare(geometry::Square const& square, 11 | color::Color const& color) 12 | : geometry::Square(square) 13 | , color::Color(color) 14 | {} 15 | 16 | ColoredSquare(int width, 17 | int height, 18 | color::Color::Intensity r, 19 | color::Color::Intensity g, 20 | color::Color::Intensity b) 21 | : ColoredSquare(geometry::Square(width, height), 22 | color::Color(r, g, b)) 23 | {} 24 | }; 25 | 26 | std::ostream& 27 | operator <<(std::ostream& out, 28 | ColoredSquare const& object) 29 | { 30 | out << static_cast(object) 31 | << " colored in " 32 | << static_cast(object); 33 | return out; 34 | } 35 | 36 | int 37 | main(int argc, char** argv) 38 | { 39 | ColoredSquare some_shape{230, 180, 80, 255, 183}; 40 | geometry::Square square{256, 256}; 41 | color::Color red{255, 0, 0}; 42 | ColoredSquare another_shape{square, red}; 43 | 44 | std::cout << "Some shape: " << some_shape << std::endl; 45 | std::cout << "Another shape: " << another_shape << std::endl; 46 | 47 | return 0; 48 | } 49 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mefyl/drake/9f1c3a3f0a96e9efa35afaa5066830731aefaaeb/logo.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Mako==1.2.2 2 | greenlet==0.4.15 3 | mistune==0.8.4 4 | orderedset==2.0.1 5 | requests==2.20.0 6 | -------------------------------------------------------------------------------- /src/bin/drake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | '''Main driver for drake.''' 4 | 5 | import os 6 | import sys 7 | 8 | PREFIX = os.path.realpath(os.path.dirname(os.path.dirname(__file__))) 9 | SEARCH = [ 10 | os.path.join('lib', 'python{}.{}'.format(*sys.version_info), 'site-packages'), 11 | 'lib', 12 | '.', 13 | ] 14 | 15 | LIB = None 16 | for p in (os.path.join(PREFIX, s) for s in SEARCH): 17 | if os.path.exists(os.path.join(p, 'drake')): 18 | LIB = p 19 | break 20 | 21 | if LIB is None: 22 | print('{}: unable to find drake libraries'.format(sys.argv[0]), file=sys.stderr) 23 | exit(1) 24 | 25 | sys.path.insert(0, LIB) 26 | 27 | import drake 28 | 29 | PATH = '.' 30 | if len(sys.argv) > 1 and not sys.argv[1].startswith('-'): 31 | PATH = sys.argv[1] 32 | del sys.argv[1] 33 | 34 | drake.run(PATH) 35 | -------------------------------------------------------------------------------- /src/drake/cmake.py: -------------------------------------------------------------------------------- 1 | import drake 2 | from functools import lru_cache 3 | from itertools import chain 4 | import os 5 | 6 | class CMakeBuilder(drake.Builder): 7 | 8 | def __init__(self, toolkit, srcs, dsts, vars, 9 | targets = None, path_to_cmake_source = None): 10 | ''' 11 | `srcs`: what we depend upon. 12 | `dsts`: what will be built. 13 | `vars`: dict variables passed to cmake via `-D`. 14 | `targets`: list of Makefile targets. 15 | `path_to_cmake_source`: path to the directory containing the CMakeFile. 16 | ''' 17 | self.__toolkit = toolkit 18 | self.__vars = vars 19 | self.__prefix = drake.Drake.current.prefix 20 | self.__path_to_cmake_source = \ 21 | drake.Path(path_to_cmake_source) if path_to_cmake_source \ 22 | else drake.path_source() / self.__prefix 23 | self.__env = dict(os.environ) 24 | self.__env.update({ 25 | 'CC': ' '.join(toolkit.command_c), 26 | 'CXX': ' '.join(toolkit.command_cxx), 27 | }) 28 | self.__cmake_cache = drake.node('CMakeCache.txt') 29 | self.__targets = targets 30 | # cmake 3 compat 31 | self.__vars.update({'CMAKE_SYSTEM_PROCESSOR': 'x86_64'}) 32 | if self.toolkit.os is drake.os.windows: 33 | self.__vars.update({ 34 | 'CMAKE_ASM_NASM_COMPILER': self.toolkit.cxx[0:-3] + 'as', 35 | 'CMAKE_RC_COMPILER': self.toolkit.cxx[0:-3] + 'windres', 36 | 'CMAKE_SYSTEM_NAME': 'Windows', 37 | }) 38 | dsts = list(dsts) 39 | dsts.append(self.__cmake_cache) 40 | # Call __init__ last, make __cmake_cache is declared a dsts, so 41 | # that it has a build-tree path, not a source-tree one. 42 | super().__init__(srcs = srcs, dsts = dsts) 43 | 44 | @property 45 | def toolkit(self): 46 | return self.__toolkit 47 | 48 | def execute(self): 49 | # cmake fails if the cache was moved. 50 | cpath = str(self.__cmake_cache.path()) 51 | if os.path.exists(cpath): 52 | os.unlink(cpath) 53 | if not self.cmd(' '.join(self.cmake_cmd), 54 | self.cmake_cmd, 55 | cwd = self.__prefix, 56 | env = self.__env): 57 | return False 58 | if self.__targets is None: 59 | return self.cmd('make', self.make_cmd, cwd = self.__prefix) 60 | for t in self.__targets: 61 | if isinstance(t, str): 62 | wd, tgt = '', t 63 | else: 64 | wd, tgt = t[0], t[1] 65 | if not self.cmd('make %s' % tgt, ['make', tgt], cwd = self.__prefix / wd): 66 | return False 67 | return True 68 | 69 | @property 70 | @lru_cache(maxsize=1) 71 | def cmake_cmd(self): 72 | cmd = ['cmake'] 73 | for k, v in sorted(self.__vars.items()): 74 | cmd.append('-D%s=%s' % (k, v)) 75 | cmd.append(str(self.__path_to_cmake_source)) 76 | return cmd 77 | 78 | @property 79 | def make_cmd(self): 80 | return ['make'] 81 | def hash(self): 82 | return (self.cmake_cmd, self.make_cmd) 83 | def __str__(self): 84 | return '%s' % (self.__class__.__name__) 85 | -------------------------------------------------------------------------------- /src/drake/command.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2019, Quentin "mefyl" Hocquet 2 | # 3 | # This software is provided "as is" without warranty of any kind, 4 | # either expressed or implied, including but not limited to the 5 | # implied warranties of fitness for a particular purpose. 6 | # 7 | # See the LICENSE file for more information. 8 | 9 | import drake 10 | import subprocess 11 | 12 | class Command: 13 | 14 | def __init__(self, path = None): 15 | if isinstance(path, self.__class__): 16 | self.__path = drake.Path(path.__path) 17 | self.__version = drake.Version(path.__version) 18 | else: 19 | if path is None: 20 | self.__path = drake.Path(self.__class__.name) 21 | else: 22 | self.__path = drake.Path(path) 23 | try: 24 | output = self._get_version() 25 | except Exception as e: 26 | raise Exception('Unable to find %s' % self.path) from e 27 | try: 28 | self.__version = self._parse_version(output) 29 | except Exception as e: 30 | raise Exception('Unable to parse %s version from %r' % \ 31 | (self.__class.name, output)) from e 32 | 33 | def _get_version(self): 34 | return subprocess.check_output( 35 | [str(self.path), '--version']).decode() 36 | 37 | def _parse_version(self, v): 38 | return drake.Version(v) 39 | 40 | @property 41 | def path(self): 42 | return self.__path 43 | 44 | @property 45 | def version(self): 46 | return self.__version 47 | -------------------------------------------------------------------------------- /src/drake/cxx/bison.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2019, Quentin "mefyl" Hocquet 2 | # 3 | # This software is provided "as is" without warranty of any kind, 4 | # either expressed or implied, including but not limited to the 5 | # implied warranties of fitness for a particular purpose. 6 | # 7 | # See the LICENSE file for more information. 8 | 9 | from .. import Builder, Node, Path, node 10 | 11 | class Bison: 12 | 13 | def __init__(self, bison = 'bison'): 14 | 15 | self.__bison = bison 16 | 17 | def plug(self, toolkit): 18 | 19 | toolkit.hook_bin_src_add(self.hook_bin_src) 20 | 21 | def hook_bin_src(self, src): 22 | 23 | if isinstance(src, BisonSource): 24 | builder = BisonCompiler(src, self) 25 | return builder.cc() 26 | 27 | def compile(self, path, dst): 28 | 29 | return '%s --defines --report=all -Dparse.error=verbose -Dlr.default-reductions=consistent --xml %s -o %s' % (self.__bison, path, dst) 30 | 31 | 32 | class BisonSource(Node): 33 | 34 | def __init__(self, path): 35 | 36 | Node.__init__(self, path) 37 | 38 | Node.extensions['y'] = BisonSource 39 | Node.extensions['yy'] = BisonSource 40 | 41 | 42 | class BisonCompiler(Builder): 43 | 44 | name = 'Bison compilation' 45 | 46 | def __init__(self, source, bison): 47 | 48 | self.__source = source 49 | self.__bison = bison 50 | 51 | base_path = source.name() 52 | 53 | grammar_cc_path = Path(base_path) 54 | grammar_cc_path.extension = 'cc' 55 | self.__grammar_cc = node(grammar_cc_path) 56 | 57 | grammar_hh_path = Path(base_path) 58 | grammar_hh_path.extension = 'hh' 59 | self.__grammar_hh = node(grammar_hh_path) 60 | 61 | for base in ['location', 'position', 'stack']: 62 | path = base_path.dirname() / ('%s.hh' % base) 63 | self.__dict__['_BisonCompiler__%s' % base] = node(path) 64 | 65 | self.__grammar_hh = node(grammar_hh_path) 66 | 67 | Builder.__init__(self, [source], 68 | [self.__grammar_cc, 69 | self.__grammar_hh, 70 | self.__location, 71 | self.__position, 72 | self.__stack, 73 | ]) 74 | 75 | def execute(self): 76 | 77 | return self.cmd('Bison %s' % self.__source, 78 | self.__bison.compile(self.__source.path(), 79 | self.__grammar_cc.path())) 80 | 81 | def cc(self): 82 | 83 | return self.__grammar_cc 84 | -------------------------------------------------------------------------------- /src/drake/cxx/boost.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2019, Quentin "mefyl" Hocquet 2 | # 3 | # This software is provided "as is" without warranty of any kind, 4 | # either expressed or implied, including but not limited to the 5 | # implied warranties of fitness for a particular purpose. 6 | # 7 | # See the LICENSE file for more information. 8 | 9 | import drake 10 | import drake.cxx 11 | import itertools 12 | import os.path 13 | import sys 14 | 15 | from .. import Path, Version 16 | from . import Config, StaticLib 17 | 18 | def find(prefix = None, 19 | cxx_toolkit = None, 20 | version = Version(), 21 | prefer_shared = True): 22 | if isinstance(prefix, Boost): 23 | if prefix.version not in version: 24 | raise Exception('given Boost %s does not fit ' 25 | 'the requested version %s' % 26 | (prefix.version, version)) 27 | return prefix 28 | else: 29 | return Boost(prefix = prefix, 30 | cxx_toolkit = cxx_toolkit, 31 | version = version, 32 | prefer_shared = prefer_shared) 33 | 34 | class Boost(drake.Configuration): 35 | 36 | """Configuration for the Boost C++ library collection""" 37 | 38 | __libraries = { 39 | 'locale': 'locale', 40 | 'test': 'unit_test_framework', 41 | 'thread': 'thread', 42 | 'timer': 'timer', 43 | 'system': 'system', 44 | 'filesystem': 'filesystem', 45 | 'signals': 'signals', 46 | 'date_time': 'date_time', 47 | 'regex': 'regex', 48 | 'program_options': 'program_options', 49 | 'chrono': 'chrono', 50 | 'context': 'context', 51 | 'coroutine': 'coroutine', 52 | 'iostreams': 'iostreams', 53 | 'python': list(itertools.chain( 54 | *(('python3{}'.format(v), 'python-3.{}'.format(v)) for v in range(7)))) \ 55 | + ['python3', 'python'] 56 | } 57 | 58 | def __init__(self, 59 | cxx_toolkit = None, 60 | prefix = None, 61 | version = Version(), 62 | version_effective = None, 63 | prefer_shared = True): 64 | """Find and create a configuration for Boost. 65 | 66 | prefix -- Where to find Boost, should contain 67 | include/boost/version.hpp among others. /usr and 68 | /usr/local are searched by default. If relative, it 69 | is rooted in the source tree. 70 | version -- Requested version. 71 | prefer_shared -- Check dynamic libraries first. 72 | """ 73 | # Fix arguments 74 | cxx_toolkit = cxx_toolkit or drake.cxx.Toolkit() 75 | self.__cxx_toolkit = cxx_toolkit 76 | self.__prefer_shared = prefer_shared 77 | # Compute the search path. 78 | if prefix is None: 79 | test = [path.dirname() for path in cxx_toolkit.include_path 80 | if path.basename() == 'include'] 81 | else: 82 | test = [Path(prefix)] 83 | token = drake.Path('boost/version.hpp') 84 | include_subdirs = {drake.Path('include')} 85 | for prefix in test: 86 | for subdir in include_subdirs: 87 | if not (prefix / subdir).exists(): 88 | continue 89 | include_subdirs = include_subdirs.union( 90 | (subdir / p for p in (prefix / subdir).list() 91 | if p.startswith('boost-'))) 92 | tokens = map(lambda p: p / token, include_subdirs) 93 | prefixes = self._search_many_all(list(tokens), test) 94 | miss = [] 95 | if version_effective is not None: 96 | assert prefix is not None 97 | # Try every search path 98 | for path, include_subdir in prefixes: 99 | path = drake.path_build(path) 100 | include_subdir = include_subdir.without_suffix(token) 101 | # Create basic configuration for version checking. 102 | cfg = Config() 103 | cfg.add_system_include_path(path.without_prefix(drake.path_build()) / include_subdir) 104 | self.__lib_path = path / 'lib' 105 | cfg.lib_path(path.without_prefix(drake.path_build()) / 'lib') 106 | # Check the version. 107 | if version_effective is None: 108 | version_eff = cxx_toolkit.preprocess( 109 | '#include \nBOOST_VERSION', 110 | config = cfg) 111 | version_eff = int(version_eff.split('\n')[-2].strip()) 112 | version_eff = Version(version_eff // 100000, 113 | version_eff // 100 % 1000, 114 | version_eff % 100) 115 | else: 116 | version_eff = version_effective 117 | if version_eff not in version: 118 | miss.append(version_eff) 119 | continue 120 | # Fill configuration 121 | self.__prefix = path 122 | self.__cfg = cfg 123 | for prop in self.__libraries: 124 | setattr(self, '_Boost__config_%s_dynamic' % prop, None) 125 | setattr(self, '_Boost__config_%s_static' % prop, None) 126 | setattr(self, '_Boost__config_%s_dynamic_header' % prop, None) 127 | setattr(self, '_Boost__config_%s_static_header' % prop, None) 128 | setattr(self, '_Boost__%s_dynamic' % prop, None) 129 | setattr(self, '_Boost__%s_static' % prop, None) 130 | self.__version = version_eff 131 | return 132 | 133 | raise Exception('no matching boost for the requested version ' 134 | '(%s) in %s. Found versions: %s.' % \ 135 | (version, self._format_search([path for path, include_subdir in prefixes]), 136 | ', '.join(map(str, miss)))) 137 | 138 | @property 139 | def prefer_shared(self): 140 | return self.__prefer_shared 141 | 142 | def __find_lib(self, lib, lib_path, cxx_toolkit, static): 143 | # Suffixes 144 | suffixes = ['-mt', ''] 145 | if static: 146 | suffixes.append('-mt-s') 147 | suffixes.append('-s') 148 | if isinstance(cxx_toolkit, drake.cxx.VisualToolkit): 149 | suffix = '-vc%s0-mt-%s_%s' % (cxx_toolkit.version, 150 | self.version.major, 151 | self.version.minor) 152 | suffixes = [suffix] + suffixes 153 | if cxx_toolkit.os is drake.os.macos: 154 | suffix = '-%s_%s' % (self.version.major, self.version.minor) 155 | suffixes = [suffix] + suffixes 156 | mgw = '-mgw-mt-%s_%s' % (self.version.major, self.version.minor) 157 | suffixes += [mgw] 158 | # Variants 159 | variants = [''] 160 | if lib == 'thread' and cxx_toolkit.os is drake.os.windows: 161 | variants.append('_win32') 162 | if isinstance(lib, str): 163 | lib = (lib,) 164 | for lib, suffix, variant in itertools.product(lib, suffixes, variants): 165 | libname = 'boost_%s%s%s' % (lib, variant, suffix) 166 | tests = [] 167 | if static: 168 | filename = cxx_toolkit.libname_static(self.__cfg, libname) 169 | tests.append(lib_path / filename) 170 | else: 171 | filename = cxx_toolkit.libname_dyn(libname, self.__cfg) 172 | tests.append(lib_path / ('%s.%s' % (filename, self.__version))) 173 | tests.append(lib_path / filename) 174 | for test in tests: 175 | # Look for a node if we build our own boost. 176 | if test.absolute(): 177 | drake_path = test.without_prefix(drake.path_root()) 178 | else: 179 | drake_path = test 180 | node = drake.Drake.current.nodes.get(drake_path, None) 181 | if node is not None: 182 | return node 183 | # Otherwise look on the filesystem. 184 | if test.exists(): 185 | path = os.path.realpath(str(test)) 186 | if static: 187 | return drake.cxx.StaticLib(path) 188 | else: 189 | return drake.cxx.DynLib(path) 190 | raise Exception( 191 | 'Unable to find %s Boost %s library in %s' % \ 192 | ('static' if static else 'dynamic', lib, lib_path)) 193 | 194 | def config(self): 195 | return self.__cfg 196 | 197 | def __repr__(self): 198 | return 'Boost(prefix = %s)' % repr(self.__prefix) 199 | 200 | @property 201 | def version(self): 202 | return self.__version 203 | 204 | 205 | for prop, library in Boost._Boost__libraries.items(): 206 | def unclosure(prop, library): 207 | def library_getter(self, static): 208 | name = '_Boost__%s_%s' % (prop, 209 | 'static' if static else 'dynamic') 210 | if getattr(self, name) is None: 211 | lib = self._Boost__find_lib(library, 212 | self._Boost__lib_path, 213 | self._Boost__cxx_toolkit, 214 | static = static) 215 | setattr(self, name, lib) 216 | return getattr(self, name) 217 | setattr(Boost, '%s_dynamic' % prop, 218 | property(lambda self: library_getter(self, False))) 219 | setattr(Boost, '%s_static' % prop, 220 | property(lambda self: library_getter(self, True))) 221 | def config_getter(self, static = None, link = True): 222 | if static is None: 223 | static = not self._Boost__prefer_shared 224 | name = '_Boost__config_%s_%s' % \ 225 | (prop, 'static' if static else 'dynamic') 226 | if getattr(self, name) is None: 227 | lib = library_getter(self, static) 228 | c = Config() 229 | macro = prop.upper() 230 | macro += static and '_STATIC' or '_DYN' 231 | c.define('BOOST_%s_LINK' % macro, 1) 232 | if prop.startswith('python') and static: 233 | c.define('BOOST_PYTHON_STATIC_LIB') 234 | setattr(self, name + '_header', Config(c)) 235 | c.library_add(lib) 236 | setattr(self, name, Config(c)) 237 | return drake.cxx.Config(getattr(self, name + ('_header' if not link else ''))) 238 | setattr(Boost, 'config_%s' % prop, config_getter) 239 | unclosure(prop, library) 240 | -------------------------------------------------------------------------------- /src/drake/cxx/curl.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2019, Quentin "mefyl" Hocquet 2 | # 3 | # This software is provided "as is" without warranty of any kind, 4 | # either expressed or implied, including but not limited to the 5 | # implied warranties of fitness for a particular purpose. 6 | # 7 | # See the LICENSE file for more information. 8 | 9 | import drake 10 | import drake.cxx 11 | import sys 12 | 13 | from .. import Exception, Path, Version, srctree 14 | from . import Config, StaticLib 15 | 16 | class Curl(drake.Configuration): 17 | 18 | """Configuration for the Curl library.""" 19 | 20 | def __init__(self, prefix = None): 21 | """Find and create a configuration for Boost. 22 | 23 | prefix -- Where to find curl, should contain 24 | include/curl/curl.h. 25 | """ 26 | # Compute the search path. 27 | if prefix is None: 28 | test = [Path('/usr'), Path('/usr/local')] 29 | else: 30 | test = [Path(prefix)] 31 | for i in range(len(test)): 32 | if not test[i].absolute: 33 | test[i] = srctree() / test[i] 34 | self.__prefix = self._search_all('include/curl/curl.h', test)[0] 35 | self.__config = drake.cxx.Config() 36 | self.__config.add_system_include_path(self.__prefix / 'include') 37 | self.__config.lib_path(self.__prefix / 'lib') 38 | 39 | def config(self): 40 | 41 | return self.__config 42 | 43 | def __repr__(self): 44 | return 'Curl(prefix = %s)' % repr(self.__prefix) 45 | -------------------------------------------------------------------------------- /src/drake/cxx/flex.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2019, Quentin "mefyl" Hocquet 2 | # 3 | # This software is provided "as is" without warranty of any kind, 4 | # either expressed or implied, including but not limited to the 5 | # implied warranties of fitness for a particular purpose. 6 | # 7 | # See the LICENSE file for more information. 8 | 9 | from .. import Builder, Node, Path, node 10 | 11 | 12 | class Flex: 13 | 14 | def __init__(self, flex = 'flex'): 15 | 16 | self.__flex = flex 17 | 18 | def plug(self, toolkit): 19 | 20 | toolkit.hook_bin_src_add(self.hook_bin_src) 21 | 22 | def hook_bin_src(self, src): 23 | 24 | if isinstance(src, FlexSource): 25 | builder = FlexCompiler(src, self) 26 | return builder.cc() 27 | 28 | def compile(self, src, dst): 29 | 30 | return '%s -+ -o%s -Ca %s' % (self.__flex, dst, src) 31 | 32 | 33 | class FlexSource(Node): 34 | 35 | def __init__(self, path): 36 | 37 | Node.__init__(self, path) 38 | 39 | Node.extensions['l'] = FlexSource 40 | Node.extensions['ll'] = FlexSource 41 | 42 | 43 | class FlexCompiler(Builder): 44 | 45 | name = 'Flex compilation' 46 | 47 | def __init__(self, source, flex): 48 | 49 | self.__source = source 50 | self.__flex = flex 51 | 52 | base_path = source.name() 53 | scanner_cc_path = Path(base_path) 54 | scanner_cc_path.extension = 'cc' 55 | self.__scanner_cc = node(scanner_cc_path) 56 | Builder.__init__(self, [source], [self.__scanner_cc]) 57 | 58 | def execute(self): 59 | 60 | if not self.cmd('Flex %s' % self.__source, 61 | self.__flex.compile(self.__source.path(), 62 | self.__scanner_cc.path())): 63 | return False 64 | return self.cmd('Fixing flex include', 65 | 'sed -i s,FlexLexer.h,parser/flex-lexer.hh, %s' % self.__scanner_cc) 66 | 67 | def cc(self): 68 | 69 | return self.__scanner_cc 70 | -------------------------------------------------------------------------------- /src/drake/cxx/ipp.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2019, Quentin "mefyl" Hocquet 2 | # 3 | # This software is provided "as is" without warranty of any kind, 4 | # either expressed or implied, including but not limited to the 5 | # implied warranties of fitness for a particular purpose. 6 | # 7 | # See the LICENSE file for more information. 8 | 9 | import drake 10 | import drake.cxx 11 | import sys 12 | 13 | from .. import Exception, Path, Version, srctree 14 | from . import Config, StaticLib 15 | 16 | class IPP(drake.cxx.LibraryConfiguration): 17 | 18 | """Configuration for the IPP library.""" 19 | 20 | def __init__(self, prefix = None): 21 | """Find and create a configuration for IPP. 22 | 23 | prefix -- Where to find curl, should contain 24 | include/ipp.h. 25 | """ 26 | drake.cxx.LibraryConfiguration.__init__(self, 'include/ipp.h', prefix) 27 | -------------------------------------------------------------------------------- /src/drake/cxx/opengl.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2019, Quentin "mefyl" Hocquet 2 | # 3 | # This software is provided "as is" without warranty of any kind, 4 | # either expressed or implied, including but not limited to the 5 | # implied warranties of fitness for a particular purpose. 6 | # 7 | # See the LICENSE file for more information. 8 | 9 | import platform 10 | from .. import Path, Exception, srctree 11 | from . import Config, StaticLib 12 | 13 | class OpenGL: 14 | 15 | def __init__(self, prefix = None): 16 | 17 | test = [] 18 | test_framework = [] 19 | if prefix is None: 20 | test = ['/usr', '/usr/local'] 21 | if platform.system() == 'Darwin': 22 | test_framework += ['/System/Library/Frameworks/OpenGL.framework'] 23 | else: 24 | test = [prefix] 25 | if platform.system() == 'Darwin': 26 | test_framework += [prefix] 27 | 28 | searched_file = 'glext.h' 29 | for path in test: 30 | 31 | p = Path(path) 32 | if not p.absolute: 33 | p = srctree() / p 34 | 35 | if (p / 'include/GL' / searched_file).exists(): 36 | 37 | self.prefix = path 38 | 39 | self.cfg = Config() 40 | self.cfg.add_system_include_path('%s/include' % self.prefix) 41 | self.cfg.lib_path('%s/lib' % self.prefix) 42 | if platform.system() == 'Windows': 43 | self.cfg.lib('opengl32') 44 | else: 45 | self.cfg.lib('GL') 46 | 47 | return 48 | 49 | if platform.system() == 'Darwin': 50 | for path in test_framework: 51 | 52 | p = Path(path) 53 | if not p.absolute: 54 | p = srctree() / p 55 | 56 | if (p / 'Headers' / searched_file).exists(): 57 | 58 | self.prefix = p 59 | self.cfg = Config() 60 | self.cfg.add_system_include_path(self.prefix / 'Headers') 61 | self.cfg.framework_add('Cocoa') 62 | self.cfg.framework_add('OpenGL') 63 | 64 | return 65 | 66 | raise Exception('unable to find %s in %s' % (searched_file, ', '.join(test + test_framework))) 67 | 68 | def config(self): 69 | 70 | return self.cfg 71 | -------------------------------------------------------------------------------- /src/drake/cxx/sdl.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2019, Quentin "mefyl" Hocquet 2 | # 3 | # This software is provided "as is" without warranty of any kind, 4 | # either expressed or implied, including but not limited to the 5 | # implied warranties of fitness for a particular purpose. 6 | # 7 | # See the LICENSE file for more information. 8 | 9 | from .. import Path, Exception, srctree 10 | from . import Config, StaticLib 11 | 12 | class Sdl: 13 | 14 | def __init__(self, prefix = None): 15 | 16 | if prefix is None: 17 | test = ['/usr', '/usr/local'] 18 | else: 19 | test = [prefix] 20 | 21 | for path in test: 22 | p = Path(path) 23 | if not p.absolute: 24 | p = srctree() / p 25 | if (p / 'include/SDL/SDL.h').exists(): 26 | 27 | self.prefix = path 28 | 29 | self.cfg = Config() 30 | self.cfg.add_system_include_path('%s/include' % self.prefix) 31 | 32 | self.cfg_core = Config() 33 | self.cfg_core.lib_path('%s/lib' % self.prefix) 34 | self.cfg_core.lib('SDL') 35 | self.cfg_core.lib('SDLmain') 36 | 37 | self.cfg_ttf = Config() 38 | self.cfg_ttf.lib_path('%s/lib' % self.prefix) 39 | self.cfg_ttf.lib('SDL_ttf') 40 | 41 | self.cfg_image = Config() 42 | self.cfg_image.lib_path('%s/lib' % self.prefix) 43 | self.cfg_image.lib('SDL_image') 44 | 45 | return 46 | 47 | raise Exception('unable to find SDL/SDL.h in %s' % ', '.join(test)) 48 | 49 | def config(self): 50 | 51 | return self.cfg 52 | 53 | 54 | def core(self): 55 | 56 | return self.cfg_core 57 | 58 | 59 | def ttf(self): 60 | 61 | return self.cfg_ttf 62 | 63 | 64 | def image(self): 65 | 66 | return self.cfg_image 67 | -------------------------------------------------------------------------------- /src/drake/cxx/sofia_sip.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2019, Quentin "mefyl" Hocquet 2 | # 3 | # This software is provided "as is" without warranty of any kind, 4 | # either expressed or implied, including but not limited to the 5 | # implied warranties of fitness for a particular purpose. 6 | # 7 | # See the LICENSE file for more information. 8 | 9 | import drake 10 | import drake.cxx 11 | import sys 12 | 13 | from .. import Exception, Path, Version, srctree 14 | from . import Config, StaticLib 15 | 16 | class SofiaSIP(drake.Configuration): 17 | 18 | """Configuration for the Sofia SIP library.""" 19 | 20 | def __init__(self, prefix = None): 21 | """Find and create a configuration for Boost. 22 | 23 | prefix -- Where to find sofia-sip, should sofia-sip/sip.h. 24 | """ 25 | # Compute the search path. 26 | if prefix is None: 27 | test = [Path('/usr'), Path('/usr/local')] 28 | else: 29 | test = [Path(prefix)] 30 | self.__prefix = self._search_all('include/sofia-sip-1.12/sofia-sip/sip.h', test)[0] 31 | self.__config = drake.cxx.Config() 32 | self.__config.add_system_include_path(self.__prefix / 'include/sofia-sip-1.12') 33 | self.__config.lib_path(self.__prefix / 'lib') 34 | 35 | def config(self): 36 | return self.__config 37 | 38 | def __repr__(self): 39 | return 'SofiaSIP(prefix = %s)' % repr(self.__prefix) 40 | -------------------------------------------------------------------------------- /src/drake/debian.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2014, Quentin "mefyl" Hocquet 2 | # 3 | # This software is provided "as is" without warranty of any kind, 4 | # either expressed or implied, including but not limited to the 5 | # implied warranties of fitness for a particular purpose. 6 | # 7 | # See the LICENSE file for more information. 8 | 9 | import drake 10 | import math 11 | import os 12 | import shutil 13 | 14 | from itertools import chain 15 | 16 | class Packager(drake.Builder): 17 | 18 | def __init__(self, 19 | basename, 20 | attributes, 21 | sources, 22 | path, 23 | destination = '.', 24 | preload = None, 25 | cleanup_source_directory = True): 26 | path = drake.Path(path) 27 | self.__destination = drake.Path(destination) 28 | basename = drake.Path(basename) 29 | self.__target = drake.node(self.__destination / basename) 30 | self.__destination = drake.path_build(self.__destination) 31 | self.__path = drake.path_build(path) 32 | self.__preload = preload 33 | self.__attrs = attributes 34 | self.__control = drake.node(path / 'DEBIAN/control') 35 | if preload is not None: 36 | sources += [preload] 37 | self.__cleanup_source_directory = cleanup_source_directory 38 | super().__init__( 39 | sources, [self.__control, self.__target]) 40 | 41 | @property 42 | def path(self): 43 | return self.__path 44 | 45 | def execute(self): 46 | if 'Installed-Size' not in self.__attrs: 47 | self.__attrs['Installed-Size'] = math.ceil(sum( 48 | os.path.getsize(os.path.join(p, f)) 49 | for p, _, files in os.walk(str(self.__path)) 50 | for f in files) / 1024) 51 | with open(str(self.__control.path()), 'w') as f: 52 | for k, v in self.__attrs.items(): 53 | print('%s: %s' % (k, v), file = f) 54 | if self.__cleanup_source_directory: 55 | self.cleanup_source_directory(self.__path) 56 | os.chmod(str(self.__path / 'DEBIAN'), 0o755) 57 | os.chmod(str(self.__path / 'DEBIAN/control'), 0o644) 58 | try: 59 | os.chmod(str(self.__path / 'DEBIAN/postinst'), 0o755) 60 | except: 61 | pass 62 | env = { 63 | 'PATH': os.environ['PATH'], 64 | } 65 | if self.__preload is not None: 66 | path = drake.path_root() / self.__preload.path() 67 | env['LD_PRELOAD'] = str(path) 68 | return self.cmd('Package %s' % self.__target, 69 | self.command, env = env) 70 | 71 | @property 72 | def command(self): 73 | chown = ['chown', '-R', 'root:0', self.__path] 74 | chmod = ['chmod', '-R', 'a+rX', self.__path] 75 | dpkg = ['dpkg-deb', '-b', self.__path, self.__destination] 76 | import pipes 77 | def escape(cmd): 78 | return (pipes.quote(str(a)) for a in cmd) 79 | res = ['fakeroot', 'sh', '-e', '-c'] 80 | import sys 81 | # OS X requires that the commands passed to fakeroot are properly quoted. 82 | if sys.platform == 'darwin': 83 | res.append(pipes.quote(' '.join(chain(escape(chown), 84 | [';'], 85 | escape(chmod), 86 | [';'], 87 | escape(dpkg))))) 88 | else: 89 | res.append(' '.join(chain(escape(chown), 90 | [';'], 91 | escape(chmod), 92 | [';'], 93 | escape(dpkg)))) 94 | return res 95 | 96 | @property 97 | def package(self): 98 | return self.__target 99 | -------------------------------------------------------------------------------- /src/drake/debug.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2011-2019, Quentin "mefyl" Hocquet 2 | # 3 | # This software is provided "as is" without warranty of any kind, 4 | # either expressed or implied, including but not limited to the 5 | # implied warranties of fitness for a particular purpose. 6 | # 7 | # See the LICENSE file for more information. 8 | 9 | import os 10 | import sys 11 | import threading 12 | import drake 13 | 14 | DEBUG_TRACE = 1 15 | DEBUG_TRACE_PLUS = 2 16 | DEBUG_DEPS = 2 17 | DEBUG_SCHED = 3 18 | 19 | _DEBUG = 0 20 | if 'DRAKE_DEBUG' in os.environ: 21 | _DEBUG = int(os.environ['DRAKE_DEBUG']) 22 | _INDENT = 0 23 | _DEBUG_SEM = threading.Semaphore(1) 24 | 25 | 26 | def debug(msg, lvl = 1): 27 | if lvl <= _DEBUG: 28 | with _DEBUG_SEM: 29 | print('%s%s' % (' ' * _INDENT * 2, msg), file = sys.stderr) 30 | 31 | 32 | class indentation: 33 | 34 | def __enter__(self): 35 | global _INDENT 36 | _INDENT += 1 37 | 38 | def __exit__(self, type, value, traceback): 39 | global _INDENT 40 | _INDENT -= 1 41 | -------------------------------------------------------------------------------- /src/drake/deprecation.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2019, Quentin "mefyl" Hocquet 2 | # 3 | # This software is provided "as is" without warranty of any kind, 4 | # either expressed or implied, including but not limited to the 5 | # implied warranties of fitness for a particular purpose. 6 | # 7 | # See the LICENSE file for more information. 8 | 9 | import warnings 10 | import functools 11 | 12 | def deprecated(func): 13 | '''This is a decorator which can be used to mark functions 14 | as deprecated. It will result in a warning being emmitted 15 | when the function is used.''' 16 | @functools.wraps(func) 17 | def f(*args, **kwargs): 18 | warnings.warn( 19 | 'Call to deprecated function {}.'.format(func.__name__), 20 | category=DeprecationWarning, 21 | stacklevel=2) 22 | return func(*args, **kwargs) 23 | return f 24 | -------------------------------------------------------------------------------- /src/drake/docker.py: -------------------------------------------------------------------------------- 1 | import collections 2 | import drake 3 | import json 4 | import os 5 | import shutil 6 | import subprocess 7 | 8 | from itertools import chain 9 | 10 | def parents(p): 11 | while p != drake.Path.dot: 12 | yield p 13 | p = p.dirname() 14 | 15 | def rootify(paths): 16 | res = set() 17 | for p in paths: 18 | insert = True 19 | for e in set(res): 20 | if e.prefix_of(p): 21 | insert = False 22 | break 23 | elif p.prefix_of(e): 24 | res.remove(e) 25 | if insert: 26 | res.add(p) 27 | return res 28 | 29 | def installed_files(nodes): 30 | res = set() 31 | for node in nodes: 32 | res.add(node) 33 | for dep in node.dependencies_recursive: 34 | res.add(dep) 35 | return res 36 | 37 | class DockerFile(drake.Node): 38 | 39 | def __init__(self, name, image, maintainer = None, labels = {}): 40 | super().__init__(name) 41 | self.__image = image 42 | self.__maintainer = maintainer 43 | self.__labels = labels 44 | self.__adds = {} 45 | self.__cmd = [] 46 | self.__entry_point = [] 47 | self.__ports = [] 48 | self.__steps = [] 49 | self.__volumes = [] 50 | DockerFile.Builder(self) 51 | 52 | def add(self, nodes, path): 53 | if isinstance(nodes, collections.Iterable): 54 | for node in nodes: 55 | self.add(node, path) 56 | else: 57 | self.__adds.setdefault(drake.Path(path), set()).add(nodes) 58 | 59 | def cmd(self, cmd): 60 | if isinstance(cmd, list): 61 | self.__cmd = cmd 62 | elif isinstance(cmd, str): 63 | self.__cmd = cmd.split() 64 | else: 65 | raise Exception('invalid CMD type: %s' % type(cmd)) 66 | 67 | def entry_point(self, entry): 68 | if isinstance(entry, list): 69 | self.__entry_point = entry 70 | elif isinstance(entry, str): 71 | self.__entry_point = entry.split() 72 | else: 73 | raise Exception('invalid ENTRYPOINT type: %s' % type(cmd)) 74 | 75 | def ports(self, ports): 76 | self.__ports = ports 77 | 78 | def volumes(self, volumes): 79 | if not isinstance(volumes, list): 80 | raise Exception('invalid VOLUMES type: %s', type(volumes)) 81 | self.__volumes = volumes 82 | 83 | def run(self, cmd): 84 | self.__steps.append(('RUN', cmd)) 85 | 86 | def env(self, k, v): 87 | self.__steps.append(('ENV', '{} {}'.format(k, v))) 88 | 89 | def workdir(self, path): 90 | self.__steps.append(('WORKDIR', path)) 91 | 92 | @property 93 | def image(self): 94 | return self.__image 95 | 96 | @property 97 | def maintainer(self): 98 | return self.__maintainer 99 | 100 | @property 101 | def labels(self): 102 | return self.__labels 103 | 104 | @property 105 | def adds(self): 106 | return chain(*self.__adds.values()) 107 | 108 | @property 109 | def cmd_(self): 110 | return self.__cmd 111 | 112 | @property 113 | def entry_point_(self): 114 | return self.__entry_point 115 | 116 | @property 117 | def ports_(self): 118 | return self.__ports 119 | 120 | @property 121 | def steps(self): 122 | return self.__steps 123 | 124 | @property 125 | def volumes_(self): 126 | return self.__volumes 127 | 128 | def hash(self): 129 | return { 130 | 'add': str(self.__adds), 131 | 'cmd': self.__cmd, 132 | 'entry-point': self.__entry_point, 133 | 'image': self.__image, 134 | 'labels': self.__labels, 135 | 'maintainer': self.__maintainer, 136 | 'ports': self.__ports, 137 | 'steps': self.__steps, 138 | 'volumes': self.__volumes, 139 | } 140 | 141 | class Builder(drake.Builder): 142 | 143 | def __init__(self, dockerfile): 144 | self.__dockerfile = dockerfile 145 | super().__init__([], [dockerfile]) 146 | 147 | def execute(self): 148 | root = self.__dockerfile.path().dirname() 149 | self.output('Generate %s' % self.__dockerfile) 150 | with open(str(self.__dockerfile.path()), 'w') as f: 151 | print('FROM %s' % self.__dockerfile.image, file = f) 152 | if self.__dockerfile.maintainer is not None: 153 | print('MAINTAINER %s' % self.__dockerfile.maintainer, 154 | file = f) 155 | for k, v in self.__dockerfile.labels.items(): 156 | print('LABEL %s="%s"' % (k, v), file = f) 157 | for cmd, args in self.__dockerfile.steps: 158 | print('{} {}'.format(cmd, args), file = f) 159 | for p, nodes in self.__dockerfile._DockerFile__adds.items(): 160 | for add in rootify( 161 | chain(*(parents(n.path().without_prefix(root)) 162 | for n in installed_files(nodes) 163 | if n is not drake.Path.dot))): 164 | dest = "%s/%s" % (p, add) \ 165 | if os.path.isdir(str(drake.Path(root) / add)) \ 166 | else p 167 | print('ADD %s %s' % (add, dest), file = f) 168 | for p in self.__dockerfile.ports_: 169 | print('EXPOSE %s' % p, file = f) 170 | if self.__dockerfile.volumes_: 171 | print('VOLUME %s' % json.dumps(self.__dockerfile.volumes_), file = f) 172 | if self.__dockerfile.entry_point_: 173 | print('ENTRYPOINT %s' % json.dumps(self.__dockerfile.entry_point_), 174 | file = f) 175 | if self.__dockerfile.cmd_: 176 | print('CMD %s' % json.dumps(self.__dockerfile.cmd_), file = f) 177 | return True 178 | 179 | def hash(self): 180 | return self.__dockerfile.hash() 181 | 182 | 183 | class DockerImage(drake.BaseNode): 184 | 185 | def __init__(self, name, repository, tag): 186 | path = drake.Drake.current.prefix / name 187 | path = drake.Path(path._Path__path, False, True) 188 | super().__init__(path) 189 | self.__repository = repository 190 | self.__tag = tag 191 | 192 | def missing(self): 193 | i = subprocess.check_output( 194 | ['docker', 'images', '-q', 195 | '%s:%s' % (self.__repository, self.__tag)]) 196 | return not bool(i) 197 | 198 | #def local_mtime(self): 199 | # docker inspect -f '{{ .Created }}' infinit:0.6.0-rc-169-g16efa2c 200 | 201 | @property 202 | def repository(self): 203 | return self.__repository 204 | 205 | @property 206 | def tag(self): 207 | return self.__tag 208 | 209 | 210 | class DockerBuilder(drake.Builder): 211 | 212 | def __init__(self, image, docker_file): 213 | self.__image = image 214 | self.__docker_file = docker_file 215 | drake.Builder.__init__( 216 | self, 217 | chain(docker_file.adds, [self.__docker_file]), 218 | [self.__image]) 219 | self.__path = self.__docker_file.path().dirname() 220 | 221 | def execute(self): 222 | # Cleanup 223 | effective = set() 224 | for path, dirs, files in os.walk(str(self.__path)): 225 | path = drake.Path(path) 226 | for f in chain(files, dirs): 227 | effective.add(path / f) 228 | expected = set( 229 | chain(*(parents(n.path()) 230 | for n in installed_files(self.__docker_file.adds)))) 231 | for g in rootify(p.without_prefix(self.__path) 232 | for p in (effective - expected) 233 | if p is not self.__docker_file.path()): 234 | g = self.__path / g 235 | self.output('Cleanup %s' % g) 236 | g = str(g) 237 | if os.path.isdir(g): 238 | shutil.rmtree(str(g)) 239 | else: 240 | os.remove(g) 241 | tag = ':'.join((self.__image.repository, self.__image.tag)) 242 | self.cmd('Docker build image %s' % tag, 243 | ['docker', 'build', 244 | '--force-rm', 245 | '--tag', tag, 246 | self.__path, 247 | ], 248 | throw = True) 249 | return True 250 | -------------------------------------------------------------------------------- /src/drake/enumeration.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2013-2019, Quentin "mefyl" Hocquet 2 | # 3 | # This software is provided "as is" without warranty of any kind, 4 | # either expressed or implied, including but not limited to the 5 | # implied warranties of fitness for a particular purpose. 6 | # 7 | # See the LICENSE file for more information. 8 | 9 | class Enumeration(type): 10 | 11 | def __new__(self, name, bases, dct, 12 | values = [], orderable = False): 13 | return super(Enumeration, self).__new__(self, name, bases, dct) 14 | 15 | def __init__(self, name, bases, dct, 16 | values = [], orderable = False): 17 | super(Enumeration, self).__init__(name, bases, dct) 18 | self.__instances = {} 19 | self.__orderable = orderable 20 | for value in values: 21 | self(name = value) 22 | 23 | def __register(self, instance): 24 | if self.__orderable: 25 | instance.index = len(self.__instances) 26 | self.__instances[instance.name] = instance 27 | setattr(self, instance.name, instance) 28 | 29 | def __iter__(self): 30 | return iter(self.__instances.values()) 31 | 32 | class Enumerated(metaclass = Enumeration): 33 | 34 | def __init__(self, name): 35 | self.__name = name 36 | self.__class__._Enumeration__register(self) 37 | 38 | def __str__(self): 39 | return '%s.%s' % (self.__class__.__name__, self.name) 40 | 41 | @property 42 | def name(self): 43 | return self.__name 44 | 45 | def __eq__(self, other): 46 | return self is other 47 | 48 | def __lt__(self, other): 49 | return self.index < other.index 50 | 51 | def __le__(self, other): 52 | return self.index <= other.index 53 | 54 | def __hash__(self): 55 | return hash(self.name) 56 | -------------------------------------------------------------------------------- /src/drake/gRPC/__init__.py: -------------------------------------------------------------------------------- 1 | import drake 2 | 3 | class Generator(drake.Builder): 4 | '''Base builder for generating protobuf serialization and gRPC files from a 5 | .proto file. 6 | ''' 7 | def __init__(self, proto, protoc, targets, plugin = None): 8 | self.__proto = proto 9 | if protoc is None: 10 | self.__protoc = drake.node(drake.which.which('protoc')) 11 | self.__protoc = protoc 12 | self.__plugin = plugin 13 | self.__output = proto.name().dirname() 14 | srcs = [proto, protoc] 15 | if plugin: 16 | srcs.append(plugin) 17 | super().__init__(srcs, targets) 18 | 19 | @property 20 | def proto(self): 21 | return self.__proto 22 | 23 | @property 24 | def protoc(self): 25 | return self.__protoc 26 | 27 | @property 28 | def plugin(self): 29 | return self.__plugin 30 | 31 | @property 32 | def output_dir(self): 33 | return drake.path_build(self.__output) 34 | 35 | class CxxGen(Generator): 36 | '''Generate the C++ protobuf serialization and gRPC files from a .proto. 37 | 38 | Resulting files for will be: 39 | - xxx.pb.h 40 | - xxx.pb.cc 41 | - xxx.grpc.pb.h 42 | - xxx.grpc.pb.cc 43 | ''' 44 | 45 | def __init__(self, proto, protoc = None, plugin = None): 46 | name = proto.name_relative 47 | dsts = drake.nodes( 48 | name.with_extension('pb.h'), 49 | name.with_extension('pb.cc'), 50 | name.with_extension('grpc.pb.h'), 51 | name.with_extension('grpc.pb.cc'), 52 | ) 53 | if plugin is None: 54 | plugin = drake.node('/usr/local/bin/grpc_cpp_plugin') 55 | super().__init__(proto, protoc = protoc, plugin = plugin, targets = dsts) 56 | 57 | def execute(self): 58 | self.cmd('GRPC protoc %s' % self.proto, self.protoc_grpc_cmd, throw = True) 59 | self.cmd('GRPC protoc cc %s' % self.proto, self.protoc_cc_cmd, throw = True) 60 | return True 61 | 62 | @property 63 | def protoc_grpc_cmd(self): 64 | return [ 65 | self.protoc, 66 | '-I', self.proto.path().dirname(), 67 | '--grpc_out=%s' % self.output_dir, 68 | '--plugin=protoc-gen-grpc=%s' % self.plugin.path(), 69 | self.proto.path(), 70 | ] 71 | 72 | @property 73 | def protoc_cc_cmd(self): 74 | return [ 75 | self.protoc, 76 | '-I', self.proto.path().dirname(), 77 | '--cpp_out=%s' % self.output_dir, 78 | self.proto.path(), 79 | ] 80 | 81 | def hash(self): 82 | return { 83 | 'grpc-cmd': list(map(str, self.protoc_grpc_cmd)), 84 | 'protoc-cmd': list(map(str, self.protoc_cc_cmd)), 85 | } 86 | 87 | class PyGen(Generator): 88 | '''Generate the python protobuf serialization and gRPC files from a .proto. 89 | 90 | Resulting files for will be: 91 | - xxx_pb2.py 92 | - xxx_pb2_grpc.py 93 | ''' 94 | 95 | def __init__(self, proto, protoc = None, plugin = None): 96 | name = proto.name_relative.without_last_extension() 97 | dsts = drake.nodes( 98 | '{}_pb2.py'.format(name), 99 | '{}_pb2_grpc.py'.format(name), 100 | ) 101 | if plugin is None: 102 | plugin = drake.node('/usr/local/bin/grpc_python_plugin') 103 | super().__init__(proto, protoc = protoc, plugin = plugin, targets = dsts) 104 | 105 | def execute(self): 106 | self.cmd('GRPC protoc python %s' % self.proto, self.command, throw = True) 107 | return True 108 | 109 | @property 110 | def command(self): 111 | return [ 112 | self.protoc, 113 | '-I', self.proto.path().dirname(), 114 | '--python_out=%s' % self.output_dir, 115 | '--grpc_python_out=%s' % self.output_dir, 116 | '--plugin=protoc-gen-grpc_python=%s' % self.plugin.path(), 117 | self.proto.path(), 118 | ] 119 | 120 | def hash(self): 121 | return list(map(str, self.command)) 122 | 123 | class GoGen(Generator): 124 | '''Generate the Go protobuf serialization and gRPC files from a .proto. 125 | 126 | Resulting files for will be: 127 | - xxx.pb.go 128 | ''' 129 | 130 | def __init__(self, proto, protoc = None, plugin = None, toolkit = None): 131 | name = proto.name_relative.without_last_extension() 132 | dsts = drake.nodes( 133 | name.with_extension('pb.go'), 134 | ) 135 | srcs = [] 136 | if plugin is None: 137 | if toolkit is not None: 138 | if toolkit.os == 'windows': 139 | plugin = drake.node('%s/bin/%s/protoc-gen-go.exe' % ( 140 | toolkit.path, toolkit.platform_str())) 141 | else: 142 | plugin = drake.node('%s/bin/protoc-gen-go' % toolkit.path) 143 | if not plugin.builder: 144 | drake.go.FetchPackage( 145 | url = 'github.com/golang/protobuf/protoc-gen-go', 146 | toolkit = toolkit, 147 | targets = [ 148 | plugin, 149 | ], 150 | ) 151 | elif os.environ.get('GOPATH'): 152 | plugin = drake.node('%s/bin/protoc-gen-go' % os.environ['GOPATH']) 153 | elif os.environ.get('GOROOT'): 154 | plugin = drake.node('%s/bin/protoc-gen-go' % os.environ['GOROOT']) 155 | self.__toolkit = toolkit 156 | super().__init__(proto, protoc = protoc, plugin = plugin, targets = dsts) 157 | 158 | def execute(self): 159 | self.cmd('GRPC protoc Go %s' % self.proto, self.command, throw = True) 160 | return True 161 | 162 | @property 163 | def command(self): 164 | return [ 165 | self.protoc, 166 | '-I', self.proto.path().dirname(), 167 | '--go_out=plugins=grpc:%s' % self.output_dir, 168 | '--plugin=protoc-gen-go=%s' % self.plugin.path(), 169 | self.proto.path(), 170 | ] 171 | 172 | def hash(self): 173 | return list(map(str, self.command)) 174 | -------------------------------------------------------------------------------- /src/drake/git.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2009-2019, Quentin "mefyl" Hocquet 2 | # 3 | # This software is provided "as is" without warranty of any kind, 4 | # either expressed or implied, including but not limited to the 5 | # implied warranties of fitness for a particular purpose. 6 | # 7 | # See the LICENSE file for more information. 8 | 9 | import drake 10 | import drake.command 11 | import os 12 | import subprocess 13 | 14 | from . import VirtualNode, Path 15 | from datetime import date 16 | from functools import lru_cache 17 | 18 | class GitCommand(drake.command.Command): 19 | 20 | name = 'git' 21 | 22 | def _parse_version(self, v): 23 | return drake.Version(v.split(' ')[-1]) 24 | 25 | class Git(VirtualNode): 26 | 27 | """An iterable node with information on a git repository. 28 | 29 | # With the following dummy git repository. 30 | >>> def run(cmd): 31 | ... import subprocess 32 | ... subprocess.check_output(cmd) 33 | >>> os.chdir('/tmp') 34 | >>> path = Path('.drake.git') 35 | >>> path.remove() 36 | >>> if 'GIT_DIR' in os.environ: 37 | ... del os.environ['GIT_DIR'] 38 | >>> import subprocess 39 | >>> run(['git', 'init', str(path)]) 40 | >>> os.chdir(str(path)) 41 | >>> run(['git', 'config', 'user.name', 'mefyl']) 42 | >>> run(['git', 'config', 'user.email', 'mefyl@gruntech.org']) 43 | >>> Path('somefile').touch() 44 | >>> run(['git', 'add', 'somefile']) 45 | >>> run(['git', 'commit', '-m', 'Commit message.']) 46 | >>> node = Git(path) 47 | 48 | # >>> d = node.author_date().split(' ')[0] 49 | # >>> today = date.today() 50 | # >>> assert d == '%i-%02i-%i' % (today.year, today.month, today.day) 51 | 52 | # >>> node.description() 53 | """ 54 | def __init__(self, path = None, command = GitCommand()): 55 | """Create a GitVersion. 56 | 57 | path -- path to the repository; the source dir by default. 58 | """ 59 | if path is None: 60 | path = '.' 61 | self.__path = drake.path_source(path) 62 | self.__name = drake.path_build(path) 63 | super().__init__(self.__name / 'git') 64 | 65 | def path(self): 66 | '''The location of the Git repo.''' 67 | return self.__path 68 | 69 | def run(self, args, raw = False): 70 | return self.__cmd(args, raw) 71 | 72 | def __cmd(self, args, raw = False): 73 | cmd = ['git', '-C', str(self.__path)] + args 74 | stdout = subprocess.check_output(cmd) 75 | return stdout if raw else stdout.decode('utf-8').strip() 76 | 77 | def ls_files(self, *paths): 78 | """Run git ls-files and return the list of Paths. 79 | 80 | path -- the paths to list 81 | """ 82 | if not paths: 83 | paths = [Path('.')] 84 | out = self.__cmd(['ls-files'] + list(map(str, paths))) 85 | out = out.split('\n') 86 | return list(map(drake.Path, out)) 87 | 88 | def hash(self): 89 | import hashlib 90 | hasher = hashlib.sha1() 91 | hasher.update(self.rev_parse().encode('utf-8')) 92 | hasher.update(self.version().encode('utf-8')) 93 | return hasher.hexdigest() 94 | 95 | def rev_parse(self, revision = 'HEAD', short = False): 96 | cmd = ['rev-parse'] 97 | if short: 98 | cmd += ['--short'] 99 | cmd += [revision] 100 | return self.run(cmd) 101 | 102 | def can_commit(self): 103 | '''Whether there is something ready to commit.''' 104 | try: 105 | self.run(['diff', '--cached', '--quiet']) 106 | except subprocess.CalledProcessError: 107 | return True 108 | else: 109 | return False 110 | 111 | @lru_cache(16) 112 | def author_date(self): 113 | """The author date, as given by git %ai format.""" 114 | return self.run(['log', '--pretty=format:%ai', '-n', '1']) 115 | 116 | @lru_cache(16) 117 | def description(self): 118 | """The git describe output.""" 119 | return self.run(['describe']) 120 | 121 | @lru_cache(16) 122 | def version(self): 123 | """The git describe --long output.""" 124 | return self.run(['describe', '--long']) 125 | 126 | @lru_cache(16) 127 | def revision(self): 128 | """The revision short sha1.""" 129 | return self.run([ 'log', '--pretty=format:%h', '-n', '1']) 130 | 131 | @lru_cache(16) 132 | def message(self): 133 | """The commit message.""" 134 | return self.run(['log', '--pretty=format:%s', '-n', '1']) 135 | 136 | def __iter__(self): 137 | 138 | yield ('git-author-date', self.author_date()) 139 | yield ('git-description', self.description()) 140 | yield ('git-version', self.version()) 141 | yield ('git-revision', self.revision()) 142 | yield ('git-message', self.message()) 143 | -------------------------------------------------------------------------------- /src/drake/license_file.py: -------------------------------------------------------------------------------- 1 | import drake 2 | import drake.git 3 | import os 4 | 5 | class Packager(drake.Builder): 6 | 7 | """ 8 | Only the name of the folder containing the licenses needs to be passed. 9 | The builder will automatically populate the list of source nodes by traversing 10 | the folder. 11 | """ 12 | 13 | def __init__(self, license_folder, out_file): 14 | self.__license_folder = license_folder 15 | self.__target = out_file 16 | self.__context = drake.Drake.current.prefix 17 | licenses = list() 18 | def traverse(folder, in_dir): 19 | rel_dir = '%s/%s' % (in_dir, folder) if folder else in_dir 20 | git = drake.git.Git(rel_dir) 21 | for f in git.ls_files(): 22 | path = str(drake.path_source() / self.__context / rel_dir / f) 23 | if os.path.isdir(path): 24 | traverse(f, rel_dir) 25 | else: 26 | licenses.append(drake.node('%s/%s' % (rel_dir, f))) 27 | traverse('', license_folder) 28 | super().__init__(licenses, [out_file]) 29 | self.__sorted_sources = \ 30 | list(map(lambda s: str(s), self.sources().values())) 31 | self.__sorted_sources.sort(key = lambda s: s.lower()) 32 | 33 | def execute(self): 34 | print('Generating aggregated license file: %s' % self.__target) 35 | with open(str(self.__target), 'w', encoding = 'utf-8') as out: 36 | for license in self.__sorted_sources: 37 | l_name = license.replace( 38 | '%s/%s/' % (self.__context, self.__license_folder), '') 39 | l_file = str(drake.path_source() / license) 40 | Packager.print_entry(out, l_name, l_file) 41 | return True 42 | 43 | @staticmethod 44 | def print_entry(out, name, file): 45 | out.write('# Begin: %s\n(*%s\n' % (name, 78 * '-')) 46 | with open(file, 'r', encoding = 'utf-8') as f: 47 | out.write(f.read()) 48 | out.write('\n%s*)\n# End: %s\n\n' % (78 * '-', name)) 49 | -------------------------------------------------------------------------------- /src/drake/log.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2013-2019, Quentin "mefyl" Hocquet 2 | # 3 | # This software is provided "as is" without warranty of any kind, 4 | # either expressed or implied, including but not limited to the 5 | # implied warranties of fitness for a particular purpose. 6 | # 7 | # See the LICENSE file for more information. 8 | 9 | import os 10 | import sys 11 | import threading 12 | import drake 13 | import drake.enumeration 14 | 15 | class LogLevel(drake.enumeration.Enumerated, 16 | values = ['log', 'trace', 'debug', 'dump'], 17 | orderable = True): 18 | pass 19 | 20 | 21 | class Noop: 22 | 23 | def __init__(self): 24 | pass 25 | 26 | def __enter__(self): 27 | pass 28 | 29 | def __exit__(self, type, value, traceback): 30 | pass 31 | 32 | 33 | NOOP = Noop() 34 | 35 | 36 | class NoopLogger: 37 | 38 | def log(self, component, level, message, *args): 39 | return NOOP 40 | 41 | 42 | 43 | class LoggerType(type): 44 | 45 | def __call__(self, configuration_string = None, indentation = None): 46 | if configuration_string is None: 47 | return NoopLogger() 48 | return type.__call__(self, 49 | configuration_string = configuration_string, 50 | indentation = indentation) 51 | 52 | class Logger(metaclass = LoggerType): 53 | 54 | class Indentation: 55 | 56 | def __init__(self): 57 | self.__indentation = 0 58 | 59 | def __enter__(self): 60 | self.__indentation += 1 61 | 62 | def __exit__(self, type, value, traceback): 63 | self.__indentation -= 1 64 | 65 | @property 66 | def indentation(self): 67 | return self.__indentation 68 | 69 | def __init__(self, configuration_string = None, indentation = None): 70 | self.__indentation = indentation or Logger.Indentation() 71 | def parse_log_level(string): 72 | string = string.lower() 73 | if string == 'log': 74 | return LogLevel.log 75 | elif string == 'trace': 76 | return LogLevel.trace 77 | elif string == 'debug': 78 | return LogLevel.debug 79 | elif string == 'dump': 80 | return LogLevel.dump 81 | else: 82 | raise Exception('invalid log level: %s' % string) 83 | self.__components = {} 84 | if configuration_string is not None: 85 | for component in configuration_string.split(','): 86 | colons = component.count(':') 87 | if colons == 0: 88 | level = parse_log_level(component) 89 | component = None 90 | elif colons == 1: 91 | component, level = component.split(':') 92 | level = parse_log_level(level) 93 | else: 94 | raise Exception('invalid log configuration: %s' % component) 95 | if component is not None: 96 | self.__components[component] = level 97 | 98 | def log(self, component, level, message, *args): 99 | if level <= self.__components.setdefault(component, LogLevel.log): 100 | print('%s%s' % (' ' * self.__indentation.indentation, 101 | message % args), 102 | file = sys.stderr) 103 | return self.__indentation 104 | else: 105 | return NOOP 106 | 107 | 108 | # DEBUG_TRACE = 1 109 | # DEBUG_TRACE_PLUS = 2 110 | # DEBUG_DEPS = 2 111 | # DEBUG_SCHED = 3 112 | 113 | # _DEBUG = 0 114 | # if 'DRAKE_DEBUG' in os.environ: 115 | # _DEBUG = int(os.environ['DRAKE_DEBUG']) 116 | # _INDENT = 0 117 | # _DEBUG_SEM = threading.Semaphore(1) 118 | 119 | # def debug(msg, lvl = 1): 120 | # if lvl <= _DEBUG: 121 | # with _DEBUG_SEM: 122 | # print('%s%s' % (' ' * _INDENT * 2, msg), file = sys.stderr) 123 | 124 | 125 | # class indentation: 126 | 127 | # def __enter__(self): 128 | # global _INDENT 129 | # _INDENT += 1 130 | 131 | # def __exit__(self, type, value, traceback): 132 | # global _INDENT 133 | # _INDENT -= 1 134 | -------------------------------------------------------------------------------- /src/drake/markdown.py: -------------------------------------------------------------------------------- 1 | import drake 2 | import mistune 3 | import re 4 | 5 | non_unicode_word_character = re.compile('\W') 6 | 7 | def id(text): 8 | return re.sub(non_unicode_word_character, '-', text.lower()) 9 | 10 | class HeaderIdsRenderer(mistune.Renderer): 11 | 12 | def header(self, text, level, raw = None): 13 | header = super().header(text, level, raw) 14 | position = 3 # 1 + len('h%d' % level) 15 | return header[0:position] + ' id="' + id(text) + '"' + header[position:] 16 | 17 | class Renderer(drake.Builder): 18 | 19 | def __init__( 20 | self, source, 21 | target = None, 22 | replace_before = { 23 | '_blank': '_blank', 24 | }, 25 | replace_after = { 26 | '--': '‑‑', 27 | '"': '\"', 28 | }, 29 | ): 30 | self.__source = source 31 | self.__target = target or drake.Node( 32 | self.__source.name_relative.with_extension('html') 33 | ) 34 | self.__replace_before = replace_before 35 | self.__replace_after = replace_after 36 | drake.Builder.__init__(self, [self.__source], [self.__target]) 37 | 38 | def execute(self): 39 | self.output('Render %s' % self.__target) 40 | renderer = HeaderIdsRenderer() 41 | markdown = mistune.Markdown( 42 | renderer = renderer, 43 | parse_inline_html = True, 44 | ) 45 | with open(str(self.__source.path()), encoding = 'utf-8') as input: 46 | with open(str(self.__target.path()), 'w', encoding = 'utf-8') as output: 47 | text = input.read() 48 | for k, v in self.__replace_before.items(): 49 | text = text.replace(k, v) 50 | html = markdown(text) 51 | for k, v in self.__replace_after.items(): 52 | html = html.replace(k, v) 53 | output.write(html) 54 | return True 55 | 56 | class Source(drake.Node): 57 | 58 | def __init__(self, *args, **kwargs): 59 | super().__init__(*args, **kwargs) 60 | Renderer(self) 61 | 62 | drake.Node.extensions['md'] = Source 63 | -------------------------------------------------------------------------------- /src/drake/nsis.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2013-2019, Quentin "mefyl" Hocquet 2 | # 3 | # This software is provided "as is" without warranty of any kind, 4 | # either expressed or implied, including but not limited to the 5 | # implied warranties of fitness for a particular purpose. 6 | # 7 | # See the LICENSE file for more information. 8 | 9 | import drake 10 | 11 | class Context: 12 | """The context holds the nsis script resources. 13 | """ 14 | current = None 15 | 16 | def __init__(self, 17 | target = None, 18 | resources = []): 19 | self.resources = resources 20 | self.target = target 21 | 22 | def __enter__(self): 23 | self.__previous = Context.current 24 | Context.current = self 25 | 26 | def __exit__(self, *args, **kwargs): 27 | Context.current = self.__previous 28 | 29 | class Script(drake.Node): 30 | """The nsis script file. 31 | """ 32 | 33 | def __init__(self, path, resources = [], *args, **kwargs): 34 | super().__init__(path = path, *args, **kwargs) 35 | self.__resources = resources 36 | context = Context.current 37 | if context is not None: 38 | Compiler(self, 39 | target = context.target, 40 | resources = context.resources) 41 | 42 | @property 43 | def resources(self): 44 | return self.__resources 45 | 46 | drake.Node.extensions['nsi'] = Script 47 | 48 | class Compiler(drake.Builder): 49 | """Compile the nsis script to an installer. 50 | """ 51 | def __init__(self, 52 | script, 53 | target = None, 54 | resources = [], 55 | leave_stdout = False): 56 | self.__script = script 57 | resources = resources or self.__script.resources 58 | target = target or script.name().with_extension('exe') 59 | self.__target = target 60 | self.__leave_stdout = leave_stdout 61 | super().__init__([self.__script] + resources, 62 | [self.__target]) 63 | 64 | def execute(self): 65 | return self.cmd('Compile %s' % self.__target, 66 | self.command, 67 | leave_stdout = self.__leave_stdout) 68 | 69 | @property 70 | def command(self): 71 | return ['makensis', 72 | '-NOCD', 73 | '-XOutFile %s' % drake.path_build(self.__target.name()), 74 | self.__script] 75 | 76 | @property 77 | def rendered(self): 78 | return self.__target 79 | -------------------------------------------------------------------------------- /src/drake/ocaml/menhir.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2019, Quentin "mefyl" Hocquet 2 | # 3 | # This software is provided "as is" without warranty of any kind, 4 | # either expressed or implied, including but not limited to the 5 | # implied warranties of fitness for a particular purpose. 6 | # 7 | # See the LICENSE file for more information. 8 | 9 | '''OCaml menhir parser generator support.''' 10 | 11 | import drake.ocaml 12 | 13 | class MenhirSource(drake.ocaml.Node): 14 | 15 | '''Ocaml .mly menhir source file.''' 16 | 17 | def to_objects(self, toolkit, config): 18 | 19 | '''Convert to the associated implementation and interface file.''' 20 | 21 | builder = Menhir(self) 22 | return [builder.target_source, builder.target_interface] 23 | 24 | drake.Node.extensions['mly'] = MenhirSource 25 | 26 | class Menhir(drake.Builder): 27 | 28 | '''Menhir parser generator.''' 29 | 30 | def __init__(self, source): 31 | self.__strict = True 32 | self.__source = source 33 | self.__target_source = drake.ocaml.Implementation(source.name().with_extension('ml')) 34 | self.__target_interface = drake.ocaml.Interface(source.name().with_extension('mli')) 35 | super().__init__([source], [self.__target_source, self.__target_interface]) 36 | 37 | def execute(self): 38 | base = self.__target_source.path().without_last_extension() 39 | cmd = ['menhir', self.__source, '--base', base] 40 | if self.__strict: 41 | cmd.append('--strict') 42 | self.cmd('Menhir {}'.format(self.__target_source), 43 | cmd, 44 | throw=True) 45 | return True 46 | 47 | @property 48 | def source(self): 49 | '''The menhir source file.''' 50 | return self.__source 51 | 52 | @property 53 | def target_source(self): 54 | '''The produced implementation source file.''' 55 | return self.__target_source 56 | 57 | @property 58 | def target_interface(self): 59 | '''The produced interface source file.''' 60 | return self.__target_interface 61 | 62 | @property 63 | def strict(self): 64 | '''Whether to pass `--strict` to menhir, making all grammar warnings errors.''' 65 | return self.__strict 66 | 67 | @strict.setter 68 | def strict(self, value: bool): 69 | self.__strict = value 70 | -------------------------------------------------------------------------------- /src/drake/ocaml/ocamllex.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2019, Quentin "mefyl" Hocquet 2 | # 3 | # This software is provided "as is" without warranty of any kind, 4 | # either expressed or implied, including but not limited to the 5 | # implied warranties of fitness for a particular purpose. 6 | # 7 | # See the LICENSE file for more information. 8 | 9 | '''OCaml ocamllex scanner generator support.''' 10 | 11 | import drake.ocaml 12 | 13 | class OcamllexSource(drake.ocaml.Node): 14 | 15 | '''Ocaml .mly ocamllex source file.''' 16 | 17 | def to_objects(self, toolkit, config): 18 | 19 | '''Convert to the associated implementation and interface file.''' 20 | 21 | builder = Ocamllex(self) 22 | return [builder.target] 23 | 24 | drake.Node.extensions['mll'] = OcamllexSource 25 | 26 | class Ocamllex(drake.Builder): 27 | 28 | '''Ocamllex scanner generator.''' 29 | 30 | def __init__(self, source): 31 | self.__source = source 32 | self.__target = drake.ocaml.Implementation(source.name().with_extension('ml')) 33 | super().__init__([source], [self.__target]) 34 | 35 | def execute(self): 36 | self.cmd('Ocamllex {}'.format(self.__target), 37 | ['ocamllex', self.__source, '-o', self.__target.path().with_extension('ml')], 38 | throw=True) 39 | return True 40 | 41 | @property 42 | def source(self): 43 | '''The ocamllex source file.''' 44 | return self.__source 45 | 46 | @property 47 | def target(self): 48 | '''The produced implementation source file.''' 49 | return self.__target 50 | -------------------------------------------------------------------------------- /src/drake/python/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2014, Quentin "mefyl" Hocquet 2 | # 3 | # This software is provided "as is" without warranty of any kind, 4 | # either expressed or implied, including but not limited to the 5 | # implied warranties of fitness for a particular purpose. 6 | # 7 | # See the LICENSE file for more information. 8 | 9 | import drake 10 | import itertools 11 | import drake.cxx 12 | 13 | class Package(drake.VirtualNode): 14 | 15 | '''A set of nodes constituting a python package making it easy to 16 | depend on the whole package or get the pythonpath.''' 17 | 18 | def __init__(self, name, root, nodes, fullname = None): 19 | '''Create a python Package 20 | 21 | name -- the node name 22 | root -- the package root 23 | nodes -- the package files 24 | ''' 25 | super().__init__(name) 26 | self.__root = root 27 | self.__root_source = drake.path_source(root) 28 | self.__root_build = drake.path_build(root) 29 | self.__nodes = nodes 30 | for node in nodes: 31 | self.dependency_add(node) 32 | self.__fullname = fullname or name 33 | 34 | @property 35 | def pythonpath(self): 36 | '''A list of path to add to the pythonpath to find this module. 37 | 38 | >>> with drake.Drake('srcdir'): 39 | ... pkg = drake.python.Package( 40 | ... 'somepkg', 'path/to', drake.nodes('path/to/__init__.py')) 41 | ... pkg.pythonpath 42 | [Path("srcdir/path/to"), Path("path/to")] 43 | ''' 44 | return [self.__root_source, self.__root_build] 45 | 46 | @property 47 | def root(self): 48 | return self.__root 49 | 50 | @property 51 | def nodes(self): 52 | return list(self.__nodes) 53 | 54 | @property 55 | def fullname(self): 56 | return self.__fullname 57 | 58 | def find(python = None, 59 | version = None): 60 | if version is None: 61 | versions = (drake.Version(3, 6), 62 | drake.Version(3, 5), 63 | drake.Version(3, 4), 64 | drake.Version(3, 3), 65 | drake.Version(3, 2)) 66 | elif not isinstance(version, collections.Iterable): 67 | versions = (version,) 68 | else: # version is already a list. 69 | versions = version 70 | tk = drake.cxx.Toolkit() 71 | windows = tk.os is drake.os.windows 72 | if python is None and drake.cxx.PkgConfig.available: 73 | def options(): 74 | for v in versions: 75 | yield ('python', v) 76 | yield ('python3', v) # CentOS 77 | yield ('python-%s' % v, v) # Gentoo 78 | for pkg_name, version in options(): 79 | pkg = drake.cxx.PkgConfig(pkg_name, version = version) 80 | if pkg.exists: 81 | python3 = pkg.prefix 82 | break 83 | include_dir = list(itertools.chain( 84 | ('include',), 85 | *(('include/python%s' % v, 86 | 'include/python%sm' % v) for v in versions))) 87 | python = drake.cxx.find_library( 88 | 'pyconfig.h', 89 | prefix = python, 90 | include_dir = include_dir) 91 | python_version = tk.preprocess('''\ 92 | #include 93 | PY_MAJOR_VERSION 94 | PY_MINOR_VERSION''', config = python) 95 | python_version = python_version.split('\n')[-3:-1] 96 | python_version = drake.Version(*map(int, python_version)) 97 | if windows: 98 | python_bin = 'python.exe' 99 | else: 100 | python_bin = 'bin/python%s' % python_version 101 | python.python_interpreter = python.prefix / python_bin 102 | python.version = python_version 103 | return python 104 | -------------------------------------------------------------------------------- /src/drake/redhat.py: -------------------------------------------------------------------------------- 1 | import drake 2 | import os 3 | import shutil 4 | 5 | class Packager(drake.Builder): 6 | 7 | def __init__(self, 8 | basename, 9 | version, 10 | dist, 11 | arch, 12 | top_dir, 13 | sources, 14 | destination = '.'): 15 | if '-' in version: 16 | raise Exception('RPM cannot have "-" in version') 17 | self.__top_dir = drake.Path(os.getcwd()) / top_dir 18 | self.__spec = top_dir / 'SPECS' / ('%s.spec' % basename) 19 | self.__arch = arch 20 | self.__dist = dist 21 | self.__rpm_name = '%(base)s-%(ver)s-1.%(dist)s.%(arch)s.rpm' % \ 22 | {'base': basename, 'ver': version, 'dist': dist, 'arch': self.__arch} 23 | self.__destination = drake.Path(destination) 24 | self.__target = drake.node('%s/%s' % (destination, self.__rpm_name)) 25 | all_sources = list() 26 | for s in sources: 27 | all_sources.append(s) 28 | if isinstance(s, drake.cxx.Executable): 29 | for d in s.dependencies_recursive: 30 | all_sources.append(d) 31 | super().__init__(all_sources, [self.__target]) 32 | 33 | def execute(self): 34 | rpm_dir = '%s/RPMS' % self.__top_dir 35 | if os.path.exists(rpm_dir): 36 | shutil.rmtree(rpm_dir) 37 | install_dir = '%s/BUILDROOT' % self.__top_dir 38 | for root, _, files in os.walk(install_dir): 39 | for f in files: 40 | f_path = ('%s/%s' % (root, f)).replace('%s/' % os.getcwd(), '', 1) 41 | if f_path not in [str(s) for s in self.sources()]: 42 | os.remove(str(f_path)) 43 | for node in self.sources(): 44 | if isinstance(node, drake.cxx.Executable): 45 | if not self.cmd('Undo prelink %s' % node, self.undo_prelink_cmd(node)): 46 | return False 47 | os.makedirs(rpm_dir) 48 | if not self.cmd('Package %s' % self.__rpm_name, self.rpm_build_cmd(dir)): 49 | return False 50 | return self.cmd('Copy %s' % self.__target, self.cp_rpm_cmd(dir)) 51 | 52 | def undo_prelink_cmd(self, path): 53 | return ['prelink', '-u', str(path)] 54 | 55 | def rpm_build_cmd(self, root_dir): 56 | return [ 57 | 'rpmbuild', '-bb', str(self.__spec), 58 | '--define', '_topdir %s' % self.__top_dir, 59 | '--define', 'dist .%s' % self.__dist, 60 | '--buildroot', '%s/BUILDROOT' % self.__top_dir, 61 | ] 62 | 63 | def cp_rpm_cmd(self, root_dir): 64 | source = '%(top_dir)s/RPMS/%(arch)s/%(name)s' % \ 65 | {'top_dir': self.__top_dir, 'arch': self.__arch, 'name': self.__rpm_name} 66 | return ['cp', source, str(self.__destination)] 67 | 68 | @property 69 | def package(self): 70 | return self.__target 71 | -------------------------------------------------------------------------------- /src/drake/templating.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2013-2019, Quentin "mefyl" Hocquet 2 | # 3 | # This software is provided "as is" without warranty of any kind, 4 | # either expressed or implied, including but not limited to the 5 | # implied warranties of fitness for a particular purpose. 6 | # 7 | # See the LICENSE file for more information. 8 | 9 | import drake 10 | import io 11 | import re 12 | import tempfile 13 | 14 | class Context: 15 | 16 | current = None 17 | 18 | def __init__(self, 19 | content = {}, 20 | sources = [], 21 | template_dir = [], 22 | pythonpath = (), 23 | hooks = {}, 24 | post_process = None 25 | ): 26 | self.content = content 27 | self.sources = sources 28 | self.pythonpath = pythonpath 29 | self.hooks = hooks 30 | self.template_dir = template_dir 31 | self.post_process = post_process 32 | 33 | def __enter__(self): 34 | self.__previous = Context.current 35 | Context.current = self 36 | return self 37 | 38 | def __exit__(self, *args, **kwargs): 39 | Context.current = self.__previous 40 | 41 | class Template(drake.Node): 42 | 43 | def __init__(self, *args, **kwargs): 44 | super().__init__(*args, **kwargs) 45 | context = Context.current 46 | if context is not None: 47 | Renderer(self, 48 | content = context.content, 49 | sources = context.sources, 50 | pythonpath = context.pythonpath, 51 | hooks = context.hooks, 52 | lookup = context.template_dir, 53 | post_process = context.post_process 54 | ) 55 | 56 | drake.Node.extensions['tmpl'] = Template 57 | 58 | 59 | class Renderer(drake.Converter): 60 | 61 | def __init__(self, 62 | template, 63 | content = {}, 64 | sources = [], 65 | pythonpath = (), 66 | hooks = {}, 67 | lookup = [], 68 | post_process = None): 69 | self.__template = template 70 | self.__hooks = hooks 71 | dst = template.name_relative.without_last_extension() 72 | self.__target = drake.node(dst) 73 | super().__init__(self.__template, 74 | self.__target, 75 | additional_sources = sources) 76 | self.__content = content 77 | self.__lookup = lookup 78 | self.__post_process = post_process 79 | self.__pythonpath = [] 80 | for path in pythonpath: 81 | self.__pythonpath.append(drake.path_source(path)) 82 | self.__pythonpath.append(drake.path_build(path)) 83 | 84 | mako_re = re.compile('%endfor') 85 | 86 | def execute(self): 87 | self.output('Render %s' % self.__target) 88 | import mako.template, mako.lookup 89 | import mako.runtime 90 | import sys 91 | path = str(self.__template.path(absolute = True)) 92 | previous = sys.path 93 | sys.path = sys.path[:] 94 | modules = set(sys.modules) 95 | for source in self.__hooks.keys(): 96 | for hook in self.__hooks[source]: 97 | hook(self.__content, source) 98 | try: 99 | sys.path = [str(path) for path in self.__pythonpath] + sys.path 100 | with open(path, 'r') as tpl, \ 101 | tempfile.NamedTemporaryFile(mode = 'w') as content: 102 | line_number = 1 103 | def print_line_number(): 104 | if isinstance(self.__target, (drake.cxx.Source, 105 | drake.cxx.Header)): 106 | print('# %d "%s"' % (line_number, path), file = content) 107 | print_line_number() 108 | for line in tpl: 109 | print(line, file = content, end = '') 110 | if Renderer.mako_re.search(line): print_line_number() 111 | line_number += 1 112 | content.flush() 113 | lookup = mako.lookup.TemplateLookup(directories = self.__lookup) 114 | tpl = mako.template.Template(filename = content.name, lookup = lookup) 115 | with drake.WritePermissions(self.__target): 116 | with open(str(self.__target.path()), 'w') as f: 117 | ctx = mako.runtime.Context(f, **self.__content) 118 | tpl.render_context(ctx) 119 | import shutil 120 | shutil.copymode(str(self.__template.path()), 121 | str(self.__target.path())) 122 | if self.__post_process: 123 | self.__post_process(self.__target) 124 | return True 125 | finally: 126 | # Restore path. 127 | sys.path = previous 128 | # Unload modules to avoid side effects. 129 | for added in set(sys.modules) - modules: 130 | del sys.modules[added] 131 | 132 | @property 133 | def rendered(self): 134 | return self.__target 135 | 136 | def hash(self): 137 | return { 138 | 'content': self.__content, 139 | 'pythonpath': self.__pythonpath, 140 | } 141 | 142 | def __str__(self): 143 | return 'rendering of %s' % self.__target 144 | -------------------------------------------------------------------------------- /src/drake/threadpool.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2012-2019, Quentin "mefyl" Hocquet 2 | # 3 | # This software is provided "as is" without warranty of any kind, 4 | # either expressed or implied, including but not limited to the 5 | # implied warranties of fitness for a particular purpose. 6 | # 7 | # See the LICENSE file for more information. 8 | 9 | import sys 10 | import threading 11 | 12 | class ThreadPool: 13 | 14 | class Runner(threading.Thread): 15 | 16 | def __init__(self, pool): 17 | threading.Thread.__init__(self) 18 | self.__cond = threading.Condition() 19 | self.__f = None 20 | self.__pool = pool 21 | self.__ready = False 22 | self.__stop = False 23 | self.start() 24 | 25 | def run(self): 26 | while True: 27 | #print('%s: process' % self, file = sys.stderr) 28 | with self.__cond: 29 | if self.__stop: 30 | #print('%s: stop' % self) 31 | self.__pool._ThreadPool__threads.remove(self) 32 | return 33 | if self.__f is None: 34 | #print('%s: wait for job' % self, file = sys.stderr) 35 | self.__cond.wait() 36 | if self.__stop: 37 | #print('%s: stop' % self, file = sys.stderr) 38 | try: 39 | self.__pool._ThreadPool__threads.remove(self) 40 | except: 41 | # If the thread was starting a job, it was already 42 | # removed from the set, but that's OK. 43 | pass 44 | return 45 | #print('%s: run job' % self, file = sys.stderr) 46 | self.__f() 47 | self.__f = None 48 | with self.__pool._ThreadPool__lock: 49 | #print('%s: register as available' % self, file = sys.stderr) 50 | self.__pool._ThreadPool__running.remove(self) 51 | self.__pool._ThreadPool__threads.add(self) 52 | 53 | def wake(self, f): 54 | with self.__cond: 55 | self.__f = f 56 | self.__cond.notify() 57 | 58 | def stop(self): 59 | with self.__cond: 60 | self.__stop = True 61 | self.__cond.notify() 62 | self.join() 63 | 64 | def __init__(self): 65 | self.__threads = set() 66 | self.__running = set() 67 | self.__lock = threading.Semaphore(1) 68 | self.__cond = threading.Condition() 69 | 70 | def run(self, f): 71 | with self.__lock: 72 | if self.__threads: 73 | thread = iter(self.__threads).__next__() 74 | self.__threads.remove(thread) 75 | else: 76 | thread = ThreadPool.Runner(self) 77 | self.__running.add(thread) 78 | thread.wake(f) 79 | 80 | def stop(self): 81 | threads = set() 82 | # Collect the Threads and then stop them. Do not stop the Threads 83 | # while holding self.__lock, because Thread.stop joins the thread, 84 | # which might require self.__lock to finish, hence a dead-lock. 85 | with self.__lock: 86 | threads = threads.union(self.__threads) 87 | threads = threads.union(self.__running) 88 | for thread in threads: 89 | thread.stop() 90 | -------------------------------------------------------------------------------- /src/drake/utils.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2011-2019, Quentin "mefyl" Hocquet 2 | # 3 | # This software is provided "as is" without warranty of any kind, 4 | # either expressed or implied, including but not limited to the 5 | # implied warranties of fitness for a particular purpose. 6 | # 7 | # See the LICENSE file for more information. 8 | 9 | import re 10 | 11 | def re_map(f, regexp, string): 12 | """Find regexp in string and map f on the matches. 13 | 14 | Replace every occurence of regexp in string by the application of f 15 | to the match. 16 | 17 | >>> re_map(lambda x: '<%s>' % x.capitalize(), 'a|e|i|o|u', 'This was a triumph') 18 | 'Ths ws trmph' 19 | """ 20 | match = re.search(regexp, string) 21 | while match: 22 | prefix = string[:match.span()[0]] 23 | suffix = string[match.span()[1]:] 24 | string = '%s%s%s' % (prefix, f(match.group()), suffix) 25 | match = re.search(regexp, string) 26 | return string 27 | 28 | def camel_case(s): 29 | """Convert the given indentifier to camel case. 30 | 31 | Converts dashes or underscore separated identifiers to camel case. 32 | 33 | >>> camel_case('foo') 34 | 'foo' 35 | >>> camel_case('foo-bar') 36 | 'fooBar' 37 | >>> camel_case('foo_bar_baz_quux') 38 | 'fooBarBazQuux' 39 | """ 40 | return re_map(lambda s: s[1].capitalize(), 41 | re.compile('[-_][a-zA-Z]'), s) 42 | 43 | 44 | def shell_escape(s): 45 | """Escape a string to be a single shell entity. 46 | 47 | s -- the string or string-convertible to espace. 48 | 49 | >>> shell_escape('foo') 50 | 'foo' 51 | >>> shell_escape('foo bar') 52 | "'foo bar'" 53 | >>> shell_escape('foo$bar') 54 | "'foo$bar'" 55 | >>> shell_escape('foo"bar') 56 | '\\'foo"bar\\'' 57 | >>> shell_escape('$foobar') 58 | "'$foobar'" 59 | >>> shell_escape("foo'bar") 60 | "'foo'\\\\''bar'" 61 | """ 62 | s = str(s) 63 | for special in [' ', '$', '"', "'"]: 64 | if s.find(special) != -1: 65 | s = s.replace("'", "'\\''") 66 | s = "'%s'" % s 67 | break 68 | return s 69 | 70 | def property_memoize(f): 71 | def result(self): 72 | prop = '_%s__%s' % (self.__class__.__name__, f.__name__) 73 | if not hasattr(self, prop): 74 | setattr(self, prop, f(self)) 75 | return getattr(self, prop) 76 | return property(result) 77 | 78 | def pretty_listing(c, any = False, quantifier = None): 79 | ''' 80 | Format a collection of items into a human readable listing. 81 | 82 | >>> pretty_listing([]) 83 | 'none' 84 | >>> pretty_listing(['foo']) 85 | 'foo' 86 | >>> pretty_listing(['foo', 'bar']) 87 | 'foo and bar' 88 | >>> pretty_listing(['foo', 'bar', 'baz']) 89 | 'foo, bar and baz' 90 | >>> pretty_listing(['foo', 'bar', 'baz'], any = True) 91 | 'foo, bar or baz' 92 | >>> pretty_listing(['foo', 'bar', 'baz'], quantifier = True) 93 | 'all of foo, bar and baz' 94 | >>> pretty_listing(['foo', 'bar', 'baz'], 95 | ... any = True, quantifier = True) 96 | 'any of foo, bar or baz' 97 | ''' 98 | import types 99 | if isinstance(c, types.GeneratorType): 100 | c = list(c) 101 | elif not isinstance(c, (list, tuple)): 102 | return str(c) 103 | if len(c) == 0: 104 | return 'none' 105 | elif len(c) == 1: 106 | return str(c[0]) 107 | return '{}{} {} {}'.format( 108 | '{} of '.format('any' if any else 'all') if quantifier else '', 109 | ', '.join(map(str, c[:-1])), 'or' if any else 'and', c[-1]) 110 | -------------------------------------------------------------------------------- /src/drake/valgrind.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2013-2019, Quentin "mefyl" Hocquet 2 | # 3 | # This software is provided "as is" without warranty of any kind, 4 | # either expressed or implied, including but not limited to the 5 | # implied warranties of fitness for a particular purpose. 6 | # 7 | # See the LICENSE file for more information. 8 | 9 | import drake 10 | import os 11 | import subprocess 12 | 13 | class Valgrind: 14 | 15 | def __init__(self, path = None): 16 | if path is None: 17 | path = drake.Path('valgrind') 18 | elif isinstance(path, Valgrind): 19 | self.__path = drake.Path(path.__path) 20 | self.__version = drake.Version(path.__version) 21 | return 22 | self.__path = drake.Path(path) 23 | try: 24 | output = subprocess.check_output([str(self.path), '--version']) 25 | except Exception as e: 26 | raise Exception('Unable to find %s' % self.path) from e 27 | output = output.decode() 28 | prefix = 'valgrind-' 29 | if not output.startswith(prefix): 30 | raise Exception('Unable to parse valgrind version: %s' % output) 31 | output = output[len(prefix):] 32 | self.__version = drake.Version(output) 33 | 34 | @property 35 | def path(self): 36 | return self.__path 37 | 38 | @property 39 | def version(self): 40 | return self.__version 41 | 42 | 43 | class ValgrindRunner(drake.Runner): 44 | 45 | def __init__(self, 46 | exe, 47 | name = None, 48 | args = None, 49 | env = None, 50 | stdin = None, 51 | valgrind = None, 52 | valgrind_args = None): 53 | super().__init__(exe, name = name, args = args, env = env, stdin = stdin) 54 | self.__valgrind = Valgrind(valgrind) 55 | self.__valgrind_log = self._node('valgrind') 56 | self.__valgrind_log.builder = self 57 | self.valgrind_reporting = drake.Runner.Reporting.on_failure 58 | self.__valgrind_args = valgrind_args or [] 59 | 60 | @property 61 | def command(self): 62 | return [ 63 | str(self.__valgrind.path), 64 | '--leak-check=full', 65 | '--num-callers=50', 66 | '--log-file=%s' % self.__valgrind_log.path(), 67 | '--error-exitcode=1', 68 | ] + self.__valgrind_args + super().command 69 | 70 | def _report(self, status): 71 | super()._report(status) 72 | if self._must_report(self.valgrind_reporting, status): 73 | self._report_node(self.__valgrind_log) 74 | -------------------------------------------------------------------------------- /src/drake/version/__init__.py: -------------------------------------------------------------------------------- 1 | import drake 2 | import drake.git 3 | import collections 4 | import itertools 5 | 6 | Version = drake.Version 7 | 8 | class VersionGenerator(drake.Builder): 9 | 10 | def __init__(self, output, git = None, production_build = True): 11 | git = git or drake.git.Git() 12 | drake.Builder.__init__(self, [git], [output]) 13 | self.__git = git 14 | self.__output = output 15 | self.__production_build = production_build 16 | 17 | def execute(self): 18 | self.output('Generate %s' % self.__output.path()) 19 | chunks = collections.OrderedDict() 20 | if self.__production_build: 21 | version = self.__git.description() 22 | else: 23 | version = '%s-dev' % self.__git.version().split('-')[0] 24 | chunks['version'] = version 25 | chunks['major'], chunks['minor'], chunks['subminor'] = \ 26 | map(int, version.split('-')[0].split('.')) 27 | with open(str(self.__output.path()), 'w') as f: 28 | variables = (self._variable(*item) for item in chunks.items()) 29 | for line in itertools.chain( 30 | self._prologue(), variables, self._epilogue()): 31 | print(line, file = f) 32 | return True 33 | 34 | def _prologue(self): 35 | return iter(()) 36 | 37 | def _epilogue(self): 38 | return iter(()) 39 | 40 | def _variable(self, name, value): 41 | raise NotImplementedError() 42 | 43 | def hash(self): 44 | return self.__production_build 45 | -------------------------------------------------------------------------------- /src/drake/which.py: -------------------------------------------------------------------------------- 1 | import os 2 | import stat 3 | import sys 4 | 5 | _PATH = os.environ.get('PATH', '').split(os.pathsep) 6 | _IS_WINDOWS = sys.platform.lower().startswith('win') 7 | 8 | def is_executable(p, *path): 9 | path = os.path.join(p, *path) 10 | return os.path.exists(path) and \ 11 | not os.path.isdir(path) and \ 12 | os.stat(path)[stat.ST_MODE] & stat.S_IXUSR 13 | 14 | def which(binary): 15 | if os.path.isabs(binary) and is_executable(binary): 16 | return binary 17 | for dir_ in _PATH: 18 | exe = os.path.join(dir_, binary) 19 | if is_executable(exe): 20 | return exe 21 | if _IS_WINDOWS and not binary.lower().endswith('.exe'): 22 | return which(binary + '.exe') 23 | return None 24 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .test 3 | -------------------------------------------------------------------------------- /tests/HTTPDownload: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | '''Check HTTPDownload.''' 4 | 5 | import drake 6 | 7 | from utils import * 8 | 9 | content = b'HTTPDownload test payload' 10 | 11 | import http.server 12 | import socketserver 13 | 14 | beacons = set() 15 | 16 | class Handler(http.server.BaseHTTPRequestHandler): 17 | 18 | def _set_headers(self): 19 | if 'fail' in self.requestline: 20 | beacons.add('fail') 21 | self.send_response(404) 22 | return 23 | self.send_response(200) 24 | if 'binary' in self.requestline: 25 | beacons.add('binary') 26 | self.send_header('Content-type', 'application/octet-stream') 27 | elif 'html' in self.requestline: 28 | beacons.add('html') 29 | self.send_header('Content-type', 'text/html') 30 | else: 31 | self.send_header('Content-type', 'text/plain') 32 | self.end_headers() 33 | 34 | def do_GET(self): 35 | path = self.requestline.split(' ')[1] 36 | self._set_headers() 37 | self.wfile.write(content) 38 | self.wfile.write(b'\n') 39 | self.wfile.write(path.encode('latin-1')) 40 | 41 | httpd = socketserver.TCPServer(("", 0), Handler) 42 | 43 | import threading 44 | class HTTPThread(threading.Thread): 45 | 46 | def run(self): 47 | httpd.serve_forever() 48 | 49 | HTTPThread(daemon = True).start() 50 | port = httpd.server_address[1] 51 | 52 | with Drake() as d: 53 | def download(path = 'some/path', fail = False, *args, **kwargs): 54 | url = 'http://localhost:{}/{}'.format(port, path) 55 | f = drake.download(url, *args, **kwargs) 56 | try: 57 | f.build() 58 | except Exception as e: 59 | if fail: 60 | return str(e.__context__) 61 | else: 62 | raise 63 | else: 64 | assert not fail 65 | with open(str(f.path()), 'rb') as f: 66 | assertEq(f.read(), content + b'\n/' + path.encode('latin-1')) 67 | download('path') 68 | download('subdir/rename', where = 'subdir', name = 'rename') 69 | fp = '4d70b1b5648a8a5408cb0ec07ef38477' 70 | download('subdir/rename', name = 'fingerprinted', 71 | fingerprint = fp) 72 | assert fp in download( 73 | 'subdir/rename', name = 'wrong_fingerprint', 74 | fingerprint = '00000000000000000000000000000000', 75 | fail = True) 76 | download('binary', name = 'binary') 77 | download('html', name = 'html') 78 | assert 'fail' in download('fail', name = '404', fail = True) 79 | assert 'binary' in beacons 80 | assert 'fail' in beacons 81 | assert 'html' in beacons 82 | -------------------------------------------------------------------------------- /tests/base/builder/drake-build.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import drake, os.path, sys 4 | 5 | i = int(sys.argv[1]) 6 | 7 | print(i) 8 | 9 | try: 10 | drake.set_srctree('.') 11 | root = drake.raw_include('drakefile.py') 12 | tgt = drake.Node.nodes['tgt'] 13 | 14 | if i == 1: 15 | if os.path.exists(str(tgt)): 16 | os.remove(str(tgt)) 17 | assert not os.path.exists(str(tgt)) 18 | assert not root.run 19 | # Check it's built 20 | tgt.build() 21 | assert os.path.exists(str(tgt)) 22 | assert root.run 23 | elif i == 2: 24 | # Check it's not rebuilt 25 | tgt.build() 26 | assert os.path.exists(str(tgt)) 27 | assert not root.run 28 | os.remove(str(tgt)) 29 | except drake.Exception as e: 30 | print('%s: %s' % (sys.argv[0], e)) 31 | exit(1) 32 | -------------------------------------------------------------------------------- /tests/base/builder/drakefile.py: -------------------------------------------------------------------------------- 1 | import drake 2 | 3 | run = False 4 | 5 | class FooBuilder(drake.Builder): 6 | 7 | name = 'foo' 8 | 9 | def __init__(self, dst): 10 | 11 | drake.Builder.__init__(self, [], [dst]) 12 | self.dst = dst 13 | 14 | def execute(self): 15 | 16 | global run 17 | f = open(str(self.dst), 'w') 18 | print >> f, 'foo' 19 | f.close() 20 | run = True 21 | return True 22 | 23 | def configure(): 24 | 25 | tgt = drake.Node('tgt') 26 | FooBuilder(tgt) 27 | -------------------------------------------------------------------------------- /tests/base/builder/iter: -------------------------------------------------------------------------------- 1 | 2 2 | -------------------------------------------------------------------------------- /tests/base/change-dynamic-dependency: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | '''Check that removing and then changing a dynamic dependency results in the 4 | new dependency being built.''' 5 | 6 | import drake 7 | import os 8 | import tempfile 9 | import utils 10 | 11 | class DependentBuilder(drake.TouchBuilder): 12 | 13 | def deps_handler(builder, path, t): 14 | return drake.node(path, t) 15 | 16 | deps = 'some.identifier' 17 | 18 | drake.Builder.register_deps_handler(deps, deps_handler) 19 | 20 | def __init__(self, dsts, deps): 21 | self.__deps = deps 22 | super().__init__(dsts) 23 | 24 | def dependencies(self): 25 | for dep in self.__deps: 26 | self.add_dynsrc(self.deps, dep) 27 | 28 | with tempfile.TemporaryDirectory() as working_dir: 29 | 30 | def build(name): 31 | with utils.Drake(working_dir) as d: 32 | dyn_dep = drake.node(name) 33 | drake.TouchBuilder([dyn_dep]) 34 | target = drake.node('target') 35 | DependentBuilder([target], [dyn_dep]) 36 | target.build() 37 | objs = os.listdir(working_dir) 38 | assert str(dyn_dep.path()) in objs 39 | assert str(target.path()) in objs 40 | 41 | # Build `target` and `dyn_dep_1` which it depends on dynamically. 42 | build('dyn_dep_1') 43 | 44 | # Remove the `dyn_dep_1` file. 45 | os.remove('%s/dyn_dep_1' % working_dir) 46 | objs = os.listdir(working_dir) 47 | assert 'dyn_dep_1' not in objs 48 | 49 | # Change the dynamic dependency to `dyn_dep_2` and rebuild. 50 | build('dyn_dep_2') 51 | -------------------------------------------------------------------------------- /tests/base/command-line: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | '''Check the build chain stops when a builder fails.''' 4 | 5 | import drake 6 | import utils 7 | import sys 8 | 9 | 10 | def bool_cli(foo: bool): 11 | print('plop:', foo) 12 | assert foo is False 13 | 14 | with utils.Drake() as d: 15 | d.configure = bool_cli 16 | sys.argv.append('--foo=false') 17 | d.run() 18 | del sys.argv[-1] 19 | 20 | def positional(a : int = 0, b : int = '1'): 21 | assert a == b 22 | 23 | with utils.Drake() as d: 24 | d.configure = positional 25 | d.run(a = '1') 26 | 27 | with utils.Drake() as d: 28 | d.configure = positional 29 | d.run(1) 30 | -------------------------------------------------------------------------------- /tests/base/copy/A/B/drakefile: -------------------------------------------------------------------------------- 1 | import drake 2 | 3 | src = None 4 | 5 | def configure(): 6 | 7 | global src 8 | src = drake.Node('src.txt') 9 | -------------------------------------------------------------------------------- /tests/base/copy/A/B/src.txt: -------------------------------------------------------------------------------- 1 | test 2 | -------------------------------------------------------------------------------- /tests/base/copy/A/drakefile: -------------------------------------------------------------------------------- 1 | import drake 2 | 3 | B = None 4 | 5 | def configure(): 6 | 7 | global B 8 | B = drake.include('B') 9 | 10 | drake.copy(B.src, 'lib', strip_prefix = True) 11 | -------------------------------------------------------------------------------- /tests/base/copy/drakefile: -------------------------------------------------------------------------------- 1 | import drake 2 | 3 | def configure(): 4 | 5 | A = drake.include('A') 6 | src = drake.copy(A.B.src, 'lib', strip_prefix = True) 7 | assert(src.path() == 'lib/src.txt') 8 | -------------------------------------------------------------------------------- /tests/base/copy/test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import drake, os.path, sys 4 | 5 | with drake.Drake('.') as d: 6 | d.run() 7 | -------------------------------------------------------------------------------- /tests/base/cyclic-dependencies: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | '''Check that removing and then changing a dynamic dependency results in the 4 | new dependency being built.''' 5 | 6 | import os 7 | import tempfile 8 | 9 | import drake 10 | 11 | with tempfile.TemporaryDirectory() as wd: 12 | os.chdir(wd) 13 | with drake.Drake(): 14 | nodes = drake.nodes('a', 'b', 'c', 'd', 'e') 15 | try: 16 | drake.Builder([nodes[0]], [nodes[0]]) 17 | except drake.CyclicDependency: 18 | pass 19 | else: 20 | raise Exception('cyclic dependency not detected') 21 | assert nodes[0].builder is None 22 | drake.Builder([nodes[0]], [nodes[1]]) 23 | drake.Builder([nodes[1]], [nodes[2]]) 24 | try: 25 | drake.Builder([nodes[2]], [nodes[0]]) 26 | except drake.CyclicDependency: 27 | pass 28 | else: 29 | raise Exception('cyclic dependency not detected') 30 | assert nodes[0].builder is None 31 | 32 | 33 | with tempfile.TemporaryDirectory() as wd: 34 | os.chdir(wd) 35 | with drake.Drake(): 36 | nodes = drake.nodes('a', 'b', 'c', 'd', 'e', 'f') 37 | drake.Builder([nodes[1]], [nodes[2]]) 38 | drake.Builder([nodes[2]], [nodes[3]]) 39 | drake.Builder([nodes[3]], [nodes[4]]) 40 | drake.Builder([nodes[4], nodes[0]], [nodes[5]]) 41 | 42 | drake.Builder([drake.node('z')], [nodes[0]]) 43 | try: 44 | drake.Builder([nodes[5]], [nodes[4]]) 45 | except drake.CyclicDependency: 46 | pass 47 | except: 48 | raise Exception('cyclic dependency not detected') 49 | -------------------------------------------------------------------------------- /tests/base/dependency: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- python -*- 3 | 4 | import drake 5 | import os 6 | import stat 7 | import tempfile 8 | 9 | from utils import * 10 | 11 | os.environ['PYTHONDONTWRITEBYTECODE'] = '1' 12 | 13 | def drakefile(): 14 | exe = drake.node('main.py') 15 | exe.dependency_add(drake.node('utils.py')) 16 | runner = drake.Runner(exe) 17 | return drake.node('main.py.out') 18 | 19 | with tempfile.TemporaryDirectory() as wd: 20 | 21 | os.chdir(wd) 22 | with open('utils.py', 'w') as f: 23 | print('value = 42', file = f) 24 | with open('main.py', 'w') as f: 25 | print('''#!/usr/bin/env python3 26 | 27 | import utils 28 | 29 | print(utils.value) 30 | ''', file = f) 31 | os.chmod('main.py', stat.S_IRUSR | stat.S_IXUSR) 32 | 33 | # Run the test and check output 34 | with Drake(wd): 35 | drakefile().build() 36 | with open('main.py.out', 'r') as f: 37 | assertEq(f.read(), '42\n') 38 | 39 | with open('main.py.out', 'w') as f: 40 | print('43', file = f) 41 | 42 | # Check it's not re-run 43 | with Drake(wd): 44 | drakefile().build() 45 | with open('main.py.out', 'r') as f: 46 | assertEq(f.read(), '43\n') 47 | 48 | with open('utils.py', 'w') as f: 49 | print('value = 51', file = f) 50 | 51 | # Check it's re-run after changing a dependency 52 | with Drake(wd): 53 | drakefile().build() 54 | with open('main.py.out', 'r') as f: 55 | assert f.read() == '51\n' 56 | -------------------------------------------------------------------------------- /tests/base/deps-dyn/src: -------------------------------------------------------------------------------- 1 | foobar 2 | -------------------------------------------------------------------------------- /tests/base/deps-dyn/test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- python -*- 3 | 4 | import drake, os 5 | 6 | depname = 'drake.test.depname' 7 | add = True 8 | builder = None 9 | deps = False 10 | dyndeps = False 11 | run = False 12 | 13 | class DynNode(drake.Node): 14 | 15 | pass 16 | 17 | def handler(b, path, type): 18 | 19 | global dyndeps 20 | global builder 21 | dyndeps = True 22 | assert b == builder 23 | assert path == 'src' 24 | assert type == DynNode 25 | return drake.node(path, type) 26 | 27 | drake.Builder.register_deps_handler(depname, handler) 28 | 29 | class DynBuilder(drake.Builder): 30 | 31 | def __init__(self, dst): 32 | 33 | drake.Builder.__init__(self, [], [dst]) 34 | self.dst = dst 35 | 36 | def dependencies(self): 37 | 38 | global deps 39 | import sys 40 | deps = True 41 | if add: 42 | self.src = DynNode('src') 43 | self.add_dynsrc(depname, self.src) 44 | 45 | def execute(self): 46 | 47 | global run 48 | if add: 49 | os.system('cp %s %s' % (self.src, self.dst)) 50 | else: 51 | os.system('touch %s' % (self.dst)) 52 | run = True 53 | return True 54 | 55 | def test(): 56 | 57 | global add, builder, deps, dyndeps, run 58 | 59 | add = True 60 | builder = None 61 | deps = False 62 | dyndeps = False 63 | run = False 64 | 65 | drake.Path('src').touch() 66 | drake.Path('.drake').remove() 67 | 68 | with drake.Drake(): 69 | # Check The dependency is added 70 | drake.reset() 71 | drake.Path('dst').remove() 72 | dst = drake.Node('dst') 73 | builder = DynBuilder(dst) 74 | dst.build() 75 | assert deps 76 | deps = False 77 | assert run 78 | run = False 79 | assert not dyndeps 80 | 81 | # Check The dependency is restored 82 | drake.reset() 83 | dst = drake.Node('dst') 84 | builder = DynBuilder(dst) 85 | dst.build() 86 | assert dyndeps 87 | dyndeps = False 88 | assert not deps 89 | assert not run 90 | 91 | # Check The dependency is discarded 92 | drake.reset() 93 | dst = drake.Node('dst') 94 | builder = DynBuilder(dst) 95 | assert drake.Path('src').exists() 96 | drake.Path('src').remove() 97 | add = False 98 | dst.build() 99 | 100 | test() 101 | -------------------------------------------------------------------------------- /tests/base/deps/drake-build.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import drake, os.path, sys 4 | 5 | i = int(sys.argv[1]) 6 | 7 | try: 8 | drake.set_srctree('.') 9 | root = drake.raw_include('drakefile.py') 10 | dst = drake.Node.nodes['dst'] 11 | src = drake.Node.nodes['src'] 12 | 13 | if i == 1: 14 | if os.path.exists(str(dst)): 15 | os.remove(str(dst)) 16 | with open(str(src), 'w') as f: 17 | print >> f, 'foo' 18 | assert not os.path.exists(str(dst)) 19 | assert not root.run 20 | # Check it's built 21 | dst.build() 22 | assert os.path.exists(str(dst)) 23 | assert root.run 24 | elif i == 2: 25 | # Check it's not rebuilt 26 | dst.build() 27 | assert not root.run 28 | elif i == 3: 29 | # Check it's rebuilt 30 | with open(str(src), 'w') as f: 31 | print >> f, 'bar' 32 | dst.build() 33 | assert root.run 34 | os.remove(str(dst)) 35 | except drake.Exception, e: 36 | print '%s: %s' % (sys.argv[0], e) 37 | exit(1) 38 | -------------------------------------------------------------------------------- /tests/base/deps/drakefile.py: -------------------------------------------------------------------------------- 1 | import drake, os 2 | 3 | run = False 4 | 5 | class CopyBuilder(drake.Builder): 6 | 7 | def __init__(self, src, dst): 8 | 9 | drake.Builder.__init__(self, [src], [dst]) 10 | self.src = src 11 | self.dst = dst 12 | 13 | def execute(self): 14 | 15 | global run 16 | os.system('cp %s %s' % (self.src, self.dst)) 17 | run = True 18 | return True 19 | 20 | def configure(): 21 | 22 | src = drake.Node('src') 23 | dst = drake.Node('dst') 24 | CopyBuilder(src, dst) 25 | -------------------------------------------------------------------------------- /tests/base/deps/iter: -------------------------------------------------------------------------------- 1 | 3 2 | -------------------------------------------------------------------------------- /tests/base/dynamic-termination: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | '''Check that terminating a build while within a dynamic dependency 4 | build is not considered as that dependency having failed, thus 5 | retriggering the parent object build.''' 6 | 7 | import drake 8 | from utils import * 9 | 10 | 11 | sem = drake.sched.Semaphore() 12 | 13 | class LockedBuilder(drake.Builder): 14 | 15 | def execute(self): 16 | sem.lock() 17 | 18 | beacon = False 19 | 20 | class Builder(drake.Builder): 21 | 22 | def execute(self): 23 | global beacon 24 | beacon = True 25 | 26 | 27 | with Drake(jobs = 2, kill_builders_on_failure = True) as d: 28 | root = drake.node('root') 29 | fail = drake.node('fail') 30 | interrupted = drake.node('interrupted') 31 | dyn = drake.node('dyn') 32 | LockedBuilder([], [dyn]) 33 | Builder([], [interrupted]).add_dynsrc('test', drake.node('dyn')) 34 | drake.Builder([interrupted, fail], [root]) 35 | failer = FailBuilder([], [fail]) 36 | try: 37 | root.build() 38 | except drake.Builder.Failed as e: 39 | assert e.builder is failer 40 | assert not beacon 41 | else: 42 | raise Exception('should have failed') 43 | -------------------------------------------------------------------------------- /tests/base/failure: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | '''Check the build chain stops when a builder fails.''' 4 | 5 | import drake 6 | import utils 7 | 8 | class FailBuilder(drake.Builder): 9 | 10 | def execute(self): 11 | return False 12 | 13 | 14 | class SuccessBuilder(drake.Builder): 15 | 16 | def execute(self): 17 | return True 18 | 19 | 20 | with utils.Drake() as d: 21 | source = drake.touch('source') 22 | intermediate = drake.node('intermediate') 23 | fail = FailBuilder([source], [intermediate]) 24 | target = drake.node('target') 25 | SuccessBuilder([intermediate], [target]) 26 | try: 27 | target.build() 28 | except drake.Builder.Failed as e: 29 | assert e.builder is fail 30 | else: 31 | raise Exception('build should have failed') 32 | -------------------------------------------------------------------------------- /tests/base/failure-cmd: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | '''Check a non-zero return code is considered a failure.''' 4 | 5 | import drake 6 | import utils 7 | 8 | 9 | class TouchShellCommand(drake.ShellCommand): 10 | 11 | def execute(self): 12 | for target in self.targets(): 13 | target.path().touch() 14 | return super().execute() 15 | 16 | 17 | with utils.Drake() as d: 18 | source = drake.touch('source') 19 | target = drake.node('target') 20 | b = TouchShellCommand([source], [target], ['false']) 21 | try: 22 | import sys 23 | target.build() 24 | 25 | except drake.Builder.Failed as e: 26 | assert e.builder is b 27 | else: 28 | raise Exception('build should have failed') 29 | -------------------------------------------------------------------------------- /tests/base/interrupt-dynamic-dependency: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | '''''' 4 | 5 | import drake 6 | import drake.sched 7 | import os 8 | import tempfile 9 | import threading 10 | import utils 11 | 12 | class BlockBuilder(drake.TouchBuilder): 13 | 14 | def __init__(self, targets): 15 | super().__init__(targets) 16 | self.sem = threading.Semaphore(0) 17 | 18 | def execute(self): 19 | def job(): 20 | self.sem.acquire() 21 | return drake.TouchBuilder.execute(self) 22 | return self._run_job(job) 23 | 24 | 25 | class DependentBuilder(drake.TouchBuilder): 26 | 27 | def deps_handler(builder, path, t): 28 | return drake.node(path, t) 29 | 30 | deps = 'some.identifier' 31 | 32 | drake.Builder.register_deps_handler(deps, deps_handler) 33 | 34 | def __init__(self, dsts, deps): 35 | self.__deps = deps 36 | super().__init__(dsts) 37 | 38 | def dependencies(self): 39 | for dep in self.__deps: 40 | self.add_dynsrc(self.deps, dep) 41 | 42 | with tempfile.TemporaryDirectory() as working_dir: 43 | with utils.Drake(working_dir) as d: 44 | dyn1 = drake.node('dyn1') 45 | drake.TouchBuilder([dyn1]) 46 | dyn2 = drake.node('dyn2') 47 | drake.TouchBuilder([dyn2]) 48 | target = drake.node('target') 49 | DependentBuilder([target], [dyn1, dyn2]) 50 | target.build() 51 | drake.sched.Coroutine(target.build, str(target), d.scheduler) 52 | d.scheduler.run() 53 | os.remove('%s/target' % working_dir) 54 | os.remove('%s/dyn1' % working_dir) 55 | os.remove('%s/dyn2' % working_dir) 56 | with utils.Drake(working_dir) as d: 57 | d.jobs_set(3) 58 | dyn1 = drake.node('dyn1') 59 | block = BlockBuilder([dyn1]) 60 | dyn2 = drake.node('dyn2') 61 | class FailBuilder(drake.Builder): 62 | def execute(self): 63 | block.sem.release() 64 | return False 65 | fail = FailBuilder([], [dyn2]) 66 | target = drake.node('target') 67 | DependentBuilder([target], [dyn1, dyn2]) 68 | drake.sched.Coroutine(target.build, str(target), d.scheduler) 69 | try: 70 | d.scheduler.run() 71 | except drake.Builder.Failed as e: 72 | assert e.builder == fail 73 | else: 74 | assert False 75 | assert os.path.exists('dyn1') 76 | -------------------------------------------------------------------------------- /tests/base/mtime: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import drake 4 | import os 5 | import tempfile 6 | 7 | from utils import * 8 | 9 | class Unhashable(drake.Node): 10 | 11 | def __init__(self, *args, **kwargs): 12 | self.hashed = False 13 | super().__init__(*args, **kwargs) 14 | 15 | def hash(self): 16 | self.hashed = True 17 | return super().hash() 18 | 19 | with tempfile.TemporaryDirectory() as wd: 20 | 21 | os.chdir(wd) 22 | with open('unhashable', 'w') as f: 23 | print('foo', file = f) 24 | import time 25 | time.sleep(2) 26 | 27 | def drakefile(): 28 | n = Unhashable('unhashable') 29 | d = drake.node('dest') 30 | TouchBuilder([n], [d]) 31 | return n, d 32 | 33 | with drake.Drake(wd, use_mtime = True) as d: 34 | n, d = drakefile() 35 | d.build() 36 | assert n.hashed 37 | 38 | def with_mtime(): 39 | n, d = drakefile() 40 | d.build() 41 | assert not n.hashed 42 | 43 | def without_mtime(): 44 | n, d = drakefile() 45 | d.build() 46 | assert n.hashed 47 | 48 | 49 | # Force mtime usage 50 | print('-' * 72) 51 | with drake.Drake(wd, use_mtime = True) as d: 52 | with_mtime() 53 | 54 | # Disable mtime usage 55 | print('-' * 72) 56 | with drake.Drake(wd, use_mtime = False) as d: 57 | without_mtime() 58 | 59 | # Disable mtime usage through env 60 | print('-' * 72) 61 | try: 62 | os.environ['DRAKE_MTIME'] = '0' 63 | with drake.Drake(wd) as d: 64 | without_mtime() 65 | finally: 66 | del os.environ['DRAKE_MTIME'] 67 | 68 | # Default mtime usage (true) 69 | print('-' * 72) 70 | with drake.Drake(wd) as d: 71 | with_mtime() 72 | 73 | # Touch source file 74 | with open('unhashable', 'w') as f: 75 | print('foo', file = f) 76 | 77 | # Without mtime, should rehash 78 | print('-' * 72) 79 | with drake.Drake(wd, use_mtime = False) as d: 80 | without_mtime() 81 | 82 | # With mtime, should rehash and touch dest file 83 | print('-' * 72) 84 | with drake.Drake(wd, adjust_mtime_future = True) as d: 85 | without_mtime() 86 | 87 | # With mtime, should now be stable and not rehash 88 | print('-' * 72) 89 | with drake.Drake(wd, use_mtime = True) as d: 90 | with_mtime() 91 | -------------------------------------------------------------------------------- /tests/base/no-builder-to-make/test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- python -*- 3 | 4 | import drake 5 | 6 | with drake.Drake(): 7 | source = drake.node('drake.node') 8 | source.path().remove() 9 | target = drake.copy(source, 'output') 10 | target.path().remove() 11 | try: 12 | target.build() 13 | except drake.NoBuilder: 14 | pass 15 | -------------------------------------------------------------------------------- /tests/base/obsolete-path-cache: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | '''Check that the path of a built node is updated to the build tree. 4 | 5 | ''' 6 | 7 | import drake 8 | from utils import * 9 | 10 | with Drake(root = 'src') as d: 11 | target = drake.node('target') 12 | print(target.path()) 13 | assertEq(target.path(), 'src/target') 14 | drake.TouchBuilder(target) 15 | assertEq(target.path(), 'target') 16 | -------------------------------------------------------------------------------- /tests/base/path/drakefile: -------------------------------------------------------------------------------- 1 | import drake 2 | 3 | def configure(): 4 | drake.include('sub') 5 | -------------------------------------------------------------------------------- /tests/base/path/sub/drakefile: -------------------------------------------------------------------------------- 1 | import drake 2 | 3 | class TouchBuilder(drake.Builder): 4 | 5 | def __init__(self, target): 6 | self.__target = target 7 | drake.Builder.__init__(self, [], [target]) 8 | 9 | def execute(self): 10 | return self.cmd('Touch %s' % self.targets()[0], ['touch', self.targets()[0].path()]) 11 | 12 | def configure(): 13 | n = drake.node('foo') 14 | assert n.name_relative == 'foo' 15 | assert n.name() == 'sub/foo' 16 | TouchBuilder(n) 17 | assert n.path() == 'sub/foo' 18 | assert drake.path_build('bar') == 'sub/bar' 19 | assert drake.path_build('/bar') == '/bar' 20 | -------------------------------------------------------------------------------- /tests/base/path/test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import drake 4 | import sys 5 | 6 | sys.argv += ['--clean'] 7 | 8 | drake.run('.') 9 | -------------------------------------------------------------------------------- /tests/base/range: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | '''Test drake.Range.''' 4 | 5 | from drake import Range 6 | import utils 7 | 8 | # 9 | # Comparisons. 10 | # 11 | 12 | # Equality. 13 | utils.assertEq(Range(1, 10), Range(1, 10)) 14 | utils.assertEq(Range(10, 10), Range(10)) 15 | utils.assertNotEq(Range(10), Range(11)) 16 | utils.assertNotEq(Range(1, 10), Range(10, 10)) 17 | utils.assertNotEq(Range(10, 11), Range(10, 10)) 18 | # Gt. 19 | utils.assertGt(Range(10), Range(9)) 20 | utils.assertGt(Range(10), Range(9, 200)) 21 | utils.assertNotGt(Range(10), Range(10)) 22 | utils.assertNotGt(Range(10), Range(11)) 23 | # Ge 24 | utils.assertGe(Range(10), Range(10)) 25 | utils.assertGe(Range(10, 12), Range(10, 13)) 26 | utils.assertGe(Range(10, 12), Range(10, 11)) 27 | utils.assertNotGe(Range(9), Range(10)) 28 | # Lt 29 | utils.assertLt(Range(10), Range(11)) 30 | utils.assertLt(Range(10), Range(11, 200)) 31 | utils.assertNotLt(Range(10), Range(10)) 32 | utils.assertNotLt(Range(11), Range(10)) 33 | # Le 34 | utils.assertLe(Range(10), Range(10)) 35 | utils.assertLe(Range(10, 13), Range(10, 12)) 36 | utils.assertLe(Range(10, 11), Range(10, 12)) 37 | utils.assertNotLe(Range(10), Range(9)) 38 | 39 | # 40 | # Inclusion. 41 | # 42 | 43 | # Value. 44 | utils.assertIn(10, Range(10)) 45 | utils.assertNotIn(10, Range(9)) 46 | utils.assertNotIn(10, Range(11)) 47 | utils.assertIn(10, Range(10, 100)) 48 | utils.assertIn(100, Range(10, 100)) 49 | utils.assertNotIn(9, Range(10, 100)) 50 | utils.assertNotIn(101, Range(10, 100)) 51 | # Range. 52 | utils.assertIn(Range(9), Range(9)) 53 | utils.assertNotIn(Range(9), Range(10)) 54 | utils.assertIn(Range(9), Range(8, 10)) 55 | utils.assertIn(Range(8), Range(8, 10)) 56 | utils.assertIn(Range(10), Range(8, 10)) 57 | utils.assertNotIn(Range(7), Range(8, 10)) 58 | utils.assertNotIn(Range(11), Range(8, 10)) 59 | utils.assertIn(Range(10, 12), Range(10, 12)) 60 | utils.assertIn(Range(10, 12), Range(10, 13)) 61 | utils.assertIn(Range(10, 12), Range(9, 12)) 62 | utils.assertNotIn(Range(10, 12), Range(10, 11)) 63 | utils.assertNotIn(Range(9, 11), Range(10, 11)) 64 | 65 | # 66 | # Printers. 67 | # 68 | 69 | # str. 70 | utils.assertEq(str(Range(10)), '10') 71 | utils.assertEq(str(Range(10, 10)), '10') 72 | utils.assertEq(str(Range(10, 12)), '[10, 12]') 73 | 74 | # repr. 75 | utils.assertEq(repr(Range(10)), 'Range(10)') 76 | utils.assertEq(repr(Range(10, 10)), 'Range(10)') 77 | utils.assertEq(repr(Range(10, 12)), 'Range(10, 12)') 78 | -------------------------------------------------------------------------------- /tests/base/runner-env: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | '''Check Runner.env works, check global environment is preserved, 4 | check builder is re-run if the environment changes.''' 5 | 6 | import drake 7 | import stat 8 | import tempfile 9 | 10 | from utils import * 11 | 12 | class CommandBuilder(drake.Builder): 13 | 14 | def __init__(self, exe, target): 15 | super().__init__([exe], [target]) 16 | self.__exe = exe 17 | self.__target = target 18 | 19 | def execute(self): 20 | self.cmd(pretty = 'Execute {}'.format(self.__exe), 21 | cmd = ['./{}'.format(self.__exe)], 22 | env = {'BAZ': 'quux'}, throw = True) 23 | self.__target.path().touch() 24 | return True 25 | 26 | os.environ.update({'FOO': 'bar'}) 27 | os.environ.update({'BAZ': 'nope'}) 28 | 29 | def configure(env): 30 | exe = drake.node('test') 31 | drake.WriteBuilder( 32 | '''#!/bin/sh -e 33 | test $FOO = bar 34 | test $BAZ = quux''', 35 | exe, 36 | permissions = stat.S_IXUSR) 37 | r = drake.Runner(exe, env = env) 38 | return exe, r 39 | 40 | with tempfile.TemporaryDirectory() as wd: 41 | 42 | with Drake(wd) as d: 43 | exe, r = configure({'BAZ': 'quux'}) 44 | r.status.build() 45 | beacon = drake.node('beacon') 46 | r = CommandBuilder(exe, beacon) 47 | beacon.build() 48 | 49 | with Drake(wd) as d: 50 | exe, r = configure({'BAZ': 'delta charlie delta'}) 51 | try: 52 | r.status.build() 53 | except drake.Builder.Failed: 54 | pass 55 | else: 56 | raise Exception('builder should have rerun and failed') 57 | -------------------------------------------------------------------------------- /tests/base/runner/drakefile: -------------------------------------------------------------------------------- 1 | import drake 2 | import drake.cxx 3 | import sys 4 | 5 | def configure(): 6 | tk = drake.cxx.Toolkit() 7 | cfg = drake.cxx.Config() 8 | cfg.lib_path_runtime('.') 9 | lib = drake.cxx.DynLib('lib', drake.nodes('lib.cc'), tk, cfg) 10 | exe = drake.cxx.Executable('main', [lib, drake.node('main.cc')], tk, cfg) 11 | runner = drake.Runner(exe) 12 | assert lib in runner.sources().values() 13 | -------------------------------------------------------------------------------- /tests/base/runner/main.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int answer(); 4 | 5 | int main() 6 | { 7 | std::cout << "out " << answer() << std::endl; 8 | } 9 | -------------------------------------------------------------------------------- /tests/base/runner/test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- python -*- 3 | 4 | import drake 5 | import drake.cxx 6 | import subprocess 7 | import sys 8 | 9 | with drake.Drake(): 10 | with open('lib.cc', 'w') as f: 11 | print('''\ 12 | int answer() 13 | { 14 | return 42; 15 | } 16 | ''', file = f) 17 | 18 | drake.run('.') 19 | drake.reset() 20 | 21 | assert subprocess.check_output(['./main']).strip() == b'out 42' 22 | 23 | with open('lib.cc', 'w') as f: 24 | print('''\ 25 | int answer() 26 | { 27 | return 51; 28 | } 29 | ''', file = f) 30 | 31 | drake.run('.') 32 | 33 | assert subprocess.check_output(['./main']).strip() == b'out 51' 34 | -------------------------------------------------------------------------------- /tests/base/symlink: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | '''Check symlinks creation.''' 4 | 5 | import drake 6 | from utils import * 7 | 8 | with Drake() as d: 9 | source = drake.write('content', 'source') 10 | s = drake.Symlink('link', source) 11 | ss = drake.Symlink('sub/link', source) 12 | s.build() 13 | assertEq(os.readlink('link'), 'source') 14 | ss.build() 15 | assertEq(os.readlink('sub/link'), '../source') 16 | c = drake.copy(s, 'copied') 17 | c.build() 18 | assert not os.path.islink('copied/link') 19 | with open('copied/link', 'r') as f: 20 | assertEq(f.read(), 'content') 21 | sc = drake.copy(s, 'scopied', follow_symlinks = False) 22 | sc.build() 23 | assert os.path.islink('scopied/link') 24 | assertEq(os.readlink('scopied/link'), 'source') 25 | 26 | with Drake() as d: 27 | foo = drake.write('foo', 'dir/foo') 28 | bar = drake.write('foo', 'dir/bar') 29 | copies = drake.symlink([foo, bar], 'cdir') 30 | map(lambda n: n.build(), copies) 31 | -------------------------------------------------------------------------------- /tests/base/termination: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | '''Check an interrupted build is not considered to have run 4 | successfully later.''' 5 | 6 | import drake 7 | import drake.sched 8 | 9 | from utils import * 10 | 11 | sem1 = drake.sched.Semaphore(0) 12 | sem2 = drake.sched.Semaphore(0) 13 | sem3 = drake.sched.Semaphore(0) 14 | 15 | # Build root 16 | # Build shield 17 | # Build failed (blocks on sem1) [1] 18 | # Build delayed (blocks on sem3) [2] 19 | # Build interrupted (unblocks sem1, blocks on sem3) [3] 20 | # Build failed: failure [4] 21 | # Build delayed: cleanup is delayed (blocks on sem3) 22 | # Build interrupted: build is terminated. This is where the bug 23 | # used to lie: the builder was considered successful. [5] 24 | # Build intermediate: since we are still unwinding thank to 25 | # delayed. The bug was triggered here: interrupted was considered 26 | # built and the copy was launched on a missing file. [6] 27 | 28 | class FailBuilder(drake.Builder): 29 | 30 | def execute(self): 31 | sem1.lock() # [1] 32 | return False # [4] 33 | 34 | class DelayBuilder(drake.Builder): 35 | 36 | def execute(self): 37 | try: 38 | sem3.lock() # [2] 39 | except: 40 | sem3.lock() # [5] 41 | raise 42 | raise Exception('should never get there') 43 | 44 | class InterruptedBuilder(drake.Builder): 45 | 46 | def execute(self): 47 | sem1.unlock() # [3] 48 | try: 49 | sem2.lock() # [3] 50 | except: 51 | raise 52 | raise Exception('should never get there') 53 | 54 | beacon = True 55 | class UnblockBuilder(drake.Copy): 56 | 57 | def run(self): 58 | sem3.unlock() 59 | super().run() 60 | 61 | def execute(self): 62 | global beacon 63 | beacon = False # [6] 64 | raise Exception('should never get there') 65 | 66 | with Drake(jobs = 2, kill_builders_on_failure = True) as d: 67 | failed = drake.node('failed') 68 | failer = FailBuilder([], [failed]) 69 | interrupted = drake.node('interrupted') 70 | interrupter = InterruptedBuilder([], [interrupted]) 71 | delayed = drake.node('delayed') 72 | delayer = DelayBuilder([], [delayed]) 73 | shield = drake.node('shield') 74 | TouchBuilder([failed, delayed, interrupted], [shield]) 75 | intermediate = drake.copy([interrupted], 'intermediate', 76 | builder = UnblockBuilder)[0] 77 | root = drake.node('root') 78 | TouchBuilder([shield, intermediate], [root]) 79 | try: 80 | root.build() 81 | except drake.Builder.Failed as e: 82 | assert beacon 83 | assert e.builder is failer 84 | else: 85 | raise Exception('build should have failed') 86 | -------------------------------------------------------------------------------- /tests/base/termination-keep-successful: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | '''Check an interrupted build is not considered to have run 4 | successfully later.''' 5 | 6 | import drake 7 | import drake.sched 8 | 9 | from utils import * 10 | 11 | class FailBuilder(drake.Builder): 12 | 13 | def execute(self): 14 | self._run_job(lambda: False) 15 | 16 | beacon = False 17 | class SuccessBuilder(TouchBuilder): 18 | 19 | def execute(self): 20 | global beacon 21 | try: 22 | if self._run_job(lambda: TouchBuilder.execute(self)): 23 | beacon = True 24 | return True 25 | except: 26 | print('bye') 27 | raise 28 | 29 | with Drake(jobs = 2) as d: 30 | failed = drake.node('failed') 31 | failer = FailBuilder([], [failed]) 32 | built = drake.node('built') 33 | builder = SuccessBuilder([], [built]) 34 | root = drake.node('root') 35 | TouchBuilder([failed, built], [root]) 36 | try: 37 | root.build() 38 | except drake.Builder.Failed as e: 39 | assert e.builder is failer 40 | assert os.path.exists('built') 41 | assert beacon 42 | else: 43 | raise Exception('build should have failed') 44 | -------------------------------------------------------------------------------- /tests/base/termination-runaway: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | '''Check builders stop being launched when a failure occured.''' 4 | 5 | import drake 6 | import drake.sched 7 | 8 | from utils import * 9 | 10 | sem = drake.sched.Semaphore(0) 11 | 12 | class DelayBuilder(drake.Builder): 13 | 14 | def execute(self): 15 | try: 16 | sem.lock() 17 | except: 18 | sem.lock() 19 | raise 20 | raise Exception('should never get there') 21 | 22 | class ForbiddenBuilder(drake.Builder): 23 | 24 | def run(self): 25 | sem.unlock() 26 | return super().run() 27 | 28 | def execute(self): 29 | assert False 30 | 31 | with Drake(jobs = 2) as d: 32 | failed = drake.node('failed') 33 | failer = FailBuilder([], [failed]) 34 | delayed = drake.node('delayed') 35 | delayer = DelayBuilder([], [delayed]) 36 | intermediate = drake.node('intermediate') 37 | TouchBuilder([delayed, failed], [intermediate]) 38 | forbidden = drake.node('forbidden') 39 | ForbiddenBuilder([], [forbidden]) 40 | root = drake.node('root') 41 | TouchBuilder([intermediate, forbidden], [root]) 42 | try: 43 | root.build() 44 | except drake.Builder.Failed as e: 45 | assert e.builder is failer 46 | else: 47 | raise Exception('build should have failed') 48 | -------------------------------------------------------------------------------- /tests/base/version: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | '''Test drake.Version and drake.version.VersionGenerator.''' 4 | 5 | import drake 6 | import drake.version 7 | import drake.git 8 | import utils 9 | 10 | # 11 | # Version. 12 | # 13 | 14 | def check_version(v, major, minor = None, subminor = None, repr = None): 15 | utils.assertEq(v.major, major) 16 | if minor: 17 | utils.assertEq(v.minor, minor) 18 | if subminor: 19 | utils.assertEq(v.subminor, subminor) 20 | if repr: 21 | utils.assertEq(str(v), repr) 22 | 23 | check_version(drake.Version(0), 0, repr = '0') 24 | check_version(drake.Version(1), 1, repr = '1') 25 | check_version(drake.Version(1, 0), 1, 0, repr = '1.0') 26 | check_version(drake.Version(1, 1), 1, 1, repr = '1.1') 27 | check_version(drake.Version(1, 1, 0), 1, 1, 0, repr = '1.1.0') 28 | check_version(drake.Version(1, 1, 1), 1, 1, 1, repr = '1.1.1') 29 | 30 | # XXX: Implement <, <=, =>, >. 31 | 32 | # 33 | # Version Generator. 34 | # 35 | 36 | class FakeGit(drake.VirtualNode): 37 | 38 | def __init__(self, version, revision = ''): 39 | super().__init__('%s%s' % (version, revision) + '/fakegit') 40 | self.__version = version 41 | self.__revision = revision 42 | 43 | def description(self): 44 | return '-'.join([self.__version, self.__revision]) 45 | 46 | def version(self): 47 | return self.description() 48 | 49 | # NotImplementedError is thrown if VersionGenerator._variable is not 50 | # redefined. 51 | with utils.Drake() as d: 52 | 53 | impossible = drake.node('impossible') 54 | drake.version.VersionGenerator(git = FakeGit('0.0.1', ''), 55 | output = impossible) 56 | try: 57 | impossible.build() 58 | assert False 59 | except drake.Builder.Failed: 60 | pass 61 | 62 | # Test expectations. 63 | class DummyVersionGenerator(drake.version.VersionGenerator): 64 | 65 | def _variable(self, name, value): 66 | return '%s%s' % (name, value) 67 | 68 | with utils.Drake() as d: 69 | for version, revision in [ 70 | ('0.0.0', ''), 71 | ('0.0.1', '1-gea5154b') 72 | ]: 73 | fake_git = FakeGit(version, revision) 74 | output = drake.node('%s-%s' % (version, revision)) 75 | gen = DummyVersionGenerator(git = fake_git, 76 | output = output) 77 | output.build() 78 | import os 79 | assert os.path.exists(str(output)) 80 | with open(str(output)) as version_file: 81 | utils.assertEq(version_file.read(), 82 | '''version%s 83 | major%s 84 | minor%s 85 | subminor%s 86 | ''' % ('-'.join([version, revision]), *version.split('.'))) 87 | -------------------------------------------------------------------------------- /tests/cxx/boost/deps/include-version/include/boost-1_53/boost/version.hpp: -------------------------------------------------------------------------------- 1 | #ifndef VERSION_HPP 2 | # define VERSION_HPP 3 | 4 | # define BOOST_VERSION 105300 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /tests/cxx/boost/deps/no-include/include/version.hpp: -------------------------------------------------------------------------------- 1 | #ifndef VERSION_HPP 2 | # define VERSION_HPP 3 | 4 | # define BOOST_VERSION 105300 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /tests/cxx/boost/deps/no-version/include/boost/version.hpp: -------------------------------------------------------------------------------- 1 | #ifndef VERSION_HPP 2 | # define VERSION_HPP 3 | 4 | #endif 5 | -------------------------------------------------------------------------------- /tests/cxx/boost/deps/simplest/include/boost/version.hpp: -------------------------------------------------------------------------------- 1 | #ifndef VERSION_HPP 2 | # define VERSION_HPP 3 | 4 | # define BOOST_VERSION 105300 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /tests/cxx/boost/test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- python -*- 3 | 4 | import drake 5 | import drake.cxx 6 | import drake.cxx.boost 7 | 8 | def test(path, expect = True): 9 | with drake.Drake(): 10 | if expect: 11 | drake.cxx.boost.Boost(prefix = path) 12 | else: 13 | try: 14 | drake.cxx.boost.Boost(prefix = path) 15 | except: 16 | pass 17 | else: 18 | raise Exception('Boost shall not be found in %s' % path) 19 | 20 | # Simplest case 21 | test('deps/simplest') 22 | # Inexistent directory 23 | test('deps/inexistent', False) 24 | # Missing boost include dir 25 | test('deps/no-include', False) 26 | # Invalid version file 27 | test('deps/no-version', False) 28 | # Include files in a boost-$version subdirectory. 29 | test('deps/include-version') 30 | -------------------------------------------------------------------------------- /tests/cxx/chained-static-libraries: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import drake 4 | import drake.cxx 5 | 6 | from functools import partial 7 | from utils import * 8 | 9 | def check_files(folder, expected): 10 | import os 11 | s = set() 12 | for path, folders, files in os.walk(str(folder)): 13 | for f in files: 14 | s.add(path + '/' + f) 15 | assertEq(s, expected) 16 | 17 | with Drake() as d: 18 | 19 | with open('a.cc', 'w') as f: 20 | f.write('int a() { return 1; }') 21 | with open('b.cc', 'w') as f: 22 | f.write('int a(); int b() { return a() + 1; }') 23 | with open('main.cc', 'w') as f: 24 | f.write('int b(); int main() { return b() == 2; }') 25 | cxx_config = drake.cxx.Config() 26 | cxx_toolkit = drake.cxx.Toolkit() 27 | 28 | 29 | StaticLib, Executable = ( 30 | partial(f, tk = cxx_toolkit, cfg = cxx_config) 31 | for f in [drake.cxx.StaticLib, drake.cxx.Executable]) 32 | a = StaticLib('a', drake.nodes('a.cc')) 33 | b = StaticLib('b', [drake.node('b.cc'), a]) 34 | exe = Executable('exe', [drake.node('main.cc'), b]) 35 | exe.build() 36 | -------------------------------------------------------------------------------- /tests/cxx/copied-libraries: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import drake 4 | import drake.cxx 5 | 6 | from functools import partial 7 | from utils import * 8 | 9 | def check_files(folder, expected): 10 | import os 11 | s = set() 12 | for path, folders, files in os.walk(str(folder)): 13 | for f in files: 14 | s.add(path + '/' + f) 15 | assertEq(s, expected) 16 | 17 | with Drake() as d: 18 | 19 | for name in ['dyn', 'sta']: 20 | with open('{}.cc'.format(name), 'w') as f: 21 | f.write('#include "{}.hh"\n void {}() {{}}'.format(name, name)) 22 | with open('{}.hh'.format(name), 'w') as f: 23 | f.write('void {}();'.format(name)) 24 | with open('lib.cc', 'w') as f: 25 | f.write('''#include "dyn.hh" 26 | #include "sta.hh" 27 | 28 | void 29 | lib() 30 | { 31 | dyn(); 32 | sta(); 33 | }''') 34 | 35 | cxx_config = drake.cxx.Config() 36 | cxx_toolkit = drake.cxx.Toolkit() 37 | 38 | DynLib = partial(drake.cxx.DynLib, 39 | tk = cxx_toolkit, 40 | cfg = cxx_config) 41 | 42 | StaticLib = partial(drake.cxx.StaticLib, 43 | tk = cxx_toolkit, 44 | cfg = cxx_config) 45 | 46 | copy = partial(drake.copy, strip_prefix = True) 47 | 48 | # Build two libraries, one static, one dynamic. 49 | dyn = DynLib('libraries/dyn', drake.nodes('dyn.cc', 50 | 'dyn.hh')) 51 | sta = StaticLib('libraries/sta', drake.nodes('sta.cc', 52 | 'sta.hh')) 53 | 54 | # Build two libaries, one static, one dynamic which both depend on the 55 | # matching previously built libraries. 56 | lib_dynamic = DynLib('libraries/lib', 57 | drake.nodes('lib.cc', ) + [dyn]) 58 | lib_static = StaticLib('libraries/lib', 59 | drake.nodes('lib.cc', ) + [sta]) 60 | 61 | # Copy those libraries. 62 | copied_dyn = copy(lib_dynamic, 'dynamic') 63 | copied_dyn.build() 64 | copied_sta = copy(lib_static, 'static') 65 | copied_sta.build() 66 | 67 | # Check copied files. 68 | check_files(copied_dyn.path().dirname(), 69 | { 70 | 'dynamic/' + str(cxx_toolkit.libname_dyn('dyn').basename()), 71 | 'dynamic/' + str(cxx_toolkit.libname_dyn('lib').basename()), 72 | }) 73 | check_files(copied_sta.path().dirname(), 74 | { 75 | 'static/' + str( 76 | cxx_toolkit.libname_static(cxx_config, 'lib').basename() 77 | ), 78 | }) 79 | -------------------------------------------------------------------------------- /tests/cxx/cyclic-dependencies/branch-1.hh: -------------------------------------------------------------------------------- 1 | #include "branch-2.hh" 2 | -------------------------------------------------------------------------------- /tests/cxx/cyclic-dependencies/branch-2.hh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mefyl/drake/9f1c3a3f0a96e9efa35afaa5066830731aefaaeb/tests/cxx/cyclic-dependencies/branch-2.hh -------------------------------------------------------------------------------- /tests/cxx/cyclic-dependencies/one.hh: -------------------------------------------------------------------------------- 1 | #include "two.hh" 2 | -------------------------------------------------------------------------------- /tests/cxx/cyclic-dependencies/root-1.hh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mefyl/drake/9f1c3a3f0a96e9efa35afaa5066830731aefaaeb/tests/cxx/cyclic-dependencies/root-1.hh -------------------------------------------------------------------------------- /tests/cxx/cyclic-dependencies/root.hh: -------------------------------------------------------------------------------- 1 | #include "one.hh" 2 | #include "root-1.hh" 3 | -------------------------------------------------------------------------------- /tests/cxx/cyclic-dependencies/test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- python -*- 3 | 4 | import drake, drake.cxx, drake.cxx.qt, subprocess, sys 5 | 6 | # +-------+ +--------+ 7 | # |root.hh|+----->|root1.hh| 8 | # +-------+ +--------+ 9 | # + 10 | # | 11 | # | 12 | # v 13 | # +-----------+ +-------+ 14 | # |branch-2.hh| |one.hh |<---------+ 15 | # +-----------+ +-------+ | 16 | # ^ + | 17 | # | | | 18 | # | | | 19 | # + v + 20 | # +-----------+ +-------+ +--------+ 21 | # |branch-1.hh|<---+|two.hh |+---->|three.hh| 22 | # +-----------+ +-------+ +--------+ 23 | 24 | with drake.Drake(): 25 | tk = drake.cxx.Toolkit() 26 | cfg = drake.cxx.Config() 27 | root = drake.node('root.hh') 28 | root1 = drake.node('root-1.hh') 29 | one = drake.node('one.hh') 30 | two = drake.node('two.hh') 31 | three = drake.node('three.hh') 32 | b1 = drake.node('branch-1.hh') 33 | b2 = drake.node('branch-2.hh') 34 | def deps(n): 35 | return set(node for node, user in drake.cxx.inclusion_dependencies(n, tk, cfg)) 36 | assert deps(root) == set((root, root1, one, two, three, b1, b2)) 37 | assert deps(root1) == set((root1,)) 38 | assert deps(one) == set((one, two, three, b1, b2)) 39 | assert deps(two) == set((one, two, three, b1, b2)) 40 | assert deps(three) == set((one, two, three, b1, b2)) 41 | assert deps(b1) == set((b1, b2)) 42 | assert deps(b2) == set((b2,)) 43 | -------------------------------------------------------------------------------- /tests/cxx/cyclic-dependencies/three.hh: -------------------------------------------------------------------------------- 1 | #include "one.hh" 2 | -------------------------------------------------------------------------------- /tests/cxx/cyclic-dependencies/two.hh: -------------------------------------------------------------------------------- 1 | #include "branch-1.hh" 2 | #include "three.hh" 3 | -------------------------------------------------------------------------------- /tests/cxx/dependency-directory-clash/drakefile: -------------------------------------------------------------------------------- 1 | import drake 2 | import drake.cxx 3 | 4 | def configure(): 5 | 6 | cfg = drake.cxx.Config() 7 | cfg.add_local_include_path('.') 8 | sources = drake.nodes('foo.cc') 9 | foo = drake.cxx.Executable('foo', sources, drake.cxx.Toolkit(), cfg) 10 | -------------------------------------------------------------------------------- /tests/cxx/dependency-directory-clash/foo.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() 4 | {} 5 | 6 | -------------------------------------------------------------------------------- /tests/cxx/dependency-directory-clash/test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import drake 4 | 5 | drake.run('.') 6 | -------------------------------------------------------------------------------- /tests/cxx/find_library/_build/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mefyl/drake/9f1c3a3f0a96e9efa35afaa5066830731aefaaeb/tests/cxx/find_library/_build/.keep -------------------------------------------------------------------------------- /tests/cxx/find_library/deps/include/somelib/somelib.hh: -------------------------------------------------------------------------------- 1 | #ifndef SOMELIB_HH 2 | # define SOMELIB_HH 3 | 4 | 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /tests/cxx/find_library/drakefile: -------------------------------------------------------------------------------- 1 | import drake 2 | import drake.cxx 3 | 4 | def configure(): 5 | 6 | cfg = drake.cxx.LibraryConfiguration( 7 | 'somelib/somelib.hh', 8 | prefix = 'deps', 9 | ) 10 | 11 | assert 'deps/include' in cfg.config(drake.cxx.Toolkit()).system_include_path 12 | -------------------------------------------------------------------------------- /tests/cxx/find_library/test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- python -*- 3 | 4 | import drake 5 | import drake.cxx 6 | import os 7 | 8 | try: 9 | os.chdir('_build') 10 | drake.run('..') 11 | except drake.Exception as e: 12 | print('%s: %s' % (sys.argv[0], e), file = sys.stderr) 13 | exit(1) 14 | -------------------------------------------------------------------------------- /tests/cxx/generated-headers-deps/drakefile: -------------------------------------------------------------------------------- 1 | import drake 2 | import drake.cxx 3 | 4 | def configure(): 5 | generated = drake.node('include/gen/generated.hh') 6 | drake.TouchBuilder(generated) 7 | cxx_config = drake.cxx.Config() 8 | cxx_config.add_local_include_path('include') 9 | src = drake.node('main.cc') 10 | obj = drake.node('main.o') 11 | drake.cxx.Compiler(src, obj, drake.cxx.Toolkit(), cxx_config) 12 | -------------------------------------------------------------------------------- /tests/cxx/generated-headers-deps/main.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() 4 | {} 5 | -------------------------------------------------------------------------------- /tests/cxx/generated-headers-deps/test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- python -*- 3 | 4 | import drake 5 | import drake.cxx 6 | import os.path 7 | import sys 8 | 9 | with drake.Drake('.') as d: 10 | sys.argv = [sys.argv[0], 'main.o'] 11 | d.run() 12 | 13 | assert os.path.exists('include/gen/generated.hh') 14 | -------------------------------------------------------------------------------- /tests/cxx/headers-deps/drakefile: -------------------------------------------------------------------------------- 1 | import drake, drake.cxx 2 | 3 | def configure(): 4 | cxx_toolkit = drake.cxx.Toolkit() 5 | cfg = drake.cxx.Config() 6 | sources = drake.nodes('main.cc', 'test.cc') 7 | executable = drake.cxx.Executable('main', sources, cxx_toolkit, cfg) 8 | -------------------------------------------------------------------------------- /tests/cxx/headers-deps/main.cc: -------------------------------------------------------------------------------- 1 | void test(); 2 | 3 | int main() 4 | { 5 | test(); 6 | } 7 | -------------------------------------------------------------------------------- /tests/cxx/headers-deps/test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- python -*- 3 | 4 | import drake, drake.cxx, drake.cxx.qt, subprocess, sys 5 | 6 | with drake.Drake(): 7 | sys.argv = ['main'] 8 | drake.run('.') 9 | assert subprocess.check_output(['./main']) == b'test!\n' 10 | 11 | with drake.Drake(): 12 | with open('test.hh', 'w') as f: 13 | print('#define MESSAGE "updated"', file = f) 14 | drake.run('.') 15 | assert subprocess.check_output(['./main']) == b'updated!\n' 16 | -------------------------------------------------------------------------------- /tests/cxx/headers-deps/test.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "test.hh" 4 | 5 | void test() 6 | { 7 | std::cout << MESSAGE << "!" << std::endl; 8 | } 9 | -------------------------------------------------------------------------------- /tests/cxx/headers-deps/test.hh: -------------------------------------------------------------------------------- 1 | #ifndef TEST_HH 2 | # define TEST_HH 3 | 4 | # define MESSAGE "test" 5 | 6 | void test(); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /tests/cxx/pkg-config/drakefile: -------------------------------------------------------------------------------- 1 | import drake 2 | import drake.cxx 3 | import os 4 | 5 | os.environ['PKG_CONFIG_PATH'] = os.getcwd() 6 | 7 | def configure(): 8 | 9 | cfg = drake.cxx.LibraryConfiguration(name = 'somelib') 10 | assert '/beacon/include' in cfg.config().system_include_path 11 | assert '/beacon/lib' in cfg.config().library_path 12 | -------------------------------------------------------------------------------- /tests/cxx/pkg-config/somelib.pc: -------------------------------------------------------------------------------- 1 | Name: somelib 2 | Description: Some library for testing purpose. 3 | Version: 1.2.3 4 | Libs: -L/beacon/lib 5 | Cflags: -I/beacon/include 6 | -------------------------------------------------------------------------------- /tests/cxx/pkg-config/test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- python -*- 3 | 4 | import drake 5 | import drake.cxx 6 | 7 | try: 8 | drake.run('.') 9 | except drake.Exception as e: 10 | print('%s: %s' % (sys.argv[0], e), file = sys.stderr) 11 | exit(1) 12 | -------------------------------------------------------------------------------- /tests/cxx/qt/moc/drakefile: -------------------------------------------------------------------------------- 1 | import drake, drake.cxx, drake.cxx.qt 2 | 3 | def configure(cxx_toolkit = None, qt = None): 4 | if cxx_toolkit is None: 5 | cxx_toolkit = drake.cxx.Toolkit() 6 | if qt is None: 7 | qt = drake.cxx.qt.Qt(cxx_toolkit) 8 | cfg = drake.cxx.Config() 9 | cfg += qt.config() 10 | cfg += qt.config_core() 11 | qt.plug(cxx_toolkit) 12 | sources = drake.nodes('main.cc', 'widget.cc') 13 | executable = drake.cxx.Executable('main', sources, cxx_toolkit, cfg) 14 | -------------------------------------------------------------------------------- /tests/cxx/qt/moc/main.cc: -------------------------------------------------------------------------------- 1 | #include "widget.hh" 2 | 3 | int main() 4 | { 5 | Widget w; 6 | } 7 | -------------------------------------------------------------------------------- /tests/cxx/qt/moc/test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- python -*- 3 | 4 | import drake 5 | import os 6 | import sys 7 | 8 | with drake.Drake('.') as d: 9 | sys.argv = ['main'] 10 | d.run() 11 | 12 | with drake.Drake('.') as d: 13 | sys.argv = ['main'] 14 | d.run() 15 | 16 | os.remove('main') 17 | with drake.Drake('.') as d: 18 | sys.argv = ['main'] 19 | d.run() 20 | -------------------------------------------------------------------------------- /tests/cxx/qt/moc/test.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "test.hh" 4 | 5 | void test() 6 | { 7 | std::cout << MESSAGE << "!" << std::endl; 8 | } 9 | -------------------------------------------------------------------------------- /tests/cxx/qt/moc/test.hh: -------------------------------------------------------------------------------- 1 | #ifndef TEST_HH 2 | # define TEST_HH 3 | 4 | # define MESSAGE "test" 5 | 6 | void test(); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /tests/cxx/qt/moc/widget.cc: -------------------------------------------------------------------------------- 1 | #include "widget.hh" 2 | 3 | Widget::Widget() 4 | { 5 | 6 | } 7 | -------------------------------------------------------------------------------- /tests/cxx/qt/moc/widget.hh: -------------------------------------------------------------------------------- 1 | #ifndef WIDGET_HH 2 | # define WIDGET_HH 3 | 4 | # include 5 | 6 | class Widget: 7 | public QObject 8 | { 9 | public: 10 | Widget(); 11 | 12 | private: 13 | Q_OBJECT 14 | }; 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /tests/doctest: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import doctest 4 | import os 5 | import os.path 6 | import sys 7 | import unittest 8 | 9 | # Ignore DRAKE_RAW from the test environment as it will fail the docstring outputs. 10 | os.environ.pop('DRAKE_RAW', None) 11 | 12 | import drake 13 | import drake.cxx 14 | import drake.cxx.boost 15 | import drake.git 16 | import drake.go 17 | import drake.python 18 | import drake.utils 19 | import sched 20 | 21 | 22 | def test_suite(): 23 | tests = [] 24 | 25 | instance = [None] 26 | def setup(test): 27 | instance[0] = drake.Drake() 28 | instance[0].__enter__() 29 | 30 | def teardown(test): 31 | instance[0].__exit__(None, None, None) 32 | instance[0] = None 33 | 34 | tests = [doctest.DocTestSuite(module = m, 35 | setUp = setup, tearDown = teardown) 36 | for m in [ 37 | drake, 38 | drake.cxx, 39 | drake.cxx.boost, 40 | drake.git, 41 | drake.go, 42 | drake.python, 43 | drake.utils, 44 | sched, 45 | ]] 46 | return unittest.TestSuite(tests) 47 | 48 | 49 | # The test suite does not expect the time reports: 50 | # 51 | # Expected: 52 | # Touch /tmp/.drake.polish 53 | # Polishing. 54 | # Got: 55 | # Touch /tmp/.drake.polish 56 | # Finished TouchBuilder([/tmp/.drake.polish]) (0:00:00) 57 | # Polishing. 58 | os.environ['DRAKE_NO_TIME_REPORTS'] = '1' 59 | 60 | unittest.main(defaultTest='test_suite') 61 | -------------------------------------------------------------------------------- /tests/git/base: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | '''Check git command detection.''' 4 | 5 | import drake.git 6 | import os 7 | import sys 8 | 9 | from utils import * 10 | 11 | git = drake.git.GitCommand() 12 | v = drake.git.GitCommand(git).version 13 | assertEq(git.version, drake.git.GitCommand(git).version) 14 | 15 | assertExcept(lambda: drake.git.GitCommand('/bin/true')) 16 | 17 | with Drake() as d: 18 | def configure(g : drake.git.GitCommand = None): 19 | assertEq(g.version, v) 20 | d.configure = configure 21 | d.run() 22 | 23 | with Drake() as d: 24 | def configure(g : drake.git.GitCommand = None): 25 | assertEq(g, None) 26 | d.configure = configure 27 | d.run(g = False) 28 | 29 | os.environ['PATH'] = '' 30 | assertExcept(drake.git.GitCommand) 31 | -------------------------------------------------------------------------------- /tests/sched: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import unittest 4 | 5 | from drake import sched 6 | 7 | class BeaconException(Exception): 8 | pass 9 | 10 | class TestStandaloneCoroutine(unittest.TestCase): 11 | 12 | def setUp(self): 13 | self.beacon = 0 14 | 15 | def coroutine(self, n): 16 | self.beacon = n 17 | sched.coro_yield() 18 | self.beacon = n + 1 19 | 20 | def coroutine_meta(self, n): 21 | self.beacon = n 22 | sched.coro_yield() 23 | self.coroutine(n + 1) 24 | sched.coro_yield() 25 | self.beacon = n + 3 26 | 27 | def coroutine_wait(self, coro, n): 28 | sched.wait(coro) 29 | self.beacon = n 30 | 31 | def coroutine_raise(self): 32 | self.function_raise() 33 | 34 | def function_raise(self): 35 | raise BeaconException('exn') 36 | 37 | def coroutine_raise_meta(self): 38 | sched.coro_yield() 39 | self.coroutine_raise() 40 | sched.coro_yield() 41 | 42 | def test_one(self): 43 | self.c = sched.Coroutine(lambda: self.coroutine(1), 'coro') 44 | self.assertFalse(self.c.done) 45 | self.assertEqual(self.beacon, 0) 46 | self.c.step() 47 | self.assertFalse(self.c.done) 48 | self.assertEqual(self.beacon, 1) 49 | self.c.step() 50 | self.assertTrue(self.c.done) 51 | self.assertEqual(self.beacon, 2) 52 | with self.assertRaises(sched.CoroutineDone): 53 | self.c.step() 54 | 55 | def test_two(self): 56 | self.c1 = sched.Coroutine(lambda: self.coroutine(1), 'coro1') 57 | self.c2 = sched.Coroutine(lambda: self.coroutine(2), 'coro2') 58 | self.assertEqual(self.beacon, 0) 59 | self.c1.step() 60 | self.assertEqual(self.beacon, 1) 61 | self.c2.step() 62 | self.assertEqual(self.beacon, 2) 63 | self.c2.step() 64 | self.assertEqual(self.beacon, 3) 65 | self.c1.step() 66 | self.assertEqual(self.beacon, 2) 67 | with self.assertRaises(sched.CoroutineDone): 68 | self.c1.step() 69 | with self.assertRaises(sched.CoroutineDone): 70 | self.c2.step() 71 | 72 | def test_run(self): 73 | self.c = sched.Coroutine(lambda: self.coroutine(1), 'coro') 74 | self.assertFalse(self.c.done) 75 | self.assertEqual(self.beacon, 0) 76 | self.c.run() 77 | self.assertTrue(self.c.done) 78 | self.assertEqual(self.beacon, 2) 79 | with self.assertRaises(sched.CoroutineDone): 80 | self.c.step() 81 | 82 | def test_recursive(self): 83 | self.c = sched.Coroutine(lambda: self.coroutine_meta(1), 'coro') 84 | self.assertEqual(self.beacon, 0) 85 | self.c.step() 86 | self.assertEqual(self.beacon, 1) 87 | self.c.step() 88 | self.assertEqual(self.beacon, 2) 89 | self.c.step() 90 | self.assertEqual(self.beacon, 3) 91 | self.c.step() 92 | self.assertEqual(self.beacon, 4) 93 | with self.assertRaises(sched.CoroutineDone): 94 | self.c.step() 95 | 96 | def test_wait(self): 97 | self.c = sched.Coroutine(lambda: self.coroutine(1), 'coro') 98 | self.w = sched.Coroutine(lambda: self.coroutine_wait(self.c, 3), 'coro') 99 | self.assertFalse(self.c.done) 100 | self.assertFalse(self.w.done) 101 | self.assertFalse(self.w.frozen) 102 | self.w.step() 103 | self.assertTrue(self.w.frozen) 104 | with self.assertRaises(sched.CoroutineFrozen): 105 | self.w.step() 106 | self.assertEqual(self.beacon, 0) 107 | self.c.step() 108 | self.assertEqual(self.beacon, 1) 109 | self.assertTrue(self.w.frozen) 110 | with self.assertRaises(sched.CoroutineFrozen): 111 | self.w.step() 112 | self.c.step() 113 | self.assertEqual(self.beacon, 2) 114 | self.assertTrue(self.c.done) 115 | self.assertFalse(self.w.frozen) 116 | self.w.step() 117 | self.assertEqual(self.beacon, 3) 118 | self.assertTrue(self.w.done) 119 | 120 | def test_exception(self): 121 | self.r = sched.Coroutine(self.coroutine_raise, 'coro') 122 | with self.assertRaises(BeaconException): 123 | self.r.step() 124 | self.assertTrue(self.r.done) 125 | 126 | def test_exception_recurse(self): 127 | self.r = sched.Coroutine(lambda: self.coroutine_raise_meta(), 'coro') 128 | self.r.step() 129 | with self.assertRaises(BeaconException): 130 | self.r.step() 131 | self.assertTrue(self.r.done) 132 | 133 | 134 | class Sleep(sched.ThreadedOperation): 135 | def __init__(self, duration): 136 | sched.ThreadedOperation.__init__(self) 137 | self.__duration = duration 138 | 139 | def run(self): 140 | import time 141 | time.sleep(self.__duration) 142 | 143 | class TestScheduler(unittest.TestCase): 144 | 145 | def __init__(self, *args, **kwargs): 146 | unittest.TestCase.__init__(self, *args, **kwargs) 147 | 148 | def setUp(self): 149 | self.scheduler = sched.Scheduler() 150 | self.beacon1 = 0 151 | self.beacon2 = 0 152 | 153 | def coroutine1(self): 154 | self.beacon1 += 1 155 | sched.coro_yield() 156 | self.beacon1 += 1 157 | 158 | def coroutine2(self): 159 | self.beacon2 += 1 160 | sched.coro_yield() 161 | self.beacon2 += 1 162 | 163 | def coroutine_wait(self, w): 164 | #self.assertEqual(self.beacon1, 1) 165 | sched.Coroutine.current.wait(w) 166 | #self.assertEqual(self.beacon1, 2) 167 | 168 | def test_basic(self): 169 | sched.Coroutine(self.coroutine1, 'coro', self.scheduler) 170 | self.assertEqual(self.beacon1, 0) 171 | self.scheduler.run() 172 | self.assertEqual(self.beacon1, 2) 173 | 174 | def test_several(self): 175 | sched.Coroutine(self.coroutine1, 'coro1', self.scheduler) 176 | sched.Coroutine(self.coroutine2, 'coro2', self.scheduler) 177 | self.assertEqual(self.beacon1, 0) 178 | self.assertEqual(self.beacon2, 0) 179 | self.scheduler.run() 180 | self.assertEqual(self.beacon1, 2) 181 | self.assertEqual(self.beacon2, 2) 182 | 183 | def test_wait(self): 184 | c1 = sched.Coroutine(self.coroutine1, 'coro1', self.scheduler) 185 | cw = sched.Coroutine(lambda: self.coroutine_wait(c1), 'wait', 186 | self.scheduler) 187 | self.scheduler.run() 188 | 189 | def test_reactor(self): 190 | 191 | s = Sleep(1) 192 | cw = sched.Coroutine(lambda: self.coroutine_wait(s), 'wait', 193 | self.scheduler) 194 | sleeper = sched.Coroutine(s.start, 'coro_sleep', self.scheduler) 195 | self.scheduler.run() 196 | 197 | def test_fwd_exception(self): 198 | 199 | def coro(): 200 | def subcoro(): 201 | def raiser(): 202 | raise BeaconException() 203 | with sched.Scope() as scope: 204 | scope.run(raiser, 'raiser') 205 | with self.assertRaises(BeaconException): 206 | with sched.Scope() as scope: 207 | scope.run(subcoro, 'subcoro') 208 | 209 | c = sched.Coroutine(coro, 'coro', self.scheduler) 210 | self.scheduler.run() 211 | 212 | def test_semaphore_simple(self): 213 | s = sched.Semaphore(1) 214 | def lock_f(): 215 | with s: 216 | pass 217 | lock = sched.Coroutine(lock_f, 'lock', self.scheduler) 218 | self.scheduler.run() 219 | assert s.count == 1 220 | 221 | def test_semaphore(self): 222 | 223 | s = sched.Semaphore(1) 224 | beacon = [0] 225 | def lock_f(beacon): 226 | for i in range(3): 227 | s.lock() 228 | beacon[0] = beacon[0] + 1 229 | lock = sched.Coroutine(lambda: lock_f(beacon), 'lock', self.scheduler) 230 | def read_f(beacon): 231 | def check(i): 232 | # Yield twice, to make sure lock_f has an execution slot: we 233 | # just woke him, so it might be scheduled after us in the next 234 | # round. 235 | sched.coro_yield() 236 | sched.coro_yield() 237 | assert beacon[0] == i 238 | sched.coro_yield() 239 | assert beacon[0] == i 240 | s.unlock() 241 | assert beacon[0] == i 242 | check(1) 243 | check(2) 244 | check(3) 245 | read = sched.Coroutine(lambda: read_f(beacon), 'read', self.scheduler) 246 | self.scheduler.run() 247 | 248 | def test_continue_raise(self): 249 | 250 | def thrower(): 251 | raise BeaconException() 252 | 253 | def waiter(beacon): 254 | sched.coro_yield() 255 | sched.coro_yield() 256 | sched.coro_yield() 257 | beacon[0] += 1 258 | 259 | def main(): 260 | beacon = [0] 261 | try: 262 | with sched.Scope() as scope: 263 | scope.run(thrower, 'thrower') 264 | scope.run(lambda: waiter(beacon), 'waiter') 265 | finally: 266 | assert beacon[0] == 0 267 | 268 | sched.Coroutine(main, 'main', self.scheduler) 269 | try: 270 | self.scheduler.run() 271 | except BeaconException: 272 | pass 273 | else: 274 | assert False 275 | 276 | def test_break_scope(self): 277 | beacon = [0] 278 | def incrementer(beacon): 279 | while True: 280 | beacon[0] += 1 281 | sched.coro_yield() 282 | def main(): 283 | with sched.Scope() as scope: 284 | scope.run(lambda: incrementer(beacon), 'incrementer') 285 | sched.coro_yield() 286 | sched.coro_yield() 287 | raise BeaconException() 288 | scheduler = sched.Scheduler() 289 | sched.Coroutine(main, 'main', scheduler) 290 | try: 291 | scheduler.run() 292 | except BeaconException: 293 | assert beacon[0] == 1 294 | else: 295 | assert False 296 | 297 | def test_terminate_starting(self): 298 | beacon = [0] 299 | def incrementer(beacon): 300 | beacon[0] += 1 301 | def main(): 302 | with sched.Scope() as scope: 303 | scope.run(lambda: incrementer(beacon), 'incrementer') 304 | raise BeaconException() 305 | scheduler = sched.Scheduler() 306 | sched.Coroutine(main, 'main', scheduler) 307 | try: 308 | scheduler.run() 309 | except BeaconException: 310 | assert beacon[0] == 0 311 | else: 312 | assert False 313 | 314 | def test_terminate_scope(self): 315 | sync1 = sched.Semaphore(0) 316 | sync2 = sched.Semaphore(0) 317 | beacon = [False] 318 | scheduler = sched.Scheduler() 319 | coro = None 320 | def sub(): 321 | try: 322 | sync1.unlock() 323 | sync2.lock() 324 | coro.terminate() 325 | sched.Semaphore(0).lock() 326 | finally: 327 | beacon[0] = True 328 | def main(): 329 | try: 330 | with sched.Scope() as scope: 331 | scope.run(sub, 'sub') 332 | sync1.lock() 333 | sync2.unlock() 334 | except: 335 | assert beacon[0] 336 | raise 337 | coro = sched.Coroutine(main, 'main', scheduler) 338 | scheduler.run() 339 | 340 | unittest.main() 341 | -------------------------------------------------------------------------------- /tests/threadpool: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import threading 4 | import unittest 5 | 6 | from drake import threadpool 7 | 8 | class TestStandaloneCoroutine(unittest.TestCase): 9 | 10 | def setUp(self): 11 | self.__pool = threadpool.ThreadPool() 12 | self.__beacon = 0 13 | self.__lock = threading.Semaphore(0) 14 | 15 | def tearDown(self): 16 | self.__pool.stop() 17 | 18 | def inc(self): 19 | self.__beacon += 1 20 | self.__lock.release() 21 | 22 | def test_empty(self): 23 | pass 24 | 25 | def test_one(self): 26 | self.__pool.run(self.inc) 27 | self.__lock.acquire() 28 | assert self.__beacon == 1 29 | 30 | unittest.main() 31 | -------------------------------------------------------------------------------- /tests/utils.py: -------------------------------------------------------------------------------- 1 | import drake 2 | import os 3 | import shutil 4 | import tempfile 5 | 6 | class Drake: 7 | 8 | def __init__(self, dir = None, *args, **kwargs): 9 | self.__dir = dir 10 | self.__delete = False 11 | self.__drake = drake.Drake(*args, **kwargs) 12 | 13 | def __enter__(self): 14 | if self.__dir is None: 15 | self.__dir = tempfile.mkdtemp() 16 | self.__delete = True 17 | os.chdir(self.__dir) 18 | self.__drake.__enter__() 19 | return self.__drake 20 | 21 | def __exit__(self, *args): 22 | self.__drake.__exit__(*args) 23 | if self.__delete: 24 | shutil.rmtree(self.__dir) 25 | 26 | 27 | class FailBuilder(drake.Builder): 28 | 29 | def execute(self): 30 | return False 31 | 32 | 33 | class TouchBuilder(drake.Builder): 34 | 35 | def execute(self): 36 | for node in self.targets(): 37 | node.path().touch() 38 | return True 39 | 40 | 41 | class BeaconException(Exception): 42 | pass 43 | 44 | def assertEq(a, b): 45 | if a != b: 46 | raise Exception('%r != %r' % (a, b)) 47 | 48 | def assertExcept(f): 49 | try: 50 | f() 51 | except: 52 | pass 53 | else: 54 | raise Exception('%s did not except', f) 55 | 56 | def assertNotEq(a, b): 57 | if a == b: 58 | raise Exception('%r == %r' % (a, b)) 59 | 60 | def assertGt(a, b): 61 | if a <= b: 62 | raise Exception('%r <= %r' % (a, b)) 63 | 64 | def assertGe(a, b): 65 | if a < b: 66 | raise Exception('%r < %r' % (a, b)) 67 | 68 | def assertNotGt(a, b): 69 | if a > b: 70 | raise Exception('%r > %r' % (a, b)) 71 | 72 | def assertNotGe(a, b): 73 | if a >= b: 74 | raise Exception('%r >= %r' % (a, b)) 75 | 76 | def assertLt(a, b): 77 | if a >= b: 78 | raise Exception('%r >= %r' % (a, b)) 79 | 80 | def assertLe(a, b): 81 | if a > b: 82 | raise Exception('%r > %r' % (a, b)) 83 | 84 | def assertNotLt(a, b): 85 | if a < b: 86 | raise Exception('%r < %r' % (a, b)) 87 | 88 | def assertNotLe(a, b): 89 | if a <= b: 90 | raise Exception('%r <= %r' % (a, b)) 91 | 92 | def assertIn(v, c): 93 | if not v in c: 94 | raise Exception('%s not in %s' % (v, c)) 95 | 96 | def assertNotIn(v, c): 97 | if v in c: 98 | raise Exception('%s in %s' % (v, c)) 99 | -------------------------------------------------------------------------------- /zsh/_drake: -------------------------------------------------------------------------------- 1 | #compdef drake 2 | 3 | 4 | # compadd() 5 | # { 6 | # echo 7 | # echo compadd "$@" 8 | # # for arg; do 9 | # # echo "> $arg$" 10 | # # done 11 | # while test $# -ne 0; do 12 | # case $1 in 13 | # (-D) 14 | # echo "-D $2" 15 | # echo ">> ${(P)2}$" 16 | # ;; 17 | # (-d) 18 | # echo "-d $2" 19 | # for arg in ${(P)2}; do 20 | # echo ">> $arg$" 21 | # done 22 | # ;; 23 | # (-a) 24 | # echo "-a $2" 25 | # echo ">> ${(P)2}$" 26 | # ;; 27 | # esac 28 | # shift 29 | # done 30 | # } 31 | 32 | drake_options=() 33 | _drake_cache_options() 34 | { 35 | local option parts switches 36 | _call_program drake-complete-options $words[1] --complete-options | while read option 37 | do 38 | parts=(${(s: :)option}) 39 | switches=$parts[1] 40 | switches=(${(s:,:)switches}) 41 | for switch in $switches 42 | do 43 | option="(${switches})${switch}[$parts[2]]" 44 | if test $#parts -gt 2 45 | then 46 | option="$option:$parts[3]:" 47 | fi 48 | drake_options=($drake_options $option) 49 | done 50 | done 51 | } 52 | _drake_cache_options 53 | 54 | drake_modes=() 55 | _drake_cache_modes() 56 | { 57 | local mode parts 58 | _call_program drake-complete-modes $words[1] --complete-modes | while read mode 59 | do 60 | parts=(${(s: :)mode}) 61 | drake_modes=($drake_modes "--$parts[1],$parts[2]") 62 | done 63 | } 64 | _drake_cache_modes 65 | 66 | _drake() 67 | { 68 | local self 69 | self=$words[1] 70 | _arguments : $drake_options '*:: :->rest' 71 | case "$state" in 72 | (rest) 73 | _alternative \ 74 | "nodes:node:_drake_nodes" \ 75 | "modes:mode:_drake_modes" \ 76 | ;; 77 | esac 78 | } 79 | 80 | _drake_nodes() 81 | { 82 | local node 83 | _call_program drake-complete-nodes $self --complete-nodes | while read node 84 | do 85 | compadd "$@" - "$node" 86 | done 87 | } 88 | 89 | _drake_modes() 90 | { 91 | local desc mode parts 92 | desc=() 93 | for mode in $drake_modes 94 | do 95 | parts=(${(s:,:)mode}) 96 | compadd -Q -2V modes "$@" - $parts[1] 97 | desc=($desc "$parts[2] ") 98 | done 99 | #compadd -E$#desc -Q "$@" -d desc 100 | } 101 | --------------------------------------------------------------------------------