├── .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 | #
2 |
3 | The well-formed build system.
4 |
5 | [](https://gitlab.gruntech.org/mefyl/drake/commits/master) [](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 | #
2 |
3 | The well-formed build system.
4 |
5 | [](https://gitlab.gruntech.org/mefyl/drake/commits/master) [](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 |
--------------------------------------------------------------------------------