Skip to content

Commit

Permalink
Merge branch 'master' into remove_display_parameter
Browse files Browse the repository at this point in the history
  • Loading branch information
alters-mit committed Aug 12, 2020
2 parents 665a0cf + 6b4115b commit 3a30ab4
Show file tree
Hide file tree
Showing 10 changed files with 197 additions and 6 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ Python/tdw.egg-info/
__pycache__/
*/__pycache__/*
Python/dist/
Python/tdw/tdw.egg-info/
Python/tdw/tdw.egg-info/
Python/build/
19 changes: 19 additions & 0 deletions Documentation/Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,25 @@

# v1.6.x

## v1.6.5

### Frontend

- Added `freeze.py`. "Freeze" your controller into a portable binary executable.

#### Backend

- Added `controller.spec` (used for freezing controller code).
- Adjusted how Flatbuffers imports numpy so that frozen controller code works.

### Documentation

#### New Documentation

| Document | Description |
| ----------- | ------------------------------------------------------------ |
| `freeze.md` | How to freeze your controller code into a binary executable. |

## v1.6.4

### `tdw` module
Expand Down
50 changes: 50 additions & 0 deletions Documentation/misc_frontend/freeze.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Freezing your code

"Freezing" code means creating an executable binary (`.exe`, `.app`, etc.) of your controller. This executable includes all of its dependencies, which means that you can launch it on a computer that doesn't have Python or TDW installed.

## Requirements

1. Install [`tdw` module](../python/tdw.md)
2. Clone this repo
3. Write your controller

## Usage

**To test,** run `freeze.py` without any arguments; this will create a binary of the `minimal.py` example controller:

```bash
cd path/to/tdw/Python # Replace path/to with the actual path.

# Windows
py -3 freeze.py

# OS X and Linux
python3 freeze.py
```

**To freeze your controller,** add the `--controller` argument:

```bash
cd path/to/tdw/Python # Replace path/to with the actual path.

# Replace CONTROLLER with the path to your controller.

# Windows
py -3 freeze.py --controller CONTROLLER
# OS X and Linux
python3 freeze.py --controller CONTROLLER
```

## Result

`freeze.py` will create an executable located in `~/tdw_build/tdw_controller`, where `~` is your home directory. **You can run it like an other application** by double-clicking it or running it in the terminal. Likewise, you can supply arguments to the executable like you can to a Python controller.

On Linux, you need to supply a `DISPLAY` environment to run the controller if [the launch_build parameter in the Controller constructor is True](../python/controller.md):

```bash
DISPLAY=:0.0 ./my_controller
```

## Limitations

`freeze.py` can only freeze code for the operating system it is running on. For example, if it is running on OS X, it can create `tdw_controller.app` for OS X but *not* `tdw_controller.exe` for Windows. This is a limitation inherent to Python.
56 changes: 56 additions & 0 deletions Python/controller.spec
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# -*- mode: python -*-
import sys
from pathlib import Path
from PyInstaller.utils.hooks import copy_metadata
import os

# Read the config file.
config_file = Path.home().joinpath("tdw_build/tdw_controller/freeze.ini")
assert config_file.exists()
config = config_file.read_text(encoding="utf-8")
controller = Path(config.split("controller=")[1])
root_dir = str(controller.parent.resolve())
controller = str(controller.resolve())

block_cipher = None

datas = copy_metadata("tdw")
# Add TDW data files.
for root, dirs, files in os.walk("tdw"):
if "tdw.egg-info" in root or "__pycache__" in root:
continue
for file in files:
if ".py" in file:
continue
src = str(Path(root).joinpath(file).resolve())
datas.append((src, root))

a = Analysis([controller],
binaries=[],
datas=datas,
hiddenimports=[],
excludes=["FixTk", "tcl", "tk", "_tkinter", "tkinter", "Tkinter", "matplotlib", "qt5"],
hookspath=[],
runtime_hooks=[],
cipher=block_cipher,
noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='tdw_controller',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
runtime_tmpdir=None,
console=True)
if sys.platform == "darwin":
app = BUNDLE(exe,
name='tdw_controller.app',
icon=None,
bundle_identifier=None)
63 changes: 63 additions & 0 deletions Python/freeze.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from subprocess import call
from distutils import dir_util
from pathlib import Path
from argparse import ArgumentParser
from platform import system


"""
Freeze your controller into a binary executable.
Documentation: `tdw/Documentation/misc_frontend/freeze.md`
"""


if __name__ == "__main__":
root_dir = Path.home().joinpath("tdw_build")
if not root_dir.exists():
root_dir.mkdir()
# Remove an existing frozen controller.
output_dir = root_dir.joinpath("tdw_controller")
if output_dir.exists():
dir_util.remove_tree(str(output_dir.resolve()))
output_dir.mkdir(parents=True)
parser = ArgumentParser()
parser.add_argument("--controller", type=str, default="example_controllers/minimal.py",
help="The relative path from this script to your controller. "
"Example: example_controllers/minimal.py")
args = parser.parse_args()
controller = Path(args.controller)
# Parse ~ as the home directory.
if str(controller.resolve())[0] == "~":
controller = Path.home().joinpath(str(controller.resolve())[2:])

if not controller.exists():
raise Exception(f"Controller not found: {controller.resolve()}")

# Write the config file. This is used by controller.spec to point to the correct controller.
config_text = f"controller={str(controller.resolve())}"
ini_path = output_dir.joinpath("freeze.ini")
ini_path.write_text(config_text, encoding="utf-8")
p = system()

# Create the executable.
dist_path = str(output_dir.resolve()).replace("\\", "/")
spec = "controller.spec"
if p == "Linux":
call(["python3", "-m", "PyInstaller", spec, "--onefile", "--distpath", dist_path])
exe = "tdw_controller"
elif p == "Darwin":
call(["python3", "-m", "PyInstaller", spec, "--onefile", "--distpath", dist_path])
exe = "tdw_controller.app"
elif p == "Windows":
call(["py", "-3", "-m", "PyInstaller", spec, "--onefile", "--distpath", dist_path])
exe = "tdw_controller.exe"
else:
raise Exception(f"Platform not supported: {p}")

exe_path = output_dir.joinpath(exe)
assert exe_path.exists()
print(f"Created: {exe_path.resolve()}")

# Remove the config file.
ini_path.unlink()
5 changes: 3 additions & 2 deletions Python/setup.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from setuptools import setup, find_packages
from pathlib import Path

__version__ = "1.6.4.0"
__version__ = "1.6.5.0"
readme_path = Path('../README.md')
if readme_path.exists():
long_description = readme_path.read_text(encoding='utf-8')
Expand Down Expand Up @@ -30,5 +30,6 @@
packages=find_packages(),
include_package_data=True,
keywords='unity simulation ml machine-learning',
install_requires=['pyzmq', 'numpy', 'scipy', 'pillow', 'tqdm', 'psutil', 'boto3', 'botocore', 'requests'],
install_requires=['pyzmq', 'numpy', 'scipy', 'pillow', 'tqdm', 'psutil', 'boto3', 'botocore', 'requests',
"pyinstaller"],
)
2 changes: 1 addition & 1 deletion Python/tdw/flatbuffers/encode.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from .compat import memoryview_type
from .compat import import_numpy, NumpyRequiredForThisFeature

np = import_numpy()
import numpy as np

def Get(packer_type, buf, head):
""" Get decodes a value at buf[head] using `packer_type`. """
Expand Down
2 changes: 1 addition & 1 deletion Python/tdw/flatbuffers/number_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from . import packer
from .compat import import_numpy, NumpyRequiredForThisFeature

np = import_numpy()
import numpy as np

# For reference, see:
# https://docs.python.org/2/library/ctypes.html#ctypes-fundamental-data-types-2
Expand Down
2 changes: 1 addition & 1 deletion Python/tdw/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "1.6.4"
__version__ = "1.6.5"
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
| --- | --- |
| [C# code](https://github.com/threedworld-mit/tdw/blob/master/Documentation/contributions/c_sharp_sources.md) | Access to C# backend source code |
| [Releases](https://github.com/threedworld-mit/tdw/blob/master/Documentation/misc_frontend/releases.md) | Release versioning in TDW. |
| [Freezing your code](https://github.com/threedworld-mit/tdw/blob/master/Documentation/misc_frontend/freeze.md) | "Freeze" your controller into a compiled executable. |

# Remote Server

Expand Down

0 comments on commit 3a30ab4

Please sign in to comment.