diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml new file mode 100644 index 000000000..5f4be7533 --- /dev/null +++ b/.github/workflows/cibuildwheel.yml @@ -0,0 +1,124 @@ +name: cibuildwheel +# https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#concurrency +# https://docs.github.com/en/developers/webhooks-and-events/events/github-event-types#pullrequestevent +concurrency: + group: ${{ github.workflow }}-${{ github.event.number }}-${{ github.event.type }} + cancel-in-progress: true + +on: [push, pull_request] + +jobs: + build_wheels: + name: Build wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + env: + min-numpy-version: "1.19.5" + min-numpy-hash: "51/60/3f0fe5b7675a461d96b9d6729beecd3532565743278a9c3fe6dd09697fa7" + CIBW_ARCHS_MACOS: "x86_64 universal2 arm64" + strategy: + fail-fast: false + matrix: + os: [ubuntu-18.04, windows-latest, macos-10.15] + cibw_archs: ["auto"] + include: + - os: ubuntu-18.04 + cibw_archs: "aarch64" + + steps: + - name: Set up QEMU + if: matrix.cibw_archs == 'aarch64' + uses: docker/setup-qemu-action@v1 + with: + platforms: arm64 + + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - uses: actions/setup-python@v2 + name: Install Python + with: + python-version: '3.10' + + - uses: actions/cache@v2 + id: numpy-cache + with: + path: numpy-aarch64-cache/ + key: numpy-${{ matrix.cibw_archs }}-cache-${{ env.min-numpy-version }} + + - name: Install cibuildwheel + run: | + python -m pip install cibuildwheel==2.3.1 + + - name: Build minimum NumPy for aarch64 + if: matrix.cibw_archs == 'aarch64' && steps.numpy-cache.outputs.cache-hit != 'true' + run: | + wget https://files.pythonhosted.org/packages/${{ env.min-numpy-hash }}/numpy-${{ env.min-numpy-version }}.zip + unzip numpy-${{ env.min-numpy-version }}.zip + cd numpy-${{ env.min-numpy-version }} + python -m cibuildwheel --output-dir ../numpy-aarch64-cache + env: + CIBW_BUILD: "cp36-* cp37-* cp38-* cp39-* cp310-*" + CIBW_ARCHS: aarch64 + + - name: Build wheels + uses: pypa/cibuildwheel@v2.3.1 + + - uses: actions/upload-artifact@v2 + with: + path: ./wheelhouse/*.whl + + build_sdist: + name: Build source distribution + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - uses: actions/setup-python@v2 + name: Install Python + with: + python-version: '3.7' + + - name: Build sdist + run: | + python -m pip install --upgrade pip setuptools build wheel + python -m build --sdist -o dist/ + + - uses: actions/upload-artifact@v2 + with: + path: dist/*.tar.gz + + # upload_test_pypi: + # needs: [build_wheels, build_sdist] + # runs-on: ubuntu-latest + # # upload to Test PyPI for every commit on main branch + # if: github.event_name == 'push' && github.event.ref == 'refs/heads/main' + # steps: + # - uses: actions/download-artifact@v2 + # with: + # name: artifact + # path: dist + # - uses: pypa/gh-action-pypi-publish@master + # with: + # user: __token__ + # password: ${{ secrets.LABGRAPH_TEST_PYPI_TOKEN }} + # repository_url: https://test.pypi.org/legacy/ + + # upload_pypi: + # needs: [build_wheels, build_sdist] + # runs-on: ubuntu-latest + # # upload to PyPI on every tag starting with 'v' + # if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/v') + # # alternatively, to publish when a GitHub Release is created, use the following rule: + # # if: github.event_name == 'release' && github.event.action == 'published' + # steps: + # - uses: actions/download-artifact@v2 + # with: + # name: artifact + # path: dist + # - uses: pypa/gh-action-pypi-publish@master + # with: + # user: __token__ + # password: ${{ secrets.LABGRAPH_PYPI_TOKEN }} diff --git a/Dockerfile b/Dockerfile index 0048e1d5a..52e39ba5e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,7 +12,7 @@ SHELL ["/bin/bash", "--login", "-c"] RUN g++ --version # Install Python, Java, wget, vim -RUN yum install -y python2 python36 python36-devel wget java-1.8.0-openjdk \ +RUN yum install -y python2 python3 python3-devel wget java-1.8.0-openjdk \ java-1.8.0-openjdk-devel vim # Install Ant @@ -32,7 +32,7 @@ ENV JAVA_HOME="/usr/lib/jvm/java-1.8.0-openjdk" # Build Buck WORKDIR "/opt/buck" RUN ant -Run ln -s /opt/buck/bin/buck /usr/bin/buck +RUN ln -s /opt/buck/bin/buck /usr/bin/buck # Install Watchman WORKDIR "/opt/watchman" @@ -45,25 +45,117 @@ RUN cp lib/* /usr/local/lib RUN chmod 755 /usr/local/bin/watchman RUN chmod 2777 /usr/local/var/run/watchman +# Unpack the static python libraries from the manylinux image. +WORKDIR "/opt/_internal/" +RUN XZ_OPT=-9e tar -xf static-libs-for-embedding-only.tar.xz cpython-*/lib/libpython*.a + # Copy LabGraph files -WORKDIR "/opt/labgraph" +WORKDIR "/opt/labgraph/" + +# Copy labgraph into the container COPY . . +# Create a user to act as the builder. +# This is done to prevent root installs with pip. +RUN useradd -m -r builder && \ + chown -R builder /opt/labgraph +USER builder + +# Build LabGraph Wheel +RUN python3.6 -m pip install build && \ + python3.6 -m build --sdist --wheel +# Build LabGraph Wheel +RUN sed -i 's/3.6/3.7/' /opt/labgraph/third-party/python/DEFS && \ + python3.7 -m pip install build && \ + python3.7 -m build --sdist --wheel +# Build LabGraph Wheel +RUN sed -i 's/3.7/3.8/' /opt/labgraph/third-party/python/DEFS && \ + python3.8 -m pip install build && \ + python3.8 -m build --sdist --wheel +# Build LabGraph Wheel +RUN sed -i 's/3.8/3.9/' /opt/labgraph/third-party/python/DEFS && \ + python3.9 -m pip install build && \ + python3.9 -m build --sdist --wheel # Build LabGraph Wheel -RUN python3.6 setup_py36.py install --user -RUN python3.6 setup_py36.py sdist bdist_wheel -RUN python3.6 -m pip install auditwheel -RUN auditwheel repair dist/*whl -w dist/ - -# Test LabGraph -RUN python3.6 -m pytest --pyargs -v labgraph._cthulhu -RUN python3.6 -m pytest --pyargs -v labgraph.events -RUN python3.6 -m pytest --pyargs -v labgraph.graphs -RUN python3.6 -m pytest --pyargs -v labgraph.loggers -RUN python3.6 -m pytest --pyargs -v labgraph.messages -RUN python3.6 -m pytest --pyargs -v labgraph.runners.tests.test_process_manager -RUN python3.6 -m pytest --pyargs -v labgraph.runners.tests.test_aligner -RUN python3.6 -m pytest --pyargs -v labgraph.runners.tests.test_cpp -RUN python3.6 -m pytest --pyargs -v labgraph.runners.tests.test_exception -RUN python3.6 -m pytest --pyargs -v labgraph.runners.tests.test_launch -RUN python3.6 -m pytest --pyargs -v labgraph.runners.tests.test_runner +RUN sed -i 's/3.9/3.10/' /opt/labgraph/third-party/python/DEFS && \ + python3.10 -m pip install build && \ + python3.10 -m build --sdist --wheel --no-isolation + +# Build wheels for each python version +RUN find dist/*whl -exec auditwheel repair {} -w dist/ \; + +# Test LabGraph for python3.6 +RUN python3.6 -m pip install \ + dist/labgraph-2.0.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl && \ + python3.6 -m pytest --pyargs -v labgraph._cthulhu && \ + python3.6 -m pytest --pyargs -v labgraph.events && \ + python3.6 -m pytest --pyargs -v labgraph.graphs && \ + python3.6 -m pytest --pyargs -v labgraph.loggers && \ + python3.6 -m pytest --pyargs -v labgraph.messages && \ + python3.6 -m pytest --pyargs -v labgraph.runners.tests.test_process_manager && \ + python3.6 -m pytest --pyargs -v labgraph.runners.tests.test_aligner && \ + python3.6 -m pytest --pyargs -v labgraph.runners.tests.test_cpp && \ + python3.6 -m pytest --pyargs -v labgraph.runners.tests.test_exception && \ + python3.6 -m pytest --pyargs -v labgraph.runners.tests.test_launch && \ + python3.6 -m pytest --pyargs -v labgraph.runners.tests.test_runner + +# Test LabGraph for python3.7 +RUN python3.7 -m pip install \ + dist/labgraph-2.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl && \ + python3.7 -m pytest --pyargs -v labgraph._cthulhu && \ + python3.7 -m pytest --pyargs -v labgraph.events && \ + python3.7 -m pytest --pyargs -v labgraph.graphs && \ + python3.7 -m pytest --pyargs -v labgraph.loggers && \ + python3.7 -m pytest --pyargs -v labgraph.messages && \ + python3.7 -m pytest --pyargs -v labgraph.runners.tests.test_process_manager && \ + python3.7 -m pytest --pyargs -v labgraph.runners.tests.test_aligner && \ + python3.7 -m pytest --pyargs -v labgraph.runners.tests.test_cpp && \ + python3.7 -m pytest --pyargs -v labgraph.runners.tests.test_exception && \ + python3.7 -m pytest --pyargs -v labgraph.runners.tests.test_launch && \ + python3.7 -m pytest --pyargs -v labgraph.runners.tests.test_runner + +# Test LabGraph for python3.8 +RUN python3.8 -m pip install \ + dist/labgraph-2.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl && \ + python3.8 -m pytest --pyargs -v labgraph._cthulhu && \ + python3.8 -m pytest --pyargs -v labgraph.events && \ + python3.8 -m pytest --pyargs -v labgraph.graphs && \ + python3.8 -m pytest --pyargs -v labgraph.loggers && \ + python3.8 -m pytest --pyargs -v labgraph.messages && \ + python3.8 -m pytest --pyargs -v labgraph.runners.tests.test_process_manager && \ + python3.8 -m pytest --pyargs -v labgraph.runners.tests.test_aligner && \ + python3.8 -m pytest --pyargs -v labgraph.runners.tests.test_cpp && \ + python3.8 -m pytest --pyargs -v labgraph.runners.tests.test_exception && \ + python3.8 -m pytest --pyargs -v labgraph.runners.tests.test_launch && \ + python3.8 -m pytest --pyargs -v labgraph.runners.tests.test_runner + +# Test LabGraph for python3.9 +RUN python3.9 -m pip install \ + dist/labgraph-2.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl && \ + python3.9 -m pytest --pyargs -v labgraph._cthulhu && \ + python3.9 -m pytest --pyargs -v labgraph.events && \ + python3.9 -m pytest --pyargs -v labgraph.graphs && \ + python3.9 -m pytest --pyargs -v labgraph.loggers && \ + python3.9 -m pytest --pyargs -v labgraph.messages && \ + python3.9 -m pytest --pyargs -v labgraph.runners.tests.test_process_manager && \ + python3.9 -m pytest --pyargs -v labgraph.runners.tests.test_aligner && \ + python3.9 -m pytest --pyargs -v labgraph.runners.tests.test_cpp && \ + python3.9 -m pytest --pyargs -v labgraph.runners.tests.test_exception && \ + python3.9 -m pytest --pyargs -v labgraph.runners.tests.test_launch && \ + python3.9 -m pytest --pyargs -v labgraph.runners.tests.test_runner + +# Test LabGraph for python3.10 +RUN python3.10 -m pip install \ + dist/labgraph-2.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl && \ + python3.10 -m pytest --pyargs -v labgraph._cthulhu && \ + python3.10 -m pytest --pyargs -v labgraph.events && \ + python3.10 -m pytest --pyargs -v labgraph.graphs && \ + python3.10 -m pytest --pyargs -v labgraph.loggers && \ + python3.10 -m pytest --pyargs -v labgraph.messages && \ + python3.10 -m pytest --pyargs -v labgraph.runners.tests.test_process_manager && \ + python3.10 -m pytest --pyargs -v labgraph.runners.tests.test_aligner && \ + python3.10 -m pytest --pyargs -v labgraph.runners.tests.test_cpp && \ + python3.10 -m pytest --pyargs -v labgraph.runners.tests.test_exception && \ + python3.10 -m pytest --pyargs -v labgraph.runners.tests.test_launch && \ + python3.10 -m pytest --pyargs -v labgraph.runners.tests.test_runner + diff --git a/buck_ext.py b/buck_ext.py index 0490e6158..ceeca9b15 100644 --- a/buck_ext.py +++ b/buck_ext.py @@ -12,10 +12,10 @@ CONFIG_FILE = { "Windows": "win.buckconfig", -}.get(platform.system(), "unix.buckconfig") + "Darwin" : "unix.buckconfig", + "Linux" : "manylinux.buckconfig", +}[platform.system()] -if platform.system() == 'Linux' and platform.linux_distribution()[0] == 'CentOS Linux': - CONFIG_FILE = "manylinux.buckconfig" class BuckExtension(Extension): def __init__(self, name: str, target: str) -> None: diff --git a/extensions/labgraph_protocol/MANIFEST.in b/extensions/labgraph_protocol/MANIFEST.in deleted file mode 100644 index a0cf1a49e..000000000 --- a/extensions/labgraph_protocol/MANIFEST.in +++ /dev/null @@ -1,3 +0,0 @@ -include labgraph_protocol/examples/audio_player.mp3 -include labgraph_protocol/examples/display_image.jpg -include labgraph_protocol/examples/video_player.mp4 diff --git a/extensions/labgraph_protocol/README.md b/extensions/labgraph_protocol/labgraph_protocol/README.md similarity index 73% rename from extensions/labgraph_protocol/README.md rename to extensions/labgraph_protocol/labgraph_protocol/README.md index a8aaddabf..4b8d7f8d7 100644 --- a/extensions/labgraph_protocol/README.md +++ b/extensions/labgraph_protocol/labgraph_protocol/README.md @@ -12,9 +12,7 @@ Make sure to install labgraph before proceeding ``` cd labgraph/extensions/labgraph_protocol -# HACK: PyQt5-sip has to be installed before PyQt5 -pip install PyQt5-sip==4.19.18 -python setup.py install +python -m pip install . ``` ### Testing: @@ -22,7 +20,7 @@ python setup.py install To make sure things are working you can run any of the following examples ``` -python -m extensions.labgraph_protocol.labgraph_protocol.examples PROTOCOL_NAME +python -m labgraph_protocol.examples PROTOCOL_NAME ``` `PROTOCOL_NAME` can be any of: - `audio_player` diff --git a/extensions/labgraph_protocol/setup.py b/extensions/labgraph_protocol/setup.py index 29604016f..3d3c3a1d6 100644 --- a/extensions/labgraph_protocol/setup.py +++ b/extensions/labgraph_protocol/setup.py @@ -11,10 +11,10 @@ packages=find_packages(), python_requires=">=3.6", install_requires=[ - "dataclasses==0.6", + "dataclasses>=0.6", "labgraph>=2.0.0", - "PyQt5-sip==4.19.18", - "PyQt5==5.13.0", + "PyQt5-sip>=4.19.18", + "PyQt5>=5.13.0", ], include_package_data=True, ) diff --git a/extensions/labgraph_viz/labgraph_viz/README.md b/extensions/labgraph_viz/labgraph_viz/README.md index 024eafdb6..2316d566d 100644 --- a/extensions/labgraph_viz/labgraph_viz/README.md +++ b/extensions/labgraph_viz/labgraph_viz/README.md @@ -12,7 +12,7 @@ Make sure to install labgraph before proceeding ``` cd labgraph/extensions/labgraph_viz -python setup.py install +python -m pip install . ``` ### Testing: @@ -20,12 +20,12 @@ python setup.py install To make sure things are working you can run any of the following examples ``` -python -m extensions.labgraph_viz.labgraph_viz.application_example -python -m extensions.labgraph_viz.labgraph_viz.bar_plot_example -python -m extensions.labgraph_viz.labgraph_viz.heat_map_example -python -m extensions.labgraph_viz.labgraph_viz.line_plot_example -python -m extensions.labgraph_viz.labgraph_viz.scatter_plot_example -python -m extensions.labgraph_viz.labgraph_viz.spatial_plot_example +python -m labgraph_viz.examples.application_example +python -m labgraph_viz.examples.bar_plot_example +python -m labgraph_viz.examples.heat_map_example +python -m labgraph_viz.examples.line_plot_example +python -m labgraph_viz.examples.scatter_plot_example +python -m labgraph_viz.examples.spatial_plot_example ``` ### Contributers: diff --git a/extensions/labgraph_viz/labgraph_viz/application/application.py b/extensions/labgraph_viz/labgraph_viz/application/application.py index 43db842a3..a5ad25c44 100644 --- a/extensions/labgraph_viz/labgraph_viz/application/application.py +++ b/extensions/labgraph_viz/labgraph_viz/application/application.py @@ -50,7 +50,7 @@ def run_plot(self) -> None: else: for plot in self.plots: plot.stop() - + # Using a very simple layout # You can subclass and override this method to use another layout def setup_window(self) -> None: diff --git a/extensions/labgraph_viz/labgraph_viz/examples/application_example.py b/extensions/labgraph_viz/labgraph_viz/examples/application_example.py index c29c49d14..8a58360ec 100644 --- a/extensions/labgraph_viz/labgraph_viz/examples/application_example.py +++ b/extensions/labgraph_viz/labgraph_viz/examples/application_example.py @@ -47,8 +47,8 @@ class Generator(lg.Node): async def generate_noise(self) -> lg.AsyncPublisher: while True: yield self.BARPLOT_OUTPUT, BarPlotMessage( - domain=np.arange(self.config.num_features + 1), - range=np.random.rand(self.config.num_features), + domain=np.arange(self.config.num_features + 1, dtype=np.int32), + range=np.random.rand(self.config.num_features).astype(np.float64), ) await asyncio.sleep(1 / self.config.sample_rate) @@ -107,7 +107,7 @@ def setup(self) -> None: # Add plots to application self.application.plots = [self.line_plot, self.bar_plot] - + # Connect the Generator outputs to the Plot inputs def connections(self) -> lg.Connections: return ( diff --git a/extensions/labgraph_viz/labgraph_viz/examples/bar_plot_example.py b/extensions/labgraph_viz/labgraph_viz/examples/bar_plot_example.py index a575ac6f6..56b6da1fa 100644 --- a/extensions/labgraph_viz/labgraph_viz/examples/bar_plot_example.py +++ b/extensions/labgraph_viz/labgraph_viz/examples/bar_plot_example.py @@ -38,8 +38,8 @@ class Generator(lg.Node): async def generate_noise(self) -> lg.AsyncPublisher: while True: yield self.OUTPUT, RandomMessage( - domain=np.arange(self.config.num_features + 1), - range=np.random.rand(self.config.num_features), + domain=np.arange(self.config.num_features + 1, dtype=np.int32), + range=np.random.rand(self.config.num_features).astype(np.float64), ) await asyncio.sleep(1 / self.config.sample_rate) diff --git a/extensions/labgraph_viz/labgraph_viz/examples/heat_map_example.py b/extensions/labgraph_viz/labgraph_viz/examples/heat_map_example.py index 0979c54fa..740e80c2f 100644 --- a/extensions/labgraph_viz/labgraph_viz/examples/heat_map_example.py +++ b/extensions/labgraph_viz/labgraph_viz/examples/heat_map_example.py @@ -19,7 +19,7 @@ # Message for heat map data class HeatMapMessage(lg.Message): - channel_map: lg.NumpyDynamicType(dtype=np.int32) + channel_map: lg.NumpyDynamicType() data: np.ndarray @@ -113,7 +113,7 @@ def setup(self) -> None: ) self.WINDOW.HEATMAP = self.HEATMAP self.WINDOW.COLOR_MAP = self.COLOR_MAP - + # Connect the message to both the heat map and color map def connections(self) -> lg.Connections: return ( diff --git a/extensions/labgraph_viz/labgraph_viz/examples/scatter_plot_example.py b/extensions/labgraph_viz/labgraph_viz/examples/scatter_plot_example.py index f46ddb332..b9cd38be8 100644 --- a/extensions/labgraph_viz/labgraph_viz/examples/scatter_plot_example.py +++ b/extensions/labgraph_viz/labgraph_viz/examples/scatter_plot_example.py @@ -18,8 +18,8 @@ # Message for bar plot data class RandomMessage(lg.Message): - red: Dict[str, int] - green: Dict[str, int] + red: Dict[str, np.ndarray] + green: Dict[str, np.ndarray] # Configuration for the generator diff --git a/extensions/labgraph_viz/labgraph_viz/examples/spatial_plot_example.py b/extensions/labgraph_viz/labgraph_viz/examples/spatial_plot_example.py index d94000948..b10c47255 100644 --- a/extensions/labgraph_viz/labgraph_viz/examples/spatial_plot_example.py +++ b/extensions/labgraph_viz/labgraph_viz/examples/spatial_plot_example.py @@ -84,15 +84,15 @@ def setup(self) -> None: labels={"bottom": "Bottom Label", "left": "Left Label"}, points={ "sources": SpatialPlotPoints( - x=[2.5], - y=[2.5], + x=np.array([2.5]), + y=np.array([2.5]), style=SpatialPlotStyle( symbol="s", symbolSize=20 ), ), "detectors": SpatialPlotPoints( - x=[1,1,2,2,3,3,4,4], - y=[2,3,1,4,1,4,2,3], + x=np.array([1,1,2,2,3,3,4,4]), + y=np.array([2,3,1,4,1,4,2,3]), style=SpatialPlotStyle( symbol="d", symbolSize=20 ), diff --git a/extensions/labgraph_viz/labgraph_viz/plots/bar_plot.py b/extensions/labgraph_viz/labgraph_viz/plots/bar_plot.py index c9b07e21c..23cae9781 100644 --- a/extensions/labgraph_viz/labgraph_viz/plots/bar_plot.py +++ b/extensions/labgraph_viz/labgraph_viz/plots/bar_plot.py @@ -7,7 +7,7 @@ import labgraph as lg import pyqtgraph as pg from pyqtgraph.Qt import QtCore -from typing import Any, Dict, Optional +from typing import Any, Union, Optional from .common import TIMER_INTERVAL @@ -51,7 +51,7 @@ class BarPlotConfig(lg.Config): """ x_field: str y_field: str - style: Dict[str, Any] = None + style: Union[str, Any] = None external_timer: bool = False diff --git a/extensions/labgraph_viz/labgraph_viz/plots/color_map.py b/extensions/labgraph_viz/labgraph_viz/plots/color_map.py index af33587f0..ab6f0760c 100644 --- a/extensions/labgraph_viz/labgraph_viz/plots/color_map.py +++ b/extensions/labgraph_viz/labgraph_viz/plots/color_map.py @@ -123,4 +123,5 @@ def build(self) -> Any: return self.state.plot def stop(self) -> None: - self.state.timer.stop() + if not self.config.external_timer: + self.state.timer.stop() diff --git a/extensions/labgraph_viz/labgraph_viz/plots/heat_map.py b/extensions/labgraph_viz/labgraph_viz/plots/heat_map.py index 9c08e38e6..e0fbd3fb7 100644 --- a/extensions/labgraph_viz/labgraph_viz/plots/heat_map.py +++ b/extensions/labgraph_viz/labgraph_viz/plots/heat_map.py @@ -3,7 +3,7 @@ # For usage see examples/heat_map_example.py -from typing import Any, Dict, Optional, Tuple +from typing import Any, Union, Optional, Tuple import labgraph as lg import numpy as np @@ -37,7 +37,7 @@ class HeatMapConfig(lg.Config): """ data: str = None channel_map: str = None - style: Dict[str, Any] = None + style: Union[str, Any] = None shape: Tuple[int, int] = None color_map: str = "jet" external_timer: bool = False @@ -113,4 +113,5 @@ def build(self) -> Any: return self.state.plot def stop(self) -> None: - self.state.timer.stop() + if not self.config.external_timer: + self.state.timer.stop() diff --git a/extensions/labgraph_viz/labgraph_viz/plots/spatial_plot.py b/extensions/labgraph_viz/labgraph_viz/plots/spatial_plot.py index c98ec2c60..456fed4f0 100644 --- a/extensions/labgraph_viz/labgraph_viz/plots/spatial_plot.py +++ b/extensions/labgraph_viz/labgraph_viz/plots/spatial_plot.py @@ -56,7 +56,7 @@ def setup(self) -> None: color_lut = color_map._lut.view(np.ndarray) colors = (color_lut * 255).astype(int) self._color_map = pg.ColorMap(np.linspace(0.0, 1.0, colors.shape[0]), colors) - self._np_mkBrush = np.vectorize(pg.fn.mkBrush) + self._np_mkBrush = np.vectorize(pg.mkBrush) def update(self) -> None: for name, subplot in self.state.subplots.items(): diff --git a/extensions/labgraph_viz/setup.py b/extensions/labgraph_viz/setup.py index 5a6a3e99e..99479ab28 100644 --- a/extensions/labgraph_viz/setup.py +++ b/extensions/labgraph_viz/setup.py @@ -9,13 +9,13 @@ version="1.0.0", description="Some useful visualizations for labgraph", packages=find_packages(), - python_requires=">=3.6, <3.7", + python_requires=">=3.6", install_requires=[ - "dataclasses==0.6", - "labgraph>=1.0.2", - "matplotlib==3.1.1", - "numpy==1.16.4", - "PyQt5-sip==12.8.1", - "pyqtgraph==0.11.1", + "dataclasses", + "labgraph", + "matplotlib", + "numpy", + "PyQt5", + "pyqtgraph", ], ) diff --git a/extensions/psychopy_example/psychopy_example/README.md b/extensions/psychopy_example/psychopy_example/README.md index 25086e999..4d526884d 100644 --- a/extensions/psychopy_example/psychopy_example/README.md +++ b/extensions/psychopy_example/psychopy_example/README.md @@ -12,7 +12,7 @@ Make sure to install labgraph before proceeding ``` cd extensions/psychopy_example -python setup.py install +python -m pip install . ``` #### Install-time issues @@ -26,7 +26,7 @@ python setup.py install To make sure things are working you can run the following example from the labgraph dir: ``` -python -m extensions.psychopy_example.psychopy_example +python -m psychopy_example ``` ### Contributers: diff --git a/extensions/psychopy_example/setup.py b/extensions/psychopy_example/setup.py index 340295ac0..5a3f3d81b 100644 --- a/extensions/psychopy_example/setup.py +++ b/extensions/psychopy_example/setup.py @@ -3,7 +3,6 @@ from setuptools import find_packages, setup - setup( name="psychopy_example", version="1.0.0", @@ -12,19 +11,20 @@ package_data = { "": ["images/*.png"] }, - python_requires=">=3.6, <3.7", + python_requires=">=3.6", install_requires=[ # psychopy does not enforce versions, causing conflicts with labgraph deps. # Forcing specific versions at install time should help mitigate this issue. "cython", - "scipy==1.5.4", - "pandas==0.25.1", - "xarray==0.16.2", - "moviepy==1.0.1", - "pyglet==1.5.16", + "scipy>=1.5.4", + "pandas>=0.25.1", + "xarray>=0.16.2", + "moviepy>=1.0.1", + "pyglet>=1.5.16", # Actual dependencies for this package "importlib-resources~=5.2", "labgraph>=1.0.3", "psychopy~=3.2.4", ], ) + diff --git a/extensions/yaml_support/setup.py b/extensions/yaml_support/setup.py index b5805ad8b..97453a461 100644 --- a/extensions/yaml_support/setup.py +++ b/extensions/yaml_support/setup.py @@ -10,10 +10,11 @@ packages=find_packages(), python_requires=">=3.6", install_requires=[ - "labgraph==1.0.2", - "numpy==1.16.4", - "PyYAML==6.0", - "StrEnum==0.4.7", - "typed_ast==1.4.3" + "labgraph>=1.0.2", + "numpy>=1.16.4", + "PyYAML>=6.0", + "StrEnum>=0.4.7", + "typed_ast>=1.4.3" ], ) + diff --git a/extensions/yaml_support/yaml_support/README.md b/extensions/yaml_support/yaml_support/README.md index 0f8f26510..5ed4f5228 100644 --- a/extensions/yaml_support/yaml_support/README.md +++ b/extensions/yaml_support/yaml_support/README.md @@ -14,7 +14,7 @@ Make sure to install labgraph before proceeding ``` cd labgraph/extensions/yaml_support -python setup.py install +python -m pip install . ``` ### Testing: @@ -22,6 +22,6 @@ python setup.py install To make sure things are working you can run ``` -python -m extensions.yaml_support.yaml_support.tests.test_lg_yaml_api +python -m yaml_support.tests.test_lg_yaml_api ``` diff --git a/extensions/yaml_support/yaml_support/_parser/base_parser.py b/extensions/yaml_support/yaml_support/_parser/base_parser.py index 9959d20b7..854958eb0 100644 --- a/extensions/yaml_support/yaml_support/_parser/base_parser.py +++ b/extensions/yaml_support/yaml_support/_parser/base_parser.py @@ -3,7 +3,7 @@ from abc import ABCMeta, abstractmethod from typing import List -from extensions.yaml_support.yaml_support.model.base_model import BaseModel +from ..model.base_model import BaseModel class BaseParser(metaclass=ABCMeta): diff --git a/extensions/yaml_support/yaml_support/_parser/lg_units_parser.py b/extensions/yaml_support/yaml_support/_parser/lg_units_parser.py index 5e7e29b01..eb966414a 100644 --- a/extensions/yaml_support/yaml_support/_parser/lg_units_parser.py +++ b/extensions/yaml_support/yaml_support/_parser/lg_units_parser.py @@ -3,8 +3,8 @@ from typed_ast import ast3 from .base_parser import BaseParser -from extensions.yaml_support.yaml_support.model.lg_unit_model import LabGraphUnitsModel -from extensions.yaml_support.yaml_support.enums.lg_units_enum import LabGraphBuiltinUnits +from ..model.lg_unit_model import LabGraphUnitsModel +from ..enums.lg_units_enum import LabGraphBuiltinUnits from typed_ast.ast3 import ( AsyncFunctionDef, NodeVisitor, diff --git a/extensions/yaml_support/yaml_support/model/lg_unit_model.py b/extensions/yaml_support/yaml_support/model/lg_unit_model.py index 14a67d595..c5e1b1f04 100644 --- a/extensions/yaml_support/yaml_support/model/lg_unit_model.py +++ b/extensions/yaml_support/yaml_support/model/lg_unit_model.py @@ -2,8 +2,8 @@ # Copyright 2004-present Facebook. All Rights Reserved. from .base_model import BaseModel -from extensions.yaml_support.yaml_support.serializer.yaml_serializer import YamlSerializer -from extensions.yaml_support.yaml_support.enums.lg_units_enum import LabGraphBuiltinUnits +from ..serializer.yaml_serializer import YamlSerializer +from ..enums.lg_units_enum import LabGraphBuiltinUnits from typing import Dict, Union diff --git a/extensions/yaml_support/yaml_support/tests/test_lg_yaml_api.py b/extensions/yaml_support/yaml_support/tests/test_lg_yaml_api.py index d5a5201a0..1fc832dfc 100644 --- a/extensions/yaml_support/yaml_support/tests/test_lg_yaml_api.py +++ b/extensions/yaml_support/yaml_support/tests/test_lg_yaml_api.py @@ -4,9 +4,9 @@ import unittest import os import pathlib -from extensions.yaml_support.yaml_support.loader.python_file_loader import PythonFileLoader -from extensions.yaml_support.yaml_support.loader.errors.errors import PythonFileLoaderError -from extensions.yaml_support.yaml_support._parser.lg_units_parser import LabGraphUnitsParser +from ..loader.python_file_loader import PythonFileLoader +from ..loader.errors.errors import PythonFileLoaderError +from .._parser.lg_units_parser import LabGraphUnitsParser class TestLabgraphYamlAPI(unittest.TestCase): diff --git a/labgraph/graphs/tests/test_harness.py b/labgraph/graphs/tests/test_harness.py index 1769f8363..5579fbc70 100644 --- a/labgraph/graphs/tests/test_harness.py +++ b/labgraph/graphs/tests/test_harness.py @@ -2,6 +2,12 @@ # Copyright 2004-present Facebook. All Rights Reserved. import asyncio +# asyncio.Task.all_tasks deprecated in python3.7 +try: + asyncio.all_tasks +except AttributeError as e: + asyncio.all_tasks = asyncio.Task.all_tasks + import dataclasses import pytest @@ -133,7 +139,7 @@ def test_run_with_harness_max_num_results() -> None: with pytest.raises(TypeError): run_with_harness(MyNode, _not_a_generator, max_num_results=1) # type: ignore loop = get_event_loop() - for task in asyncio.Task.all_tasks(loop=loop): + for task in asyncio.all_tasks(loop=loop): task.cancel() loop.run_until_complete(loop.shutdown_asyncgens()) @@ -153,7 +159,7 @@ def test_run_async_max_num_results() -> None: with pytest.raises(TypeError): run_async(_not_a_generator, max_num_results=1) # type: ignore loop = get_event_loop() - for task in asyncio.Task.all_tasks(loop=loop): + for task in asyncio.all_tasks(loop=loop): task.cancel() loop.run_until_complete(loop.shutdown_asyncgens()) diff --git a/labgraph/runners/local_runner.py b/labgraph/runners/local_runner.py index 4b6ae4fd8..59127b790 100644 --- a/labgraph/runners/local_runner.py +++ b/labgraph/runners/local_runner.py @@ -59,7 +59,6 @@ EXCEPTION_STREAM_SUFFIX = "_EXCEPTION" - @dataclass class LocalRunnerState: """ @@ -490,6 +489,11 @@ def run(self) -> None: # e.g., in the `LocalRunner`. import asyncio + # asyncio.Task.all_tasks deprecated in python3.7 + try: + asyncio.all_tasks + except AttributeError as e: + asyncio.all_tasks = asyncio.Task.all_tasks # Create event loop loop = asyncio.new_event_loop() @@ -571,7 +575,7 @@ def run(self) -> None: logger.debug(f"{self.module}:background thread:shutting down async gens") # https://bugs.python.org/issue38559 - for task in asyncio.Task.all_tasks(loop=loop): + for task in asyncio.all_tasks(loop=loop): task.cancel() loop.run_until_complete(loop.shutdown_asyncgens()) @@ -580,7 +584,7 @@ def run(self) -> None: while True: time.sleep(ASYNCIO_SHUTDOWN_POLL_TIME) pending = [ - task for task in asyncio.Task.all_tasks(loop=loop) if not task.done() + task for task in asyncio.all_tasks(loop=loop) if not task.done() ] if len(pending) == 0: logger.debug(f"{self.module}:background thread:closing event loop") @@ -643,6 +647,11 @@ async def run_publisher_method( self, publisher_method: Callable[[], AsyncIterable[Tuple[Topic, Message]]] ) -> None: import asyncio + # asyncio.Task.all_tasks deprecated in python3.7 + try: + asyncio.all_tasks + except AttributeError as e: + asyncio.all_tasks = asyncio.Task.all_tasks async for topic, message in publisher_method(): topic_path = self.module._get_topic_path(topic) @@ -739,6 +748,11 @@ def wrap_all_callbacks( loop: The event loop to schedule the callbacks on. """ import asyncio + # asyncio.Task.all_tasks deprecated in python3.7 + try: + asyncio.all_tasks + except AttributeError as e: + asyncio.all_tasks = asyncio.Task.all_tasks def callback(message: Message) -> None: if loop.is_closed(): diff --git a/labgraph/util/typing.py b/labgraph/util/typing.py index b5df9a20d..3dd1b0105 100644 --- a/labgraph/util/typing.py +++ b/labgraph/util/typing.py @@ -4,16 +4,14 @@ import sys import typing - -# TODO: Remove all 3.8 checks once 3.6 is dead. -def is_py38() -> bool: - return sys.version_info > (3, 8) - +try: + typing.get_origin +except AttributeError as e: + import typing_compat + typing.get_origin = typing_compat.get_origin # type_ is an instance of typing._GenericAlias # generic is an instance of typing.Generic def is_generic_subclass(type_: typing.Any, generic: typing.Any) -> bool: - if is_py38(): return typing.get_origin(type_) is generic # type: ignore - else: - return issubclass(type_, generic) + diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..6bd027d3b --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,54 @@ +[project] +requires-python = ">=3.6" + +[tool.cibuildwheel] +# Install pytest before running CIBW_TEST_COMMAND +test-requires = "pytest" +# Run the project tests against the installed wheel using `nose` +test-command = "python -m pytest --pyargs -v labgraph --ignore=labgraph/devices" +# Increase pip debugging output +build-verbosity = 2 +build = ["cp36-*", "cp37-*", "cp38-*", "cp39-*", "cp310-*"] +skip = ["pp*", "*-win32"] +manylinux-x86_64-image = "quay.io/pypa/manylinux2014_x86_64:latest" +manylinux-i686-image= "quay.io/pypa/manylinux2014_i686:latest" + +[tool.cibuildwheel.linux] +# Run a script that's inside your project +before-all = "bash scripts/prepare_for_build_linux.sh" + +[tool.cibuildwheel.macos] +# Run a script that's inside your project +before-all = "bash scripts/prepare_for_build_macos.sh" +environment = { MACOSX_DEPLOYMENT_TARGET="10.15" } + +[tool.cibuildwheel.windows] +# Run a script that's inside your project +before-all = "powershell scripts/prepare_for_build_windows.bat" + +# Need to replace the python version in the DEFS file before each build. +[[tool.cibuildwheel.overrides]] +select = "cp37-*" +before-build = "sed -i 's/3.6/3.7/' /opt/labgraph/third-party/python/DEFS" +[[tool.cibuildwheel.overrides]] +select = "cp38-*" +before-build = "sed -i 's/3.6/3.8/' /opt/labgraph/third-party/python/DEFS" +[[tool.cibuildwheel.overrides]] +select = "cp39-*" +before-build = "sed -i 's/3.6/3.9/' /opt/labgraph/third-party/python/DEFS" +[[tool.cibuildwheel.overrides]] +select = "cp310-*" +before-build = "sed -i 's/3.6/3.10/' /opt/labgraph/third-party/python/DEFS" +# Do the same for windows. +# [[tool.cibuildwheel.windows.overrides]] +# select = "cp37-*" +# before-build = "get-content \".\\third-party\\python\\DEFS\" | %{$_ -replace \"3.6\",\"3.7\"}" +# [[tool.cibuildwheel.windows.overrides]] +# select = "cp38-*" +# before-build = "get-content \".\\third-party\\python\\DEFS\" | %{$_ -replace \"3.6\",\"3.8\"}" +# [[tool.cibuildwheel.windows.overrides]] +# select = "cp39-*" +# before-build = "get-content \".\\third-party\\python\\DEFS\" | %{$_ -replace \"3.6\",\"3.9\"}" +# [[tool.cibuildwheel.windows.overrides]] +# select = "cp310-*" +# before-build = "get-content \".\\third-party\\python\\DEFS\" | %{$_ -replace \"3.6\",\"3.10\"}" diff --git a/scripts/prepare_for_build_linux.sh b/scripts/prepare_for_build_linux.sh new file mode 100644 index 000000000..578bc98d4 --- /dev/null +++ b/scripts/prepare_for_build_linux.sh @@ -0,0 +1,45 @@ +# Install devtoolset-9 +yum update -y +yum install -y centos-release-scl +yum install -y devtoolset-9 +echo "source /opt/rh/devtoolset-9/enable" >> /etc/bashrc +g++ --version + +# Install Python, Java, wget, vim +yum install -y python2 python3 python3-devel wget java-1.8.0-openjdk \ + java-1.8.0-openjdk-devel vim + +# Install Ant +cd /tmp +wget https://downloads.apache.org/ant/binaries/apache-ant-1.10.11-bin.zip +unzip apache-ant-1.10.11-bin.zip \ + && mv apache-ant-1.10.11/ /opt/ant \ + && ln -s /opt/ant/bin/ant /usr/bin/ant + +# Download Buck +cd /opt +git clone https://github.com/facebook/buck.git + +# Set JAVA_HOME +ENV JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk + +# Build Buck +cd /opt/buck +ant +ln -s /opt/buck/bin/buck /usr/bin/buck + +# Install Watchman +cd /opt/ +wget https://github.com/facebook/watchman/releases/download/v2020.09.21.00/watchman-v2020.09.21.00-linux.zip +unzip watchman-v2020.09.21.00-linux.zip +rm watchman-v2020.09.21.00-linux.zip +cd /opt/watchman-v2020.09.21.00-linux +mkdir -p /usr/local/{bin,lib} /usr/local/var/run/watchman +cp bin/* /usr/local/bin +cp lib/* /usr/local/lib +chmod 755 /usr/local/bin/watchman +chmod 2777 /usr/local/var/run/watchman + +# Unpack the static python libraries from the manylinux image. +cd /opt/_internal/ +XZ_OPT=-9e tar -xf static-libs-for-embedding-only.tar.xz cpython-*/lib/libpython*.a diff --git a/scripts/prepare_for_build_macos.sh b/scripts/prepare_for_build_macos.sh new file mode 100644 index 000000000..cf8a40519 --- /dev/null +++ b/scripts/prepare_for_build_macos.sh @@ -0,0 +1,7 @@ +/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" +brew tap facebook/fb +brew update && brew upgrade +brew install watchman ant buck +mv /usr/local/Cellar/buck/2021.01.12.01_1/ /usr/local/Cellar/buck/2021.01.12.01 +brew link buck + diff --git a/scripts/prepare_for_build_windows.bat b/scripts/prepare_for_build_windows.bat new file mode 100644 index 000000000..3e2f38122 --- /dev/null +++ b/scripts/prepare_for_build_windows.bat @@ -0,0 +1,4 @@ +:: Install build deps using Chocolatey +choco install -y watchman buck ant visualstudio2019-workload-vctools --package-parameters "--allWorkloads --includeRecommended --includeOptional --passive --locale en-US" +C:\Program^ Files^ ^(x86^)\Microsoft^ Visual^ Studio\2019\BuildTools\VC\Auxiliary\Build\vcvars64.bat && set > %temp%/vcvars.txt +powershell -command "Get-Content \"$env:temp\vcvars.txt\" | Foreach-Object { if ($_ -match \"^(.*?)=(.*)$\") { Set-Content \"env:\$($matches[1])\" $matches[2] } }" diff --git a/setup.py b/setup.py index e6bd28c32..8137bb9aa 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,6 @@ ), ] - setup( name="labgraph", version="2.0.1", @@ -37,19 +36,27 @@ ext_modules=LIBRARY_EXTENSIONS, cmdclass={"build_ext": buck_build_ext}, install_requires=[ - "appdirs>=1.4.4", - "click>=7.1.2", - "h5py>=3.3.0", - "matplotlib>=3.1.2", - "mypy>=0.910", - "numpy>=1.19.5", - "psutil>=5.6.7", - "pytest>=3.10.1", - "pytest_mock>=2.0.0", - "pyzmq>=19.0.2", - "typeguard>=2.10.0", - "typing_extensions>=3.7.4", - "websockets>=8.1", - "yappi>=1.2.5", + "appdirs==1.4.4", + "click==8.0.3", + "mypy==0.931", + "pyzmq==22.3.0", + "yappi==1.3.3", + "psutil==5.9.0", + "pytest-mock==3.6.1", + "pytest==6.2.5", + "pylsl==1.15.0", + "typeguard==2.13.3", + "typing-compat==0.1.0;python_version<'3.8'", + "h5py==2.10.0;python_version<'3.8'", + "h5py==3.6.0;python_version>='3.8'", + "dataclasses==0.8;python_version=='3.6'", + "numpy==1.19.5;python_version=='3.6'", + "numpy==1.21.5;python_version=='3.7'", + "numpy==1.22.1;python_version>='3.8'", + "websockets==9.1;python_version=='3.6'", + "websockets==10.1;python_version>='3.7'", + "matplotlib==3.3.4;python_version=='3.6'", + "matplotlib==3.5.1;python_version>='3.7'", ], ) + diff --git a/setup_py36.py b/setup_py36.py deleted file mode 100644 index dea4091f1..000000000 --- a/setup_py36.py +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env python3 -# Copyright 2004-present Facebook. All Rights Reserved. - -from buck_ext import BuckExtension, buck_build_ext -from setuptools import find_packages, setup - -LIBRARY_EXTENSIONS = [ - BuckExtension( - name="cthulhubindings", - target="//:cthulhubindings#default,shared", - ), - BuckExtension( - name="labgraph_cpp", - target="//:labgraph_cpp_bindings#default,shared", - ), - BuckExtension( - name="MyCPPNodes", - target="//:MyCPPNodes#default,shared", - ), -] - - -setup( - name="labgraph", - version="1.0.3", - description="Python streaming framework", - packages=find_packages(), - package_data={"labgraph": ["tests/mypy.ini"]}, - python_requires=">=3.6, <3.7", - ext_modules=LIBRARY_EXTENSIONS, - cmdclass={"build_ext": buck_build_ext}, - install_requires=[ - "appdirs==1.4.3", - "click==7.0", - "dataclasses==0.6", - "h5py==2.10.0", - "matplotlib==3.1.1", - "mypy==0.782", - "numpy==1.16.4", - "psutil==5.6.7", - "pylsl==1.15.0", - "pytest==3.10.1", - "pytest_mock==2.0.0", - "pyzmq==18.1.0", - "typeguard==2.5.1", - "typing_extensions>=3.7.4.3", - "websockets==8.1", - "yappi==1.2.5", - ], -) \ No newline at end of file diff --git a/third-party/python/DEFS b/third-party/python/DEFS index 6c85d084e..e1655245e 100644 --- a/third-party/python/DEFS +++ b/third-party/python/DEFS @@ -30,11 +30,13 @@ def config_var(key): PYTHON_SHARED_LIB_DIR = ( config_var("BINDIR") if platform.system() == "Windows" else config_var("LIBDIR") ) -PYTHON_SHARED_LIB_FILENAME = ( - "python" + PYTHON_VERSION.replace(".", "") + ".dll" - if platform.system() == "Windows" - else config_var("LDLIBRARY") -) + +PYTHON_SHARED_LIB_FILENAME = { + "Windows": "python" + PYTHON_VERSION.replace(".", "") + ".dll", + "Darwin": "libpython" + PYTHON_VERSION + ".dylib", + "Linux": config_var("LDLIBRARY") +}[platform.system()] + PYTHON_SHARED_LIB_PATH = PYTHON_SHARED_LIB_DIR + os.sep + PYTHON_SHARED_LIB_FILENAME PYTHON_STATIC_LIB_DIR = { @@ -47,3 +49,4 @@ PYTHON_STATIC_LIB_FILENAME = "python" + PYTHON_VERSION.replace(".", "") + ".lib" PYTHON_STATIC_LIB_PATH = PYTHON_STATIC_LIB_DIR + os.sep + PYTHON_STATIC_LIB_FILENAME PYTHON_INCLUDE_DIR = config_var("INCLUDEPY") + diff --git a/unix.buckconfig b/unix.buckconfig index 09b51e1f3..bcef43222 100644 --- a/unix.buckconfig +++ b/unix.buckconfig @@ -1,5 +1,5 @@ [cxx] - cxxppflags = -Wall -std=c++17 + cxxppflags = -Wall -std=c++17 -fvisibility=hidden -fvisibility-inlines-hidden cxxpp = /usr/bin/g++ cxx = /usr/bin/g++ ld = /usr/bin/g++ diff --git a/win.buckconfig b/win.buckconfig index ed8ba91a4..631b9d241 100644 --- a/win.buckconfig +++ b/win.buckconfig @@ -4,7 +4,20 @@ cxxpp = win/cxxpp.bat cxxpp_type = windows cxx = win/cxx.bat - cxxflags = /std:c++17 /EHsc cxx_type = windows ld = win/ld.bat linker_platform = windows + + cxxppflags = \ + /DNDEBUG + + cxxflags = \ + /std:c++17 \ + /EHsc \ + /external:W0 \ + /O2 \ + /GL \ + /MD + + ldflags = \ + /LTCG