diff --git a/.git_archival.txt b/.git_archival.txt deleted file mode 100644 index 95cb3eea..00000000 --- a/.git_archival.txt +++ /dev/null @@ -1 +0,0 @@ -ref-names: $Format:%D$ diff --git a/.gitattributes b/.gitattributes index 810d9a09..974efd1d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,3 @@ -.git_archival.txt export-subst .gitattributes export-ignore .gitignore export-ignore /doc/web export-ignore diff --git a/.github/workflows/branch-man.yml b/.github/workflows/branch-man.yml index 63c55185..3e916cb7 100644 --- a/.github/workflows/branch-man.yml +++ b/.github/workflows/branch-man.yml @@ -13,7 +13,7 @@ jobs: with: repository: linkchecker/linkchecker ref: master - # Needed for setuptools_scm to extract LinkChecker version from tag + # Needed for hatch-vcs to extract LinkChecker version from tag # https://github.com/actions/checkout/issues/249 fetch-depth: 0 @@ -26,8 +26,8 @@ jobs: - name: Install Python packages run: > pip3 install dnspython beautifulsoup4 requests \ - sphinx sphinx_epytext sphinx_rtd_theme sphinx-sitemap \ - sphinx-intl setuptools_scm + hatch hatch-vcs sphinx sphinx_epytext sphinx_rtd_theme \ + sphinx-sitemap sphinx-intl - name: Prepare git environment run: | @@ -36,15 +36,11 @@ jobs: git checkout -b man-updates git remote add local ${{ github.server_url }}/${{ github.repository }} - - name: Create application version number - run: > - python3 setup.py build - - name: Build man pages run: | - make -C doc locale + hatch -e doc run locale git commit -a -m "Update doc translation catalogs" - make -C doc man + hatch -e doc run man git commit -a -m "Update man pages" - name: Build application translations catalogs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 69bc2ba5..dd720251 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -50,7 +50,7 @@ jobs: uses: actions/cache@v2 with: path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('tox.ini', 'setup.py') }} + key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('tox.ini', 'pyproject.toml') }} restore-keys: | ${{ runner.os }}-pip-${{ matrix.python-version }}- ${{ runner.os }}-pip- @@ -58,7 +58,7 @@ jobs: - name: Install Python dependencies run: | python -m pip install -U pip - python -m pip install -U setuptools wheel + python -m pip install -U hatchling hatch-vcs polib python -m pip install -U tox coveralls - name: Wait for ClamAV to be ready @@ -102,16 +102,15 @@ jobs: - name: Install Python packages run: | - pip install -r requirements.txt Sphinx sphinx-epytext sphinx-intl \ - sphinx-rtd-theme sphinx-sitemap + pip install -U -r requirements.txt hatch hatch-vcs Sphinx \ + sphinx-epytext sphinx-intl sphinx-rtd-theme sphinx-sitemap - name: Build run: | - python3 setup.py build egg_info - make -C doc code - make -C doc html - make -C doc locale - make -C doc man + hatch -e doc run code + hatch -e doc run html + hatch -e doc run locale + hatch -e doc run man make -C doc check lint: @@ -145,7 +144,7 @@ jobs: - name: Install dependencies run: | python -m pip install -U pip - python -m pip install -U setuptools wheel + python -m pip install -U hatchling hatch-vcs polib python -m pip install -U tox - name: Run ${{ matrix.toxenv }} diff --git a/.github/workflows/publish-pages.yml b/.github/workflows/publish-pages.yml index 2c73677e..5f5722a3 100644 --- a/.github/workflows/publish-pages.yml +++ b/.github/workflows/publish-pages.yml @@ -15,7 +15,7 @@ jobs: steps: - uses: actions/checkout@v2 - # Needed for setuptools_scm to extract LinkChecker version from tag + # Needed for hatch-vcs to extract LinkChecker version from tag # https://github.com/actions/checkout/issues/249 with: fetch-depth: 0 @@ -30,13 +30,13 @@ jobs: - name: Install Python packages run: > pip install dnspython beautifulsoup4 requests \ - sphinx sphinx_epytext sphinx_rtd_theme sphinx-sitemap + hatch hatch-vcs sphinx sphinx_epytext sphinx_rtd_theme \ + sphinx-sitemap - name: Build run: | - python3 setup.py build egg_info - make -C doc code - make -C doc html + hatch -e doc run code + hatch -e doc run html - name: Publish uses: peaceiris/actions-gh-pages@068dc23d9710f1ba62e86896f84735d869951305 diff --git a/.github/workflows/release-files.yml b/.github/workflows/release-files.yml index 4ecc2aab..80753973 100644 --- a/.github/workflows/release-files.yml +++ b/.github/workflows/release-files.yml @@ -12,7 +12,7 @@ jobs: steps: - uses: actions/checkout@v2 - # Needed for setuptools_scm to extract LinkChecker version from tag + # Needed for hatch-vcs to extract LinkChecker version from tag # https://github.com/actions/checkout/issues/249 with: fetch-depth: 0 @@ -25,7 +25,7 @@ jobs: - name: Install Python packages run: > - pip3 install polib setuptools_scm twine wheel + pip3 install -U hatchling hatch-vcs polib twine - name: Set SOURCE_DATE_EPOCH run: > @@ -33,7 +33,7 @@ jobs: - name: Create distribution files run: > - python3 setup.py sdist bdist_wheel + python3 -m hatchling build - name: Check distribution files run: > diff --git a/.gitignore b/.gitignore index 487cd874..233e376f 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,4 @@ coverage.xml doc/html doc/src/_build doc/src/code/linkcheck +linkcheck/_release.py diff --git a/MANIFEST.in b/MANIFEST.in index 59a2d997..8a1a0782 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -11,7 +11,6 @@ include pyoxidizer.bzl include .project include .pydevproject include .yamllint -include _release_date include linkcheck/data/linkcheckerrc recursive-include cgi-bin \ @@ -46,6 +45,8 @@ recursive-include doc \ *.yml \ Makefile \ linkcheckerrc_* +recursive-include linkcheck \ + *.mo recursive-include po \ *.po \ *.pot \ @@ -77,6 +78,8 @@ recursive-include tests \ *.xml \ *.zip \ Bookmarks +recursive-include tools \ + *.py recursive-include windows \ *.bat \ *.cer \ diff --git a/doc/Makefile b/doc/Makefile index 9f53f88f..d6458f42 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -4,7 +4,7 @@ LOCALES:=en de all: html man code: clean - PYTHONPATH=.. sphinx-autogen src/code/index.rst + python3 -m sphinx.ext.autosummary.generate src/code/index.rst html: make -C src html diff --git a/doc/changelog.txt b/doc/changelog.txt index 69ea6b8b..8c7bf41d 100644 --- a/doc/changelog.txt +++ b/doc/changelog.txt @@ -2,6 +2,11 @@ Changes: - PyXDG is no longer used +- setuptools and setup.py replaced with hatchling and pyproject.toml +- The application version is derived from git tags using hatch-vcs +- Documentation build is run using hatch +- Binary translation catalogs are compiled using polib during distribution + package building and are now included in sdist packages 10.1.0 (released 22.12.2021) diff --git a/doc/documentation.md b/doc/documentation.md index 684bb071..d8bf4d65 100644 --- a/doc/documentation.md +++ b/doc/documentation.md @@ -19,10 +19,10 @@ sphinx_sitemap Configuration ------------- -Before building either man pages or HTML, generate ``PKG-INFO`` -containing copyright, author and version with: +Before building either man pages or HTML, the package metadata needs to be +created to derive copyright, author and version values. Running Sphinx in a +hatch environment manages this for us. -``linkchecker $ ./setup.py build`` Man Pages --------- @@ -31,7 +31,7 @@ Source files are in doc/src/man. The pages can be built with: -``linkchecker/doc $ make man`` +``linkchecker $ hatch -e doc run man`` The files are saved in doc/man. @@ -46,11 +46,11 @@ HTML ``doc/src/code/index.rst`` gives an overview of the LinkChecker code, optionally a navigable copy of the LinkChecker source can be created with: -``linkchecker/doc $ make code`` +``linkchecker $ hatch -e doc run code`` Build the HTML files with: -``linkchecker/doc $ make html`` +``linkchecker $ hatch -e doc run html`` The files are saved in doc/html. diff --git a/doc/install.txt b/doc/install.txt index 1519cb87..c02057dd 100644 --- a/doc/install.txt +++ b/doc/install.txt @@ -3,13 +3,10 @@ Installation If you are upgrading from older versions of LinkChecker you should also read the upgrading documentation stored in upgrading.txt. -When installing from source, for application translations to be installed -polib_ needs to be installed before LinkChecker. After LinkChecker installation -polib_ can be removed. - -Installing from the GitHub release assets requires setuptools-scm-git-archive_. - -pip-run_ may be useful for both of these cases. +Installing a LinkChecker release uses pre-built distribution packages. Building +the distribution packages requires hatchling_ and hatch-vcs_, and for application +translations to be compiled polib_ needs to be installed. After the sdist/wheel +has been built polib_ can be removed. pip-run_ may be useful for this. There are several steps to resolve problems with detecting the character encoding of checked HTML pages: @@ -34,7 +31,9 @@ used. .. _polib: https://pypi.org/project/polib/ -.. _setuptools-scm-git-archive: https://pypi.org/project/setuptools-scm-git-archive/ +.. _hatchling: https://pypi.org/project/hatchling/ + +.. _hatch-vcs: https://pypi.org/project/hatch-vcs/ .. _venv: https://docs.python.org/3/library/venv.html#creating-virtual-environments @@ -42,12 +41,18 @@ Setup with pip ------------------ pip_ can be used to install LinkChecker on the local system. -If you want application translations, first: +To install the latest release from PyPI: +``pip3 install linkchecker`` + +Alternatively you can install the latest source from the LinkChecker GitHub repository. +First, if you want application translations: ``pip3 install polib`` -Then this command will install LinkChecker from the latest source: +Then: ``pip3 install git+https://github.com/linkchecker/linkchecker.git`` +N.B. git archive's cannot be used. + .. _pip: https://pypi.org/project/pip/ Setup for Windows @@ -73,64 +78,59 @@ Manual setup for Unix systems ----------------------------- First, install the required software. -1. Python >= 3.7 from https://www.python.org/ +1. Python hatchling package from https://pypi.org/project/hatchling/ - Be sure to also have installed the included distutils module. - On most distributions, the distutils module is included in - an extra ``python-dev`` package. +2. Python hatch-vcs package from https://pypi.org/project/hatch-vcs/ -2. Python Requests package from https://pypi.org/project/requests/ +3. Python Requests package from https://pypi.org/project/requests/ -3. Python Beautiful Soup package from https://pypi.org/project/beautifulsoup4/ +4. Python Beautiful Soup package from https://pypi.org/project/beautifulsoup4/ -4. *Optional, installation time only, for translations:* +5. Python dnspython package from https://pypi.org/project/dnspython/ + +6. *Optional, build time only, for translations:* polib Python module from https://pypi.org/project/polib/ -5. *Optional, for bash-completion:* +7. *Optional, for bash-completion:* argcomplete Python module from https://pypi.org/project/argcomplete/ -6. *Optional, for displaying country codes:* - GeoIP from https://pypi.org/project/GeoIP/ +8. *Optional, for displaying country codes:* + GeoIP from https://pypi.org/project/GeoIP/ -7. *Optional, used for Virus checking:* - ClamAv from https://www.clamav.net/ +9. *Optional, used for Virus checking:* + ClamAv from https://www.clamav.net/ -8. *Optional, to run the WSGI web interface:* - Apache from https://httpd.apache.org/ - mod_wsgi from https://pypi.org/project/mod-wsgi/ +10. *Optional, to run the WSGI web interface:* + Apache from https://httpd.apache.org/ + mod_wsgi from https://pypi.org/project/mod-wsgi/ Note for developers: if you want to regenerate the po/linkchecker.pot template from the source files, you will need xgettext with Python support. This is available in gettext >= 0.12. -Now install the application. +Clone the LinkChecker repository: -1. Compile Python modules - - Run ``python setup.py sdist --manifest-only`` to create the MANIFEST - file. - Run ``python setup.py build`` to compile the Python files. - For help about the setup.py script options, run - ``python setup.py --help``. + ``git clone https://github.com/linkchecker/linkchecker.git`` -2. - a) Installation as root - - Run ``sudo python setup.py install`` to install LinkChecker. - - b) Installation as a normal user - - Run ``python setup.py install --home $HOME``. Note that you have - to adjust your PATH and PYTHONPATH environment variables, eg. by - adding the commands ``export PYTHONPATH=$HOME/lib/python`` and - ``export PATH=$PATH:$HOME/bin`` to your shell configuration - file. - - For more information look at the `Modifying Python's search path`_ - documentation. - - .. _Modifying Python's search path: - https://docs.python.org/3/install/#inst-search-path + ``cd linkchecker`` + +Build the distribution wheel: + + ``hatchling build`` + +Now install the application from the wheel: + + ``pip install --no-index --user dist/LinkChecker--py3-none-any.whl`` + + Note that you may have to adjust your PATH and PYTHONPATH environment variables, + eg. by adding the commands ``export PYTHONPATH=$HOME/lib/python`` and + ``export PATH=$PATH:$HOME/bin`` to your shell configuration file. + + For more information look at the `Modifying Python's search path`_ + documentation. + + .. _Modifying Python's search path: + https://docs.python.org/3/install/#inst-search-path After installation diff --git a/doc/src/Makefile b/doc/src/Makefile index eeb8c0f0..518ea611 100644 --- a/doc/src/Makefile +++ b/doc/src/Makefile @@ -1,8 +1,8 @@ # You can set these variables from the command line, and also # from the environment for the first two. SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build -SPHINXINTL ?= sphinx-intl +SPHINXBUILD ?= python3 -m sphinx.cmd.build +SPHINXINTL ?= python3 -m sphinx_intl.commands SOURCEDIR = . BUILDDIR = _build LANGUAGE = en diff --git a/doc/src/conf.py b/doc/src/conf.py index 0476dcdb..aadb00e5 100644 --- a/doc/src/conf.py +++ b/doc/src/conf.py @@ -1,7 +1,3 @@ -import os -import sys -sys.path.insert(0, os.path.abspath('../..')) - # -- Project information ----------------------------------------------------- import linkcheck.configuration diff --git a/doc/translations.md b/doc/translations.md index c24039ab..abdaf17c 100644 --- a/doc/translations.md +++ b/doc/translations.md @@ -42,7 +42,7 @@ These two steps can be performed with: Create man pages: -``linkchecker/doc $ make man`` +``linkchecker/doc $ hatch -e doc run man`` After updating the source files all steps need to be repeated, if translations alone have been changed in the .po file only the last step is needed. diff --git a/doc/upgrading.txt b/doc/upgrading.txt index 4b76a159..683c9ad8 100644 --- a/doc/upgrading.txt +++ b/doc/upgrading.txt @@ -4,6 +4,10 @@ Migrating from 10.1 to 10.x --------------------------- Python 3.7 or newer is required. +Compiled application translations are now also included in the sdist package. +No need to install polib before installing any distribution package. It is +still required for building distribution packages that include translations. + Migrating from 10.0 to 10.1 --------------------------- If installing from source and application translations are needed the Python diff --git a/install-rpm.sh b/install-rpm.sh deleted file mode 100644 index 57b022ce..00000000 --- a/install-rpm.sh +++ /dev/null @@ -1,20 +0,0 @@ -python setup.py install --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES -# 'brp-compress' compresses the manpages without distutils knowing. -# The sed scripts append ".gz", ".bz2" or ".xz" suffixes to the affected -# manpage filenames. -RPM_MANDIR=usr/share/man -RPM_LOCALMANDIR=usr/local/share/man - -if [ -n "$( ls $RPM_BUILD_ROOT/$RPM_MANDIR/man*/*.bz2 2>/dev/null )" \ - -o -n "$( ls $RPM_BUILD_ROOT/$RPM_LOCALMANDIR/man*/*.bz2 2>/dev/null )" ]; then - # add .bz2 suffix - sed -i -e 's@man/man\([[:digit:]]\)/\(.\+\.[[:digit:]]\)$@man/man\1/\2.bz2@g' INSTALLED_FILES -elif [ -n "$( ls $RPM_BUILD_ROOT/$RPM_MANDIR/man*/*.xz 2>/dev/null )" \ - -o -n "$( ls $RPM_BUILD_ROOT/$RPM_LOCALMANDIR/man*/*.xz 2>/dev/null )" ]; then - # add .xz suffix - sed -i -e 's@man/man\([[:digit:]]\)/\(.\+\.[[:digit:]]\)$@man/man\1/\2.xz@g' INSTALLED_FILES -elif [ -n "$( ls $RPM_BUILD_ROOT/$RPM_MANDIR/man*/*.gz 2>/dev/null )" \ - -o -n "$( ls $RPM_BUILD_ROOT/$RPM_LOCALMANDIR/man*/*.gz 2>/dev/null )" ]; then - # add .gz suffix - sed -i -e 's@man/man\([[:digit:]]\)/\(.\+\.[[:digit:]]\)$@man/man\1/\2.gz@g' INSTALLED_FILES -fi diff --git a/linkcheck/configuration/__init__.py b/linkcheck/configuration/__init__.py index eeacc220..43d9f94a 100644 --- a/linkcheck/configuration/__init__.py +++ b/linkcheck/configuration/__init__.py @@ -23,7 +23,6 @@ import re import urllib.parse import shutil import socket -from datetime import date try: from importlib.metadata import distribution @@ -37,10 +36,11 @@ from . import confparse linkchecker_distribution = distribution(COMMAND_NAME) Version = linkchecker_distribution.metadata["Version"] try: - ReleaseDate = date.fromisoformat( - linkchecker_distribution.read_text("RELEASE_DATE")) -except (TypeError, ValueError): + from .. import _release +except ImportError: ReleaseDate = "unknown" +else: + ReleaseDate = _release.__release_date__ AppName = linkchecker_distribution.metadata["Name"] App = AppName + " " + Version Author = linkchecker_distribution.metadata["Author"] @@ -49,7 +49,7 @@ Copyright = "Copyright (C) 2000-2016 Bastian Kleineidam, 2010-2022 " + Author HtmlCopyright = ("Copyright © 2000-2016 Bastian Kleineidam, 2010-2022 " + HtmlAuthor) HtmlAppInfo = App + ", " + HtmlCopyright -Url = linkchecker_distribution.metadata["Home-page"] +Url = linkchecker_distribution.metadata["Project-URL"].split(", ")[1] SupportUrl = "https://github.com/linkchecker/linkchecker/issues" UserAgent = "Mozilla/5.0 (compatible; %s/%s; +%s)" % (AppName, Version, Url) Freeware = ( diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..c8ef5aa0 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,99 @@ +[project] +name = "LinkChecker" +dynamic = ["version"] +description = "check links in web documents or full websites" +readme = "README.rst" +keywords = ["link", "url", "site", "checking", "crawling", "verification", "validation"] +authors = [{name = "LinkChecker Authors"}] +maintainers = [{name = "LinkChecker Authors"}] +classifiers = [ + "Topic :: Internet :: WWW/HTTP :: Site Management :: Link Checking", + "Development Status :: 5 - Production/Stable", + "License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)", + "Programming Language :: Python", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", +] + +requires-python = ">=3.7" + +dependencies = [ + "importlib_metadata;python_version<'3.8'", + "requests >= 2.4", + "dnspython >= 2.0", + "beautifulsoup4 >= 4.8.1", +] + +[build-system] +requires = [ + "hatchling>=1.8.0", + "hatch-vcs", +] +build-backend = "hatchling.build" + +[project.urls] +Homepage = "https://linkchecker.github.io/linkchecker/" +"Bug Tracker" = "https://github.com/linkchecker/linkchecker/issues" +Repository = "https://github.com/linkchecker/linkchecker" +Changelog = "https://github.com/linkchecker/linkchecker/blob/master/doc/changelog.txt" + +[project.scripts] +linkchecker = "linkcheck.command.linkchecker:linkchecker" + +[tool.hatch.build] +artifacts = [ + "linkcheck/_release.py", + "linkcheck/data/locale", +] + +[tool.hatch.build.targets.sdist] +strict-naming = false + +[tool.hatch.build.targets.sdist.hooks.custom] +path = "tools/hatch_build.py" + +[tool.hatch.build.targets.wheel] +only-include = ["linkcheck"] +strict-naming = false + +[tool.hatch.build.targets.wheel.shared-data] +"doc/man/en/linkchecker.1" = "share/man/man1/linkchecker.1" +"doc/man/en/linkcheckerrc.5" = "share/man/man5/linkcheckerrc.5" +"doc/man/de/linkchecker.1" = "share/man/de/man1/linkchecker.1" +"doc/man/de/linkcheckerrc.5" = "share/man/de/man5/linkcheckerrc.5" +"cgi-bin/lconline/leer.html.en" = "share/linkchecker/examples/cgi-bin/lconline/leer.html.en" +"cgi-bin/lconline/leer.html.de" = "share/linkchecker/examples/cgi-bin/lconline/leer.html.de" +"cgi-bin/lconline/index.html" = "share/linkchecker/examples/cgi-bin/lconline/index.html" +"cgi-bin/lconline/lc_cgi.html.en" = "share/linkchecker/examples/cgi-bin/lconline/lc_cgi.html.en" +"cgi-bin/lconline/lc_cgi.html.de" = "share/linkchecker/examples/cgi-bin/lconline/lc_cgi.html.de" +"cgi-bin/lconline/check.js" = "share/linkchecker/examples/cgi-bin/lconline/check.js" +"cgi-bin/lc.wsgi" = "share/linkchecker/examples/cgi-bin/lc.wsgi" +"config/linkchecker.apache2.conf" = "share/linkchecker/examples/config/linkchecker.apache2.conf" +"config/linkchecker-completion" = "share/linkchecker/examples/config/linkchecker-completion" +"doc/examples/check_failures.sh" = "share/linkchecker/examples/check_failures.sh" +"doc/examples/check_for_x_errors.sh" = "share/linkchecker/examples/check_for_x_errors.sh" +"doc/examples/check_urls.sh" = "share/linkchecker/examples/check_urls.sh" + +[tool.hatch.version] +source = "vcs" + +[tool.hatch.version.raw-options] +local_scheme = "node-and-timestamp" +version_scheme = "post-release" + +[tool.hatch.envs.doc] +dependencies = [ + "sphinx", + "sphinx_epytext", + "sphinx_intl", + "sphinx_rtd_theme", + "sphinx-sitemap", +] + +[tool.hatch.envs.doc.scripts] +code = "make -C doc code" +html = "make -C doc html" +locale = "make -C doc locale" +man = "make -C doc man" diff --git a/setup.cfg b/setup.cfg index 4c3856b8..36182fbb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,19 +1,3 @@ -[global] -;command_packages = distcmds - -[bdist_rpm] -release = 1 -packager = Bastian Kleineidam -doc_files = doc/examples/ - cgi-bin/lconline/ -provides = linkchecker -group = Applications/Internet -install_script = install-rpm.sh -python = python - -[bdist_wheel] -universal = 0 - [check-manifest] ignore = *.rej @@ -36,7 +20,6 @@ per-file-ignores = # In several files imports intentionally cause: # E402: module level import not at top of file # F401: module imported but unused - setup.py: E402 doc/src/conf.py: E402,F821 linkcheck/__init__.py: E402,F401 linkcheck/checker/httpurl.py: E402 diff --git a/setup.py b/setup.py deleted file mode 100755 index 379de54f..00000000 --- a/setup.py +++ /dev/null @@ -1,236 +0,0 @@ -#!/usr/bin/python3 -# Copyright (C) 2000-2014 Bastian Kleineidam -# -# 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 2 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, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -""" -Setup file for the distuils module. - -It includes the following features: -- records the release date -- automatic generation of .mo locale files -- automatic permission setting on POSIX systems for installed files -""" -import sys - -if sys.version_info < (3, 7, 0, "final", 0): - raise SystemExit("This program requires Python 3.7 or later.") -import os -import stat -import subprocess -from pathlib import Path - -# import Distutils stuff -from setuptools import find_packages, setup -from distutils.command.build import build -from distutils.command.install_data import install_data -from setuptools.command.egg_info import egg_info -from setuptools.command.sdist import sdist - -try: - import polib -except ImportError: - COMPILE_TRANSLATIONS = False -else: - COMPILE_TRANSLATIONS = True - -# the application name -AppName = "LinkChecker" -Description = "check links in web documents or full websites" - -RELEASE_DATE_FILE = "_release_date" - - -def get_long_description(): - """Try to read long description from README.rst.""" - try: - return Path("README.rst").read_text() - except Exception: - return Description - - -def get_release_date(for_sdist=False): - """Return release date as a string from the most recent commit.""" - release_date = "unknown" - cp = None - try: - # need git >= 2.25.0 for %cs - cp = subprocess.run(["git", "log", "-n 1", "HEAD", "--format=%cI"], - capture_output=True, text=True) - except FileNotFoundError: - pass - if cp and cp.stdout: - release_date = cp.stdout.split("T")[0] - elif not for_sdist: - try: - release_date = Path(RELEASE_DATE_FILE).read_text() - except FileNotFoundError: - pass - return release_date - - -class MySdist(sdist): - def run(self): - Path(RELEASE_DATE_FILE).write_text(get_release_date(for_sdist=True)) - super().run() - - -class MyBuild(build): - """Custom build with translation compilation""" - - def run(self): - if COMPILE_TRANSLATIONS: - for po in Path("po").glob("*.po"): - mo = Path( - self.build_lib, "linkcheck", "data", "locale", - po.stem, "LC_MESSAGES", AppName.lower() - ).with_suffix(".mo") - pofile = polib.pofile(str(po)) - mo.parent.mkdir(exist_ok=True, parents=True) - pofile.save_as_mofile(str(mo)) - else: - print( - "warning: polib package not found: translations not compiled", - file=sys.stderr) - super().run() - - -class MyEggInfo(egg_info): - def run(self): - """Add release date to metadata.""" - super().run() - self.write_file( - "release date", - os.path.join(self.egg_info, "RELEASE_DATE"), - get_release_date() - ) - - -class MyInstallData(install_data): - """Fix file permissions.""" - - def run(self): - """Adjust permissions on POSIX systems.""" - super().run() - self.fix_permissions() - - def fix_permissions(self): - """Set correct read permissions on POSIX systems. Might also - be possible by setting umask?""" - if os.name == "posix" and not self.dry_run: - # Make the data files we just installed world-readable, - # and the directories world-executable as well. - for path in self.get_outputs(): - mode = os.stat(path)[stat.ST_MODE] - if stat.S_ISDIR(mode): - mode |= 0o11 - mode |= 0o44 - os.chmod(path, mode) - - -# scripts -myname = "LinkChecker Authors" -myemail = "" - -data_files = [ - ( - "share/linkchecker/examples", - [ - "cgi-bin/lconline/leer.html.en", - "cgi-bin/lconline/leer.html.de", - "cgi-bin/lconline/index.html", - "cgi-bin/lconline/lc_cgi.html.en", - "cgi-bin/lconline/lc_cgi.html.de", - "cgi-bin/lconline/check.js", - "cgi-bin/lc.wsgi", - "config/linkchecker.apache2.conf", - ], - ), -] - -if os.name == "posix": - data_files.append(("share/man/man1", ["doc/man/en/linkchecker.1"])) - data_files.append(("share/man/man5", ["doc/man/en/linkcheckerrc.5"])) - data_files.append(("share/man/de/man1", ["doc/man/de/linkchecker.1"])) - data_files.append(("share/man/de/man5", ["doc/man/de/linkcheckerrc.5"])) - data_files.append( - ( - "share/linkchecker/examples", - [ - "config/linkchecker-completion", - "doc/examples/check_failures.sh", - "doc/examples/check_for_x_errors.sh", - "doc/examples/check_urls.sh", - ], - ) - ) - -setup( - name=AppName, - use_scm_version={ - "local_scheme": "node-and-timestamp", - "version_scheme": "post-release", - }, - description=Description, - keywords="link,url,site,checking,crawling,verification,validation", - author=myname, - author_email=myemail, - maintainer=myname, - maintainer_email=myemail, - url="https://linkchecker.github.io/linkchecker/", - license="GPL", - long_description=get_long_description(), - long_description_content_type="text/x-rst", - cmdclass={ - "sdist": MySdist, - "build": MyBuild, - "egg_info": MyEggInfo, - "install_data": MyInstallData, - }, - packages=find_packages(include=["linkcheck", "linkcheck.*"]), - entry_points={ - "console_scripts": [ - "linkchecker = linkcheck.command.linkchecker:linkchecker" - ] - }, - data_files=data_files, - include_package_data=True, - classifiers=[ - "Topic :: Internet :: WWW/HTTP :: Site Management :: Link Checking", - "Development Status :: 5 - Production/Stable", - "License :: OSI Approved :: GNU General Public License (GPL)", - "Programming Language :: Python", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - ], - options={}, - # Requirements, usable with setuptools or the new Python packaging module. - python_requires=">= 3.7", - setup_requires=["setuptools_scm"], - install_requires=[ - "importlib_metadata;python_version<'3.8'", - "requests >= 2.4", - "dnspython >= 2.0", - "beautifulsoup4 >= 4.8.1", - ], - # Commented out since they are untested and not officially supported. - # See also doc/install.txt for more detailed dependency documentation. - # extra_requires = { - # "IP country info": ['GeoIP'], # https://pypi.org/project/GeoIP/ - # "Bash completion": ['argcomplete'], # https://pypi.org/project/argcomplete/ - # "Memory debugging": ['meliae'], # https://pypi.org/project/meliae/ - # } -) diff --git a/tools/hatch_build.py b/tools/hatch_build.py new file mode 100644 index 00000000..68578dcb --- /dev/null +++ b/tools/hatch_build.py @@ -0,0 +1,65 @@ +# Copyright (C) 2022 Chris Mayo +# +# 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 2 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, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +from pathlib import Path +import shutil +import subprocess + +from hatchling.builders.hooks.plugin.interface import BuildHookInterface + +try: + import polib +except ImportError: + COMPILE_TRANSLATIONS = False +else: + COMPILE_TRANSLATIONS = True + +LOCALE_DIR = ("linkcheck", "data", "locale") +RELEASE_PY = ("linkcheck", "_release.py") + + +class CustomBuildHook(BuildHookInterface): + def clean(self, versions): + Path(*RELEASE_PY).unlink(missing_ok=True) + shutil.rmtree(str(Path(*LOCALE_DIR)), ignore_errors=True) + + def initialize(self, version, build_data): + cp = None + committer_date = "unknown" + try: + cp = subprocess.run(["git", "log", "-n 1", "HEAD", "--format=%cs"], + capture_output=True, text=True) + except FileNotFoundError: + pass + else: + if cp and cp.stdout: + committer_date = cp.stdout.strip() + + Path(*RELEASE_PY).write_text( + f"__release_date__ = \"{committer_date}\"\n") + + if COMPILE_TRANSLATIONS: + for po in Path("po").glob("*.po"): + mo = Path( + *LOCALE_DIR, + po.stem, "LC_MESSAGES", "linkchecker", + ).with_suffix(".mo") + pofile = polib.pofile(str(po)) + mo.parent.mkdir(exist_ok=True, parents=True) + pofile.save_as_mofile(str(mo)) + else: + self.app.display_warning( + "polib package not found: translations not compiled")