|
1 | 1 | # Notes on Packaging
|
2 | 2 |
|
3 | 3 | ## Background
|
4 |
| -* The dnp3-python version 0.2.0 is an extension and repackaging of [pydnp3](https://github.com/ChargePoint/pydnp3) version 0.1.0 |
| 4 | + |
| 5 | +* The dnp3-python version 0.2.0 is an extension and repackaging of [pydnp3](https://github.com/ChargePoint/pydnp3) |
| 6 | + version 0.1.0 |
5 | 7 | * The wrapper provides more user-friendly and out-of-the-box examples (under /src/dnp3demo)
|
6 |
| -* The wrapper enables features to install from [The Python Package Index (Pypi)](https://pypi.org/), |
| 8 | +* The wrapper enables features to install from [The Python Package Index (Pypi)](https://pypi.org/), |
7 | 9 | while the original package requires installing from the source code.
|
8 | 10 |
|
9 | 11 | ## Overview
|
| 12 | + |
10 | 13 | * While, building package from the source code is NOT needed when using the dnp3-python package,
|
11 | 14 | it is still required for packing for distribution (i.e., building wheel and publishing to pypi.)
|
12 | 15 | * The dependencies to build c++ binding code are cmake and pybind11.
|
13 |
| - * CMake >= 2.8.12 is required. |
14 |
| - * a special version of pybind11 is used and located at /deps/pybind11. |
15 |
| -* The c++ source code is based on [opendnp3](https://github.com/automatak/dnp3), |
| 16 | + * CMake >= 2.8.12 is required. |
| 17 | + * a special version of pybind11 is used and located at /deps/pybind11. |
| 18 | +* The c++ source code is based on [opendnp3](https://github.com/automatak/dnp3), |
16 | 19 | forked and version-pinned at /deps/dnp3.
|
17 | 20 | the binding code are located at /src and its sub-folders.
|
18 | 21 | * setup.py describes the packaging configuration. (tested with with python==3.8.13, setuptools==63.4.1)
|
19 |
| - * to build and install the package locally, run `python setup.py install` |
20 |
| - * to build wheel, run `python setup.py bdist_wheel [--plat-name=manylinux1_x86_64]` |
| 22 | + * to build and install the package locally, run `python setup.py install` |
| 23 | + * to build wheel, run `python setup.py bdist_wheel [--plat-name=manylinux1_x86_64]` |
| 24 | + |
| 25 | +## Notes on working with recurse-submodules |
| 26 | + |
| 27 | +* In order to build the binary (and the wheel) from the source code, the dnp3-python relies on pin-versioned "dnp3" |
| 28 | + and "pybind11". |
| 29 | +* Note that both the aforementioned source code repo need to present locally at the `/deps`. |
| 30 | +* Both of them are included in git as submodules. (See [.gitmodules](../.gitmodules)). To include the submodule, |
| 31 | + use `git clone <dnp3-python repo> --recurse-submodules`. The `--recurse-submodules` option makes sure nested |
| 32 | + submodules are cloned. (Note that the submodule "dnp3" include another submodule "asio".) |
| 33 | +* If the submodules were not cloned at the beginning, run `git submodule update --init --recursive`. |
| 34 | +* To work with submodules, please see the following references: |
| 35 | + * [Submodules - Git](https://git-scm.com/book/en/v2/Git-Tools-Submodules)) |
| 36 | + * [Pull latest changes for all git submodules](https://stackoverflow.com/questions/1030169/pull-latest-changes-for-all-git-submodules) |
| 37 | + * [How to create Git submodules in GitHub and GitLab by example](https://www.theserverside.com/blog/Coffee-Talk-Java-News-Stories-and-Opinions/How-to-add-submodules-to-GitHub-repos) |
| 38 | + * [Cleans and resets a git repo and its submodules - gists · GitHub](https://gist.github.com/nicktoumpelis/11214362) |
| 39 | + * [How to checkout old git commit including all submodules recursively?](https://stackoverflow.com/questions/15124430/how-to-checkout-old-git-commit-including-all-submodules-recursively) |
21 | 40 |
|
22 | 41 | ## Notes on Building native Python + CPP-binding Python Package
|
| 42 | + |
23 | 43 | #### Intro
|
| 44 | + |
24 | 45 | * Need to build + package a python project with cpp-binding code.
|
25 | 46 | * Desired result
|
26 |
| - * pip install <package_name:dnp3_python> |
27 |
| - * able to import cpp-binding modules, e.g., “from pydnp3 import opendnp3” |
28 |
| - * able to import native python code, e.g., “from dnp3_python.dnp3station import master_new” |
| 47 | + * pip install <package_name:dnp3_python> |
| 48 | + * able to import cpp-binding modules, e.g., “from pydnp3 import opendnp3” |
| 49 | + * able to import native python code, e.g., “from dnp3_python.dnp3station import master_new” |
29 | 50 | * Note:
|
30 |
| - * the package is a wrapper on “pydnp3” project by repackaging its cpp-binding functionality |
31 |
| - and extended/redesigned API in Python. |
32 |
| - * the package inherited “pydnp3” project’s root module naming, |
33 |
| - i.e., “pydnp3” for cpp-binding related functionality. |
34 |
| - * the extended method adopted root module naming, “dnp3_python”. |
35 |
| - (There is a discussion at the end of this memo about the reason for using different root module name.) |
| 51 | + * the package is a wrapper on “pydnp3” project by repackaging its cpp-binding functionality |
| 52 | + and extended/redesigned API in Python. |
| 53 | + * the package inherited “pydnp3” project’s root module naming, |
| 54 | + i.e., “pydnp3” for cpp-binding related functionality. |
| 55 | + * the extended method adopted root module naming, “dnp3_python”. |
| 56 | + (There is a discussion at the end of this memo about the reason for using different root module name.) |
| 57 | + |
36 | 58 | #### Ingredients
|
37 |
| -* Reference: [Package Discovery and Namespace Packages](https://setuptools.pypa.io/en/latest/userguide/package_discovery.html) |
| 59 | + |
| 60 | +* |
| 61 | + |
| 62 | +Reference: [Package Discovery and Namespace Packages](https://setuptools.pypa.io/en/latest/userguide/package_discovery.html) |
| 63 | + |
38 | 64 | * Key configuration in `setup.py`
|
39 | 65 | ```
|
40 | 66 | packages=find_namespace_packages(
|
|
74 | 100 | ```
|
75 | 101 |
|
76 | 102 | #### Key takeaways
|
77 |
| -* Using find_namespace_packages to “automatically” find packages |
| 103 | +
|
| 104 | +* Using find_namespace_packages to “automatically” find packages |
78 | 105 | (assuming defined sub-modules properly, i.e., with `__init__.py`)
|
79 |
| -* Using trailing * to include submodules |
| 106 | +* Using trailing * to include submodules |
80 | 107 | (e.g., dnp3_python* will include dnp3_python and dnp3_python/dnp3station)
|
81 | 108 | * Verifying with artifact structure (e.g., whl structure or tar structure)
|
82 | 109 |
|
83 | 110 | #### Discussion:
|
| 111 | +
|
84 | 112 | * dnp3_python is a package mixed with cpp binding binary and native Python source code.
|
85 | 113 | * the cpp binding path is resolved by using dynamic binary (i.e., *.so file)
|
86 | 114 | * the name space is called “pydnp3”
|
87 | 115 | * To avoid namespace conflict, use different root namespace for python source code package.
|
88 |
| - * e.g., at one point, the pacakge adopted the structure /src/pydnp3/dnp3station, |
89 |
| - with the attempt to achieve `from pydnp3.dnp3station.master_new import *`. |
90 |
| - As a result, it will create a “pydnp3/dnp3station” dir at the site-package path. |
91 |
| - * However, under the aforementioned structure, the cpp binding submodules are not resolvable, |
92 |
| - e.g., not able to achieve “from pydnp3 import opendnp3”. |
93 |
| - python will find the native “pydnp3/” first and ignore the package path linked to `*.so` file. |
94 |
| - |
| 116 | + * e.g., at one point, the pacakge adopted the structure /src/pydnp3/dnp3station, |
| 117 | + with the attempt to achieve `from pydnp3.dnp3station.master_new import *`. |
| 118 | + As a result, it will create a “pydnp3/dnp3station” dir at the site-package path. |
| 119 | + * However, under the aforementioned structure, the cpp binding submodules are not resolvable, |
| 120 | + e.g., not able to achieve “from pydnp3 import opendnp3”. |
| 121 | + python will find the native “pydnp3/” first and ignore the package path linked to `*.so` file. |
| 122 | +
|
| 123 | +## Example: building binary from C++ source code |
| 124 | +
|
| 125 | +* Demo OS: Ubuntu 22.04 -- use [OsBoxes VM](https://www.osboxes.org/ubuntu/) in this demo. |
| 126 | +* Virtual Environment Tool (e.g., conda, virtualenv) -- use conda in this demo. |
| 127 | +* Python version 3.10. |
| 128 | +
|
| 129 | +#### prepared the source code (including submodules) for build |
| 130 | +
|
| 131 | +```bash |
| 132 | +$ git clone <dnp3-python-repo> --recurse-submodules |
| 133 | +Cloning into 'dnp3-python'... |
| 134 | +remote: Enumerating objects: 1633, done. |
| 135 | +remote: Counting objects: 100% (15/15), done. |
| 136 | +remote: Compressing objects: 100% (15/15), done. |
| 137 | +remote: Total 1633 (delta 2), reused 3 (delta 0), pack-reused 1618 |
| 138 | +Receiving objects: 100% (1633/1633), 12.09 MiB | 16.00 MiB/s, done. |
| 139 | +Resolving deltas: 100% (1275/1275), done. |
| 140 | +Submodule 'deps/dnp3' (https://github.com/kefeimo/opendnp3.git) registered for path 'deps/dnp3' |
| 141 | +Submodule 'deps/pybind11' (https://github.com/Kisensum/pybind11.git) registered for path 'deps/pybind11' |
| 142 | +Cloning into '/home/kefei/project/dnp3-python/deps/dnp3'... |
| 143 | +remote: Enumerating objects: 96789, done. |
| 144 | +remote: Counting objects: 100% (2074/2074), done. |
| 145 | +remote: Compressing objects: 100% (834/834), done. |
| 146 | +remote: Total 96789 (delta 1296), reused 1674 (delta 1152), pack-reused 94715 |
| 147 | +Receiving objects: 100% (96789/96789), 25.70 MiB | 2.01 MiB/s, done. |
| 148 | +Resolving deltas: 100% (68129/68129), done. |
| 149 | +Cloning into '/home/kefei/project/dnp3-python/deps/pybind11'... |
| 150 | +remote: Enumerating objects: 9840, done. |
| 151 | +remote: Total 9840 (delta 0), reused 0 (delta 0), pack-reused 9840 |
| 152 | +Receiving objects: 100% (9840/9840), 3.46 MiB | 1.99 MiB/s, done. |
| 153 | +Resolving deltas: 100% (6648/6648), done. |
| 154 | +Submodule path 'deps/dnp3': checked out '7d84673d165a4a075590a5f146ed1a4ba35d4e49' |
| 155 | +Submodule 'deps/asio' (https://github.com/chriskohlhoff/asio.git) registered for path 'deps/dnp3/deps/asio' |
| 156 | +Cloning into '/home/kefei/project/dnp3-python/deps/dnp3/deps/asio'... |
| 157 | +remote: Enumerating objects: 62636, done. |
| 158 | +remote: Counting objects: 100% (669/669), done. |
| 159 | +remote: Compressing objects: 100% (269/269), done. |
| 160 | +remote: Total 62636 (delta 462), reused 513 (delta 400), pack-reused 61967 |
| 161 | +Receiving objects: 100% (62636/62636), 25.34 MiB | 2.24 MiB/s, done. |
| 162 | +Resolving deltas: 100% (45783/45783), done. |
| 163 | +Submodule path 'deps/dnp3/deps/asio': checked out '28d9b8d6df708024af5227c551673fdb2519f5bf' |
| 164 | +Submodule path 'deps/pybind11': checked out '338d615e12ce41ee021724551841de3cbe0bc1df' |
| 165 | +Submodule 'tools/clang' (https://github.com/wjakob/clang-cindex-python3) registered for path 'deps/pybind11/tools/clang' |
| 166 | +Cloning into '/home/kefei/project/dnp3-python/deps/pybind11/tools/clang'... |
| 167 | +remote: Enumerating objects: 368, done. |
| 168 | +remote: Counting objects: 100% (13/13), done. |
| 169 | +remote: Compressing objects: 100% (10/10), done. |
| 170 | +remote: Total 368 (delta 3), reused 10 (delta 3), pack-reused 355 |
| 171 | +Receiving objects: 100% (368/368), 159.34 KiB | 1.20 MiB/s, done. |
| 172 | +Resolving deltas: 100% (154/154), done. |
| 173 | +Submodule path 'deps/pybind11/tools/clang': checked out '6a00cbc4a9b8e68b71caf7f774b3f9c753ae84d5' |
| 174 | +``` |
| 175 | + |
| 176 | +#### install build-essential |
| 177 | + |
| 178 | +```bash |
| 179 | +$ sudo apt-get update && sudo apt-get install build-essential |
| 180 | +Hit:1 http://us.archive.ubuntu.com/ubuntu jammy InRelease |
| 181 | +Hit:2 http://security.ubuntu.com/ubuntu jammy-security InRelease |
| 182 | +Hit:3 http://us.archive.ubuntu.com/ubuntu jammy-updates InRelease |
| 183 | +Hit:4 http://us.archive.ubuntu.com/ubuntu jammy-backports InRelease |
| 184 | +Reading package lists... Done |
| 185 | +Reading package lists... Done |
| 186 | +Building dependency tree... Done |
| 187 | +Reading state information... Done |
| 188 | +... |
| 189 | +``` |
| 190 | + |
| 191 | +#### create a conda environment called dnp3-sandbox (Python==3.10) |
| 192 | + |
| 193 | +```bash |
| 194 | +$ conda create -n dnp3-sandbox python=3.10 |
| 195 | +Collecting package metadata (current_repodata.json): done |
| 196 | +Solving environment: done |
| 197 | + |
| 198 | +## Package Plan ## |
| 199 | + |
| 200 | + environment location: /home/kefei/miniconda3/envs/dnp3-sandbox |
| 201 | + |
| 202 | + added / updated specs: |
| 203 | + - python=3.10 |
| 204 | + |
| 205 | + |
| 206 | +The following NEW packages will be INSTALLED: |
| 207 | +... |
| 208 | +``` |
| 209 | + |
| 210 | +#### activate the virtual environment and install required dependency for build (i.e., cmake) |
| 211 | + |
| 212 | +```bash |
| 213 | +$ conda activate dnp3-sandbox |
| 214 | +(dnp3-sandbox) $ pip install cmake |
| 215 | +Collecting cmake |
| 216 | + Downloading cmake-3.25.0-py2.py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (23.7 MB) |
| 217 | + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 23.7/23.7 MB 2.1 MB/s eta 0:00:00 |
| 218 | +Installing collected packages: cmake |
| 219 | +Successfully installed cmake-3.25.0 |
| 220 | +``` |
| 221 | + |
| 222 | +#### (at repo root path) run `python setup.py install` |
| 223 | + |
| 224 | +Watch the artifact created at "build/" folder. |
| 225 | +e.g., `lib.linux-x86_64-cpython-310/pydnp3.cpython-310-x86_64-linux-gnu.so` |
| 226 | + |
| 227 | +```bash |
| 228 | +running install |
| 229 | +/home/kefei/miniconda3/envs/dnp3-sandbox/lib/python3.10/site-packages/setuptools/command/install.py:34: SetuptoolsDeprecationWarning: setup.py install is deprecated. Use build and pip and other standards-based tools. |
| 230 | + warnings.warn( |
| 231 | +/home/kefei/miniconda3/envs/dnp3-sandbox/lib/python3.10/site-packages/setuptools/command/easy_install.py:144: EasyInstallDeprecationWarning: easy_install command is deprecated. Use build and pip and other standards-based tools. |
| 232 | + warnings.warn( |
| 233 | +running bdist_egg |
| 234 | +running egg_info |
| 235 | +writing src/dnp3_python.egg-info/PKG-INFO |
| 236 | +writing dependency_links to src/dnp3_python.egg-info/dependency_links.txt |
| 237 | +writing entry points to src/dnp3_python.egg-info/entry_points.txt |
| 238 | +writing top-level names to src/dnp3_python.egg-info/top_level.txt |
| 239 | +reading manifest file 'src/dnp3_python.egg-info/SOURCES.txt' |
| 240 | +adding license file 'LICENSE' |
| 241 | +... |
| 242 | +Processing dnp3_python-0.2.3b2-py3.10-linux-x86_64.egg |
| 243 | +removing '/home/kefei/miniconda3/envs/dnp3-sandbox/lib/python3.10/site-packages/dnp3_python-0.2.3b2-py3.10-linux-x86_64.egg' (and everything under it) |
| 244 | +creating /home/kefei/miniconda3/envs/dnp3-sandbox/lib/python3.10/site-packages/dnp3_python-0.2.3b2-py3.10-linux-x86_64.egg |
| 245 | +Extracting dnp3_python-0.2.3b2-py3.10-linux-x86_64.egg to /home/kefei/miniconda3/envs/dnp3-sandbox/lib/python3.10/site-packages |
| 246 | +dnp3-python 0.2.3b2 is already the active version in easy-install.pth |
| 247 | +Installing dnp3demo script to /home/kefei/miniconda3/envs/dnp3-sandbox/bin |
| 248 | + |
| 249 | +Installed /home/kefei/miniconda3/envs/dnp3-sandbox/lib/python3.10/site-packages/dnp3_python-0.2.3b2-py3.10-linux-x86_64.egg |
| 250 | +Processing dependencies for dnp3-python==0.2.3b2 |
| 251 | +Finished processing dependencies for dnp3-python==0.2.3b2 |
| 252 | +``` |
| 253 | +
|
| 254 | +(View full log |
| 255 | +at [cmake-build-dnp3-python.log](https://gist.github.com/kefeimo/35b7536c235c31ba0abf0da7fa7c4125#file-cmake-build-dnp3-python-log)) |
| 256 | +
|
| 257 | +#### run `python setup.py bdist_wheel --plat-name=manylinux1_x86_64` to build wheel |
| 258 | +
|
| 259 | +Watch the artifact created at "dist/" folder. |
| 260 | +e.g., `dnp3_python-0.2.3b2-cp310-cp310-manylinux1_x86_64.whl` |
| 261 | +
|
| 262 | +```bash |
| 263 | +$ python setup.py bdist_wheel --plat-name=manylinux1_x86_64 |
| 264 | +running bdist_wheel |
| 265 | +running build |
| 266 | +running build_py |
| 267 | +running build_ext |
| 268 | +CMake Deprecation Warning at CMakeLists.txt:1 (cmake_minimum_required): |
| 269 | + Compatibility with CMake < 2.8.12 will be removed from a future version of |
| 270 | + CMake. |
| 271 | + |
| 272 | + Update the VERSION argument <min> value or use a ...<max> suffix to tell |
| 273 | + CMake that the project does not need compatibility with older versions. |
| 274 | +... |
| 275 | +adding 'dnp3_python-0.2.3b2.dist-info/METADATA' |
| 276 | +adding 'dnp3_python-0.2.3b2.dist-info/WHEEL' |
| 277 | +adding 'dnp3_python-0.2.3b2.dist-info/entry_points.txt' |
| 278 | +adding 'dnp3_python-0.2.3b2.dist-info/top_level.txt' |
| 279 | +adding 'dnp3_python-0.2.3b2.dist-info/RECORD' |
| 280 | +removing build/bdist.linux-x86_64/wheel |
| 281 | +``` |
| 282 | +
|
| 283 | +More information |
| 284 | +about [building wheels](https://wheel.readthedocs.io/en/stable/user_guide.html?highlight=bdist_wheel#building-wheels), |
| 285 | +[The Story of Wheel](https://wheel.readthedocs.io/en/stable/story.html?highlight=bdist_wheel#the-story-of-wheel), |
| 286 | +[manylinux](https://github.com/pypa/manylinux) and `--plat-name` |
| 287 | +in [Running setuptools commands](https://setuptools.pypa.io/en/latest/deprecated/commands.html). |
| 288 | +
|
| 289 | +## Useful Resource |
| 290 | +
|
| 291 | +* [Setup Tools Doc](https://setuptools.pypa.io/en/latest/) |
| 292 | +* [wheel Doc](https://wheel.readthedocs.io/en/stable/index.html) |
0 commit comments