Skip to content

Commit 5eb28c6

Browse files
committed
Initial commit
0 parents  commit 5eb28c6

22 files changed

+1202
-0
lines changed

.gitignore

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
*.egg-info
2+
*.pyc
3+
__pycache__
4+
5+
.vscode
6+
7+
/dist
8+
/build

.gitmodules

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "robotpy_build/pybind11"]
2+
path = robotpy_build/pybind11
3+
url = https://github.com/pybind/pybind11.git

MANIFEST.in

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
recursive-include robotpy_build/pybind11/include *.h
2+
recursive-include robotpy_build/templates *.j2

README.md

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
robotpy-build
2+
=============
3+
4+
This is a build tool designed to meet the needs of RobotPy's various wrapper
5+
libraries build needs, chiefly around:
6+
7+
* Managing upstream binary dependencies
8+
* Autogenerating pybind11 wrappers around those dependencies
9+
* Building wheels from those generated wrappers
10+
11+
Requires Python 3.6+
12+
13+
Workflow
14+
--------
15+
16+
There are two types of generated artifacts from RobotPy wrapper library
17+
projects:
18+
19+
* sdist - This should contain enough information to build a wheel
20+
* This is NOT intended to be usable offline, because upstream artifacts
21+
can get to be fairly large
22+
* wheel - Platform specific build installable via pip
23+
* Should contain headers and libraries necessary for other projects
24+
to build from it
25+
26+
Eventual goal is to support cross-compilation somehow so we can build
27+
various types of artifacts via CI
28+
29+
Usage
30+
-----
31+
32+
TODO: This tool is still very much under development

robotpy_build/__init__.py

Whitespace-only changes.

robotpy_build/command/__init__.py

Whitespace-only changes.

robotpy_build/command/build_dl.py

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from distutils.core import Command
2+
3+
4+
class BuildDl(Command):
5+
6+
command_name = "build_dl"
7+
description = "Downloads files"
8+
user_options = []
9+
wrappers = []
10+
11+
def initialize_options(self):
12+
pass
13+
14+
def finalize_options(self):
15+
pass
16+
17+
def run(self):
18+
for wrapper in self.wrappers:
19+
wrapper.on_build_dl()

robotpy_build/command/build_ext.py

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
from setuptools import setup, Extension
2+
from setuptools.command.build_ext import build_ext
3+
import sys
4+
import setuptools
5+
6+
7+
# As of Python 3.6, CCompiler has a `has_flag` method.
8+
# cf http://bugs.python.org/issue26689
9+
def has_flag(compiler, flagname):
10+
"""Return a boolean indicating whether a flag name is supported on
11+
the specified compiler.
12+
"""
13+
import tempfile
14+
15+
with tempfile.NamedTemporaryFile("w", suffix=".cpp") as f:
16+
f.write("int main (int argc, char **argv) { return 0; }")
17+
try:
18+
compiler.compile([f.name], extra_postargs=[flagname])
19+
except setuptools.distutils.errors.CompileError:
20+
return False
21+
return True
22+
23+
24+
def cpp_flag(compiler):
25+
"""Return the -std=c++[11/14/17] compiler flag.
26+
The newer version is prefered over c++11 (when it is available).
27+
"""
28+
flags = ["-std=c++17", "-std=c++14", "-std=c++11"]
29+
30+
for flag in flags:
31+
if has_flag(compiler, flag):
32+
return flag
33+
34+
raise RuntimeError("Unsupported compiler -- at least C++11 support is needed!")
35+
36+
37+
class BuildExt(build_ext):
38+
"""A custom build extension for adding compiler-specific options."""
39+
40+
c_opts = {"msvc": ["/EHsc"], "unix": []}
41+
l_opts = {"msvc": [], "unix": []}
42+
43+
if sys.platform == "darwin":
44+
darwin_opts = ["-stdlib=libc++", "-mmacosx-version-min=10.7"]
45+
c_opts["unix"] += darwin_opts
46+
l_opts["unix"] += darwin_opts
47+
48+
def build_extensions(self):
49+
ct = self.compiler.compiler_type
50+
opts = self.c_opts.get(ct, [])
51+
link_opts = self.l_opts.get(ct, [])
52+
if ct == "unix":
53+
opts.append("-s") # strip
54+
opts.append("-g0") # remove debug symbols
55+
opts.append('-DVERSION_INFO="%s"' % self.distribution.get_version())
56+
opts.append(cpp_flag(self.compiler))
57+
if has_flag(self.compiler, "-fvisibility=hidden"):
58+
opts.append("-fvisibility=hidden")
59+
elif ct == "msvc":
60+
opts.append('/DVERSION_INFO=\\"%s\\"' % self.distribution.get_version())
61+
for ext in self.extensions:
62+
ext.extra_compile_args = opts
63+
ext.extra_link_args = link_opts
64+
65+
# self._gather_global_includes()
66+
67+
build_ext.build_extensions(self)
68+
69+
def run(self):
70+
71+
# files need to be generated before building can occur
72+
self.run_command("build_gen")
73+
74+
build_ext.run(self)
75+
76+
77+
# ext_modules = [
78+
# Extension(
79+
# "python_example",
80+
# ["src/main.cpp"],
81+
# include_dirs=[
82+
# # include dirs: iterate robotpy-build entrypoints, retrieve
83+
# ],
84+
# # library_dirs=
85+
# # libraries=
86+
# language="c++",
87+
# )
88+
# ]
89+

