
Demo Project
============

This walkthrough will take you through all the steps to create a simple
robotpy-build project that autogenerates a working wrapper around a C++
class and it's methods.

This demo should work on Linux, OSX, and Windows. Make sure you have
robotpy_build installed first!

.. note:: This demo shows building a python wrapper around C++ code that is
          self-contained in this project. However, ``robotpy-build`` also 
          supports wrapping externally compiled libraries and inter-package
          shared library dependencies.

Files + descriptions
--------------------

.. note:: If you're lazy, the files for this demo are checked into the 
          robotpy-build repository at ``examples/demo``.

All of the content required for this demo is contained inline below. Let's
start by creating a new directory for your project, and we're going to create
the following files:

setup.py
~~~~~~~~

Traditionally python projects required a setup.py that contained all the
information and logic needed to compile/install the project. Every project
that uses robotpy-build has an identical ``setup.py`` that looks like this:

.. literalinclude:: ../examples/demo/setup.py

pyproject.toml
~~~~~~~~~~~~~~

Projects that use robotpy-build must add a ``pyproject.toml`` to the root of
their project as specified in `PEP 518 <https://www.python.org/dev/peps/pep-0518>`_.
This file is used to configure your project.

Comments describing the function of each section can be found inline below.

.. literalinclude:: ../examples/demo/pyproject.toml

.. seealso:: For detailed information about the contents of ``pyproject.toml``
             see :ref:`pyproject`.

rpydemo/__init__.py
~~~~~~~~~~~~~~~~~~~

.. code-block:: python

    # file is empty for now

rpydemo/src/demo.cpp
~~~~~~~~~~~~~~~~~~~~

This is the (very simple) C++ code that we will wrap so that it can be
called from python.

.. literalinclude:: ../examples/demo/rpydemo/src/demo.cpp

rpydemo/include/demo.h
~~~~~~~~~~~~~~~~~~~~~~

This is the C++ header file for the code that we're wrapping. In ``pyproject.toml``
we told ``robotpy-build`` to parse this file and autogenerate wrappers for it.

For simple C++ code such as this, autogeneration will 'just work' and no other
customization is required. However, certain C++ code (templates and sometimes code
that depends on templated types, and other complex circumstances) will require
providing customization in a YAML file.

.. literalinclude:: ../examples/demo/rpydemo/include/demo.h

rpydemo/src/main.cpp
~~~~~~~~~~~~~~~~~~~~

Finally, you need to define your pybind11 python module. Custom ``pybind11``
projects would use a ``PYBIND11_MODULE`` macro to define a module, but it's
easier to use the ``RPYBUILD_PYBIND11_MODULE`` macro which automatically sets
the module name when robotpy-build compiles the file.

.. literalinclude:: ../examples/demo/rpydemo/src/main.cpp

.. note:: If you wanted to add your own handwritten pybind11 code here, you
          can add it in addition to the ``initWrapper`` call made here. See
          the pybind11 documentation for more details.

Install the project
-------------------

When developing a new project, it's easiest to just install in 'develop' mode
which will build/install everything in the currect directory.

.. code-block:: sh

    $ python3 setup.py develop

.. note:: When using develop mode for robotpy-build projects, it is recommended
          to invoke setup.py directly instead of using ``pip3 install -e``

Adjust the project
------------------

As we've currently built the project, the CPython extension will be built 
as ``rpydemo._rpydemo``. For example:

.. code-block:: pycon

    >>> from rpydemo._rpydemo import DemoClass
    >>> DemoClass
    <class 'rpydemo._rpydemo.DemoClass'>

While that works, we really would like users to be able to access our module
directly by importing them into ``__init__.py``. There's a robotpy_build command
for generating the contents of ``__init__.py``:

.. code-block:: sh

    $ python -m robotpy_build create-imports rpydemo rpydemo._rpydemo

The output from this you can put into your ``rpydemo/__init__.py``. It'll look
like this:

.. code-block:: python

    # autogenerated by 'robotpy-build create-imports rpydemo rpydemo._rpydemo'
    from ._rpydemo import DemoClass, add2

    __all__ = ["DemoClass", "add2"]

Now when we put this in our ``__init__.py``, that allows this to work instead:

.. code-block:: pycon

    >>> from rpydemo import DemoClass
    >>> DemoClass
    <class 'rpydemo._rpydemo.DemoClass'>

Trying out the project
----------------------

Alright, now that all the pieces are assembled, we can try out our project:

.. code-block:: pycon

    >>> import rpydemo
    >>> rpydemo.add2(2)
    4
    >>> d = rpydemo.DemoClass()
    >>> d.setX(2)
    >>> d.getX()
    2

More Examples
-------------

The integration tests in ``tests/cpp`` contains a python project that contains
several autogenerated wrappers packages and various customizations.
