diff --git a/.github/workflows/python-package-with-uv.yml b/.github/workflows/python-package-with-uv.yml new file mode 100644 index 0000000000..a3bcd5f1fb --- /dev/null +++ b/.github/workflows/python-package-with-uv.yml @@ -0,0 +1,192 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: OpenTimelineIO w/ uv + +# for configuring which build will be a C++ coverage build / coverage report +env: + GH_COV_PY: "3.10" + GH_COV_OS: ubuntu-latest + GH_DEPENDABOT: dependabot + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + cpp_build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + # Unfortunately the CMake test target is OS dependent so we set it as + # a variable here. + include: + - os: ubuntu-latest + OTIO_TEST_TARGET: test + - os: macos-latest + OTIO_TEST_TARGET: test + + env: + OTIO_BUILD_CONFIG: Release + OTIO_BUILD_DIR: ${{ github.workspace }}/build + OTIO_INSTALL_DIR: ${{ github.workspace }}/install + OTIO_CONSUMER_TEST_BUILD_DIR: ${{ github.workspace }}/consumertest + + steps: + - uses: actions/checkout@v4 + with: + submodules: 'recursive' + - name: Install coverage dependency + if: matrix.os == env.GH_COV_OS && github.actor != env.GH_DEPENDABOT + run: | + sudo apt-get install lcov + - name: Build + run: | + cmake -E make_directory ${{ env.OTIO_BUILD_DIR }} + cd ${{ env.OTIO_BUILD_DIR }} + cmake ${{ github.workspace }} -DCMAKE_INSTALL_PREFIX=${{ env.OTIO_INSTALL_DIR }} -DOTIO_SHARED_LIBS=OFF -DOTIO_CXX_COVERAGE=ON + cmake --build . --config ${{ env.OTIO_BUILD_CONFIG }} + - name: Run tests + run: | + cd ${{ env.OTIO_BUILD_DIR }} + cmake --build . --target ${{ matrix.OTIO_TEST_TARGET }} --config ${{ env.OTIO_BUILD_CONFIG }} + - name: Collect code coverage + if: matrix.os == env.GH_COV_OS && github.actor != env.GH_DEPENDABOT + run: | + cd ${{ env.OTIO_BUILD_DIR }} + lcov --rc lcov_branch_coverage=1 --rc no_exception_branch=1 --ignore-errors mismatch --capture -b . --directory . --output-file=coverage.info -q + cat coverage.info | sed "s/SF:.*src/SF:src/g" > coverage.filtered.info + lcov --remove coverage.filtered.info '*/tests/*' --output-file=coverage.filtered.info -q + lcov --list coverage.filtered.info + # TODO: Should the Codecov web pages show the results of the C++ or Python tests? + - name: Upload coverage to Codecov + if: matrix.os == env.GH_COV_OS && github.actor != env.GH_DEPENDABOT + uses: codecov/codecov-action@v3.1.4 + with: + files: ${{ env.OTIO_BUILD_DIR }}/coverage.filtered.info + flags: unittests + name: opentimelineio-codecov + fail_ci_if_error: true + - name: Install + run: | + cd ${{ env.OTIO_BUILD_DIR }} + cmake --build . --target install --config ${{ env.OTIO_BUILD_CONFIG }} + - name: Consumer tests + run: | + cmake -E make_directory ${{ env.OTIO_CONSUMER_TEST_BUILD_DIR }} + cd ${{ env.OTIO_CONSUMER_TEST_BUILD_DIR }} + cmake ${{ github.workspace }}/tests/consumer -DCMAKE_PREFIX_PATH=${{ env.OTIO_INSTALL_DIR }} + + py_build_test: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + # python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] + python-version: ['3.11', '3.12'] + # No include/exclude needed for this simplified matrix yet + # No shell specification needed, uv handles environment activation + + env: + OTIO_CXX_COVERAGE_BUILD: ON + OTIO_CXX_BUILD_TMP_DIR: ${{ github.workspace }}/build + + steps: + - uses: actions/checkout@v4 + with: + submodules: 'recursive' + + - name: Install uv + uses: astral-sh/setup-uv@v3 + + - name: Set up Python ${{ matrix.python-version }} + run: uv python install ${{ matrix.python-version }} + + - name: Install C++ coverage dependency (Linux only) + if: matrix.os == 'ubuntu-latest' && matrix.python-version == env.GH_COV_PY && github.actor != env.GH_DEPENDABOT + run: | + echo 'OTIO_CXX_DEBUG_BUILD=1' >> $GITHUB_ENV + sudo apt-get update && sudo apt-get install -y lcov + + - name: Install project and development dependencies + run: | + uv sync --all-extras --dev + + - name: Run check-manifest and lint check + run: uv run make ci-prebuild + + - name: Run tests w/ python coverage + run: uv run make ci-postbuild + + # (only on ubuntu/specific python version) + - name: Generate C++ coverage report + if: matrix.os == 'ubuntu-latest' && matrix.python-version == env.GH_COV_PY && github.actor != env.GH_DEPENDABOT + run: uv run make lcov + + - name: Upload coverage to Codecov + if: matrix.os == 'ubuntu-latest' && matrix.python-version == env.GH_COV_PY && github.actor != env.GH_DEPENDABOT + uses: codecov/codecov-action@v4 + with: + flags: py-unittests + name: py-opentimelineio-codecov + fail_ci_if_error: false + env: + # based on: https://github.com/codecov/codecov-action?tab=readme-ov-file#usage + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + + package_wheels: + needs: py_build_test + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest] + python-build: ['cp311', 'cp312'] + exclude: + - { os: macos-latest, python-build: 'cp37' } + steps: + - uses: actions/checkout@v4 + + - name: Build wheels (Python 3) + uses: pypa/cibuildwheel@v2.22.0 + with: + output-dir: wheelhouse + env: + CIBW_BUILD: ${{ matrix.python-build }}* + CIBW_SKIP: '*musllinux*' + CIBW_ARCHS_LINUX: native + CIBW_MANYLINUX_X86_64_IMAGE: manylinux2014 + CIBW_MANYLINUX_AARCH64_IMAGE: manylinux2014 + MACOSX_DEPLOYMENT_TARGET: 10.14 + + - uses: actions/upload-artifact@v4 + with: + name: wheel-${{ matrix.os }}-${{ matrix.python-build }} + path: ./wheelhouse/*.whl + + package_sdist: + needs: py_build_test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: 'recursive' + + - uses: actions/setup-python@v5.4.0 + + - name: Install pypa/build + run: python -m pip install build --user + + - name: Generate sdist + run: python -m build -s . + + - uses: actions/upload-artifact@v4 + with: + name: sdist + path: dist diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 2c19b23d02..dd33bfa5f6 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -69,15 +69,15 @@ jobs: cat coverage.info | sed "s/SF:.*src/SF:src/g" > coverage.filtered.info lcov --remove coverage.filtered.info '*/tests/*' --output-file=coverage.filtered.info -q lcov --list coverage.filtered.info -# \todo Should the Codecov web pages show the results of the C++ or Python tests? -# - name: Upload coverage to Codecov -# if: matrix.os == env.GH_COV_OS && github.actor != env.GH_DEPENDABOT -# uses: codecov/codecov-action@v3.1.4 -# with: -# files: ${{ env.OTIO_BUILD_DIR }}/coverage.filtered.info -# flags: unittests -# name: opentimelineio-codecov -# fail_ci_if_error: true + # TODO: Should the Codecov web pages show the results of the C++ or Python tests? + - name: Upload coverage to Codecov + if: matrix.os == env.GH_COV_OS && github.actor != env.GH_DEPENDABOT + uses: codecov/codecov-action@v3.1.4 + with: + files: ${{ env.OTIO_BUILD_DIR }}/coverage.filtered.info + flags: unittests + name: opentimelineio-codecov + fail_ci_if_error: true - name: Install run: | cd ${{ env.OTIO_BUILD_DIR }} diff --git a/.gitignore b/.gitignore index dd2e1c7e4a..f233965570 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,6 @@ cpp_cov_html/ lcov_html_report/ *.so *.pyd + +# uv +uv.lock diff --git a/pyproject.toml b/pyproject.toml index 4ef0814172..9070d5e263 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,6 +6,65 @@ requires = [ ] build-backend = "setuptools.build_meta" +[project] +name = "OpenTimelineIO" +version = "0.18.0.dev1" +description = "Editorial interchange format and API" +readme = "README.md" +requires-python = ">=3.7, !=3.9.0" +license = { text = "Apache-2.0" } +authors = [ + { name = "Contributors to the OpenTimelineIO project", email = "otio-discussion@lists.aswf.io" } +] +keywords = ["film", "tv", "editing", "editorial", "edit", "non-linear", "edl", "time"] +classifiers = [ + "Development Status :: 4 - Beta", + "Topic :: Multimedia :: Graphics", + "Topic :: Multimedia :: Video", + "Topic :: Multimedia :: Video :: Display", + "Topic :: Multimedia :: Video :: Non-Linear Editor", + "Topic :: Software Development :: Libraries :: Python Modules", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Operating System :: OS Independent", + "Natural Language :: English", +] + +dependencies = [ + 'importlib_metadata>=1.4; python_version < "3.8"', +] + +[project.urls] +Homepage = "http://opentimeline.io" +Source = "https://github.com/AcademySoftwareFoundation/OpenTimelineIO" +Documentation = "https://opentimelineio.readthedocs.io/" +Issues = "https://github.com/AcademySoftwareFoundation/OpenTimelineIO/issues" + +[project.scripts] +otiocat = "opentimelineio.console.otiocat:main" +otioconvert = "opentimelineio.console.otioconvert:main" +otiopluginfo = "opentimelineio.console.otiopluginfo:main" +otiostat = "opentimelineio.console.otiostat:main" +otiotool = "opentimelineio.console.otiotool:main" +otioview = "opentimelineview.console:main" +otioautogen_serialized_schema_docs = "opentimelineio.console.autogen_serialized_datamodel:main" + +[project.optional-dependencies] +dev = [ + "check-manifest", + "flake8>=3.5", + "coverage>=4.5", + "urllib3>=1.24.3", +] +view = [ + 'PySide2~=5.11; platform_machine=="x86_64"', + 'PySide6~=6.2; platform_machine=="aarch64"', +] + [tool.cibuildwheel.linux] archs = ["x86_64", "aarch64"] diff --git a/setup.py b/setup.py index 6f277ddbfa..099c278f10 100644 --- a/setup.py +++ b/setup.py @@ -3,11 +3,11 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright Contributors to the OpenTimelineIO project -"""Setup.py for installing OpenTimelineIO +"""Setup.py for installing OpenTimelineIO using setuptools. -For more information: -- see README.md -- http://opentimeline.io +This file is retained for compatibility and to handle the custom C++ build +process via CMake. Project metadata, dependencies, and entry points are +defined in pyproject.toml following PEP 621. """ import multiprocessing @@ -217,6 +217,7 @@ def cmake_install(self): # Metadata that gets stamped into the __init__ files during the build phase. +# This might be removable in the future if version stamping is handled differently. PROJECT_METADATA = { "version": "0.18.0.dev1", "author": 'Contributors to the OpenTimelineIO project', @@ -294,41 +295,11 @@ def run(self): OpenTimelineIO format.""" setup( - name='OpenTimelineIO', - description='Editorial interchange format and API', - long_description=LONG_DESCRIPTION, - long_description_content_type='text/markdown', - url='http://opentimeline.io', - project_urls={ - 'Source': - 'https://github.com/AcademySoftwareFoundation/OpenTimelineIO', - 'Documentation': - 'https://opentimelineio.readthedocs.io/', - 'Issues': - 'https://github.com/AcademySoftwareFoundation/OpenTimelineIO/issues', - }, - - classifiers=[ - 'Development Status :: 4 - Beta', - 'Topic :: Multimedia :: Graphics', - 'Topic :: Multimedia :: Video', - 'Topic :: Multimedia :: Video :: Display', - 'Topic :: Multimedia :: Video :: Non-Linear Editor', - 'Topic :: Software Development :: Libraries :: Python Modules', - 'License :: OSI Approved :: Apache Software License', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', - 'Operating System :: OS Independent', - 'Natural Language :: English', - ], - - keywords='film tv editing editorial edit non-linear edl time', - - platforms='any', + # name, version, description, author, license, etc. are in pyproject.toml + # url, project_urls are in pyproject.toml + # classifiers are in pyproject.toml + # keywords are in pyproject.toml + # platforms='any', # Handled by build system/wheels package_data={ 'opentimelineio': [ @@ -356,38 +327,10 @@ def run(self): 'opentimelineview': 'src/opentimelineview', }, - # Disallow 3.9.0 because of https://github.com/python/cpython/pull/22670 - python_requires='>=3.7, !=3.9.0', # noqa: E501 - - install_requires=[ - 'importlib_metadata>=1.4; python_version < "3.8"', - ], - entry_points={ - 'console_scripts': [ - 'otiocat = opentimelineio.console.otiocat:main', - 'otioconvert = opentimelineio.console.otioconvert:main', - 'otiopluginfo = opentimelineio.console.otiopluginfo:main', - 'otiostat = opentimelineio.console.otiostat:main', - 'otiotool = opentimelineio.console.otiotool:main', - 'otioview = opentimelineview.console:main', - ( - 'otioautogen_serialized_schema_docs = ' - 'opentimelineio.console.autogen_serialized_datamodel:main' - ), - ], - }, - extras_require={ - 'dev': [ - 'check-manifest', - 'flake8>=3.5', - 'coverage>=4.5', - 'urllib3>=1.24.3' - ], - 'view': [ - 'PySide2~=5.11; platform.machine=="x86_64"', - 'PySide6~=6.2; platform.machine=="aarch64"' - ] - }, + # python_requires is in pyproject.toml + # install_requires is in pyproject.toml + # entry_points are in pyproject.toml + # extras_require is in pyproject.toml # because we need to open() the adapters manifest, we aren't zip-safe zip_safe=False,