robotpy_build/command/build_gen.py

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from distutils.core import Command
2+
3+
4+
class BuildGen(Command):
5+
6+
command_name = "build_gen"
7+
description = "Generates source files"
8+
user_options = []
9+
wrappers = []
10+
11+
def initialize_options(self):
12+
pass
13+
14+
def finalize_options(self):
15+
pass
16+
17+
def run(self):
18+
# files need to be downloaded before building can occur
19+
self.run_command("build_dl")
20+
21+
for wrapper in self.wrappers:
22+
wrapper.on_build_gen()

robotpy_build/configs.py

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# For validating pyproject.toml
2+
3+
from schematics.models import Model
4+
from schematics.types import ModelType, BooleanType, StringType, ListType, DictType
5+
6+
7+
class WrapperConfig(Model):
8+
"""
9+
Wrapper configurations
10+
"""
11+
12+
# List of extra headers to export
13+
extra_headers = ListType(StringType, default=[])
14+
15+
# List of robotpy-build library dependencies
16+
# .. would be nice to auto-infer this from the python install dependencies
17+
depends = ListType(StringType, default=[])
18+
19+
#
20+
# Download settings
21+
#
22+
23+
# Library name
24+
libname = StringType(required=True)
25+
26+
# Name of artifact to download, if different than libname
27+
artname = StringType(default="")
28+
29+
# URL to download
30+
baseurl = StringType(required=True)
31+
32+
# Version of artifact to download
33+
version = StringType(required=True)
34+
35+
#
36+
# Wrapper generation settings
37+
#
38+
39+
# Source files to compile
40+
sources = ListType(StringType, default=[])
41+
42+
# List of dictionaries: each dictionary key is the function
43+
# name for the initialization function, the value is the
44+
# header that is being wrapped
45+
generate = ListType(DictType(StringType))
46+
47+
# Path to a data.yml to use during code generation
48+
generation_data = StringType()

robotpy_build/download.py

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import atexit
2+
import os
3+
from os.path import exists, join
4+
import posixpath
5+
import shutil
6+
import sys
7+
import tempfile
8+
import zipfile
9+
10+
11+
from urllib.request import urlretrieve, urlcleanup
12+
13+
14+
def _download(url):
15+
"""
16+
Downloads a file to a temporary directory
17+
"""
18+
19+
print("Downloading", posixpath.basename(url))
20+
21+
def _reporthook(count, blocksize, totalsize):
22+
percent = int(count * blocksize * 100 / totalsize)
23+
sys.stdout.write("\r%02d%%" % percent)
24+
sys.stdout.flush()
25+
26+
filename, _ = urlretrieve(url, reporthook=_reporthook)
27+
atexit.register(urlcleanup)
28+
return filename
29+
30+
31+
def download_and_extract_zip(url, to=None):
32+
"""
33+
Utility method intended to be useful for downloading/extracting
34+
third party source zipfiles
35+
36+
:param to: is either a string or a dict of {src: dst}
37+
"""
38+
39+
if to is None:
40+
# generate temporary directory
41+
tod = tempfile.TemporaryDirectory()
42+
to = tod.name
43+
atexit.register(tod.cleanup)
44+
45+
zip_fname = None
46+
cache = os.environ.get("RPY_DEVDIR")
47+
if cache:
48+
os.makedirs(cache, exist_ok=True)
49+
cache_fname = join(cache, posixpath.basename(url))
50+
if not exists(cache_fname):
51+
zip_fname = _download(url)
52+
shutil.copy(zip_fname, cache_fname)
53+
zip_fname = cache_fname
54+
else:
55+
zip_fname = _download(url)
56+
57+
with zipfile.ZipFile(zip_fname) as z:
58+
if isinstance(to, str):
59+
z.extractall(to)
60+
return to
61+
else:
62+
for src, dst in to.items():
63+
with z.open(src, "r") as zfp:
64+
with open(dst, "wb") as fp:
65+
shutil.copyfileobj(zfp, fp)

0 commit comments

Comments
 (0)