Switch to UV tooling (#2540)

This commit is contained in:
Veronica Berglyd Olsen
2025-10-22 22:20:15 +02:00
committed by GitHub
28 changed files with 291 additions and 224 deletions
+13 -12
View File
@@ -6,27 +6,28 @@ jobs:
buildAssets: buildAssets:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Python Setup - name: Checkout Source
uses: actions/setup-python@v6 uses: actions/checkout@v5
with:
python-version: "3.13"
architecture: x64
- name: Install Packages (apt) - name: Install System Packages
run: | run: |
sudo apt update sudo apt update
sudo apt install qttools5-dev-tools latexmk texlive texlive-latex-extra sudo apt install qttools5-dev-tools latexmk texlive texlive-latex-extra
- name: Checkout Source - name: Install UV and Python
uses: actions/checkout@v5 uses: astral-sh/setup-uv@v6
with:
python-version: "3.13"
enable-cache: true
- name: Install Packages (pip) - name: Sync UV
run: pip install -U -r requirements.txt -r docs/requirements.txt run: |
uv sync --no-dev --group docs
- name: Build Assets - name: Build Assets
run: | run: |
python pkgutils.py build-assets uv run --no-sync pkgutils.py build-assets
python pkgutils.py icons optional uv run --no-sync pkgutils.py icons optional
- name: Upload Artifacts - name: Upload Artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
+7 -3
View File
@@ -14,14 +14,18 @@ jobs:
with: with:
python-version: "3.13" python-version: "3.13"
architecture: x64 architecture: x64
- name: Install Packages (apt)
- name: Checkout Source
uses: actions/checkout@v5
- name: Install System Packages
run: | run: |
sudo apt update sudo apt update
sudo apt install qttools5-dev-tools sudo apt install qttools5-dev-tools
- name: Checkout Source
uses: actions/checkout@v5
- name: Build Assets - name: Build Assets
run: python pkgutils.py qtlrelease run: python pkgutils.py qtlrelease
- name: Upload Artifacts - name: Upload Artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
+18 -13
View File
@@ -14,24 +14,29 @@ jobs:
checkSyntax: checkSyntax:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Python Setup
uses: actions/setup-python@v6
with:
python-version: 3
architecture: x64
- name: Checkout Source - name: Checkout Source
uses: actions/checkout@v5 uses: actions/checkout@v5
- name: Install Dependencies
run: pip install -r requirements.txt -r requirements-dev.txt - name: Install UV and Python
uses: astral-sh/setup-uv@v6
with:
enable-cache: true
- name: Sync UV
run: |
uv sync --no-dev --group lint
- name: Ruff Check - name: Ruff Check
run: | run: |
ruff --version uv run --no-sync ruff --version
ruff check uv run --no-sync ruff check
- name: Pyright Check - name: Pyright Check
run: | run: |
pyright --version uv run --no-sync pyright --version
pyright uv run --no-sync pyright
- name: Isort Check - name: Isort Check
run: | run: |
isort --version uv run --no-sync isort --version
isort --check . uv run --no-sync isort --check .
+29 -18
View File
@@ -14,33 +14,44 @@ jobs:
testLinux: testLinux:
strategy: strategy:
matrix: matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] python-version:
- "3.11"
- "3.12"
- "3.13"
- "3.14"
fail-fast: false fail-fast: false
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Python Setup
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
architecture: x64
- name: Install Packages (apt)
run: |
sudo apt update
sudo apt install libenchant-2-dev qttools5-dev-tools
- name: Checkout Source - name: Checkout Source
uses: actions/checkout@v5 uses: actions/checkout@v5
- name: Install Dependencies (pip)
- name: Install System Packages
run: | run: |
pip install -U -r requirements.txt -r tests/requirements.txt sudo apt update
- name: Run Build Commands sudo apt install qttools5-dev-tools
- name: Install UV and Python
uses: astral-sh/setup-uv@v6
with:
python-version: ${{ matrix.python-version }}
- name: Sync UV
run: | run: |
python pkgutils.py qtlrelease uv sync --no-dev --group test
python pkgutils.py sample
- name: Build Assets
run: |
uv run --no-sync pkgutils.py qtlrelease
uv run --no-sync pkgutils.py sample
- name: Run Tests - name: Run Tests
run: | run: |
export QT_QPA_PLATFORM=offscreen export QT_QPA_PLATFORM=offscreen
python -m pytest -v --cov=novelwriter --timeout=60 uv run --no-sync coverage run -m pytest -v --timeout=60
uv run --no-sync coverage xml
- name: Upload to Codecov - name: Upload to Codecov
uses: codecov/codecov-action@v5 uses: codecov/codecov-action@v5
env: with:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true
+19 -13
View File
@@ -14,24 +14,30 @@ jobs:
testMac: testMac:
runs-on: macos-latest runs-on: macos-latest
steps: steps:
- name: Python Setup
uses: actions/setup-python@v6
with:
python-version: "3.13"
architecture: x64
- name: Install Packages (brew)
run: |
brew install enchant
- name: Checkout Source - name: Checkout Source
uses: actions/checkout@v5 uses: actions/checkout@v5
- name: Install Dependencies (pip)
- name: Install System Packages
run: | run: |
pip install -U pyobjc -r requirements.txt -r tests/requirements.txt brew install enchant
- name: Install UV and Python
uses: astral-sh/setup-uv@v6
with:
python-version: "3.13"
- name: Sync UV
run: |
uv sync --no-dev --group test --group macos
- name: Run Tests - name: Run Tests
run: | run: |
export QT_QPA_PLATFORM=offscreen export QT_QPA_PLATFORM=offscreen
python -m pytest -v --cov=novelwriter --timeout=60 uv run --no-sync coverage run -m pytest -v --timeout=60
uv run --no-sync coverage xml
- name: Upload to Codecov - name: Upload to Codecov
uses: codecov/codecov-action@v5 uses: codecov/codecov-action@v5
env: with:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true
+15 -10
View File
@@ -14,20 +14,25 @@ jobs:
testWin: testWin:
runs-on: windows-latest runs-on: windows-latest
steps: steps:
- name: Python Setup
uses: actions/setup-python@v6
with:
python-version: "3.13"
architecture: x64
- name: Checkout Source - name: Checkout Source
uses: actions/checkout@v5 uses: actions/checkout@v5
- name: Install Dependencies (pip)
- name: Install UV and Python
uses: astral-sh/setup-uv@v6
with:
python-version: "3.13"
- name: Sync UV
run: | run: |
pip install -U -r requirements.txt -r tests/requirements.txt uv sync --no-dev --group test
- name: Run Tests - name: Run Tests
run: | run: |
python -m pytest -v --cov=novelwriter --timeout=60 uv run --no-sync coverage run -m pytest -v --timeout=60
uv run --no-sync coverage xml
- name: Upload to Codecov - name: Upload to Codecov
uses: codecov/codecov-action@v5 uses: codecov/codecov-action@v5
env: with:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true
+1
View File
@@ -13,6 +13,7 @@ setup.iss
/setup/macos/Info.plist /setup/macos/Info.plist
/setup/windows/build /setup/windows/build
.venv .venv
/uv.lock
# Translations # Translations
/novelwriter/assets/i18n/*.qm /novelwriter/assets/i18n/*.qm
-8
View File
@@ -1,8 +0,0 @@
docutils>=0.17.1
pygments>=2.7
sphinx-book-theme
sphinx-copybutton
sphinx-design
sphinx-favicon
sphinx-intl
sphinx>=5.0
+24 -5
View File
@@ -7,6 +7,7 @@ Running from Source
.. _GitHub: https://github.com/vkbo/novelWriter/releases .. _GitHub: https://github.com/vkbo/novelWriter/releases
.. _PyPi: https://pypi.org/project/novelWriter/ .. _PyPi: https://pypi.org/project/novelWriter/
.. _Sphinx Docs: https://www.sphinx-doc.org/ .. _Sphinx Docs: https://www.sphinx-doc.org/
.. _uv: https://docs.astral.sh/uv/
This chapter describes various ways of running novelWriter directly from the source code, and how This chapter describes various ways of running novelWriter directly from the source code, and how
to build the various components like the translation files and documentation. to build the various components like the translation files and documentation.
@@ -28,6 +29,7 @@ by running:
.. _docs_technical_source_depend: .. _docs_technical_source_depend:
Dependencies Dependencies
============ ============
@@ -43,11 +45,20 @@ The following Python packages are needed to run all features of novelWriter:
If you want spell checking, you must install the ``PyEnchant`` package. The spell check library If you want spell checking, you must install the ``PyEnchant`` package. The spell check library
must be at least 3.0 to work with Windows. On Linux, 2.0 also works fine. must be at least 3.0 to work with Windows. On Linux, 2.0 also works fine.
If you install from PyPi, these dependencies should be installed automatically. If you install from If you install novelWriter from PyPi, these dependencies should be installed automatically.
source, dependencies can still be installed from PyPi with:
You can run novelWriter directly from source with uv_:
.. code-block:: bash .. code-block:: bash
uv run novelwriter
If you prefer to install dependencies using ``pip``, you must first generate the
``requirements.txt`` file:
.. code-block:: bash
python pkgutils.py gen-req
pip install -r requirements.txt pip install -r requirements.txt
.. note:: .. note::
@@ -136,12 +147,14 @@ running:
Building the Documentation Building the Documentation
========================== ==========================
A local copy of this documentation can be generated as HTML. This requires installing some Python A local copy of this documentation can be generated as HTML.
packages from PyPi:
If you're using ``pip``, you must first generate the ``requirements.txt`` file:
.. code-block:: bash .. code-block:: bash
pip install -r docs/requirements.txt python pkgutils.py gen-req docs
pip install -r requirements.txt
The documentation can then be built from the root folder in the source code by running: The documentation can then be built from the root folder in the source code by running:
@@ -149,6 +162,12 @@ The documentation can then be built from the root folder in the source code by r
make -C docs html make -C docs html
Or you can run directly with uv_:
.. code-block:: bash
uv run make -C docs html
If successful, the documentation should be available in the ``docs/build/html`` folder and you can If successful, the documentation should be available in the ``docs/build/html`` folder and you can
open the ``index.html`` file in your browser. open the ``index.html`` file in your browser.
+20 -15
View File
@@ -4,23 +4,12 @@
Running Tests Running Tests
************* *************
.. _uv: https://docs.astral.sh/uv/
The novelWriter source code is well covered by tests. The test framework used for the development The novelWriter source code is well covered by tests. The test framework used for the development
is ``pytest`` with the use of an extension for Qt. is ``pytest`` with the use of an extension for Qt.
Dependencies
============
The dependencies for running the tests can be installed with:
.. code-block:: bash
pip install -r tests/requirements.txt
This will install a couple of extra packages for coverage and test management. The minimum
requirement is ``pytest`` and ``pytest-qt``.
Simple Test Run Simple Test Run
=============== ===============
@@ -28,19 +17,35 @@ To run the tests, you simply need to execute the following from the root of the
.. code-block:: bash .. code-block:: bash
pytest uv run pytest
This uses uv_. See below for manually installing dependencies using ``pip``.
Since several of the tests involve opening up the novelWriter GUI, you may want to disable the GUI Since several of the tests involve opening up the novelWriter GUI, you may want to disable the GUI
for the duration of the test run. Moving your mouse while the tests are running may otherwise for the duration of the test run. Moving your mouse while the tests are running may otherwise
interfere with the execution of some tests. interfere with the execution of some tests.
You can disable the renderring of the GUI by setting the flag ``QT_QPA_PLATFORM=offscreen``: You can disable the rendering of the GUI by setting the flag ``QT_QPA_PLATFORM=offscreen``:
.. code-block:: bash .. code-block:: bash
export QT_QPA_PLATFORM=offscreen pytest export QT_QPA_PLATFORM=offscreen pytest
Dependencies
------------
To generate the requirements file and install dependencies using ``pip``, run:
.. code-block:: bash
python pkgutils.py gen-req app test
pip install -r tests/requirements.txt
This will install a couple of extra packages for coverage and test management. The minimum
requirement is ``pytest`` and ``pytest-qt``.
Advanced Options Advanced Options
================ ================
-8
View File
@@ -6,14 +6,6 @@ novelWriter Start Script
import os import os
import sys import sys
try:
import PyQt6.QtCore # noqa: F401
import PyQt6.QtGui # noqa: F401
import PyQt6.QtWidgets # noqa: F401
except Exception:
print("ERROR: Failed to load dependency PyQt6")
sys.exit(1)
os.curdir = os.path.abspath(os.path.dirname(__file__)) os.curdir = os.path.abspath(os.path.dirname(__file__))
if __name__ == "__main__": if __name__ == "__main__":
+5 -5
View File
@@ -49,9 +49,9 @@ __license__ = "GPLv3"
__author__ = "Veronica Berglyd Olsen" __author__ = "Veronica Berglyd Olsen"
__maintainer__ = "Veronica Berglyd Olsen" __maintainer__ = "Veronica Berglyd Olsen"
__email__ = "code@vkbo.net" __email__ = "code@vkbo.net"
__version__ = "2.8a2" __version__ = "2.8a3"
__hexversion__ = "0x020800a2" __hexversion__ = "0x020800a3"
__date__ = "2025-07-16" __date__ = "2025-10-18"
__status__ = "Stable" __status__ = "Stable"
__domain__ = "novelwriter.io" __domain__ = "novelwriter.io"
@@ -206,9 +206,9 @@ def main(sysArgs: list | None = None) -> GuiMain | None:
# Check Packages and Versions # Check Packages and Versions
errorData = [] errorData = []
errorCode = 0 errorCode = 0
if sys.hexversion < 0x030a00f0: if sys.hexversion < 0x030b00f0:
errorData.append( errorData.append(
f"At least Python 3.10 is required, found {CONFIG.verPyString}" f"At least Python 3.11 is required, found {CONFIG.verPyString}"
) )
errorCode |= 0x04 errorCode |= 0x04
if CONFIG.verQtValue < 0x060400: if CONFIG.verQtValue < 0x060400:
+1 -1
View File
@@ -615,7 +615,7 @@ class GuiTheme:
lookup = f"{prefix}{name} {key}" lookup = f"{prefix}{name} {key}"
keys.append(lookup) keys.append(lookup)
data[lookup] = (file.stem, name, mode == "dark", file) data[lookup] = (file.stem, name, mode == "dark", file)
except Exception: # noqa: PERF203 except Exception:
logger.error("Could not read file: %s", file) logger.error("Could not read file: %s", file)
logException() logException()
+30 -5
View File
@@ -40,7 +40,10 @@ import utils.build_windows
import utils.docs import utils.docs
import utils.icon_themes import utils.icon_themes
from utils.common import ROOT_DIR, SETUP_DIR, extractVersion, readFile, stripVersion, writeFile from utils.common import (
ROOT_DIR, SETUP_DIR, extractReqs, extractVersion, readFile, stripVersion,
writeFile
)
OS_LINUX = sys.platform.startswith("linux") OS_LINUX = sys.platform.startswith("linux")
OS_DARWIN = sys.platform.startswith("darwin") OS_DARWIN = sys.platform.startswith("darwin")
@@ -61,7 +64,7 @@ def installPackages(args: argparse.Namespace) -> None:
print("=======================") print("=======================")
print("") print("")
installQueue = ["pip", "-r requirements.txt"] installQueue = ["pip", *extractReqs(["app"])]
if args.mac: if args.mac:
installQueue.append("pyobjc") installQueue.append("pyobjc")
elif args.win: elif args.win:
@@ -130,6 +133,15 @@ def genMacOSPlist(args: argparse.Namespace) -> None:
writeFile(outDir / "Info.plist", plistXML) writeFile(outDir / "Info.plist", plistXML)
def genReqFiles(args: argparse.Namespace) -> None:
"""Generate requirements.txt file from pyproject.toml."""
select = [s.strip().lower() for s in args.groups] if args.groups else ["app"]
(ROOT_DIR / "requirements.txt").write_text(
"\n".join(extractReqs(select)),
encoding="utf-8"
)
if __name__ == "__main__": if __name__ == "__main__":
"""Parse command line options and run the commands.""" """Parse command line options and run the commands."""
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
@@ -222,7 +234,7 @@ if __name__ == "__main__":
cmdBuildHtmlDocs = parsers.add_parser( cmdBuildHtmlDocs = parsers.add_parser(
"docs-html", help="Build the HTML docs." "docs-html", help="Build the HTML docs."
) )
cmdBuildHtmlDocs.add_argument("lang", nargs="+") cmdBuildHtmlDocs.add_argument("lang", nargs="+", help="Language codes to generate docs for.")
cmdBuildHtmlDocs.set_defaults(func=utils.docs.buildHtmlDocs) cmdBuildHtmlDocs.set_defaults(func=utils.docs.buildHtmlDocs)
# Build Sample # Build Sample
@@ -295,10 +307,23 @@ if __name__ == "__main__":
cmdBuildClean.set_defaults(func=cleanBuildDirs) cmdBuildClean.set_defaults(func=cleanBuildDirs)
# Generate MacOS PList File # Generate MacOS PList File
cmdBuildMacOSPlist = parsers.add_parser( cmdGenMacOSPlist = parsers.add_parser(
"gen-plist", help="Generate an Info.plist for use in a MacOS Bundle." "gen-plist", help="Generate an Info.plist for use in a MacOS Bundle."
) )
cmdBuildMacOSPlist.set_defaults(func=genMacOSPlist) cmdGenMacOSPlist.set_defaults(func=genMacOSPlist)
# Generate Requirement File
cmdGenReq = parsers.add_parser(
"gen-req", help="Generate a requirements.txt file for pip."
)
cmdGenReq.add_argument(
"groups", nargs="*", help=(
"Groups to generate for, or 'all' to generate for all groups. "
"Use 'app' to generate for just the core application. "
"Defaults to app dependencies."
)
)
cmdGenReq.set_defaults(func=genReqFiles)
args = parser.parse_args() args = parser.parse_args()
args.func(args) args.func(args)
+31 -13
View File
@@ -4,16 +4,15 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "novelWriter" name = "novelWriter"
authors = [ authors = [{ name = "Veronica Berglyd Olsen", email = "code@vkbo.net" }]
{name = "Veronica Berglyd Olsen", email = "code@vkbo.net"},
]
description = "A plain text editor for planning and writing novels" description = "A plain text editor for planning and writing novels"
readme = { file = "setup/description_pypi.md", content-type = "text/markdown" } readme = { file = "setup/description_pypi.md", content-type = "text/markdown" }
license = "GPL-3.0-or-later AND Apache-2.0 AND CC-BY-4.0" license = "GPL-3.0-or-later AND Apache-2.0 AND CC-BY-4.0"
license-files = ["LICENSE.md", "setup/LICENSE-Apache-2.0.txt"] license-files = ["LICENSE.md", "setup/LICENSE-Apache-2.0.txt"]
dynamic = ["version"]
requires-python = ">=3.11"
classifiers = [ classifiers = [
"Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.13",
@@ -25,12 +24,32 @@ classifiers = [
"Natural Language :: English", "Natural Language :: English",
"Topic :: Text Editors", "Topic :: Text Editors",
] ]
requires-python = ">=3.10"
dependencies = [ dependencies = [
"pyqt6>=6.4", "pyqt6>=6.4",
"pyenchant>=3.0.0", "pyenchant>=3.3.0", # 3.3 is needed for MacOS AARCH64 builds
] ]
dynamic = ["version"]
[dependency-groups]
dev = [
{ include-group = "build" },
{ include-group = "docs" },
{ include-group = "test" },
{ include-group = "lint" },
]
build = ["build", "setuptools>=77.0.3"]
docs = [
"docutils>=0.17.1",
"pygments>=2.7",
"sphinx-book-theme",
"sphinx-copybutton",
"sphinx-design",
"sphinx-favicon",
"sphinx-intl",
"sphinx>=5.0",
]
test = ["coverage>=7.2.0", "pytest-qt", "pytest-timeout", "pytest>=6.0.0"]
lint = ["isort", "pyright", "ruff"]
macos = ["pyobjc"]
[project.urls] [project.urls]
Homepage = "https://novelwriter.io" Homepage = "https://novelwriter.io"
@@ -42,13 +61,13 @@ Issues = "https://github.com/vkbo/novelWriter/issues"
novelwriter = "novelwriter:main" novelwriter = "novelwriter:main"
[tool.setuptools.dynamic] [tool.setuptools.dynamic]
version = {attr = "novelwriter.__version__"} version = { attr = "novelwriter.__init__.__version__" }
[tool.setuptools.packages.find] [tool.setuptools.packages.find]
include = ["novelwriter*"] include = ["novelwriter*"]
[tool.isort] [tool.isort]
py_version="310" py_version = "311"
line_length = 99 line_length = 99
wrap_length = 79 wrap_length = 79
multi_line_output = 5 multi_line_output = 5
@@ -133,7 +152,7 @@ include = ["novelwriter"]
exclude = ["**/__pycache__"] exclude = ["**/__pycache__"]
reportIncompatibleMethodOverride = false reportIncompatibleMethodOverride = false
pythonVersion = "3.10" pythonVersion = "3.11"
[tool.pytest.ini_options] [tool.pytest.ini_options]
log_level = "DEBUG" log_level = "DEBUG"
@@ -146,9 +165,8 @@ markers = [
[tool.coverage.run] [tool.coverage.run]
branch = false branch = false
source = ["novelwriter"]
[tool.coverage.report] [tool.coverage.report]
precision = 2 precision = 2
exclude_also = [ exclude_also = ["if TYPE_CHECKING:"]
"if TYPE_CHECKING:"
]
-5
View File
@@ -1,5 +0,0 @@
-r requirements.txt
-r requirements-dev.txt
-r docs/requirements.txt
-r setup/requirements.txt
-r tests/requirements.txt
-4
View File
@@ -1,4 +0,0 @@
build
isort
pyright
ruff
-2
View File
@@ -1,2 +0,0 @@
pyqt6>=6.4
pyenchant>=3.0.0
+19 -10
View File
@@ -3,6 +3,7 @@
import argparse import argparse
import os import os
import shlex
import subprocess import subprocess
import sys import sys
@@ -12,27 +13,35 @@ if __name__ == "__main__":
parser.add_argument("-o", action="store_true", help="Run off screen") parser.add_argument("-o", action="store_true", help="Run off screen")
parser.add_argument("-r", action="store_true", help="Generate reports") parser.add_argument("-r", action="store_true", help="Generate reports")
parser.add_argument("-t", action="store_true", help="Generate terminal report") parser.add_argument("-t", action="store_true", help="Generate terminal report")
parser.add_argument("-m", help="Test modules") parser.add_argument("-u", action="store_true", help="Generate uncovered terminal report")
parser.add_argument("-k", help="Test filters") parser.add_argument("-m", help="Test modules", metavar="MARKEXPR")
parser.add_argument("-k", help="Test filters", metavar="EXPRESSION")
args = parser.parse_args() args = parser.parse_args()
env = os.environ.copy() env = os.environ.copy()
env["QT_SCALE_FACTOR"] = "1.0" env["QT_SCALE_FACTOR"] = "1.0"
cmd = [sys.executable, "-m", "pytest", "-vv"]
if args.r or args.t or args.u:
cmd = ["coverage", "run", "-m"]
else:
cmd = [sys.executable, "-m"]
cmd += ["pytest", "-vv"]
if args.o: if args.o:
env["QT_QPA_PLATFORM"] = "offscreen" env["QT_QPA_PLATFORM"] = "offscreen"
if args.r or args.t:
cmd += ["--cov=novelwriter"]
if args.r:
cmd += ["--cov-report=xml", "--cov-report=html"]
if args.t:
cmd += ["--cov-report=term"]
if args.m: if args.m:
cmd += ["-m", args.m] cmd += ["-m", args.m]
if args.k: if args.k:
cmd += ["-k", args.k] cmd += ["-k", args.k]
print("Calling:", " ".join(cmd)) print("Calling:", shlex.join(cmd))
subprocess.call(cmd, env=env) subprocess.call(cmd, env=env)
if args.r:
subprocess.call(["coverage", "xml"])
subprocess.call(["coverage", "html"])
if args.t and not args.u:
subprocess.call(["coverage", "report"])
if args.u:
subprocess.call(["coverage", "report", "--skip-covered "])
+5 -3
View File
@@ -4,24 +4,26 @@ Section: text
Priority: optional Priority: optional
Build-Depends: Build-Depends:
dh-python, dh-python,
pybuild-plugin-pyproject,
python3-build,
python3-setuptools, python3-setuptools,
python3-all, python3-all,
debhelper (>= 9), debhelper (>= 9),
python3 (>=3.10), python3 (>=3.11),
python3-pyqt6 (>= 6.4), python3-pyqt6 (>= 6.4),
python3-pyqt6.qtsvg (>= 6.4), python3-pyqt6.qtsvg (>= 6.4),
python3-enchant (>= 2.0), python3-enchant (>= 2.0),
qt6-image-formats-plugins (>= 6.4) qt6-image-formats-plugins (>= 6.4)
Standards-Version: 4.5.1 Standards-Version: 4.5.1
Homepage: https://novelwriter.io Homepage: https://novelwriter.io
X-Python3-Version: >= 3.10 X-Python3-Version: >= 3.11
Package: novelwriter Package: novelwriter
Architecture: all Architecture: all
Depends: Depends:
${misc:Depends}, ${misc:Depends},
${python3:Depends}, ${python3:Depends},
python3 (>=3.10), python3 (>=3.11),
python3-pyqt6 (>= 6.4), python3-pyqt6 (>= 6.4),
python3-pyqt6.qtsvg (>= 6.4), python3-pyqt6.qtsvg (>= 6.4),
python3-enchant (>= 2.0), python3-enchant (>= 2.0),
+2 -2
View File
@@ -1,7 +1,7 @@
#! /bin/bash #! /bin/bash
if [[ -z "$1" || -z "$2" || -z "$3" ]]; then if [[ -z "$1" || -z "$2" || -z "$3" ]]; then
echo "Not enouch input arguments" echo "Not enough input arguments"
exit 1 exit 1
fi fi
@@ -108,8 +108,8 @@ conda install -c conda-forge enchant hunspell-en --yes
# Install dependencies # Install dependencies
echo "Installing Python dependencies ..." echo "Installing Python dependencies ..."
python3 pkgutils.py gen-req
pip install -r "$SRC_DIR/requirements.txt" pip install -r "$SRC_DIR/requirements.txt"
pip install pyenchant==3.3.0rc1
# Leave conda env # Leave conda env
conda deactivate conda deactivate
+3 -23
View File
@@ -1,36 +1,23 @@
#!/bin/bash #!/bin/bash
set -e set -e
ENVPATH=/tmp/nwBuild
if [ ! -f pkgutils.py ]; then if [ ! -f pkgutils.py ]; then
echo "Must be called from the root folder of the source" echo "Must be called from the root folder of the source"
exit 1 exit 1
fi fi
echo ""
echo " Create Python Env"
echo "================================================================================"
echo ""
if [ ! -d $ENVPATH ]; then
python3 -m venv $ENVPATH
fi
source $ENVPATH/bin/activate
pip3 install -U build twine -r requirements.txt -r docs/requirements.txt
echo "" echo ""
echo " Building Dependencies" echo " Building Dependencies"
echo "================================================================================" echo "================================================================================"
echo "" echo ""
python3 pkgutils.py build-assets uv run pkgutils.py build-assets
python3 pkgutils.py icons optional uv run pkgutils.py icons optional
echo "" echo ""
echo " Building Packages" echo " Building Packages"
echo "================================================================================" echo "================================================================================"
echo "" echo ""
python3 -m build uv build
mkdir -pv dist_upload mkdir -pv dist_upload
cp -v dist/novelwriter-*.whl dist_upload/ cp -v dist/novelwriter-*.whl dist_upload/
cd dist_upload cd dist_upload
@@ -38,13 +25,6 @@ FILE=$(ls -t | head -1)
shasum -a 256 $FILE | tee $FILE.sha256 shasum -a 256 $FILE | tee $FILE.sha256
cd .. cd ..
echo ""
echo " Checking Packages"
echo "================================================================================"
echo ""
twine check dist/*
deactivate
echo "" echo ""
echo " Done!" echo " Done!"
echo "================================================================================" echo "================================================================================"
+4 -12
View File
@@ -1,8 +1,6 @@
#!/bin/bash #!/bin/bash
set -e set -e
ENVPATH=/tmp/nwBuild
if [ ! -f pkgutils.py ]; then if [ ! -f pkgutils.py ]; then
echo "Must be called from the root folder of the source" echo "Must be called from the root folder of the source"
exit 1 exit 1
@@ -12,18 +10,12 @@ echo ""
echo " Building Dependencies" echo " Building Dependencies"
echo "================================================================================" echo "================================================================================"
echo "" echo ""
if [ ! -d $ENVPATH ]; then uv run pkgutils.py build-assets
python3 -m venv $ENVPATH uv run pkgutils.py icons optional
fi
source $ENVPATH/bin/activate
pip3 install -r requirements.txt -r docs/requirements.txt
python3 pkgutils.py build-assets
python3 pkgutils.py icons optional
deactivate
echo "" echo ""
echo " Building Linux Packages" echo " Building Linux Packages"
echo "================================================================================" echo "================================================================================"
echo "" echo ""
python3 pkgutils.py build-deb --sign uv run pkgutils.py build-deb --sign
python3 pkgutils.py build-ubuntu --sign uv run pkgutils.py build-ubuntu --sign
-2
View File
@@ -1,2 +0,0 @@
setuptools>=77.0.3
twine
-5
View File
@@ -1,5 +0,0 @@
coverage>=7.2.0
pytest-cov
pytest-qt
pytest-timeout
pytest>=6.0.0
+1
View File
@@ -183,6 +183,7 @@ def launchpad(args: argparse.Namespace) -> None:
("24.04", "noble", True), ("24.04", "noble", True),
("25.04", "plucky", True), ("25.04", "plucky", True),
("25.10", "questing", False), ("25.10", "questing", False),
("26.04", "resolute", False),
] ]
print("Building Ubuntu packages for:") print("Building Ubuntu packages for:")
+2 -3
View File
@@ -30,7 +30,7 @@ import zipfile
from pathlib import Path from pathlib import Path
from utils.common import ( from utils.common import (
ROOT_DIR, SETUP_DIR, copySourceCode, extractVersion, readFile, ROOT_DIR, SETUP_DIR, copySourceCode, extractReqs, extractVersion, readFile,
removeRedundantQt, systemCall, writeFile removeRedundantQt, systemCall, writeFile
) )
@@ -45,7 +45,6 @@ def prepareCode(outDir: Path) -> None:
files = [ files = [
ROOT_DIR / "CREDITS.md", ROOT_DIR / "CREDITS.md",
ROOT_DIR / "LICENSE.md", ROOT_DIR / "LICENSE.md",
ROOT_DIR / "requirements.txt",
SETUP_DIR / "iss_license.txt", SETUP_DIR / "iss_license.txt",
SETUP_DIR / "windows" / "novelWriter.ico", SETUP_DIR / "windows" / "novelWriter.ico",
SETUP_DIR / "windows" / "novelWriter.exe", SETUP_DIR / "windows" / "novelWriter.exe",
@@ -85,7 +84,7 @@ def installRequirements(libDir: Path) -> None:
"""Install dependencies.""" """Install dependencies."""
print("Install dependencies ...") print("Install dependencies ...")
systemCall([ systemCall([
sys.executable, "-m", "pip", "install", "-r", "requirements.txt", "--target", libDir sys.executable, "-m", "pip", "install", *extractReqs(["app"]), "--target", libDir
]) ])
print("Done") print("Done")
print("") print("")
+21 -3
View File
@@ -23,6 +23,7 @@ from __future__ import annotations
import shutil import shutil
import subprocess import subprocess
import sys import sys
import tomllib
from pathlib import Path from pathlib import Path
@@ -30,6 +31,18 @@ ROOT_DIR = Path(__file__).parent.parent
SETUP_DIR = ROOT_DIR / "setup" SETUP_DIR = ROOT_DIR / "setup"
def extractReqs(groups: list[str]) -> list[str]:
"""Extract dependency groups from pyproject.toml."""
data = tomllib.loads((ROOT_DIR / "pyproject.toml").read_text(encoding="utf-8"))
reqs = []
if "app" in groups or "all" in groups:
reqs += data["project"]["dependencies"]
for group in data["dependency-groups"]:
if group in groups or "all" in groups:
reqs += [d for d in data["dependency-groups"][group] if isinstance(d, str)]
return reqs
def extractVersion(beQuiet: bool = False) -> tuple[str, str, str]: def extractVersion(beQuiet: bool = False) -> tuple[str, str, str]:
"""Extract the novelWriter version number without having to import """Extract the novelWriter version number without having to import
anything else from the main package. anything else from the main package.
@@ -92,14 +105,19 @@ def copySourceCode(dst: Path) -> None:
def copyPackageFiles(dst: Path, oldLicense: bool = False) -> None: def copyPackageFiles(dst: Path, oldLicense: bool = False) -> None:
"""Copy files needed for packaging.""" """Copy files needed for packaging."""
copyFiles = ["LICENSE.md", "setup/LICENSE-Apache-2.0.txt", "CREDITS.md", "pyproject.toml"] copyFiles = [
ROOT_DIR / "LICENSE.md",
SETUP_DIR / "LICENSE-Apache-2.0.txt",
ROOT_DIR / "CREDITS.md",
ROOT_DIR / "pyproject.toml",
]
for copyFile in copyFiles: for copyFile in copyFiles:
shutil.copyfile(copyFile, dst / copyFile) shutil.copyfile(copyFile, dst / copyFile.name)
print("Copied:", copyFile, flush=True) print("Copied:", copyFile, flush=True)
writeFile(dst / "MANIFEST.in", ( writeFile(dst / "MANIFEST.in", (
"include LICENSE.md\n" "include LICENSE.md\n"
"include setup/LICENSE-Apache-2.0.txt\n" "include LICENSE-Apache-2.0.txt\n"
"include CREDITS.md\n" "include CREDITS.md\n"
"recursive-include novelwriter/assets *\n" "recursive-include novelwriter/assets *\n"
)) ))