From 0306701e6fc15871eeb5e96a283f7e75fa4a9d02 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Thu, 16 Jan 2025 21:32:56 +0100 Subject: [PATCH] Move asset builder functions --- pkgutils.py | 312 ++---------------------------------------------- utils/assets.py | 279 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 287 insertions(+), 304 deletions(-) create mode 100644 utils/assets.py diff --git a/pkgutils.py b/pkgutils.py index f84db12c..16bc3fa6 100755 --- a/pkgutils.py +++ b/pkgutils.py @@ -31,10 +31,8 @@ import datetime import shutil import subprocess import sys -import zipfile - -from pathlib import Path +import utils.assets import utils.build_appimage import utils.build_binary import utils.build_debian @@ -115,296 +113,6 @@ def cleanBuildDirs(args: argparse.Namespace) -> None: return -# =============================================================================================== # -# Additional Builds -# =============================================================================================== # - -## -# Build PDF Manual (manual) -## - -def buildPdfManual(args: argparse.Namespace | None = None) -> None: - """This function will build the documentation as manual.pdf.""" - print("") - print("Building PDF Manual") - print("===================") - print("") - - buildFile = ROOT_DIR / "docs" / "build" / "latex" / "manual.pdf" - finalFile = ROOT_DIR / "novelwriter" / "assets" / "manual.pdf" - finalFile.unlink(missing_ok=True) - - try: - subprocess.call(["make", "clean"], cwd="docs") - exCode = subprocess.call(["make", "latexpdf"], cwd="docs") - if exCode == 0: - print("") - buildFile.rename(finalFile) - else: - raise Exception(f"Build returned error code {exCode}") - - print("PDF manual build: OK") - print("") - - except Exception as exc: - print("PDF manual build: FAILED") - print("") - print(str(exc)) - print("") - print("Dependencies:") - print(" * pip install sphinx") - print(" * Package latexmk") - print(" * LaTeX build system") - print("") - print(" On Debian/Ubuntu, install: python3-sphinx latexmk texlive texlive-latex-extra") - print("") - sys.exit(1) - - if not finalFile.is_file(): - print("No output file was found!") - print("") - sys.exit(1) - - return - - -## -# Sample Project ZIP File Builder (sample) -## - -def buildSampleZip(args: argparse.Namespace | None = None) -> None: - """Bundle the sample project into a single zip file to be saved into - the novelwriter/assets folder for further bundling into builds. - """ - print("") - print("Building Sample ZIP File") - print("========================") - print("") - - srcSample = ROOT_DIR / "sample" - dstSample = ROOT_DIR / "novelwriter" / "assets" / "sample.zip" - - if srcSample.is_dir(): - dstSample.unlink(missing_ok=True) - with zipfile.ZipFile(dstSample, "w") as zipObj: - print("Compressing: nwProject.nwx") - zipObj.write(srcSample / "nwProject.nwx", "nwProject.nwx") - for doc in (srcSample / "content").iterdir(): - print(f"Compressing: content/{doc.name}") - zipObj.write(doc, f"content/{doc.name}") - - else: - print("Error: Could not find sample project source directory.") - sys.exit(1) - - print("") - print("Built file: %s" % dstSample) - print("") - - return - - -## -# Import Translations (import-i18n) -## - -def importI18nUpdates(args: argparse.Namespace) -> None: - """Import new translation files from a zip file.""" - print("") - print("Import Updated Translations") - print("===========================") - print("") - - fileName = Path(args.file).absolute() - if not fileName.is_file(): - print("File not found ...") - sys.exit(1) - - dstPath = ROOT_DIR / "novelwriter" / "assets" / "i18n" - srcPath = ROOT_DIR / "i18n" - - print(f"Loading file: {fileName}") - with zipfile.ZipFile(fileName) as zipObj: - for item in zipObj.namelist(): - if item.startswith("nw_") and item.endswith(".ts"): - zipObj.extract(item, srcPath) - print(f"Extracted: {item} > {srcPath / item}") - elif item.startswith("project_") and item.endswith(".json"): - zipObj.extract(item, dstPath) - print(f"Extracted: {item} > {dstPath / item}") - else: - print(f"Skipped: {item}") - - print("") - - return - - -## -# Qt Linguist TS Builder (qtlupdate) -## - -def updateTranslationSources(args: argparse.Namespace) -> None: - """Build the lang.ts files for Qt Linguist.""" - print("") - print("Building Qt Translation Files") - print("=============================") - - try: - # Using the pylupdate tool from PyQt6 as it supports TS file format 2.1. - from PyQt6.lupdate.lupdate import lupdate - except ImportError: - print("ERROR: This command requires lupdate from PyQt6") - print("On Debian/Ubuntu, install: pyqt6-dev-tools") - sys.exit(1) - - print("") - print("Scanning Source Tree:") - print("") - - sources = list((ROOT_DIR / "novelwriter").glob("**/*.py")) - sources.insert(0, ROOT_DIR / "i18n" / "qtbase.py") - for source in sources: - print(source.relative_to(ROOT_DIR)) - - print("") - print("TS Files to Update:") - print("") - - translations = [] - for item in [Path(str(f)).absolute() for f in args.files]: - if not (item.name.startswith("nw_") and item.suffix == ".ts"): - print(f"Skipped: {item}") - continue - - if item.is_file(): - translations.append(item) - print(f"Added: {item}") - elif item.exists(): - continue - else: # Create an empty new language file - langCode = item.name[3:-3] - writeFile(item, ( - "\n" - "\n" - f"\n" - )) - translations.append(item) - print(f"Created: {item}") - - print("") - print("Updating Language Files:") - print("") - - lupdate( - sources=[str(f) for f in sources], - translation_files=[str(f) for f in translations], - no_obsolete=True, - no_summary=False, - ) - - print("") - - return - - -## -# Qt Linguist QM Builder (qtlrelease) -## - -def buildTranslationAssets(args: argparse.Namespace | None = None) -> None: - """Build the lang.qm files for Qt Linguist.""" - print("") - print("Building Qt Localisation Files") - print("==============================") - - print("") - print("TS Files to Build:") - print("") - - srcDir = ROOT_DIR / "i18n" - dstDir = ROOT_DIR / "novelwriter" / "assets" / "i18n" - - srcList = [] - for item in srcDir.iterdir(): - if item.is_file() and item.suffix == ".ts" and item.name != "nw_base.ts": - srcList.append(item) - print(item) - - print("") - print("Building Translation Files:") - print("") - - try: - subprocess.call(["lrelease", "-verbose", *srcList]) - except Exception as exc: - print("Qt Linguist tools seem to be missing") - print("On Debian/Ubuntu, install: qttools5-dev-tools") - print(str(exc)) - sys.exit(1) - - print("") - print("Moving QM Files to Assets") - print("") - - dstRel = dstDir.relative_to(ROOT_DIR) - for item in srcDir.iterdir(): - if item.is_file() and item.suffix == ".qm": - item.rename(dstDir / item.name) - print("Moved: %s -> %s" % (item.relative_to(ROOT_DIR), dstRel / item.name)) - - print("") - - return - - -## -# Clean Assets (clean-assets) -## - -def cleanBuiltAssets(args: argparse.Namespace | None = None) -> None: - """Remove assets built by this script.""" - print("") - print("Removing Built Assets") - print("=====================") - print("") - - assets = [ - ROOT_DIR / "novelwriter" / "assets" / "sample.zip", - ROOT_DIR / "novelwriter" / "assets" / "manual.pdf", - ] - assets.extend((ROOT_DIR / "novelwriter" / "assets" / "i18n").glob("*.qm")) - for asset in assets: - if asset.is_file(): - asset.unlink() - print(f"Deleted: {asset.relative_to(ROOT_DIR)}") - - print("") - - return - - -## -# Build Assets (build-assets) -## - -def buildAllAssets(args: argparse.Namespace) -> None: - """Build all assets.""" - cleanBuiltAssets() - buildPdfManual() - buildSampleZip() - buildTranslationAssets() - return - - -# =============================================================================================== # -# Python Packaging -# =============================================================================================== # - -## -# Generate MacOS PList -## - def genMacOSPlist(args: argparse.Namespace) -> None: """Set necessary values for .plist file for MacOS build.""" outDir = SETUP_DIR / "macos" @@ -428,10 +136,6 @@ def genMacOSPlist(args: argparse.Namespace) -> None: return -# =============================================================================================== # -# Process Command Line -# =============================================================================================== # - if __name__ == "__main__": """Parse command line options and run the commands.""" parser = argparse.ArgumentParser( @@ -478,7 +182,7 @@ if __name__ == "__main__": "qtlimport", help="Import updated i18n files from a Crowdin zip file." ) cmdImportTS.add_argument("file", help="Path to zip file from Crowdin") - cmdImportTS.set_defaults(func=importI18nUpdates) + cmdImportTS.set_defaults(func=utils.assets.importI18nUpdates) # Update i18n Sources cmdUpdateTS = parsers.add_parser( @@ -490,37 +194,37 @@ if __name__ == "__main__": ) ) cmdUpdateTS.add_argument("files", nargs="+") - cmdUpdateTS.set_defaults(func=updateTranslationSources) + cmdUpdateTS.set_defaults(func=utils.assets.updateTranslationSources) # Build i18n Files cmdBuildQM = parsers.add_parser( "qtlrelease", help="Build the language files for internationalisation." ) - cmdBuildQM.set_defaults(func=buildTranslationAssets) + cmdBuildQM.set_defaults(func=utils.assets.buildTranslationAssets) # Build Manual cmdBuildManual = parsers.add_parser( "manual", help="Build the help documentation as a PDF (requires LaTeX)." ) - cmdBuildManual.set_defaults(func=buildPdfManual) + cmdBuildManual.set_defaults(func=utils.assets.buildPdfManual) # Build Sample cmdBuildSample = parsers.add_parser( "sample", help="Build the sample project zip file and add it to assets." ) - cmdBuildSample.set_defaults(func=buildSampleZip) + cmdBuildSample.set_defaults(func=utils.assets.buildSampleZip) # Clean Assets cmdCleanAssets = parsers.add_parser( "clean-assets", help="Delete assets built by manual, sample and qtlrelease." ) - cmdCleanAssets.set_defaults(func=cleanBuiltAssets) + cmdCleanAssets.set_defaults(func=utils.assets.cleanBuiltAssets) # Build Assets cmdBuildAssets = parsers.add_parser( "build-assets", help="Build all assets. Includes manual, sample and qtlrelease." ) - cmdBuildAssets.set_defaults(func=buildAllAssets) + cmdBuildAssets.set_defaults(func=utils.assets.buildAllAssets) # Python Packaging # ================ diff --git a/utils/assets.py b/utils/assets.py new file mode 100644 index 00000000..b9512dfc --- /dev/null +++ b/utils/assets.py @@ -0,0 +1,279 @@ +""" +novelWriter – Assets +==================== + +This file is a part of novelWriter +Copyright (C) 2025 Veronica Berglyd Olsen and novelWriter contributors + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" +from __future__ import annotations + +import argparse +import subprocess +import sys +import zipfile + +from pathlib import Path + +from utils.common import ROOT_DIR, writeFile + + +def buildPdfManual(args: argparse.Namespace | None = None) -> None: + """This function will build the documentation as manual.pdf.""" + print("") + print("Building PDF Manual") + print("===================") + print("") + + buildFile = ROOT_DIR / "docs" / "build" / "latex" / "manual.pdf" + finalFile = ROOT_DIR / "novelwriter" / "assets" / "manual.pdf" + finalFile.unlink(missing_ok=True) + + try: + subprocess.call(["make", "clean"], cwd="docs") + exCode = subprocess.call(["make", "latexpdf"], cwd="docs") + if exCode == 0: + print("") + buildFile.rename(finalFile) + else: + raise Exception(f"Build returned error code {exCode}") + + print("PDF manual build: OK") + print("") + + except Exception as exc: + print("PDF manual build: FAILED") + print("") + print(str(exc)) + print("") + print("Dependencies:") + print(" * pip install sphinx") + print(" * Package latexmk") + print(" * LaTeX build system") + print("") + print(" On Debian/Ubuntu, install: python3-sphinx latexmk texlive texlive-latex-extra") + print("") + sys.exit(1) + + if not finalFile.is_file(): + print("No output file was found!") + print("") + sys.exit(1) + + return + + +def buildSampleZip(args: argparse.Namespace | None = None) -> None: + """Bundle the sample project into a single zip file to be saved into + the novelwriter/assets folder for further bundling into builds. + """ + print("") + print("Building Sample ZIP File") + print("========================") + print("") + + srcSample = ROOT_DIR / "sample" + dstSample = ROOT_DIR / "novelwriter" / "assets" / "sample.zip" + + if srcSample.is_dir(): + dstSample.unlink(missing_ok=True) + with zipfile.ZipFile(dstSample, "w") as zipObj: + print("Compressing: nwProject.nwx") + zipObj.write(srcSample / "nwProject.nwx", "nwProject.nwx") + for doc in (srcSample / "content").iterdir(): + print(f"Compressing: content/{doc.name}") + zipObj.write(doc, f"content/{doc.name}") + + else: + print("Error: Could not find sample project source directory.") + sys.exit(1) + + print("") + print("Built file: %s" % dstSample) + print("") + + return + + +def importI18nUpdates(args: argparse.Namespace) -> None: + """Import new translation files from a zip file.""" + print("") + print("Import Updated Translations") + print("===========================") + print("") + + fileName = Path(args.file).absolute() + if not fileName.is_file(): + print("File not found ...") + sys.exit(1) + + dstPath = ROOT_DIR / "novelwriter" / "assets" / "i18n" + srcPath = ROOT_DIR / "i18n" + + print(f"Loading file: {fileName}") + with zipfile.ZipFile(fileName) as zipObj: + for item in zipObj.namelist(): + if item.startswith("nw_") and item.endswith(".ts"): + zipObj.extract(item, srcPath) + print(f"Extracted: {item} > {srcPath / item}") + elif item.startswith("project_") and item.endswith(".json"): + zipObj.extract(item, dstPath) + print(f"Extracted: {item} > {dstPath / item}") + else: + print(f"Skipped: {item}") + + print("") + + return + + +def updateTranslationSources(args: argparse.Namespace) -> None: + """Build the lang.ts files for Qt Linguist.""" + print("") + print("Building Qt Translation Files") + print("=============================") + + try: + from PyQt6.lupdate.lupdate import lupdate + except ImportError: + print("ERROR: This command requires lupdate from PyQt6") + print("On Debian/Ubuntu, install: pyqt6-dev-tools") + sys.exit(1) + + print("") + print("Scanning Source Tree:") + print("") + + sources = list((ROOT_DIR / "novelwriter").glob("**/*.py")) + sources.insert(0, ROOT_DIR / "i18n" / "qtbase.py") + for source in sources: + print(source.relative_to(ROOT_DIR)) + + print("") + print("TS Files to Update:") + print("") + + translations = [] + for item in [Path(str(f)).absolute() for f in args.files]: + if not (item.name.startswith("nw_") and item.suffix == ".ts"): + print(f"Skipped: {item}") + continue + + if item.is_file(): + translations.append(item) + print(f"Added: {item}") + elif item.exists(): + continue + else: # Create an empty new language file + langCode = item.name[3:-3] + writeFile(item, ( + "\n" + "\n" + f"\n" + )) + translations.append(item) + print(f"Created: {item}") + + print("") + print("Updating Language Files:") + print("") + + lupdate( + sources=[str(f) for f in sources], + translation_files=[str(f) for f in translations], + no_obsolete=True, + no_summary=False, + ) + + print("") + + return + + +def buildTranslationAssets(args: argparse.Namespace | None = None) -> None: + """Build the lang.qm files for Qt Linguist.""" + print("") + print("Building Qt Localisation Files") + print("==============================") + + print("") + print("TS Files to Build:") + print("") + + srcDir = ROOT_DIR / "i18n" + dstDir = ROOT_DIR / "novelwriter" / "assets" / "i18n" + + srcList = [] + for item in srcDir.iterdir(): + if item.is_file() and item.suffix == ".ts" and item.name != "nw_base.ts": + srcList.append(item) + print(item) + + print("") + print("Building Translation Files:") + print("") + + try: + subprocess.call(["lrelease", "-verbose", *srcList]) + except Exception as exc: + print("Qt Linguist tools seem to be missing") + print("On Debian/Ubuntu, install: qttools5-dev-tools") + print(str(exc)) + sys.exit(1) + + print("") + print("Moving QM Files to Assets") + print("") + + dstRel = dstDir.relative_to(ROOT_DIR) + for item in srcDir.iterdir(): + if item.is_file() and item.suffix == ".qm": + item.rename(dstDir / item.name) + print("Moved: %s -> %s" % (item.relative_to(ROOT_DIR), dstRel / item.name)) + + print("") + + return + + +def cleanBuiltAssets(args: argparse.Namespace | None = None) -> None: + """Remove assets built by this script.""" + print("") + print("Removing Built Assets") + print("=====================") + print("") + + assets = [ + ROOT_DIR / "novelwriter" / "assets" / "sample.zip", + ROOT_DIR / "novelwriter" / "assets" / "manual.pdf", + ] + assets.extend((ROOT_DIR / "novelwriter" / "assets" / "i18n").glob("*.qm")) + for asset in assets: + if asset.is_file(): + asset.unlink() + print(f"Deleted: {asset.relative_to(ROOT_DIR)}") + + print("") + + return + + +def buildAllAssets(args: argparse.Namespace) -> None: + """Build all assets.""" + cleanBuiltAssets() + buildPdfManual() + buildSampleZip() + buildTranslationAssets() + return