From 5a95fd008f2c2d0175c8fca7ba5a66cbb527e979 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Fri, 10 Jul 2020 20:00:26 +0200 Subject: [PATCH 001/131] new release cycle --- CHANGES.md | 4 ++++ setup.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 7ee8cb3..b63c8e6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,9 @@ # Changes +## 0.2.2 (2020-XX-XX) + +- (TBD) + ## 0.2.1 (2020-07-10) - FIX: CI tests failed due to dependency issue in Python 3.4, see issue #13. diff --git a/setup.py b/setup.py index 94dacb7..e74fe70 100644 --- a/setup.py +++ b/setup.py @@ -43,7 +43,7 @@ # Bump version HERE! -_version_ = '0.2.1' +_version_ = '0.2.2' # List all versions of Python which are supported From 3a7ae3e57c76d0b6edc832ea8ef277e2a93a95a8 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 14:23:17 +0100 Subject: [PATCH 002/131] python versions --- CHANGES.md | 5 +++-- README.md | 4 ++-- setup.py | 15 ++++----------- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index b63c8e6..7592592 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,8 +1,9 @@ # Changes -## 0.2.2 (2020-XX-XX) +## 0.3.0 (2021-XX-XX) -- (TBD) +- FEATURE: Added support for Python 3.9 and 3.10. +- FEATURE: Dropped support for Python 3.4 and 3.5. ## 0.2.1 (2020-07-10) diff --git a/README.md b/README.md index 02f591c..3a88f1b 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![docs_master](https://readthedocs.org/projects/wenv/badge/?version=latest&style=flat-square "Documentation Status: master / release")](https://wenv.readthedocs.io/en/latest/) [![build_develop](https://img.shields.io/travis/pleiszenburg/wenv/develop.svg?style=flat-square "Build Status: development branch")](https://travis-ci.org/pleiszenburg/wenv) [![docs_develop](https://readthedocs.org/projects/wenv/badge/?version=develop&style=flat-square "Documentation Status: development branch")](https://wenv.readthedocs.io/en/develop/) -[![license](https://img.shields.io/pypi/l/wenv.svg?style=flat-square "Internet Systems Consortium License")](https://github.com/pleiszenburg/wenv/blob/master/LICENSE) +[![license](https://img.shields.io/pypi/l/wenv.svg?style=flat-square "GNU Lesser General Public License v2.1")](https://github.com/pleiszenburg/wenv/blob/master/LICENSE) [![status](https://img.shields.io/pypi/status/wenv.svg?style=flat-square "Project Development Status")](https://github.com/pleiszenburg/wenv/issues) [![pypi_version](https://img.shields.io/pypi/v/wenv.svg?style=flat-square "Project Development Status")](https://pypi.python.org/pypi/wenv) [![pypi_versions](https://img.shields.io/pypi/pyversions/wenv.svg?style=flat-square "Available on PyPi - the Python Package Index")](https://pypi.python.org/pypi/wenv) @@ -21,7 +21,7 @@ About Wine (from [winehq.org](https://www.winehq.org/)): *Wine (originally an ac | prerequisite | version | | --- | --- | -| [CPython](https://www.python.org/) | 3.x (tested with 3.{4,5,6,7,8}) | +| [CPython](https://www.python.org/) | 3.x (tested with 3.{6,7,8,9,10}) | | [Wine](https://www.winehq.org/) | 4.x (tested with regular & [staging](https://wine-staging.com/)) - expected to be in the user's [`PATH`](https://en.wikipedia.org/wiki/PATH_(variable)) | If you are limited to an older version of Wine such as 2.x or 3.x, see `wenv`'s [installation instructions](https://wenv.readthedocs.io/en/latest/installation.html) for details and workarounds. diff --git a/setup.py b/setup.py index e74fe70..414540d 100644 --- a/setup.py +++ b/setup.py @@ -43,12 +43,12 @@ # Bump version HERE! -_version_ = '0.2.2' +_version_ = '0.3.0' # List all versions of Python which are supported -python_minor_min = 4 -python_minor_max = 8 +python_minor_min = 6 +python_minor_max = 10 confirmed_python_versions = [ 'Programming Language :: Python :: 3.{MINOR:d}'.format(MINOR = minor) for minor in range(python_minor_min, python_minor_max + 1) @@ -65,13 +65,6 @@ raise SystemExit('You are already running Windows. No need for this package!') -# Python 3.4 dependency / CI fix -pls = 'python-language-server' -assert version_info.major == 3 -if version_info.minor <= 4: - pls += '<0.32.0' - - setup( name = 'wenv', packages = find_packages('src'), @@ -97,7 +90,7 @@ 'pytest', 'coverage', 'pytest-cov', - pls, + 'python-lsp-server', 'setuptools', 'Sphinx', 'sphinx_rtd_theme', From 326c7c7a3f67a6147b26eb567208aed2c308eea9 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 14:42:21 +0100 Subject: [PATCH 003/131] log --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 7592592..476d247 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,7 @@ - FEATURE: Added support for Python 3.9 and 3.10. - FEATURE: Dropped support for Python 3.4 and 3.5. +- FIX: Switched from unsupported `python-language-server` to supported `python-lsp-server`. ## 0.2.1 (2020-07-10) From 022f734b3d0a4940c84bd3774538b4c857765aaf Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 14:48:07 +0100 Subject: [PATCH 004/131] extra rq for lsp server --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 414540d..3ff7032 100644 --- a/setup.py +++ b/setup.py @@ -90,7 +90,7 @@ 'pytest', 'coverage', 'pytest-cov', - 'python-lsp-server', + 'python-lsp-server[all]', 'setuptools', 'Sphinx', 'sphinx_rtd_theme', From b6c36e57eb54f934cf854039e8ed7ac461379e40 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 14:48:14 +0100 Subject: [PATCH 005/131] fix class name --- src/wenv/_core/source.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wenv/_core/source.py b/src/wenv/_core/source.py index 7a11b86..6fb11ce 100644 --- a/src/wenv/_core/source.py +++ b/src/wenv/_core/source.py @@ -73,7 +73,7 @@ def get_available_python_versions(): version_downloads = ThreadPool(8).imap(lambda x: download(x, mode = 'text'), version_urls) embedded_versions = { version: [ - python_version.from_zipname(line.split('"')[1]) + PythonVersion.from_zipname(line.split('"')[1]) for line in version_download.split('\n') if all((line.startswith(' Date: Sat, 20 Nov 2021 14:54:22 +0100 Subject: [PATCH 006/131] structure cleanup --- makefile | 61 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/makefile b/makefile index f5f98b4..2374461 100644 --- a/makefile +++ b/makefile @@ -18,50 +18,55 @@ # specific language governing rights and limitations under the License. # +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# LIB +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -clean: - -rm -r build/* - -rm -r dist/* +_clean_coverage: coverage erase - make clean_py -clean_py: + +_clean_egg: + -rm -r src/*.egg-info + +_clean_py: find src/ tests/ -name '*.pyc' -exec rm -f {} + find src/ tests/ -name '*.pyo' -exec rm -f {} + find src/ tests/ -name '*~' -exec rm -f {} + find src/ tests/ -name '__pycache__' -exec rm -fr {} + -release_clean: - make clean - -rm -r src/*.egg-info - -docu: - @(cd docs; make clean; make html) +_clean_release: + -rm -r build/* + -rm -r dist/* -release: - make release_clean - python setup.py sdist bdist_wheel - gpg --detach-sign -a dist/wenv*.whl - gpg --detach-sign -a dist/wenv*.tar.gz +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# ENTRY POINTS +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -upload: - for filename in $$(ls dist/*.tar.gz dist/*.whl) ; do \ - twine upload $$filename $$filename.asc ; \ - done +clean: + make _clean_release + make _clean_coverage + make _clean_py -upload_test: - for filename in $$(ls dist/*.tar.gz dist/*.whl) ; do \ - twine upload $$filename $$filename.asc -r pypitest ; \ - done +docs: + @(cd docs; make clean; make html) install: pip install -U -e .[dev] # WENV_ARCH=win32 wenv init # WENV_ARCH=win64 wenv init -test: - make docu - make test_quick +release: + make clean + make _clean_egg + python setup.py sdist bdist_wheel + gpg --detach-sign -a dist/wenv*.whl + gpg --detach-sign -a dist/wenv*.tar.gz -test_quick: +test: make clean pytest + +upload: + for filename in $$(ls dist/*.tar.gz dist/*.whl) ; do \ + twine upload $$filename $$filename.asc ; \ + done From 2a0892f4d03c8f1435bd10b8e3600576d3ad8b3e Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 14:57:01 +0100 Subject: [PATCH 007/131] log --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 476d247..eb838f7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,7 @@ - FEATURE: Added support for Python 3.9 and 3.10. - FEATURE: Dropped support for Python 3.4 and 3.5. - FIX: Switched from unsupported `python-language-server` to supported `python-lsp-server`. +- API: New makefile structure for developers. ## 0.2.1 (2020-07-10) From 9ca451d578efaa99c807ac0e6e0c227466e539ea Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 14:58:50 +0100 Subject: [PATCH 008/131] added black support --- makefile | 3 +++ pyproject.toml | 25 +++++++++++++++++++++++++ setup.py | 1 + 3 files changed, 29 insertions(+) create mode 100644 pyproject.toml diff --git a/makefile b/makefile index 2374461..2a4f9de 100644 --- a/makefile +++ b/makefile @@ -42,6 +42,9 @@ _clean_release: # ENTRY POINTS # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +black: + black . + clean: make _clean_release make _clean_coverage diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..c910091 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,25 @@ +[build-system] +requires = ["setuptools", "wheel"] + +[tool.black] +target-version = ['py36'] +include = '\.pyi?$' +exclude = ''' +/( + \.eggs + | \.git + | \.venv + | \.cache + | \.hypothesis + | \.ipynb_checkpoints + | \.pytest_cache + | .ropenproject + | _build + | build + | buck-out + | demo_dll + | dist + | env?? + | env +)/ +''' diff --git a/setup.py b/setup.py index 3ff7032..03763c5 100644 --- a/setup.py +++ b/setup.py @@ -87,6 +87,7 @@ ], extras_require = { 'dev': [ + 'black', 'pytest', 'coverage', 'pytest-cov', From fce62d9f376605ccc0701903d64bd3419dbcdd70 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 15:05:31 +0100 Subject: [PATCH 009/131] fix env version match --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c910091..41bfe80 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ exclude = ''' | buck-out | demo_dll | dist - | env?? + | env[0-9]{2} | env )/ ''' From d0f81ca4ab6ac85df3b35d377c7fd71f02b81ef6 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 15:06:55 +0100 Subject: [PATCH 010/131] black --- setup.py | 148 +++++++++++++++++++++++++------------------------------ 1 file changed, 68 insertions(+), 80 deletions(-) diff --git a/setup.py b/setup.py index 03763c5..bf6890d 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- """ - WENV Running Python on Wine https://github.com/pleiszenburg/wenv @@ -21,107 +20,96 @@ WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. - """ - # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # IMPORT # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -from setuptools import ( - find_packages, - setup - ) import os -from sys import platform, version_info +from sys import platform +from setuptools import find_packages, setup # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # SETUP # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - # Bump version HERE! -_version_ = '0.3.0' - +_version_ = "0.3.0" # List all versions of Python which are supported python_minor_min = 6 python_minor_max = 10 confirmed_python_versions = [ - 'Programming Language :: Python :: 3.{MINOR:d}'.format(MINOR = minor) - for minor in range(python_minor_min, python_minor_max + 1) - ] - + "Programming Language :: Python :: 3.{MINOR:d}".format(MINOR=minor) + for minor in range(python_minor_min, python_minor_max + 1) +] # Fetch readme file -with open(os.path.join(os.path.dirname(__file__), 'README.md')) as f: - long_description = f.read() - +with open(os.path.join(os.path.dirname(__file__), "README.md")) as f: + long_description = f.read() # Just in case someone is actually running this on Windows ... -if platform.startswith('win'): - raise SystemExit('You are already running Windows. No need for this package!') - +if platform.startswith("win"): + raise SystemExit("You are already running Windows. No need for this package!") setup( - name = 'wenv', - packages = find_packages('src'), - package_dir = {'': 'src'}, - version = _version_, - description = 'Running Python on Wine', - long_description = long_description, - long_description_content_type = 'text/markdown', - author = 'Sebastian M. Ernst', - author_email = 'ernst@pleiszenburg.de', - url = 'https://github.com/pleiszenburg/wenv', - download_url = 'https://github.com/pleiszenburg/wenv/archive/v%s.tar.gz' % _version_, - license = 'LGPLv2', - keywords = ['wine', 'cross platform'], - scripts = [], - include_package_data = True, - python_requires = '>=3.{MINOR:d}'.format(MINOR = python_minor_min), - install_requires = [ - 'requests' - ], - extras_require = { - 'dev': [ - 'black', - 'pytest', - 'coverage', - 'pytest-cov', - 'python-lsp-server[all]', - 'setuptools', - 'Sphinx', - 'sphinx_rtd_theme', - 'twine', - 'wheel' - ] - }, - zip_safe = False, - entry_points = {'console_scripts': [ - 'wenv = wenv:cli', - '_wenv_python = wenv:shebang' - ]}, - classifiers = [ - 'Development Status :: 4 - Beta', - 'Intended Audience :: Developers', - 'Intended Audience :: Information Technology', - 'Intended Audience :: Science/Research', - 'Intended Audience :: System Administrators', - 'License :: OSI Approved :: GNU Lesser General Public License v2 (LGPLv2)', - 'Operating System :: MacOS', - 'Operating System :: POSIX :: BSD', - 'Operating System :: POSIX :: Linux', - 'Programming Language :: Python :: 3' - ] + confirmed_python_versions + [ - 'Programming Language :: Python :: 3 :: Only', - 'Programming Language :: Python :: Implementation :: CPython', - 'Topic :: Scientific/Engineering', - 'Topic :: Software Development', - 'Topic :: System :: Operating System', - 'Topic :: System :: Operating System Kernels', - 'Topic :: Utilities' - ] - ) + name="wenv", + packages=find_packages("src"), + package_dir={"": "src"}, + version=_version_, + description="Running Python on Wine", + long_description=long_description, + long_description_content_type="text/markdown", + author="Sebastian M. Ernst", + author_email="ernst@pleiszenburg.de", + url="https://github.com/pleiszenburg/wenv", + download_url="https://github.com/pleiszenburg/wenv/archive/v%s.tar.gz" % _version_, + license="LGPLv2", + keywords=["wine", "cross platform"], + scripts=[], + include_package_data=True, + python_requires=">=3.{MINOR:d}".format(MINOR=python_minor_min), + install_requires=["requests"], + extras_require={ + "dev": [ + "black", + "pytest", + "coverage", + "pytest-cov", + "python-lsp-server[all]", + "setuptools", + "Sphinx", + "sphinx_rtd_theme", + "twine", + "wheel", + ] + }, + zip_safe=False, + entry_points={ + "console_scripts": ["wenv = wenv:cli", "_wenv_python = wenv:shebang"] + }, + classifiers=[ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "Intended Audience :: Information Technology", + "Intended Audience :: Science/Research", + "Intended Audience :: System Administrators", + "License :: OSI Approved :: GNU Lesser General Public License v2 (LGPLv2)", + "Operating System :: MacOS", + "Operating System :: POSIX :: BSD", + "Operating System :: POSIX :: Linux", + "Programming Language :: Python :: 3", + ] + + confirmed_python_versions + + [ + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: Implementation :: CPython", + "Topic :: Scientific/Engineering", + "Topic :: Software Development", + "Topic :: System :: Operating System", + "Topic :: System :: Operating System Kernels", + "Topic :: Utilities", + ], +) From 799a6c2dcb318aa0ff4b0b875c44cb607e062d54 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 15:07:40 +0100 Subject: [PATCH 011/131] spaces --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index bf6890d..fd8f61c 100644 --- a/setup.py +++ b/setup.py @@ -5,9 +5,9 @@ Running Python on Wine https://github.com/pleiszenburg/wenv - setup.py: Used for package distribution + setup.py: Used for package distribution - Copyright (C) 2017-2020 Sebastian M. Ernst + Copyright (C) 2017-2020 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License From 3f23e03c730aa28cfd77a8c650084c4aa4476aef Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 15:08:26 +0100 Subject: [PATCH 012/131] black --- docs/conf.py | 80 ++++++++++++++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 476ee98..085fe37 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -7,9 +7,9 @@ Running Python on Wine https://github.com/pleiszenburg/wenv - docs/conf.py: Configures the documentation build process + docs/conf.py: Configures the documentation build process - Copyright (C) 2017-2020 Sebastian M. Ernst + Copyright (C) 2017-2020 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License @@ -25,20 +25,21 @@ """ + def fetch_version_string(): - f = open('../setup.py', 'r') - setup_py = f.read() - f.close() + f = open("../setup.py", "r") + setup_py = f.read() + f.close() - version = '' - setup_py_lines = setup_py.split('\n') - for line in setup_py_lines: - if '_version_' in line: - version = line.split("'")[1].split("'")[0] - break + version = "" + setup_py_lines = setup_py.split("\n") + for line in setup_py_lines: + if "_version_" in line: + version = line.split("'")[1].split("'")[0] + break - return version + return version # If extensions (or modules to document with autodoc) are in another directory, @@ -62,21 +63,21 @@ def fetch_version_string(): extensions = [] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] -source_suffix = '.rst' +source_suffix = ".rst" # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = 'wenv' -copyright = '2017-2019 Sebastian M. Ernst' -author = 'Sebastian M. Ernst' +project = "wenv" +copyright = "2017-2019 Sebastian M. Ernst" +author = "Sebastian M. Ernst" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -97,10 +98,10 @@ def fetch_version_string(): # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False @@ -123,7 +124,7 @@ def fetch_version_string(): # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # Custom sidebar templates, must be a dictionary that maps document names # to template names. @@ -131,12 +132,12 @@ def fetch_version_string(): # This is required for the alabaster theme # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars html_sidebars = { - '**': [ - 'about.html', - 'navigation.html', - 'relations.html', # needs 'show_related': True theme option to display - 'searchbox.html', - 'donate.html', + "**": [ + "about.html", + "navigation.html", + "relations.html", # needs 'show_related': True theme option to display + "searchbox.html", + "donate.html", ] } @@ -144,7 +145,7 @@ def fetch_version_string(): # -- Options for HTMLHelp output ------------------------------------------ # Output file base name for HTML help builder. -htmlhelp_basename = 'wenvdoc' +htmlhelp_basename = "wenvdoc" # -- Options for LaTeX output --------------------------------------------- @@ -153,15 +154,12 @@ def fetch_version_string(): # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. # # 'preamble': '', - # Latex figure (float) alignment # # 'figure_align': 'htbp', @@ -171,8 +169,7 @@ def fetch_version_string(): # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'wenv.tex', 'wenv Documentation', - 'Sebastian M. Ernst', 'manual'), + (master_doc, "wenv.tex", "wenv Documentation", "Sebastian M. Ernst", "manual"), ] @@ -180,10 +177,7 @@ def fetch_version_string(): # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'wenv', 'wenv Documentation', - [author], 1) -] +man_pages = [(master_doc, "wenv", "wenv Documentation", [author], 1)] # -- Options for Texinfo output ------------------------------------------- @@ -192,7 +186,13 @@ def fetch_version_string(): # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'wenv', 'wenv Documentation', - author, 'wenv', 'One line description of project.', - 'Miscellaneous'), + ( + master_doc, + "wenv", + "wenv Documentation", + author, + "wenv", + "One line description of project.", + "Miscellaneous", + ), ] From a954d2e1d6dfff57799c742992f64cce3348b652 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 15:10:40 +0100 Subject: [PATCH 013/131] black --- src/wenv/__init__.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/wenv/__init__.py b/src/wenv/__init__.py index c29302c..4e81016 100644 --- a/src/wenv/__init__.py +++ b/src/wenv/__init__.py @@ -6,9 +6,9 @@ Running Python on Wine https://github.com/pleiszenburg/wenv - src/wenv/__init__.py: Package init file + src/wenv/__init__.py: Package init file - Copyright (C) 2017-2020 Sebastian M. Ernst + Copyright (C) 2017-2020 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License @@ -29,15 +29,15 @@ # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ from ._core.env import ( - cli, - Env, - shebang, - ) + cli, + Env, + shebang, +) from ._core.config import ( - EnvConfig, - ) + EnvConfig, +) from ._core.errors import ( - EnvConfigParserError, - ) + EnvConfigParserError, +) -env = Env # legacy +env = Env # legacy From 2c37c73732b21eafdb2994ee467e2151397311ad Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 15:11:32 +0100 Subject: [PATCH 014/131] black --- src/wenv/__init__.py | 2 -- src/wenv/__main__.py | 6 ++---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/wenv/__init__.py b/src/wenv/__init__.py index 4e81016..23795ea 100644 --- a/src/wenv/__init__.py +++ b/src/wenv/__init__.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- """ - WENV Running Python on Wine https://github.com/pleiszenburg/wenv @@ -21,7 +20,6 @@ WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. - """ # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/src/wenv/__main__.py b/src/wenv/__main__.py index d77bf8e..58e6be5 100644 --- a/src/wenv/__main__.py +++ b/src/wenv/__main__.py @@ -1,14 +1,13 @@ # -*- coding: utf-8 -*- """ - WENV Running Python on Wine https://github.com/pleiszenburg/wenv - src/wenv/__init__.py: Package entry point + src/wenv/__init__.py: Package entry point - Copyright (C) 2017-2020 Sebastian M. Ernst + Copyright (C) 2017-2020 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License @@ -21,7 +20,6 @@ WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. - """ # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ From f851628a76613c20ddcc584f0cf54dec621e6dc5 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 15:11:57 +0100 Subject: [PATCH 015/131] black --- src/wenv/_core/__init__.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/wenv/_core/__init__.py b/src/wenv/_core/__init__.py index fe70f96..21522fd 100644 --- a/src/wenv/_core/__init__.py +++ b/src/wenv/_core/__init__.py @@ -1,14 +1,13 @@ # -*- coding: utf-8 -*- """ - WENV Running Python on Wine https://github.com/pleiszenburg/wenv - src/wenv/_core/__init__.py: Core module + src/wenv/_core/__init__.py: Core module - Copyright (C) 2017-2020 Sebastian M. Ernst + Copyright (C) 2017-2020 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License @@ -21,5 +20,4 @@ WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. - """ From 5b0ca72442577372cdade30f77ef97e8f94abe5a Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 15:13:47 +0100 Subject: [PATCH 016/131] black; doc string --- src/wenv/_core/config.py | 264 ++++++++++++++++++++------------------- 1 file changed, 137 insertions(+), 127 deletions(-) diff --git a/src/wenv/_core/config.py b/src/wenv/_core/config.py index 9ac4f12..2989879 100644 --- a/src/wenv/_core/config.py +++ b/src/wenv/_core/config.py @@ -1,14 +1,13 @@ # -*- coding: utf-8 -*- """ - WENV Running Python on Wine https://github.com/pleiszenburg/wenv - src/wenv/_core/config.py: Handles the modules configuration + src/wenv/_core/config.py: Handles the modules configuration - Copyright (C) 2017-2020 Sebastian M. Ernst + Copyright (C) 2017-2020 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License @@ -21,7 +20,6 @@ WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. - """ # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -40,127 +38,139 @@ # CONFIGURATION CLASS # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -class EnvConfig(dict): - def __init__(self, **override_dict): - - # Call parent constructur, just in case - super().__init__() - - # Get config from files as a prioritized list - for config in self._get_config_from_files(): - self.update(config) - - # Add override parameters - if len(override_dict) > 0: - self.update(override_dict) - - def __getitem__(self, key): - - env_var = 'WENV_{NAME:s}'.format(NAME = key.upper()) - if env_var in os.environ.keys(): - value = os.environ[env_var] - if len(value) > 0: - if value.isnumeric(): - return int(value) - elif value.strip().lower() in ('true', 'false'): - return {'true': True, 'false': False}[value.strip().lower()] - else: - return value - - if key in self.keys(): - return super().__getitem__(key) - - if key == 'arch': - return 'win32' # Define Wine & Wine-Python architecture - elif key == 'pythonversion': - return '3.7.4' # Define Wine-Python version - elif key == 'winedebug': - return '-all' # Wine debug output off - elif key == 'wineinstallprefix': - return None # no custom Wine installation outside of PATH - elif key == 'prefix': - install_location = os.path.abspath(__file__) - if install_location.startswith(site.USER_BASE): # Hacky way of looking for a user installation - return site.USER_BASE - else: - return sys.prefix - elif key == 'wineprefix': - return os.path.join(self['prefix'], 'share', 'wenv', self['arch']) - elif key == 'pythonprefix': - return os.path.join(self['wineprefix'], 'drive_c', 'python-%s' % self['pythonversion']) - elif key == 'offline': - return False - elif key == 'cache': - return os.path.join(self['prefix'], 'share', 'wenv', 'cache') - elif key == 'packages': - return os.path.join(self['cache'], 'packages') - elif key == '_issues_50_workaround': - return False # Workaround for zugbruecke issue #50 (symlinks ...) - else: - raise KeyError('not a valid configuration key') - - def export_envvar_dict(self): - - return { - 'WENV_' + field.upper(): '' if field is None else str(self[field]) - for field in ( - 'arch', - 'pythonversion', - 'winedebug', - 'wineinstallprefix', - 'prefix', - 'wineprefix', - 'pythonprefix', - 'offline', - 'cache', - 'packages', - '_issues_50_workaround', - ) - } - - def _get_config_from_files(self): - - # Look for config in the usual spots - for fn in [ - '/etc/wenv', - os.path.join('/etc', CONFIG_FN), - os.path.join(os.path.expanduser('~'), CONFIG_FN), - os.environ.get('WENV'), - os.path.join(os.environ.get('WENV'), CONFIG_FN) if os.environ.get('WENV') is not None else None, - os.path.join(os.getcwd(), CONFIG_FN), - ]: - - cnt_dict = self._load_config_from_file(fn) - - if cnt_dict is not None: - yield cnt_dict - - def _load_config_from_file(self, try_path): - - # If there is a path ... - if try_path is None: - return - - # Is this a file? - if not os.path.isfile(try_path): - return - - # Read file - try: - with open(try_path, 'r', encoding = 'utf-8') as f: - cnt = f.read() - except: - raise EnvConfigParserError('Config file could not be read: "%s"' % try_path) - - # Try to parse it - try: - cnt_dict = json.loads(cnt) - except: - raise EnvConfigParserError('Config file could not be parsed: "%s"' % try_path) - - # Ensure that config has the right format - if not isinstance(cnt_dict, dict): - raise EnvConfigParserError('Config file is malformed: "%s"' % try_path) - - return cnt_dict +class EnvConfig(dict): + """ + Wine Python environment configuration + """ + + def __init__(self, **override_dict): + + # Call parent constructur, just in case + super().__init__() + + # Get config from files as a prioritized list + for config in self._get_config_from_files(): + self.update(config) + + # Add override parameters + if len(override_dict) > 0: + self.update(override_dict) + + def __getitem__(self, key): + + env_var = "WENV_{NAME:s}".format(NAME=key.upper()) + if env_var in os.environ.keys(): + value = os.environ[env_var] + if len(value) > 0: + if value.isnumeric(): + return int(value) + elif value.strip().lower() in ("true", "false"): + return {"true": True, "false": False}[value.strip().lower()] + else: + return value + + if key in self.keys(): + return super().__getitem__(key) + + if key == "arch": + return "win32" # Define Wine & Wine-Python architecture + elif key == "pythonversion": + return "3.7.4" # Define Wine-Python version + elif key == "winedebug": + return "-all" # Wine debug output off + elif key == "wineinstallprefix": + return None # no custom Wine installation outside of PATH + elif key == "prefix": + install_location = os.path.abspath(__file__) + if install_location.startswith( + site.USER_BASE + ): # Hacky way of looking for a user installation + return site.USER_BASE + else: + return sys.prefix + elif key == "wineprefix": + return os.path.join(self["prefix"], "share", "wenv", self["arch"]) + elif key == "pythonprefix": + return os.path.join( + self["wineprefix"], "drive_c", "python-%s" % self["pythonversion"] + ) + elif key == "offline": + return False + elif key == "cache": + return os.path.join(self["prefix"], "share", "wenv", "cache") + elif key == "packages": + return os.path.join(self["cache"], "packages") + elif key == "_issues_50_workaround": + return False # Workaround for zugbruecke issue #50 (symlinks ...) + else: + raise KeyError("not a valid configuration key") + + def export_envvar_dict(self): + + return { + "WENV_" + field.upper(): "" if field is None else str(self[field]) + for field in ( + "arch", + "pythonversion", + "winedebug", + "wineinstallprefix", + "prefix", + "wineprefix", + "pythonprefix", + "offline", + "cache", + "packages", + "_issues_50_workaround", + ) + } + + def _get_config_from_files(self): + + # Look for config in the usual spots + for fn in [ + "/etc/wenv", + os.path.join("/etc", CONFIG_FN), + os.path.join(os.path.expanduser("~"), CONFIG_FN), + os.environ.get("WENV"), + os.path.join(os.environ.get("WENV"), CONFIG_FN) + if os.environ.get("WENV") is not None + else None, + os.path.join(os.getcwd(), CONFIG_FN), + ]: + + cnt_dict = self._load_config_from_file(fn) + + if cnt_dict is not None: + yield cnt_dict + + def _load_config_from_file(self, try_path): + + # If there is a path ... + if try_path is None: + return + + # Is this a file? + if not os.path.isfile(try_path): + return + + # Read file + try: + with open(try_path, "r", encoding="utf-8") as f: + cnt = f.read() + except: + raise EnvConfigParserError('Config file could not be read: "%s"' % try_path) + + # Try to parse it + try: + cnt_dict = json.loads(cnt) + except: + raise EnvConfigParserError( + 'Config file could not be parsed: "%s"' % try_path + ) + + # Ensure that config has the right format + if not isinstance(cnt_dict, dict): + raise EnvConfigParserError('Config file is malformed: "%s"' % try_path) + + return cnt_dict From c1efba264985522642bf64e393a60c93f3a7a1e8 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 15:17:09 +0100 Subject: [PATCH 017/131] style --- src/wenv/_core/config.py | 40 +++++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/src/wenv/_core/config.py b/src/wenv/_core/config.py index 2989879..b1474fa 100644 --- a/src/wenv/_core/config.py +++ b/src/wenv/_core/config.py @@ -65,46 +65,44 @@ def __getitem__(self, key): if len(value) > 0: if value.isnumeric(): return int(value) - elif value.strip().lower() in ("true", "false"): + if value.strip().lower() in ("true", "false"): return {"true": True, "false": False}[value.strip().lower()] - else: - return value + return value if key in self.keys(): return super().__getitem__(key) if key == "arch": return "win32" # Define Wine & Wine-Python architecture - elif key == "pythonversion": + if key == "pythonversion": return "3.7.4" # Define Wine-Python version - elif key == "winedebug": + if key == "winedebug": return "-all" # Wine debug output off - elif key == "wineinstallprefix": + if key == "wineinstallprefix": return None # no custom Wine installation outside of PATH - elif key == "prefix": + if key == "prefix": install_location = os.path.abspath(__file__) if install_location.startswith( site.USER_BASE ): # Hacky way of looking for a user installation return site.USER_BASE - else: - return sys.prefix - elif key == "wineprefix": + return sys.prefix + if key == "wineprefix": return os.path.join(self["prefix"], "share", "wenv", self["arch"]) - elif key == "pythonprefix": + if key == "pythonprefix": return os.path.join( self["wineprefix"], "drive_c", "python-%s" % self["pythonversion"] ) - elif key == "offline": + if key == "offline": return False - elif key == "cache": + if key == "cache": return os.path.join(self["prefix"], "share", "wenv", "cache") - elif key == "packages": + if key == "packages": return os.path.join(self["cache"], "packages") - elif key == "_issues_50_workaround": + if key == "_issues_50_workaround": return False # Workaround for zugbruecke issue #50 (symlinks ...) - else: - raise KeyError("not a valid configuration key") + + raise KeyError("not a valid configuration key") def export_envvar_dict(self): @@ -158,16 +156,16 @@ def _load_config_from_file(self, try_path): try: with open(try_path, "r", encoding="utf-8") as f: cnt = f.read() - except: - raise EnvConfigParserError('Config file could not be read: "%s"' % try_path) + except Exception as e: + raise EnvConfigParserError('Config file could not be read: "%s"' % try_path) from e # Try to parse it try: cnt_dict = json.loads(cnt) - except: + except Exception as e: raise EnvConfigParserError( 'Config file could not be parsed: "%s"' % try_path - ) + ) from e # Ensure that config has the right format if not isinstance(cnt_dict, dict): From 25a782de9e7571a361763a81fc40fa7416034a08 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 15:17:40 +0100 Subject: [PATCH 018/131] black --- src/wenv/_core/const.py | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/wenv/_core/const.py b/src/wenv/_core/const.py index 043d356..aaec3ca 100644 --- a/src/wenv/_core/const.py +++ b/src/wenv/_core/const.py @@ -1,14 +1,13 @@ # -*- coding: utf-8 -*- """ - WENV Running Python on Wine https://github.com/pleiszenburg/wenv - src/wenv/_core/const.py: Holds constant values, flags, types + src/wenv/_core/const.py: Holds constant values, flags, types - Copyright (C) 2017-2020 Sebastian M. Ernst + Copyright (C) 2017-2020 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License @@ -21,14 +20,13 @@ WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. - """ # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # FOLDER- AND FILENAMES # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -CONFIG_FN = '.wenv.json' +CONFIG_FN = ".wenv.json" # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # COVERAGE @@ -58,15 +56,15 @@ # https://en.wikipedia.org/wiki/ANSI_escape_code c = { - 'RESET': '\033[0;0m', - 'BOLD': '\033[;1m', - 'REVERSE': '\033[;7m', - 'GREY': '\033[1;30m', - 'RED': '\033[1;31m', - 'GREEN': '\033[1;32m', - 'YELLOW': '\033[1;33m', - 'BLUE': '\033[1;34m', - 'MAGENTA': '\033[1;35m', - 'CYAN': '\033[1;36m', - 'WHITE': '\033[1;37m' - } + "RESET": "\033[0;0m", + "BOLD": "\033[;1m", + "REVERSE": "\033[;7m", + "GREY": "\033[1;30m", + "RED": "\033[1;31m", + "GREEN": "\033[1;32m", + "YELLOW": "\033[1;33m", + "BLUE": "\033[1;34m", + "MAGENTA": "\033[1;35m", + "CYAN": "\033[1;36m", + "WHITE": "\033[1;37m", +} From dd9771f7002e6028126bf1bd01e2e62567efba86 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 15:18:55 +0100 Subject: [PATCH 019/131] black --- src/wenv/_core/env.py | 1125 +++++++++++++++++++++-------------------- 1 file changed, 590 insertions(+), 535 deletions(-) diff --git a/src/wenv/_core/env.py b/src/wenv/_core/env.py index 4af12bc..c32a02a 100644 --- a/src/wenv/_core/env.py +++ b/src/wenv/_core/env.py @@ -1,14 +1,13 @@ # -*- coding: utf-8 -*- """ - WENV Running Python on Wine https://github.com/pleiszenburg/wenv - src/wenv/_core/env.py: Managing a Wine-Python environment + src/wenv/_core/env.py: Managing a Wine-Python environment - Copyright (C) 2017-2020 Sebastian M. Ernst + Copyright (C) 2017-2020 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License @@ -21,7 +20,6 @@ WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. - """ # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -44,576 +42,633 @@ # HELPER ROUTINES # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + def _symlink(src, dest): - if not os.path.lexists(dest): - os.symlink(src, dest) + if not os.path.lexists(dest): + os.symlink(src, dest) + + if not os.path.exists(dest): + raise OSError('"{LINK:s}" could not be created'.format(LINK=dest)) + if not os.path.islink(dest): + raise OSError('"{LINK:s}" is not a symlink'.format(LINK=dest)) + if os.readlink(dest) != src: + raise OSError('"{LINK:s}" points to the wrong source'.format(LINK=dest)) - if not os.path.exists(dest): - raise OSError('"{LINK:s}" could not be created'.format(LINK = dest)) - if not os.path.islink(dest): - raise OSError('"{LINK:s}" is not a symlink'.format(LINK = dest)) - if os.readlink(dest) != src: - raise OSError('"{LINK:s}" points to the wrong source'.format(LINK = dest)) # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # PATHS # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + class Paths: + def __init__(self, pythonprefix, arch, pythonversion): + + self._pythonprefix = pythonprefix + self._pythonversion_block = PythonVersion.from_config( + arch, pythonversion + ).as_block() + + def __getitem__(self, key): + + if key == "pythonprefix": + return self._pythonprefix + elif key == "lib": + return os.path.join(self["pythonprefix"], "Lib") + elif key == "sitepackages": + return os.path.join(self["lib"], "site-packages") + elif key == "sitecustomize": + return os.path.join(self["sitepackages"], "sitecustomize.py") + elif key == "scripts": + return os.path.join(self["pythonprefix"], "Scripts") + elif key == "interpreter": + return os.path.join(self["pythonprefix"], "python.exe") + elif key == "pip": + return os.path.join(self["scripts"], "pip.exe") + elif key == "libzip": + return os.path.join( + self["pythonprefix"], "python%s.zip" % self._pythonversion_block + ) + elif key == "pth": + return os.path.join( + self["pythonprefix"], "python%s._pth" % self._pythonversion_block + ) + else: + raise KeyError("not a valid path key") - def __init__(self, pythonprefix, arch, pythonversion): - - self._pythonprefix = pythonprefix - self._pythonversion_block = PythonVersion.from_config(arch, pythonversion).as_block() - - def __getitem__(self, key): - - if key == 'pythonprefix': - return self._pythonprefix - elif key == 'lib': - return os.path.join(self['pythonprefix'], 'Lib') - elif key == 'sitepackages': - return os.path.join(self['lib'], 'site-packages') - elif key == 'sitecustomize': - return os.path.join(self['sitepackages'], 'sitecustomize.py') - elif key == 'scripts': - return os.path.join(self['pythonprefix'], 'Scripts') - elif key == 'interpreter': - return os.path.join(self['pythonprefix'], 'python.exe') - elif key == 'pip': - return os.path.join(self['scripts'], 'pip.exe') - elif key == 'libzip': - return os.path.join(self['pythonprefix'], 'python%s.zip' % self._pythonversion_block) - elif key == 'pth': - return os.path.join(self['pythonprefix'], 'python%s._pth' % self._pythonversion_block) - else: - raise KeyError('not a valid path key') # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # WINE-PYTHON ENVIRONMENT CLASS # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -class Env: - -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -# INIT -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - - def __init__(self, **kwargs): - - if 'parameter' in kwargs.keys(): # legacy API - if len(kwargs) > 1: - raise ValueError('legacy API: only allows one parameter dict, named "parameter"') - kwargs = kwargs['parameter'] - if kwargs is None: - kwargs = dict() - if not isinstance(kwargs, dict) and not isinstance(kwargs, EnvConfig): - raise TypeError('legacy API: only allows one parameter, must be a dict or EnvConfig') - if not isinstance(kwargs, EnvConfig): - kwargs = EnvConfig(**kwargs) - else: - kwargs = EnvConfig(**kwargs) - - self._p = kwargs - self._init_dicts() - - def _init_dicts(self): - """ - Initialize core dictionaries. Function can also be used for re-initialization. - """ - - # Init Wine cmd names - self._wine_dict = {'win32': 'wine', 'win64': 'wine64'} - - # Init Python environment paths - self._path_dict = Paths(self._p['pythonprefix'], self._p['arch'], self._p['pythonversion']) - # Init Python commands and scripts - self._init_cmd_dict() - # Init internal CLI commands - self._init_cli_dict() - # Init environment variables - self._init_envvar_dict() - - def _init_cmd_dict(self): - - def ls_exe(dir): - if not os.path.isdir(dir): - return - for item in os.listdir(dir): - if not item.lower().endswith('.exe'): - continue - yield item[:-4], os.path.join(dir, item) - - self._cmd_dict = { - item: path for item, path in ls_exe(self._path_dict['scripts']) - } - self._cmd_dict.update({ - item: path for item, path in ls_exe(self._path_dict['pythonprefix']) - }) - - def _init_cli_dict(self): - - self._cli_dict = { - item[5:]: getattr(self, item) - for item in dir(self) - if item.startswith('_cli_') and hasattr(getattr(self, item), '__call__') - } - - def _init_envvar_dict(self): - - self._envvar_dict = {k: os.environ[k] for k in os.environ.keys()} # HACK Required for Travis CI - self._envvar_dict.update(dict( - WINEARCH = self._p['arch'], # Architecture - WINEPREFIX = self._p['wineprefix'], # Wine prefix / directory - WINEDLLOVERRIDES = 'mscoree=d', # Disable MONO: https://unix.stackexchange.com/a/191609 - WINEDEBUG = self._p['winedebug'], # Wine debug level - PYTHONHOME = self._p['pythonprefix'], # Python home for Wine Python (can be a Unix path) - VIRTUAL_ENV = '', # Reset Unix virtual env variable - wenv is "independent" - PIP_NO_WARN_SCRIPT_LOCATION = '0', # pip will not warn that pythonprefix and scripts are not in PATH - )) - if self._p['wineinstallprefix'] not in (None, ''): # allow custom installations of Wine outside of PATH - path = self._envvar_dict.get('PATH', '') - self._envvar_dict['PATH'] = os.path.join(self._p['wineinstallprefix'], 'bin') + ':' + path - ld_library_path = self._envvar_dict.get('LD_LIBRARY_PATH', '') - self._envvar_dict['LD_LIBRARY_PATH'] = ':'.join(( - os.path.join(self._p['wineinstallprefix'], 'lib'), - os.path.join(self._p['wineinstallprefix'], 'lib64'), - ld_library_path - )) # https://wiki.winehq.org/FAQ#Can_I_install_more_than_one_Wine_version_on_my_system.3F - -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -# WINE BUG #47766 / ZUGBRUECKE BUG #49 -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - - def wine_47766_workaround(self): - """ - PathAllocCanonicalize treats path segments start with dots wrong. - https://bugs.winehq.org/show_bug.cgi?id=47766 - """ - - is_clean = lambda path: not any([seg.startswith('.') for seg in path.split(os.path.sep)]) - - pythonprefix = os.path.abspath(self._p['pythonprefix']) - - if pythonprefix != self._p['pythonprefix']: - self._p['pythonprefix'] = pythonprefix - self._init_dicts() - - if is_clean(self._p['pythonprefix']): - return - - import tempfile, hashlib - link_path = os.path.join( - tempfile.gettempdir(), - 'wenv-' + hashlib.sha256(self._p['pythonprefix'].encode('utf-8')).hexdigest()[:8], - ) - if not is_clean(link_path): - raise OSError('unable to create clean link path: "{LINK:s}"'.format(LINK = link_path)) - - if os.path.exists(self._p['pythonprefix']): - _symlink(self._p['pythonprefix'], link_path) - - self._p['pythonprefix'] = link_path - self._init_dicts() - - def wine_47766_workaround_uninstall(self): - - self.wine_47766_workaround() - - if os.path.lexists(self._p['pythonprefix']): - os.unlink(self._p['pythonprefix']) - -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -# ENSURE ENVIRONMENT -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - - def ensure(self): - - self.setup_wineprefix() - self.setup_pythonprefix() - self.wine_47766_workaround() # must run after setup_pythonprefix and before setup_pip - self.setup_pip() - -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -# DESTROY / UNINSTALL ENVIRONMENT -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - - def uninstall(self): - - # Does Wine prefix exist? - if os.path.exists(self._p['wineprefix']): - # Delete tree - shutil.rmtree(self._p['wineprefix']) - - # Does Python prefix exist? - if os.path.exists(self._p['pythonprefix']): - # Delete tree - shutil.rmtree(self._p['pythonprefix']) - - self.wine_47766_workaround_uninstall() - -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -# CACHE INSTALLATION FILES LOCALLY -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - - def cache(self): - - version = PythonVersion.from_config(self._p['arch'], self._p['pythonversion']) - os.makedirs(self._p['cache'], exist_ok = True) +class Env: - with open(os.path.join(self._p['cache'], version.as_zipname()), 'wb') as f: - f.write(self._get_python(offline = False)) - with open(os.path.join(self._p['cache'], 'get-pip.py'), 'wb') as f: - f.write(self._get_pip(offline = False)) + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + # INIT + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + def __init__(self, **kwargs): + + if "parameter" in kwargs.keys(): # legacy API + if len(kwargs) > 1: + raise ValueError( + 'legacy API: only allows one parameter dict, named "parameter"' + ) + kwargs = kwargs["parameter"] + if kwargs is None: + kwargs = dict() + if not isinstance(kwargs, dict) and not isinstance(kwargs, EnvConfig): + raise TypeError( + "legacy API: only allows one parameter, must be a dict or EnvConfig" + ) + if not isinstance(kwargs, EnvConfig): + kwargs = EnvConfig(**kwargs) + else: + kwargs = EnvConfig(**kwargs) + + self._p = kwargs + self._init_dicts() + + def _init_dicts(self): + """ + Initialize core dictionaries. Function can also be used for re-initialization. + """ + + # Init Wine cmd names + self._wine_dict = {"win32": "wine", "win64": "wine64"} + + # Init Python environment paths + self._path_dict = Paths( + self._p["pythonprefix"], self._p["arch"], self._p["pythonversion"] + ) + # Init Python commands and scripts + self._init_cmd_dict() + # Init internal CLI commands + self._init_cli_dict() + # Init environment variables + self._init_envvar_dict() + + def _init_cmd_dict(self): + def ls_exe(dir): + if not os.path.isdir(dir): + return + for item in os.listdir(dir): + if not item.lower().endswith(".exe"): + continue + yield item[:-4], os.path.join(dir, item) + + self._cmd_dict = { + item: path for item, path in ls_exe(self._path_dict["scripts"]) + } + self._cmd_dict.update( + {item: path for item, path in ls_exe(self._path_dict["pythonprefix"])} + ) + + def _init_cli_dict(self): + + self._cli_dict = { + item[5:]: getattr(self, item) + for item in dir(self) + if item.startswith("_cli_") and hasattr(getattr(self, item), "__call__") + } + + def _init_envvar_dict(self): + + self._envvar_dict = { + k: os.environ[k] for k in os.environ.keys() + } # HACK Required for Travis CI + self._envvar_dict.update( + dict( + WINEARCH=self._p["arch"], # Architecture + WINEPREFIX=self._p["wineprefix"], # Wine prefix / directory + WINEDLLOVERRIDES="mscoree=d", # Disable MONO: https://unix.stackexchange.com/a/191609 + WINEDEBUG=self._p["winedebug"], # Wine debug level + PYTHONHOME=self._p[ + "pythonprefix" + ], # Python home for Wine Python (can be a Unix path) + VIRTUAL_ENV="", # Reset Unix virtual env variable - wenv is "independent" + PIP_NO_WARN_SCRIPT_LOCATION="0", # pip will not warn that pythonprefix and scripts are not in PATH + ) + ) + if self._p["wineinstallprefix"] not in ( + None, + "", + ): # allow custom installations of Wine outside of PATH + path = self._envvar_dict.get("PATH", "") + self._envvar_dict["PATH"] = ( + os.path.join(self._p["wineinstallprefix"], "bin") + ":" + path + ) + ld_library_path = self._envvar_dict.get("LD_LIBRARY_PATH", "") + self._envvar_dict["LD_LIBRARY_PATH"] = ":".join( + ( + os.path.join(self._p["wineinstallprefix"], "lib"), + os.path.join(self._p["wineinstallprefix"], "lib64"), + ld_library_path, + ) + ) # https://wiki.winehq.org/FAQ#Can_I_install_more_than_one_Wine_version_on_my_system.3F + + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + # WINE BUG #47766 / ZUGBRUECKE BUG #49 + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + def wine_47766_workaround(self): + """ + PathAllocCanonicalize treats path segments start with dots wrong. + https://bugs.winehq.org/show_bug.cgi?id=47766 + """ + + is_clean = lambda path: not any( + [seg.startswith(".") for seg in path.split(os.path.sep)] + ) + + pythonprefix = os.path.abspath(self._p["pythonprefix"]) + + if pythonprefix != self._p["pythonprefix"]: + self._p["pythonprefix"] = pythonprefix + self._init_dicts() + + if is_clean(self._p["pythonprefix"]): + return + + import tempfile, hashlib + + link_path = os.path.join( + tempfile.gettempdir(), + "wenv-" + + hashlib.sha256(self._p["pythonprefix"].encode("utf-8")).hexdigest()[:8], + ) + if not is_clean(link_path): + raise OSError( + 'unable to create clean link path: "{LINK:s}"'.format(LINK=link_path) + ) + + if os.path.exists(self._p["pythonprefix"]): + _symlink(self._p["pythonprefix"], link_path) + + self._p["pythonprefix"] = link_path + self._init_dicts() + + def wine_47766_workaround_uninstall(self): + + self.wine_47766_workaround() + + if os.path.lexists(self._p["pythonprefix"]): + os.unlink(self._p["pythonprefix"]) + + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + # ENSURE ENVIRONMENT + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + def ensure(self): + + self.setup_wineprefix() + self.setup_pythonprefix() + self.wine_47766_workaround() # must run after setup_pythonprefix and before setup_pip + self.setup_pip() + + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + # DESTROY / UNINSTALL ENVIRONMENT + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + def uninstall(self): + + # Does Wine prefix exist? + if os.path.exists(self._p["wineprefix"]): + # Delete tree + shutil.rmtree(self._p["wineprefix"]) + + # Does Python prefix exist? + if os.path.exists(self._p["pythonprefix"]): + # Delete tree + shutil.rmtree(self._p["pythonprefix"]) + + self.wine_47766_workaround_uninstall() + + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + # CACHE INSTALLATION FILES LOCALLY + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + def cache(self): - for package in ('pip', 'setuptools', 'wheel'): - self.cache_package(package) + version = PythonVersion.from_config(self._p["arch"], self._p["pythonversion"]) - def cache_package(self, name): + os.makedirs(self._p["cache"], exist_ok=True) - os.makedirs(self._p['packages'], exist_ok = True) + with open(os.path.join(self._p["cache"], version.as_zipname()), "wb") as f: + f.write(self._get_python(offline=False)) + with open(os.path.join(self._p["cache"], "get-pip.py"), "wb") as f: + f.write(self._get_pip(offline=False)) - meta = json.loads( - download('https://pypi.org/pypi/%s/json' % name, mode = 'binary').decode('utf-8') - ) + for package in ("pip", "setuptools", "wheel"): + self.cache_package(package) - for item in meta['urls']: - with open(os.path.join(self._p['packages'], item['filename']), 'wb') as f: - f.write(download(item['url'], mode = 'binary')) + def cache_package(self, name): -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -# Fetch installer data -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + os.makedirs(self._p["packages"], exist_ok=True) - def _get_python(self, offline = False): + meta = json.loads( + download("https://pypi.org/pypi/%s/json" % name, mode="binary").decode( + "utf-8" + ) + ) - version = PythonVersion.from_config(self._p['arch'], self._p['pythonversion']) + for item in meta["urls"]: + with open(os.path.join(self._p["packages"], item["filename"]), "wb") as f: + f.write(download(item["url"], mode="binary")) - if offline: - with open(os.path.join(self._p['cache'], version.as_zipname()), 'rb') as f: - return f.read() - else: - return download(version.as_url(), mode = 'binary') + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + # Fetch installer data + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - def _get_pip(self, offline = False): + def _get_python(self, offline=False): - if offline: - with open(os.path.join(self._p['cache'], 'get-pip.py'), 'rb') as f: - return f.read() - else: - return download('https://bootstrap.pypa.io/get-pip.py', mode = 'text').encode('utf-8') + version = PythonVersion.from_config(self._p["arch"], self._p["pythonversion"]) -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -# SETUP -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + if offline: + with open(os.path.join(self._p["cache"], version.as_zipname()), "rb") as f: + return f.read() + else: + return download(version.as_url(), mode="binary") - def setup_wineprefix(self, overwrite = False): - - if not isinstance(overwrite, bool): - raise TypeError('overwrite is not a boolean') - - # Does it exist? - if os.path.exists(self._p['wineprefix']): - # Exit if overwrite flag is not set - if not overwrite: - return - # Delete if overwrite is set - shutil.rmtree(self._p['wineprefix']) - - os.makedirs(self._p['wineprefix']) # HACK Wine on Travis CI expects folder to exist - - # Start wine server into prepared environment - envvar_dict = self._envvar_dict.copy() - envvar_dict['DISPLAY'] = '' - proc = subprocess.Popen( - ['wine', 'wineboot', '-i'], - env = envvar_dict - ) - proc.wait() - if proc.returncode != 0: - sys.exit(1) - - def setup_pythonprefix(self, overwrite = False): - - if not isinstance(overwrite, bool): - raise TypeError('overwrite is not a boolean') - - # Is there a pre-existing Python installation with identical parameters? - preexisting = os.path.isfile(self._path_dict['interpreter']) - - # Is there a preexisting installation and should it be overwritten? - if preexisting and overwrite: - # Delete folder - shutil.rmtree(self._p['pythonprefix']) - - # Make sure the target directory exists - if not os.path.exists(self._p['pythonprefix']): - # Create folder - os.makedirs(self._p['pythonprefix']) - - # Only do if Python is not there OR if should be overwritten - if overwrite or not preexisting: - - # Generate in-memory file-like-object - archive_zip = BytesIO() - # Fetch Python zip file - archive_zip.write(self._get_python(self._p['offline'])) - # Unpack from memory to disk - with zipfile.ZipFile(archive_zip) as f: - f.extractall(path = self._p['pythonprefix']) # Directory created if required - - # Unpack Python library from embedded zip on disk - with zipfile.ZipFile(self._path_dict['libzip'], 'r') as f: - f.extractall(path = self._path_dict['lib']) # Directory created if required - # Remove Python library zip from disk - os.remove(self._path_dict['libzip']) - - # HACK: Fix library path in pth-file (CPython >= 3.6) - with open(self._path_dict['pth'], 'w') as f: - f.write('Lib\n.\n\n# Uncomment to run site.main() automatically\nimport site\n') - - # Create site-packages folder if it does not exist - if not os.path.exists(self._path_dict['sitepackages']): - # Create folder - os.makedirs(self._path_dict['sitepackages']) - - def setup_pip(self): - - # Exit if it exists - if os.path.isfile(self._path_dict['pip']): - return - - envvar_dict = {k: os.environ[k] for k in os.environ.keys()} - envvar_dict.update(self._p.export_envvar_dict()) - - if self._p['offline']: - proc = subprocess.Popen([ - 'wenv', 'python', - os.path.join(self._p['cache'], 'get-pip.py'), - '--no-index', '--find-links=%s' % self._p['packages'], - ], env = envvar_dict - ) - proc.wait() - else: - getpip = self._get_pip(self._p['offline']) - proc = subprocess.Popen( - ['wenv', 'python'], - stdin = subprocess.PIPE, - env = envvar_dict - ) - proc.communicate(input = getpip) - - def setup_coverage_activate(self): - - # Ensure that coverage is started with the Python interpreter - siteconfig_cnt = '' - if os.path.isfile(self._path_dict['sitecustomize']): - with open(self._path_dict['sitecustomize'], 'r') as f: - siteconfig_cnt = f.read() - if COVERAGE_STARTUP in siteconfig_cnt: - return - with open(self._path_dict['sitecustomize'], 'w') as f: - f.write(siteconfig_cnt + '\n' + COVERAGE_STARTUP) + def _get_pip(self, offline=False): -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -# PACKAGE MANAGEMENT -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + if offline: + with open(os.path.join(self._p["cache"], "get-pip.py"), "rb") as f: + return f.read() + else: + return download("https://bootstrap.pypa.io/get-pip.py", mode="text").encode( + "utf-8" + ) + + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + # SETUP + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + def setup_wineprefix(self, overwrite=False): + + if not isinstance(overwrite, bool): + raise TypeError("overwrite is not a boolean") + + # Does it exist? + if os.path.exists(self._p["wineprefix"]): + # Exit if overwrite flag is not set + if not overwrite: + return + # Delete if overwrite is set + shutil.rmtree(self._p["wineprefix"]) + + os.makedirs( + self._p["wineprefix"] + ) # HACK Wine on Travis CI expects folder to exist + + # Start wine server into prepared environment + envvar_dict = self._envvar_dict.copy() + envvar_dict["DISPLAY"] = "" + proc = subprocess.Popen(["wine", "wineboot", "-i"], env=envvar_dict) + proc.wait() + if proc.returncode != 0: + sys.exit(1) + + def setup_pythonprefix(self, overwrite=False): + + if not isinstance(overwrite, bool): + raise TypeError("overwrite is not a boolean") + + # Is there a pre-existing Python installation with identical parameters? + preexisting = os.path.isfile(self._path_dict["interpreter"]) + + # Is there a preexisting installation and should it be overwritten? + if preexisting and overwrite: + # Delete folder + shutil.rmtree(self._p["pythonprefix"]) + + # Make sure the target directory exists + if not os.path.exists(self._p["pythonprefix"]): + # Create folder + os.makedirs(self._p["pythonprefix"]) + + # Only do if Python is not there OR if should be overwritten + if overwrite or not preexisting: + + # Generate in-memory file-like-object + archive_zip = BytesIO() + # Fetch Python zip file + archive_zip.write(self._get_python(self._p["offline"])) + # Unpack from memory to disk + with zipfile.ZipFile(archive_zip) as f: + f.extractall( + path=self._p["pythonprefix"] + ) # Directory created if required + + # Unpack Python library from embedded zip on disk + with zipfile.ZipFile(self._path_dict["libzip"], "r") as f: + f.extractall( + path=self._path_dict["lib"] + ) # Directory created if required + # Remove Python library zip from disk + os.remove(self._path_dict["libzip"]) + + # HACK: Fix library path in pth-file (CPython >= 3.6) + with open(self._path_dict["pth"], "w") as f: + f.write( + "Lib\n.\n\n# Uncomment to run site.main() automatically\nimport site\n" + ) + + # Create site-packages folder if it does not exist + if not os.path.exists(self._path_dict["sitepackages"]): + # Create folder + os.makedirs(self._path_dict["sitepackages"]) + + def setup_pip(self): + + # Exit if it exists + if os.path.isfile(self._path_dict["pip"]): + return + + envvar_dict = {k: os.environ[k] for k in os.environ.keys()} + envvar_dict.update(self._p.export_envvar_dict()) + + if self._p["offline"]: + proc = subprocess.Popen( + [ + "wenv", + "python", + os.path.join(self._p["cache"], "get-pip.py"), + "--no-index", + "--find-links=%s" % self._p["packages"], + ], + env=envvar_dict, + ) + proc.wait() + else: + getpip = self._get_pip(self._p["offline"]) + proc = subprocess.Popen( + ["wenv", "python"], stdin=subprocess.PIPE, env=envvar_dict + ) + proc.communicate(input=getpip) + + def setup_coverage_activate(self): + + # Ensure that coverage is started with the Python interpreter + siteconfig_cnt = "" + if os.path.isfile(self._path_dict["sitecustomize"]): + with open(self._path_dict["sitecustomize"], "r") as f: + siteconfig_cnt = f.read() + if COVERAGE_STARTUP in siteconfig_cnt: + return + with open(self._path_dict["sitecustomize"], "w") as f: + f.write(siteconfig_cnt + "\n" + COVERAGE_STARTUP) + + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + # PACKAGE MANAGEMENT + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + def install_package(self, name, update=False): + """ + Thin wrapper for `wenv pip install` + """ + + if not isinstance(name, str): + raise TypeError("name must be str") + if len(name) == 0: + raise ValueError("name must not be empty") + if not isinstance(update, bool): + raise TypeError("update must be bool") + + cmd = ["wenv", "pip", "install"] + if update: + cmd.append("-U") + cmd.append(name) + + envvar_dict = {k: os.environ[k] for k in os.environ.keys()} + envvar_dict.update(self._p.export_envvar_dict()) + + proc = subprocess.Popen( + cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=envvar_dict + ) + outs, errs = proc.communicate() + if proc.returncode != 0: + raise SystemError('installing package "%s" failed' % name, outs, errs) + + def uninstall_package(self, name): + """ + Thin wrapper for `wenv pip uninstall -y` + """ + + if not isinstance(name, str): + raise TypeError("name must be str") + if len(name) == 0: + raise ValueError("name must not be empty") + + envvar_dict = {k: os.environ[k] for k in os.environ.keys()} + envvar_dict.update(self._p.export_envvar_dict()) + + proc = subprocess.Popen( + ["wenv", "pip", "uninstall", "-y", name], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=envvar_dict, + ) + outs, errs = proc.communicate() + if proc.returncode != 0: + raise SystemError('uninstalling package "%s" failed' % name, outs, errs) + + def list_packages(self): + """ + Thin wrapper for `wenv pip list --format json` + """ + + envvar_dict = {k: os.environ[k] for k in os.environ.keys()} + envvar_dict.update(self._p.export_envvar_dict()) + + proc = subprocess.Popen( + ["wenv", "pip", "list", "--format", "json"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=envvar_dict, + ) + outs, errs = proc.communicate() + if proc.returncode != 0: + raise SystemError('uninstalling package "%s" failed' % name, outs, errs) + + return json.loads(outs.decode("utf-8")) + + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + # CLI + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + def _cli_init(self): + "sets up an environment (Wine prefix, Python interpreter, pip, setuptools, wheel)" + + self.ensure() + + def _cli_init_coverage(self): + "enables coverage analysis inside wenv" + + self.setup_coverage_activate() + + def _cli_cache(self): + "fetches installation files and caches them for offline usage (Python interpreter, pip, setuptools, wheel)" + + self.cache() + + def _cli_clean(self): + "removes current environment (Python interpreter, pip, setuptools, wheel, all installed packages)" + + self.uninstall() + + def _cli_help(self): + "prints this help text" + + def colorize(text): + for color, key in ( + ("GREEN", "python"), + ("CYAN", "pip"), + ("YELLOW", "wheel"), + ("MAGENTA", "pytest"), + ("MAGENTA", "py.test"), + ): + text = text.replace(key, c[color] + key + c["RESET"]) + return text + + sys.stdout.write( + HELP_STR.format( + CLIS="\n".join( + [ + "- wenv {CLI:s}: {HELP:s}".format( + CLI=key, + HELP=self._cli_dict[key].__doc__, + ) + for key in sorted(self._cli_dict.keys()) + ] + ), + SCRIPTS=colorize( + "\n".join( + [ + "- wenv {SCRIPT:s}".format( + SCRIPT=key, + ) + for key in sorted(self._cmd_dict.keys()) + ] + ) + ), + ) + ) + sys.stdout.flush() + + def cli(self): + + # No command passed + if len(sys.argv) < 2: + sys.stderr.write("There was no command passed.\n") + sys.stderr.flush() + sys.exit(1) + + # Separate command and arguments + cmd, param = sys.argv[1], sys.argv[2:] + + # Allow -h and --help + if cmd in ["-h", "--help"]: + cmd = "help" + + # Special CLI command + if cmd in self._cli_dict.keys(): + self._cli_dict[cmd]() + sys.exit(0) + + # Command is unknown + if cmd not in self._cmd_dict.keys(): + sys.stderr.write('Unknown command or script: "{CMD:s}"\n'.format(CMD=cmd)) + sys.stderr.flush() + sys.exit(1) + + # Get Wine depending on arch + wine = self._wine_dict[self._p["arch"]] + + self.wine_47766_workaround() + + # Replace this process with Wine + os.execvpe( + wine, + (wine, self._cmd_dict[cmd]) + + tuple(param), # Python 3.4: No in-place unpacking of param + self._envvar_dict, + ) + + def shebang(self): + """Working around a lack of Unix specification ... + https://stackoverflow.com/q/4303128/1672565 + https://unix.stackexchange.com/q/63979/28301 + https://lists.gnu.org/archive/html/bug-sh-utils/2002-04/msg00020.html + """ + + if len(sys.argv) < 2: + raise OSError( + "entry point meant to be used as a shebang but no file name was provided" + ) + + # Get Wine depending on arch + wine = self._wine_dict[self._p["arch"]] + + self.wine_47766_workaround() + + # Replace this process with Wine + os.execvpe( + wine, (wine, self._cmd_dict["python"], sys.argv[1]), self._envvar_dict + ) - def install_package(self, name, update = False): - """ - Thin wrapper for `wenv pip install` - """ - - if not isinstance(name, str): - raise TypeError('name must be str') - if len(name) == 0: - raise ValueError('name must not be empty') - if not isinstance(update, bool): - raise TypeError('update must be bool') - - cmd = ['wenv', 'pip', 'install'] - if update: - cmd.append('-U') - cmd.append(name) - - envvar_dict = {k: os.environ[k] for k in os.environ.keys()} - envvar_dict.update(self._p.export_envvar_dict()) - - proc = subprocess.Popen( - cmd, - stdout = subprocess.PIPE, stderr = subprocess.PIPE, - env = envvar_dict - ) - outs, errs = proc.communicate() - if proc.returncode != 0: - raise SystemError('installing package "%s" failed' % name, outs, errs) - - def uninstall_package(self, name): - """ - Thin wrapper for `wenv pip uninstall -y` - """ - - if not isinstance(name, str): - raise TypeError('name must be str') - if len(name) == 0: - raise ValueError('name must not be empty') - - envvar_dict = {k: os.environ[k] for k in os.environ.keys()} - envvar_dict.update(self._p.export_envvar_dict()) - - proc = subprocess.Popen( - ['wenv', 'pip', 'uninstall', '-y', name], - stdout = subprocess.PIPE, stderr = subprocess.PIPE, - env = envvar_dict - ) - outs, errs = proc.communicate() - if proc.returncode != 0: - raise SystemError('uninstalling package "%s" failed' % name, outs, errs) - - def list_packages(self): - """ - Thin wrapper for `wenv pip list --format json` - """ - - envvar_dict = {k: os.environ[k] for k in os.environ.keys()} - envvar_dict.update(self._p.export_envvar_dict()) - - proc = subprocess.Popen( - ['wenv', 'pip', 'list', '--format', 'json'], - stdout = subprocess.PIPE, stderr = subprocess.PIPE, - env = envvar_dict - ) - outs, errs = proc.communicate() - if proc.returncode != 0: - raise SystemError('uninstalling package "%s" failed' % name, outs, errs) - - return json.loads(outs.decode('utf-8')) - -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -# CLI -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - - def _cli_init(self): - "sets up an environment (Wine prefix, Python interpreter, pip, setuptools, wheel)" - - self.ensure() - - def _cli_init_coverage(self): - "enables coverage analysis inside wenv" - - self.setup_coverage_activate() - - def _cli_cache(self): - "fetches installation files and caches them for offline usage (Python interpreter, pip, setuptools, wheel)" - - self.cache() - - def _cli_clean(self): - "removes current environment (Python interpreter, pip, setuptools, wheel, all installed packages)" - - self.uninstall() - - def _cli_help(self): - "prints this help text" - - def colorize(text): - for color, key in ( - ('GREEN', 'python'), - ('CYAN', 'pip'), - ('YELLOW', 'wheel'), - ('MAGENTA', 'pytest'), - ('MAGENTA', 'py.test'), - ): - text = text.replace(key, c[color] + key + c['RESET']) - return text - - sys.stdout.write(HELP_STR.format( - CLIS = '\n'.join([ - '- wenv {CLI:s}: {HELP:s}'.format( - CLI = key, - HELP = self._cli_dict[key].__doc__, - ) - for key in sorted(self._cli_dict.keys()) - ]), - SCRIPTS = colorize('\n'.join([ - '- wenv {SCRIPT:s}'.format( - SCRIPT = key, - ) - for key in sorted(self._cmd_dict.keys()) - ])) - )) - sys.stdout.flush() - - def cli(self): - - # No command passed - if len(sys.argv) < 2: - sys.stderr.write('There was no command passed.\n') - sys.stderr.flush() - sys.exit(1) - - # Separate command and arguments - cmd, param = sys.argv[1], sys.argv[2:] - - # Allow -h and --help - if cmd in ['-h', '--help']: - cmd = 'help' - - # Special CLI command - if cmd in self._cli_dict.keys(): - self._cli_dict[cmd]() - sys.exit(0) - - # Command is unknown - if cmd not in self._cmd_dict.keys(): - sys.stderr.write('Unknown command or script: "{CMD:s}"\n'.format(CMD = cmd)) - sys.stderr.flush() - sys.exit(1) - - # Get Wine depending on arch - wine = self._wine_dict[self._p['arch']] - - self.wine_47766_workaround() - - # Replace this process with Wine - os.execvpe( - wine, - (wine, self._cmd_dict[cmd]) + tuple(param), # Python 3.4: No in-place unpacking of param - self._envvar_dict - ) - - def shebang(self): - """Working around a lack of Unix specification ... - https://stackoverflow.com/q/4303128/1672565 - https://unix.stackexchange.com/q/63979/28301 - https://lists.gnu.org/archive/html/bug-sh-utils/2002-04/msg00020.html - """ - - if len(sys.argv) < 2: - raise OSError('entry point meant to be used as a shebang but no file name was provided') - - # Get Wine depending on arch - wine = self._wine_dict[self._p['arch']] - - self.wine_47766_workaround() - - # Replace this process with Wine - os.execvpe( - wine, - (wine, self._cmd_dict['python'], sys.argv[1]), - self._envvar_dict - ) # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # CLI EXPORT # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + def cli(): - Env().cli() + Env().cli() + def shebang(): - Env().shebang() + Env().shebang() From f6347e04c1ecc37c175c575eaa60161fb4952101 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 15:47:38 +0100 Subject: [PATCH 020/131] new paths module --- src/wenv/_core/paths.py | 90 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 src/wenv/_core/paths.py diff --git a/src/wenv/_core/paths.py b/src/wenv/_core/paths.py new file mode 100644 index 0000000..3971ee5 --- /dev/null +++ b/src/wenv/_core/paths.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- + +""" +WENV +Running Python on Wine +https://github.com/pleiszenburg/wenv + + src/wenv/_core/path.py: Wine-Python environment paths + + Copyright (C) 2017-2020 Sebastian M. Ernst + + +The contents of this file are subject to the GNU Lesser General Public License +Version 2.1 ("LGPL" or "License"). You may not use this file except in +compliance with the License. You may obtain a copy of the License at +https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt +https://github.com/pleiszenburg/wenv/blob/master/LICENSE + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +""" + +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# IMPORT +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +import os + +from .source import PythonVersion + +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# PATHS +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +class Paths: + """ + Wine Python environment paths + """ + + def __init__(self, pythonprefix, arch, pythonversion): + + self._pythonprefix = pythonprefix + self._pythonversion_block = PythonVersion.from_config( + arch, pythonversion + ).as_block() + + def __getitem__(self, key): + + if key == "pythonprefix": + return self._pythonprefix + if key == "lib": + return os.path.join(self["pythonprefix"], "Lib") + if key == "sitepackages": + return os.path.join(self["lib"], "site-packages") + if key == "sitecustomize": + return os.path.join(self["sitepackages"], "sitecustomize.py") + if key == "scripts": + return os.path.join(self["pythonprefix"], "Scripts") + if key == "interpreter": + return os.path.join(self["pythonprefix"], "python.exe") + if key == "pip": + return os.path.join(self["scripts"], "pip.exe") + if key == "libzip": + return os.path.join( + self["pythonprefix"], "python%s.zip" % self._pythonversion_block + ) + if key == "pth": + return os.path.join( + self["pythonprefix"], "python%s._pth" % self._pythonversion_block + ) + + raise KeyError("not a valid path key") + + @staticmethod + def symlink(src, dest): + """ + Generates a symlink and checks result + """ + + if not os.path.lexists(dest): + os.symlink(src, dest) + + if not os.path.exists(dest): + raise OSError('"{LINK:s}" could not be created'.format(LINK=dest)) + if not os.path.islink(dest): + raise OSError('"{LINK:s}" is not a symlink'.format(LINK=dest)) + if os.readlink(dest) != src: + raise OSError('"{LINK:s}" points to the wrong source'.format(LINK=dest)) From 6030f874c0277ec4bcc2268d4a93d11ae117b190 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 15:48:00 +0100 Subject: [PATCH 021/131] black; refactor; package list error handling bug fix --- src/wenv/_core/env.py | 147 ++++++++++++------------------------------ 1 file changed, 42 insertions(+), 105 deletions(-) diff --git a/src/wenv/_core/env.py b/src/wenv/_core/env.py index c32a02a..f3499af 100644 --- a/src/wenv/_core/env.py +++ b/src/wenv/_core/env.py @@ -36,78 +36,19 @@ from .config import EnvConfig from .const import c, COVERAGE_STARTUP, HELP_STR +from .paths import Paths from .source import download, PythonVersion # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -# HELPER ROUTINES +# CLASS # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -def _symlink(src, dest): - - if not os.path.lexists(dest): - os.symlink(src, dest) - - if not os.path.exists(dest): - raise OSError('"{LINK:s}" could not be created'.format(LINK=dest)) - if not os.path.islink(dest): - raise OSError('"{LINK:s}" is not a symlink'.format(LINK=dest)) - if os.readlink(dest) != src: - raise OSError('"{LINK:s}" points to the wrong source'.format(LINK=dest)) - - -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -# PATHS -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - - -class Paths: - def __init__(self, pythonprefix, arch, pythonversion): - - self._pythonprefix = pythonprefix - self._pythonversion_block = PythonVersion.from_config( - arch, pythonversion - ).as_block() - - def __getitem__(self, key): - - if key == "pythonprefix": - return self._pythonprefix - elif key == "lib": - return os.path.join(self["pythonprefix"], "Lib") - elif key == "sitepackages": - return os.path.join(self["lib"], "site-packages") - elif key == "sitecustomize": - return os.path.join(self["sitepackages"], "sitecustomize.py") - elif key == "scripts": - return os.path.join(self["pythonprefix"], "Scripts") - elif key == "interpreter": - return os.path.join(self["pythonprefix"], "python.exe") - elif key == "pip": - return os.path.join(self["scripts"], "pip.exe") - elif key == "libzip": - return os.path.join( - self["pythonprefix"], "python%s.zip" % self._pythonversion_block - ) - elif key == "pth": - return os.path.join( - self["pythonprefix"], "python%s._pth" % self._pythonversion_block - ) - else: - raise KeyError("not a valid path key") - +class Env: # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -# WINE-PYTHON ENVIRONMENT CLASS +# INIT # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -class Env: - - # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - # INIT - # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - def __init__(self, **kwargs): if "parameter" in kwargs.keys(): # legacy API @@ -128,6 +69,10 @@ def __init__(self, **kwargs): kwargs = EnvConfig(**kwargs) self._p = kwargs + self._cmd_dict = None + self._cli_dict = None + self._envvar_dict = None + self._init_dicts() def _init_dicts(self): @@ -150,20 +95,17 @@ def _init_dicts(self): self._init_envvar_dict() def _init_cmd_dict(self): - def ls_exe(dir): - if not os.path.isdir(dir): + + def ls_exe(directory): + if not os.path.isdir(directory): return - for item in os.listdir(dir): + for item in os.listdir(directory): if not item.lower().endswith(".exe"): continue - yield item[:-4], os.path.join(dir, item) + yield item[:-4], os.path.join(directory, item) - self._cmd_dict = { - item: path for item, path in ls_exe(self._path_dict["scripts"]) - } - self._cmd_dict.update( - {item: path for item, path in ls_exe(self._path_dict["pythonprefix"])} - ) + self._cmd_dict = dict(ls_exe(self._path_dict["scripts"])) + self._cmd_dict.update(dict(ls_exe(self._path_dict["pythonprefix"]))) def _init_cli_dict(self): @@ -175,9 +117,7 @@ def _init_cli_dict(self): def _init_envvar_dict(self): - self._envvar_dict = { - k: os.environ[k] for k in os.environ.keys() - } # HACK Required for Travis CI + self._envvar_dict = os.environ.copy() self._envvar_dict.update( dict( WINEARCH=self._p["arch"], # Architecture @@ -208,9 +148,9 @@ def _init_envvar_dict(self): ) ) # https://wiki.winehq.org/FAQ#Can_I_install_more_than_one_Wine_version_on_my_system.3F - # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - # WINE BUG #47766 / ZUGBRUECKE BUG #49 - # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# WINE BUG #47766 / ZUGBRUECKE BUG #49 +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ def wine_47766_workaround(self): """ @@ -244,7 +184,7 @@ def wine_47766_workaround(self): ) if os.path.exists(self._p["pythonprefix"]): - _symlink(self._p["pythonprefix"], link_path) + Paths.symlink(self._p["pythonprefix"], link_path) self._p["pythonprefix"] = link_path self._init_dicts() @@ -256,9 +196,9 @@ def wine_47766_workaround_uninstall(self): if os.path.lexists(self._p["pythonprefix"]): os.unlink(self._p["pythonprefix"]) - # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - # ENSURE ENVIRONMENT - # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# ENSURE ENVIRONMENT +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ def ensure(self): @@ -267,9 +207,9 @@ def ensure(self): self.wine_47766_workaround() # must run after setup_pythonprefix and before setup_pip self.setup_pip() - # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - # DESTROY / UNINSTALL ENVIRONMENT - # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# DESTROY / UNINSTALL ENVIRONMENT +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ def uninstall(self): @@ -285,9 +225,9 @@ def uninstall(self): self.wine_47766_workaround_uninstall() - # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - # CACHE INSTALLATION FILES LOCALLY - # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# CACHE INSTALLATION FILES LOCALLY +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ def cache(self): @@ -317,9 +257,9 @@ def cache_package(self, name): with open(os.path.join(self._p["packages"], item["filename"]), "wb") as f: f.write(download(item["url"], mode="binary")) - # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - # Fetch installer data - # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# Fetch installer data +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ def _get_python(self, offline=False): @@ -341,9 +281,9 @@ def _get_pip(self, offline=False): "utf-8" ) - # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - # SETUP - # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# SETUP +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ def setup_wineprefix(self, overwrite=False): @@ -460,9 +400,9 @@ def setup_coverage_activate(self): with open(self._path_dict["sitecustomize"], "w") as f: f.write(siteconfig_cnt + "\n" + COVERAGE_STARTUP) - # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - # PACKAGE MANAGEMENT - # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# PACKAGE MANAGEMENT +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ def install_package(self, name, update=False): """ @@ -530,13 +470,13 @@ def list_packages(self): ) outs, errs = proc.communicate() if proc.returncode != 0: - raise SystemError('uninstalling package "%s" failed' % name, outs, errs) + raise SystemError('listing packages failed', outs, errs) return json.loads(outs.decode("utf-8")) - # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - # CLI - # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# CLI +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ def _cli_init(self): "sets up an environment (Wine prefix, Python interpreter, pip, setuptools, wheel)" @@ -658,17 +598,14 @@ def shebang(self): wine, (wine, self._cmd_dict["python"], sys.argv[1]), self._envvar_dict ) - # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # CLI EXPORT # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - def cli(): Env().cli() - def shebang(): Env().shebang() From a73ba486d83e47ef996528ac22cc6a0691399737 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 15:48:39 +0100 Subject: [PATCH 022/131] black --- src/wenv/_core/errors.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/wenv/_core/errors.py b/src/wenv/_core/errors.py index f526e50..7d501d8 100644 --- a/src/wenv/_core/errors.py +++ b/src/wenv/_core/errors.py @@ -1,14 +1,13 @@ # -*- coding: utf-8 -*- """ - WENV Running Python on Wine https://github.com/pleiszenburg/wenv - src/wenv/_core/errors.py: Exceptions + src/wenv/_core/errors.py: Exceptions - Copyright (C) 2017-2020 Sebastian M. Ernst + Copyright (C) 2017-2020 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License @@ -21,7 +20,6 @@ WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. - """ # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -29,4 +27,4 @@ # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ class EnvConfigParserError(Exception): - pass + pass From 3bd6dbc3c68f3572baa621f5b60b3dd1460e5fcd Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 15:49:47 +0100 Subject: [PATCH 023/131] black --- src/wenv/_core/source.py | 395 +++++++++++++++++++++------------------ 1 file changed, 215 insertions(+), 180 deletions(-) diff --git a/src/wenv/_core/source.py b/src/wenv/_core/source.py index 6fb11ce..d498e56 100644 --- a/src/wenv/_core/source.py +++ b/src/wenv/_core/source.py @@ -1,14 +1,13 @@ # -*- coding: utf-8 -*- """ - WENV Running Python on Wine https://github.com/pleiszenburg/wenv - src/wenv/_core/source.py: Obtaining Python and pip locally or remotely + src/wenv/_core/source.py: Obtaining Python and pip locally or remotely - Copyright (C) 2017-2020 Sebastian M. Ernst + Copyright (C) 2017-2020 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License @@ -21,7 +20,6 @@ WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. - """ # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -36,194 +34,231 @@ # ROUTINES # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -def download(down_url, mode = 'binary'): +def download(down_url, mode="binary"): + + assert mode in ("text", "binary") + assert isinstance(down_url, str) - assert mode in ('text', 'binary') - assert isinstance(down_url, str) + r = requests.get(down_url) - r = requests.get(down_url) + assert r.ok + r.raise_for_status() - assert r.ok - r.raise_for_status() + if r.encoding is not None: + assert mode == "text" and isinstance(r.text, str) + return r.text + else: + assert mode == "binary" and isinstance(r.content, bytes) + return r.content - if r.encoding is not None: - assert mode == 'text' and isinstance(r.text, str) - return r.text - else: - assert mode == 'binary' and isinstance(r.content, bytes) - return r.content def get_available_python_versions(): - versions = [ - tuple(int(nr) for nr in line.split('"')[1][:-1].split('.')) - for line in download('https://www.python.org/ftp/python/', mode = 'text').split('\n') - if all([ - line.startswith(' 0} + + sorted_versions = { + "win32": { + version_tuple: sorted( + [version for version in versions if version.arch == "win32"] + ) + for version_tuple, versions in embedded_versions.items() + }, + "win64": { + version_tuple: sorted( + [version for version in versions if version.arch == "win64"] + ) + for version_tuple, versions in embedded_versions.items() + }, + } + + return sorted_versions + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # CLASS: PYTHON VERSION # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -class PythonVersion: - def __init__(self, arch, major, minor, maintenance, build = 'stable'): +class PythonVersion: + def __init__(self, arch, major, minor, maintenance, build="stable"): - if not isinstance(arch, str): - raise TypeError('arch must be str') - if arch not in ('win32', 'win64'): - raise ValueError('Unknown arch: ' + arch) - if any((not isinstance(item, int) for item in (major, minor, maintenance))): - raise TypeError('Unknown type for major, minor or maintenance') - if major <= 2: - raise ValueError('Only Python 3 and newer supported') - if not isinstance(build, str): - raise TypeError('build must be str') + if not isinstance(arch, str): + raise TypeError("arch must be str") + if arch not in ("win32", "win64"): + raise ValueError("Unknown arch: " + arch) + if any((not isinstance(item, int) for item in (major, minor, maintenance))): + raise TypeError("Unknown type for major, minor or maintenance") + if major <= 2: + raise ValueError("Only Python 3 and newer supported") + if not isinstance(build, str): + raise TypeError("build must be str") - self._arch = arch - self._major, self._minor, self._maintenance = major, minor, maintenance - self._build = 'stable' if build == '' else build - - def __str__(self): - - return '%d.%d.%d.%s' % ( - self._major, self._minor, self._maintenance, self._build - ) - - def __repr__(self): - - return '' % ( - self._major, self._minor, self._maintenance, self._build, self._arch - ) - - def __eq__(self, other): - return self._as_sort() == other._as_sort() - def __gt__(self, other): - return self._as_sort() > other._as_sort() - def __lt__(self, other): - return self._as_sort() < other._as_sort() - def _as_sort(self): - return '%04d-%04d-%04d-%s' % (self._major, self._minor, self._maintenance, self._build) - - @property - def arch(self): - - return self._arch - - def as_block(self): - - return '%d%d' % (self._major, self._minor) - - def as_config(self): - - return str(self) - - def as_url(self): - - return 'https://www.python.org/ftp/python/%d.%d.%d/' % ( - self._major, self._minor, self._maintenance - ) + self.as_zipname() - - def as_zipname(self): - - build = '' if self._build == 'stable' else self._build - arch = 'amd64' if self._arch == 'win64' else self._arch - sub_tuple = (self._major, self._minor, self._maintenance, build, arch) - - if build.startswith('post'): - return 'python-%d.%d.%d.%s-embed-%s.zip' % sub_tuple - else: - return 'python-%d.%d.%d%s-embed-%s.zip' % sub_tuple - - @classmethod - def from_config(cls, arch, version): - - if not isinstance(version, str): - raise TypeError('version must be str') - segments = version.split('.') - if not len(segments) in (3, 4): - raise ValueError('wrong number of version segments') - if len(segments) == 3: - segments.append('stable') - if not all((segment.isnumeric() for segment in segments[:3])): - raise ValueError('version segments are not numeric') - segments = tuple([int(segment) for segment in segments[:3]] + [segments[3]]) - - return cls(arch, *segments) - - @classmethod - def from_zipname(cls, zip_name): - - if not isinstance(zip_name, str): - raise TypeError('zip_name must be str') - - fragments = zip_name.split('-') - fragments.append(fragments[3].split('.')[1]) - fragments[3] = fragments[3].split('.')[0] - - if not fragments[0] == 'python': - raise ValueError('fagment[0] != "python"') - if not fragments[2] == 'embed': - raise ValueError('fagment[2] != "embed"') - if not fragments[3] in ('win32', 'amd64'): - raise ValueError('fagment[3] not in in ("win32", "amd64")') - if not fragments[4] == 'zip': - raise ValueError('fagment[4] != "zip"') - - arch = 'win32' if fragments[3] == 'win32' else 'win64' - release = [ - int(f) if f.isnumeric() else f - for f in fragments[1].split('.') - ] - - if isinstance(release[2], str): - if not len(release[2]) > 0: - raise ValueError('broken maintenance/build fragment') - for pos, char in enumerate(release[2]): - if not char.isdigit(): - break - release.append(release[2][pos:]) - release[2] = release[2][:pos] - if not release[2].isnumeric(): - raise ValueError('maintenance fragment not numeric') - release[2] = int(release[2]) - - if len(release) == 3: - release.append('stable') - if not len(release) == 4: - raise ValueError('release does not have 4 fragments (major,minor,maintenance,build)') - - return cls(arch, *release) + self._arch = arch + self._major, self._minor, self._maintenance = major, minor, maintenance + self._build = "stable" if build == "" else build + + def __str__(self): + + return "%d.%d.%d.%s" % ( + self._major, + self._minor, + self._maintenance, + self._build, + ) + + def __repr__(self): + + return "" % ( + self._major, + self._minor, + self._maintenance, + self._build, + self._arch, + ) + + def __eq__(self, other): + return self._as_sort() == other._as_sort() + + def __gt__(self, other): + return self._as_sort() > other._as_sort() + + def __lt__(self, other): + return self._as_sort() < other._as_sort() + + def _as_sort(self): + return "%04d-%04d-%04d-%s" % ( + self._major, + self._minor, + self._maintenance, + self._build, + ) + + @property + def arch(self): + + return self._arch + + def as_block(self): + + return "%d%d" % (self._major, self._minor) + + def as_config(self): + + return str(self) + + def as_url(self): + + return ( + "https://www.python.org/ftp/python/%d.%d.%d/" + % (self._major, self._minor, self._maintenance) + + self.as_zipname() + ) + + def as_zipname(self): + + build = "" if self._build == "stable" else self._build + arch = "amd64" if self._arch == "win64" else self._arch + sub_tuple = (self._major, self._minor, self._maintenance, build, arch) + + if build.startswith("post"): + return "python-%d.%d.%d.%s-embed-%s.zip" % sub_tuple + else: + return "python-%d.%d.%d%s-embed-%s.zip" % sub_tuple + + @classmethod + def from_config(cls, arch, version): + + if not isinstance(version, str): + raise TypeError("version must be str") + segments = version.split(".") + if not len(segments) in (3, 4): + raise ValueError("wrong number of version segments") + if len(segments) == 3: + segments.append("stable") + if not all((segment.isnumeric() for segment in segments[:3])): + raise ValueError("version segments are not numeric") + segments = tuple([int(segment) for segment in segments[:3]] + [segments[3]]) + + return cls(arch, *segments) + + @classmethod + def from_zipname(cls, zip_name): + + if not isinstance(zip_name, str): + raise TypeError("zip_name must be str") + + fragments = zip_name.split("-") + fragments.append(fragments[3].split(".")[1]) + fragments[3] = fragments[3].split(".")[0] + + if not fragments[0] == "python": + raise ValueError('fagment[0] != "python"') + if not fragments[2] == "embed": + raise ValueError('fagment[2] != "embed"') + if not fragments[3] in ("win32", "amd64"): + raise ValueError('fagment[3] not in in ("win32", "amd64")') + if not fragments[4] == "zip": + raise ValueError('fagment[4] != "zip"') + + arch = "win32" if fragments[3] == "win32" else "win64" + release = [int(f) if f.isnumeric() else f for f in fragments[1].split(".")] + + if isinstance(release[2], str): + if not len(release[2]) > 0: + raise ValueError("broken maintenance/build fragment") + for pos, char in enumerate(release[2]): + if not char.isdigit(): + break + release.append(release[2][pos:]) + release[2] = release[2][:pos] + if not release[2].isnumeric(): + raise ValueError("maintenance fragment not numeric") + release[2] = int(release[2]) + + if len(release) == 3: + release.append("stable") + if not len(release) == 4: + raise ValueError( + "release does not have 4 fragments (major,minor,maintenance,build)" + ) + + return cls(arch, *release) From 952dd73a53652125d9aa86396495c4bfbff2cbe9 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 15:52:27 +0100 Subject: [PATCH 024/131] new module for python version --- src/wenv/_core/pythonversion.py | 176 ++++++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 src/wenv/_core/pythonversion.py diff --git a/src/wenv/_core/pythonversion.py b/src/wenv/_core/pythonversion.py new file mode 100644 index 0000000..dbbceae --- /dev/null +++ b/src/wenv/_core/pythonversion.py @@ -0,0 +1,176 @@ +# -*- coding: utf-8 -*- + +""" +WENV +Running Python on Wine +https://github.com/pleiszenburg/wenv + + src/wenv/_core/pythonversion.py: Parse and handle Python versions + + Copyright (C) 2017-2020 Sebastian M. Ernst + + +The contents of this file are subject to the GNU Lesser General Public License +Version 2.1 ("LGPL" or "License"). You may not use this file except in +compliance with the License. You may obtain a copy of the License at +https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt +https://github.com/pleiszenburg/wenv/blob/master/LICENSE + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +""" + +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# CLASS +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +class PythonVersion: + """ + Parse and handle Python versions + """ + + def __init__(self, arch, major, minor, maintenance, build="stable"): + + if not isinstance(arch, str): + raise TypeError("arch must be str") + if arch not in ("win32", "win64"): + raise ValueError("Unknown arch: " + arch) + if any((not isinstance(item, int) for item in (major, minor, maintenance))): + raise TypeError("Unknown type for major, minor or maintenance") + if major <= 2: + raise ValueError("Only Python 3 and newer supported") + if not isinstance(build, str): + raise TypeError("build must be str") + + self._arch = arch + self._major, self._minor, self._maintenance = major, minor, maintenance + self._build = "stable" if build == "" else build + + def __str__(self): + + return "%d.%d.%d.%s" % ( + self._major, + self._minor, + self._maintenance, + self._build, + ) + + def __repr__(self): + + return "" % ( + self._major, + self._minor, + self._maintenance, + self._build, + self._arch, + ) + + def __eq__(self, other): + return self._as_sort() == other._as_sort() + + def __gt__(self, other): + return self._as_sort() > other._as_sort() + + def __lt__(self, other): + return self._as_sort() < other._as_sort() + + def _as_sort(self): + return "%04d-%04d-%04d-%s" % ( + self._major, + self._minor, + self._maintenance, + self._build, + ) + + @property + def arch(self): + + return self._arch + + def as_block(self): + + return "%d%d" % (self._major, self._minor) + + def as_config(self): + + return str(self) + + def as_url(self): + + return ( + "https://www.python.org/ftp/python/%d.%d.%d/" + % (self._major, self._minor, self._maintenance) + + self.as_zipname() + ) + + def as_zipname(self): + + build = "" if self._build == "stable" else self._build + arch = "amd64" if self._arch == "win64" else self._arch + sub_tuple = (self._major, self._minor, self._maintenance, build, arch) + + if build.startswith("post"): + return "python-%d.%d.%d.%s-embed-%s.zip" % sub_tuple + else: + return "python-%d.%d.%d%s-embed-%s.zip" % sub_tuple + + @classmethod + def from_config(cls, arch, version): + + if not isinstance(version, str): + raise TypeError("version must be str") + segments = version.split(".") + if not len(segments) in (3, 4): + raise ValueError("wrong number of version segments") + if len(segments) == 3: + segments.append("stable") + if not all((segment.isnumeric() for segment in segments[:3])): + raise ValueError("version segments are not numeric") + segments = tuple([int(segment) for segment in segments[:3]] + [segments[3]]) + + return cls(arch, *segments) + + @classmethod + def from_zipname(cls, zip_name): + + if not isinstance(zip_name, str): + raise TypeError("zip_name must be str") + + fragments = zip_name.split("-") + fragments.append(fragments[3].split(".")[1]) + fragments[3] = fragments[3].split(".")[0] + + if not fragments[0] == "python": + raise ValueError('fagment[0] != "python"') + if not fragments[2] == "embed": + raise ValueError('fagment[2] != "embed"') + if not fragments[3] in ("win32", "amd64"): + raise ValueError('fagment[3] not in in ("win32", "amd64")') + if not fragments[4] == "zip": + raise ValueError('fagment[4] != "zip"') + + arch = "win32" if fragments[3] == "win32" else "win64" + release = [int(f) if f.isnumeric() else f for f in fragments[1].split(".")] + + if isinstance(release[2], str): + if not len(release[2]) > 0: + raise ValueError("broken maintenance/build fragment") + for pos, char in enumerate(release[2]): + if not char.isdigit(): + break + release.append(release[2][pos:]) + release[2] = release[2][:pos] + if not release[2].isnumeric(): + raise ValueError("maintenance fragment not numeric") + release[2] = int(release[2]) + + if len(release) == 3: + release.append("stable") + if not len(release) == 4: + raise ValueError( + "release does not have 4 fragments (major,minor,maintenance,build)" + ) + + return cls(arch, *release) From 636f1f1c14b3b198a547c8ac7a06e23c67a7ad31 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 15:53:25 +0100 Subject: [PATCH 025/131] run on new python version module --- src/wenv/_core/env.py | 3 +- src/wenv/_core/paths.py | 2 +- src/wenv/_core/source.py | 153 +-------------------------------------- 3 files changed, 5 insertions(+), 153 deletions(-) diff --git a/src/wenv/_core/env.py b/src/wenv/_core/env.py index f3499af..2cb5417 100644 --- a/src/wenv/_core/env.py +++ b/src/wenv/_core/env.py @@ -37,7 +37,8 @@ from .config import EnvConfig from .const import c, COVERAGE_STARTUP, HELP_STR from .paths import Paths -from .source import download, PythonVersion +from .pythonversion import PythonVersion +from .source import download # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # CLASS diff --git a/src/wenv/_core/paths.py b/src/wenv/_core/paths.py index 3971ee5..bd71391 100644 --- a/src/wenv/_core/paths.py +++ b/src/wenv/_core/paths.py @@ -28,7 +28,7 @@ import os -from .source import PythonVersion +from .pythonversion import PythonVersion # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # PATHS diff --git a/src/wenv/_core/source.py b/src/wenv/_core/source.py index d498e56..7f3e65e 100644 --- a/src/wenv/_core/source.py +++ b/src/wenv/_core/source.py @@ -30,6 +30,8 @@ import requests +from .pythonversion import PythonVersion + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # ROUTINES # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -111,154 +113,3 @@ def get_available_python_versions(): } return sorted_versions - - -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -# CLASS: PYTHON VERSION -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - - -class PythonVersion: - def __init__(self, arch, major, minor, maintenance, build="stable"): - - if not isinstance(arch, str): - raise TypeError("arch must be str") - if arch not in ("win32", "win64"): - raise ValueError("Unknown arch: " + arch) - if any((not isinstance(item, int) for item in (major, minor, maintenance))): - raise TypeError("Unknown type for major, minor or maintenance") - if major <= 2: - raise ValueError("Only Python 3 and newer supported") - if not isinstance(build, str): - raise TypeError("build must be str") - - self._arch = arch - self._major, self._minor, self._maintenance = major, minor, maintenance - self._build = "stable" if build == "" else build - - def __str__(self): - - return "%d.%d.%d.%s" % ( - self._major, - self._minor, - self._maintenance, - self._build, - ) - - def __repr__(self): - - return "" % ( - self._major, - self._minor, - self._maintenance, - self._build, - self._arch, - ) - - def __eq__(self, other): - return self._as_sort() == other._as_sort() - - def __gt__(self, other): - return self._as_sort() > other._as_sort() - - def __lt__(self, other): - return self._as_sort() < other._as_sort() - - def _as_sort(self): - return "%04d-%04d-%04d-%s" % ( - self._major, - self._minor, - self._maintenance, - self._build, - ) - - @property - def arch(self): - - return self._arch - - def as_block(self): - - return "%d%d" % (self._major, self._minor) - - def as_config(self): - - return str(self) - - def as_url(self): - - return ( - "https://www.python.org/ftp/python/%d.%d.%d/" - % (self._major, self._minor, self._maintenance) - + self.as_zipname() - ) - - def as_zipname(self): - - build = "" if self._build == "stable" else self._build - arch = "amd64" if self._arch == "win64" else self._arch - sub_tuple = (self._major, self._minor, self._maintenance, build, arch) - - if build.startswith("post"): - return "python-%d.%d.%d.%s-embed-%s.zip" % sub_tuple - else: - return "python-%d.%d.%d%s-embed-%s.zip" % sub_tuple - - @classmethod - def from_config(cls, arch, version): - - if not isinstance(version, str): - raise TypeError("version must be str") - segments = version.split(".") - if not len(segments) in (3, 4): - raise ValueError("wrong number of version segments") - if len(segments) == 3: - segments.append("stable") - if not all((segment.isnumeric() for segment in segments[:3])): - raise ValueError("version segments are not numeric") - segments = tuple([int(segment) for segment in segments[:3]] + [segments[3]]) - - return cls(arch, *segments) - - @classmethod - def from_zipname(cls, zip_name): - - if not isinstance(zip_name, str): - raise TypeError("zip_name must be str") - - fragments = zip_name.split("-") - fragments.append(fragments[3].split(".")[1]) - fragments[3] = fragments[3].split(".")[0] - - if not fragments[0] == "python": - raise ValueError('fagment[0] != "python"') - if not fragments[2] == "embed": - raise ValueError('fagment[2] != "embed"') - if not fragments[3] in ("win32", "amd64"): - raise ValueError('fagment[3] not in in ("win32", "amd64")') - if not fragments[4] == "zip": - raise ValueError('fagment[4] != "zip"') - - arch = "win32" if fragments[3] == "win32" else "win64" - release = [int(f) if f.isnumeric() else f for f in fragments[1].split(".")] - - if isinstance(release[2], str): - if not len(release[2]) > 0: - raise ValueError("broken maintenance/build fragment") - for pos, char in enumerate(release[2]): - if not char.isdigit(): - break - release.append(release[2][pos:]) - release[2] = release[2][:pos] - if not release[2].isnumeric(): - raise ValueError("maintenance fragment not numeric") - release[2] = int(release[2]) - - if len(release) == 3: - release.append("stable") - if not len(release) == 4: - raise ValueError( - "release does not have 4 fragments (major,minor,maintenance,build)" - ) - - return cls(arch, *release) From 5aa820bec263471b0ccd2751862f293029618331 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 15:54:20 +0100 Subject: [PATCH 026/131] black --- tests/lib/__init__.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/lib/__init__.py b/tests/lib/__init__.py index b3d2e91..01a1f4c 100644 --- a/tests/lib/__init__.py +++ b/tests/lib/__init__.py @@ -1,14 +1,13 @@ # -*- coding: utf-8 -*- """ - WENV Running Python on Wine https://github.com/pleiszenburg/wenv - tests/lib/__init__.py: Test library module + tests/lib/__init__.py: Test library module - Copyright (C) 2017-2020 Sebastian M. Ernst + Copyright (C) 2017-2020 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License @@ -21,7 +20,6 @@ WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. - """ # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ From c0b537e6292940e80bbeabe9c677045de23c5ffa Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 15:54:49 +0100 Subject: [PATCH 027/131] black --- tests/lib/const.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/lib/const.py b/tests/lib/const.py index 160a732..d6d434d 100644 --- a/tests/lib/const.py +++ b/tests/lib/const.py @@ -1,14 +1,13 @@ # -*- coding: utf-8 -*- """ - WENV Running Python on Wine https://github.com/pleiszenburg/wenv - tests/lib/const.py: Holds constant values, flags, types + tests/lib/const.py: Holds constant values, flags, types - Copyright (C) 2017-2020 Sebastian M. Ernst + Copyright (C) 2017-2020 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License @@ -21,12 +20,11 @@ WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. - """ # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # CONST # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -ARCHS = ['win32', 'win64'] +ARCHS = ["win32", "win64"] DEFAULT_TIMEOUT = 600 From 808fa14dc34a14cdda903b68706a4a09ae5a8673 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 15:56:38 +0100 Subject: [PATCH 028/131] black; env copy fix --- tests/lib/param.py | 66 ++++++++++++++++++++++------------------------ 1 file changed, 32 insertions(+), 34 deletions(-) diff --git a/tests/lib/param.py b/tests/lib/param.py index 36b482d..c30b5bb 100644 --- a/tests/lib/param.py +++ b/tests/lib/param.py @@ -1,14 +1,13 @@ # -*- coding: utf-8 -*- """ - WENV Running Python on Wine https://github.com/pleiszenburg/wenv - tests/lib/const.py: Holds constant values, flags, types + tests/lib/const.py: Holds constant values, flags, types - Copyright (C) 2017-2020 Sebastian M. Ernst + Copyright (C) 2017-2020 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License @@ -21,53 +20,52 @@ WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. - """ # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # IMPORT # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -from .const import ARCHS, DEFAULT_TIMEOUT - import os import re import signal import subprocess +from .const import ARCHS, DEFAULT_TIMEOUT + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # ROUTINES # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + def get_context(): - for arch in ARCHS: - yield arch + for arch in ARCHS: + yield arch + # https://stackoverflow.com/a/14693789/1672565 -_ansi_escape_8bit = re.compile(br'(?:\x1B[@-_]|[\x80-\x9F])[0-?]*[ -/]*[@-~]') - -def run_process(cmd_list, env = None, timeout = DEFAULT_TIMEOUT): - - if env is None: - env = {} - envvar_dict = {k: os.environ[k] for k in os.environ.keys()} - envvar_dict.update(env) - - proc = subprocess.Popen( - cmd_list, - stdout = subprocess.PIPE, - stderr = subprocess.PIPE, - env = envvar_dict - ) - try: - outs, errs = proc.communicate(timeout = timeout) - except subprocess.TimeoutExpired: - os.kill(proc.pid, signal.SIGINT) - outs, errs = proc.communicate() - - return ( - _ansi_escape_8bit.sub(b'', outs).decode('utf-8'), - errs.decode('utf-8'), - proc.returncode - ) +_ansi_escape_8bit = re.compile(br"(?:\x1B[@-_]|[\x80-\x9F])[0-?]*[ -/]*[@-~]") + + +def run_process(cmd_list, env=None, timeout=DEFAULT_TIMEOUT): + + if env is None: + env = {} + envvar_dict = os.environ.copy() + envvar_dict.update(env) + + proc = subprocess.Popen( + cmd_list, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=envvar_dict + ) + try: + outs, errs = proc.communicate(timeout=timeout) + except subprocess.TimeoutExpired: + os.kill(proc.pid, signal.SIGINT) + outs, errs = proc.communicate() + + return ( + _ansi_escape_8bit.sub(b"", outs).decode("utf-8"), + errs.decode("utf-8"), + proc.returncode, + ) From dccd27442a8d9b490b37b1947a291de34712987d Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 15:57:03 +0100 Subject: [PATCH 029/131] black --- tests/__init__.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/__init__.py b/tests/__init__.py index 66229fa..04657b0 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,14 +1,13 @@ # -*- coding: utf-8 -*- """ - WENV Running Python on Wine https://github.com/pleiszenburg/wenv - tests/__init__.py: Test module + tests/__init__.py: Test module - Copyright (C) 2017-2020 Sebastian M. Ernst + Copyright (C) 2017-2020 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License @@ -21,5 +20,4 @@ WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. - """ From adc1cb81002327543bb8a4897acd35e4d6b902e9 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 15:57:46 +0100 Subject: [PATCH 030/131] tests/test_init.py --- tests/test_init.py | 87 +++++++++++++++++++++++----------------------- 1 file changed, 44 insertions(+), 43 deletions(-) diff --git a/tests/test_init.py b/tests/test_init.py index 4fdd787..aa202b3 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -1,14 +1,13 @@ # -*- coding: utf-8 -*- """ - WENV Running Python on Wine https://github.com/pleiszenburg/wenv - tests/test_init.py: Testing clean & init + tests/test_init.py: Testing clean & init - Copyright (C) 2017-2020 Sebastian M. Ernst + Copyright (C) 2017-2020 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License @@ -21,10 +20,8 @@ WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. - """ - # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # IMPORT # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -37,62 +34,66 @@ # TEST(s) # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -@pytest.mark.parametrize('arch', get_context()) + +@pytest.mark.parametrize("arch", get_context()) def test_init(arch): - out, err, code = run_process(['wenv', 'clean'], env = {'WENV_ARCH': arch}) + out, err, code = run_process(["wenv", "clean"], env={"WENV_ARCH": arch}) + + assert code == 0 + assert len(err.strip()) == 0 - assert code == 0 - assert len(err.strip()) == 0 + out, err, code = run_process(["wenv", "help"], env={"WENV_ARCH": arch}) - out, err, code = run_process(['wenv', 'help'], env = {'WENV_ARCH': arch}) + assert code == 0 + assert len(err.strip()) == 0 + assert "wenv pip" not in out + # assert 'wenv python' not in out # TODO - assert code == 0 - assert len(err.strip()) == 0 - assert 'wenv pip' not in out - # assert 'wenv python' not in out # TODO + out, err, code = run_process(["wenv", "init"], env={"WENV_ARCH": arch}) - out, err, code = run_process(['wenv', 'init'], env = {'WENV_ARCH': arch}) + assert code == 0 + # assert len(err.strip()) == 0 # pip output goes to stderr - assert code == 0 - # assert len(err.strip()) == 0 # pip output goes to stderr + out, err, code = run_process(["wenv", "help"], env={"WENV_ARCH": arch}) - out, err, code = run_process(['wenv', 'help'], env = {'WENV_ARCH': arch}) + assert code == 0 + assert len(err.strip()) == 0 + assert "wenv pip" in out + # assert 'wenv python' in out # TODO - assert code == 0 - assert len(err.strip()) == 0 - assert 'wenv pip' in out - # assert 'wenv python' in out # TODO -@pytest.mark.parametrize('arch', get_context()) +@pytest.mark.parametrize("arch", get_context()) def test_init_offline(arch): - out, err, code = run_process(['wenv', 'clean'], env = {'WENV_ARCH': arch}) + out, err, code = run_process(["wenv", "clean"], env={"WENV_ARCH": arch}) - assert code == 0 - assert len(err.strip()) == 0 + assert code == 0 + assert len(err.strip()) == 0 - out, err, code = run_process(['wenv', 'help'], env = {'WENV_ARCH': arch}) + out, err, code = run_process(["wenv", "help"], env={"WENV_ARCH": arch}) - assert code == 0 - assert len(err.strip()) == 0 - assert 'wenv pip' not in out - # assert 'wenv python' not in out # TODO + assert code == 0 + assert len(err.strip()) == 0 + assert "wenv pip" not in out + # assert 'wenv python' not in out # TODO - out, err, code = run_process(['wenv', 'cache'], env = {'WENV_ARCH': arch}) + out, err, code = run_process(["wenv", "cache"], env={"WENV_ARCH": arch}) - assert code == 0 - assert len(err.strip()) == 0 - assert 'wenv pip' not in out + assert code == 0 + assert len(err.strip()) == 0 + assert "wenv pip" not in out - out, err, code = run_process(['wenv', 'init'], env = {'WENV_ARCH': arch, 'WENV_OFFLINE': 'true'}) + out, err, code = run_process( + ["wenv", "init"], env={"WENV_ARCH": arch, "WENV_OFFLINE": "true"} + ) - assert code == 0 - # assert len(err.strip()) == 0 # pip output goes to stderr + assert code == 0 + # assert len(err.strip()) == 0 # pip output goes to stderr - out, err, code = run_process(['wenv', 'help'], env = {'WENV_ARCH': arch}) + out, err, code = run_process(["wenv", "help"], env={"WENV_ARCH": arch}) - assert code == 0 - assert len(err.strip()) == 0 - assert 'wenv pip' in out - # assert 'wenv python' in out # TODO + assert code == 0 + assert len(err.strip()) == 0 + assert "wenv pip" in out + # assert 'wenv python' in out # TODO From cda7d9e033c662f5d2f2aa2f666913446c4b7ecf Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 15:58:34 +0100 Subject: [PATCH 031/131] black --- tests/test_pip.py | 135 +++++++++++++++++++++++----------------------- 1 file changed, 66 insertions(+), 69 deletions(-) diff --git a/tests/test_pip.py b/tests/test_pip.py index d47ec9b..a7be415 100644 --- a/tests/test_pip.py +++ b/tests/test_pip.py @@ -1,14 +1,13 @@ # -*- coding: utf-8 -*- """ - WENV Running Python on Wine https://github.com/pleiszenburg/wenv - tests/test_util.py: Testing pip + tests/test_util.py: Testing pip - Copyright (C) 2017-2020 Sebastian M. Ernst + Copyright (C) 2017-2020 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License @@ -21,10 +20,8 @@ WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. - """ - # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # IMPORT # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -39,71 +36,71 @@ # TEST(s) # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -@pytest.mark.parametrize('arch', get_context()) +@pytest.mark.parametrize("arch", get_context()) def test_pip(arch): - out, err, code = run_process(['wenv', 'pip', 'list'], env = {'WENV_ARCH': arch}) - assert code == 0 - assert len(err.strip()) == 0 - assert 'pip' in out - assert 'setuptools' in out - assert 'pytest' not in out - - out, err, code = run_process( - ['wenv', 'pip', 'install', 'pytest'], - env = {'WENV_ARCH': arch} - ) - assert code == 0 - assert len(err.strip()) == 0 - - out, err, code = run_process(['wenv', 'pip', 'list'], env = {'WENV_ARCH': arch}) - assert code == 0 - assert len(err.strip()) == 0 - assert 'pip' in out - assert 'setuptools' in out - assert 'pytest' in out - -@pytest.mark.parametrize('arch', get_context()) + out, err, code = run_process(["wenv", "pip", "list"], env={"WENV_ARCH": arch}) + assert code == 0 + assert len(err.strip()) == 0 + assert "pip" in out + assert "setuptools" in out + assert "pytest" not in out + + out, err, code = run_process( + ["wenv", "pip", "install", "pytest"], env={"WENV_ARCH": arch} + ) + assert code == 0 + assert len(err.strip()) == 0 + + out, err, code = run_process(["wenv", "pip", "list"], env={"WENV_ARCH": arch}) + assert code == 0 + assert len(err.strip()) == 0 + assert "pip" in out + assert "setuptools" in out + assert "pytest" in out + + +@pytest.mark.parametrize("arch", get_context()) def test_pip_api(arch): - env = Env(arch = arch) - - out, err, code = run_process(['wenv', 'pip', 'list'], env = {'WENV_ARCH': arch}) - assert code == 0 - assert len(err.strip()) == 0 - assert 'pip' in out - assert 'setuptools' in out - assert 'requests' not in out - - packages = [package['name'] for package in env.list_packages()] - assert 'pip' in packages - assert 'setuptools' in packages - assert 'requests' not in packages - - env.install_package('requests') - - out, err, code = run_process(['wenv', 'pip', 'list'], env = {'WENV_ARCH': arch}) - assert code == 0 - assert len(err.strip()) == 0 - assert 'pip' in out - assert 'setuptools' in out - assert 'requests' in out - - packages = [package['name'] for package in env.list_packages()] - assert 'pip' in packages - assert 'setuptools' in packages - assert 'requests' in packages - - env.uninstall_package('requests') - - out, err, code = run_process(['wenv', 'pip', 'list'], env = {'WENV_ARCH': arch}) - assert code == 0 - assert len(err.strip()) == 0 - assert 'pip' in out - assert 'setuptools' in out - assert 'requests' not in out - - packages = [package['name'] for package in env.list_packages()] - assert 'pip' in packages - assert 'setuptools' in packages - assert 'requests' not in packages + env = Env(arch=arch) + + out, err, code = run_process(["wenv", "pip", "list"], env={"WENV_ARCH": arch}) + assert code == 0 + assert len(err.strip()) == 0 + assert "pip" in out + assert "setuptools" in out + assert "requests" not in out + + packages = [package["name"] for package in env.list_packages()] + assert "pip" in packages + assert "setuptools" in packages + assert "requests" not in packages + + env.install_package("requests") + + out, err, code = run_process(["wenv", "pip", "list"], env={"WENV_ARCH": arch}) + assert code == 0 + assert len(err.strip()) == 0 + assert "pip" in out + assert "setuptools" in out + assert "requests" in out + + packages = [package["name"] for package in env.list_packages()] + assert "pip" in packages + assert "setuptools" in packages + assert "requests" in packages + + env.uninstall_package("requests") + + out, err, code = run_process(["wenv", "pip", "list"], env={"WENV_ARCH": arch}) + assert code == 0 + assert len(err.strip()) == 0 + assert "pip" in out + assert "setuptools" in out + assert "requests" not in out + + packages = [package["name"] for package in env.list_packages()] + assert "pip" in packages + assert "setuptools" in packages + assert "requests" not in packages From b29023c0305add38e91093db3766da1e1f494d94 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 15:59:07 +0100 Subject: [PATCH 032/131] black --- tests/test_python.py | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/tests/test_python.py b/tests/test_python.py index 54c99b4..53c9003 100644 --- a/tests/test_python.py +++ b/tests/test_python.py @@ -1,14 +1,13 @@ # -*- coding: utf-8 -*- """ - WENV Running Python on Wine https://github.com/pleiszenburg/wenv - tests/test_util.py: Testing Python interpreter + tests/test_util.py: Testing Python interpreter - Copyright (C) 2017-2020 Sebastian M. Ernst + Copyright (C) 2017-2020 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License @@ -21,10 +20,8 @@ WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. - """ - # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # IMPORT # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -37,20 +34,22 @@ # TEST(s) # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -@pytest.mark.parametrize('arch', get_context()) +@pytest.mark.parametrize("arch", get_context()) def test_python(arch): - out, err, code = run_process(['wenv', 'python', '-m', 'platform'], env = {'WENV_ARCH': arch}) - - assert code == 0 - assert len(err.strip()) == 0 - assert 'Windows' in out - - out, err, code = run_process( - ['wenv', 'python', '-c', 'import platform; print(platform.machine())'], - env = {'WENV_ARCH': arch} - ) - assert code == 0 - assert len(err.strip()) == 0 - out = out.strip() - assert (arch == 'win32' and out == 'x86') ^ (arch == 'win64' and out == 'AMD64') + out, err, code = run_process( + ["wenv", "python", "-m", "platform"], env={"WENV_ARCH": arch} + ) + + assert code == 0 + assert len(err.strip()) == 0 + assert "Windows" in out + + out, err, code = run_process( + ["wenv", "python", "-c", "import platform; print(platform.machine())"], + env={"WENV_ARCH": arch}, + ) + assert code == 0 + assert len(err.strip()) == 0 + out = out.strip() + assert (arch == "win32" and out == "x86") ^ (arch == "win64" and out == "AMD64") From a7edb977026b344104f3ebf7588437b4e6114ad7 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 15:59:35 +0100 Subject: [PATCH 033/131] log --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index eb838f7..26e0a39 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,7 @@ - FEATURE: Added support for Python 3.9 and 3.10. - FEATURE: Dropped support for Python 3.4 and 3.5. - FIX: Switched from unsupported `python-language-server` to supported `python-lsp-server`. +- FIX: Error handling in package listing for Wine Python environments was broken. - API: New makefile structure for developers. ## 0.2.1 (2020-07-10) From ed33889dd5c49876490b6aaf1eacd16ad9eb3b8b Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 16:34:26 +0100 Subject: [PATCH 034/131] handle arm64 versions --- src/wenv/_core/pythonversion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wenv/_core/pythonversion.py b/src/wenv/_core/pythonversion.py index dbbceae..8882b68 100644 --- a/src/wenv/_core/pythonversion.py +++ b/src/wenv/_core/pythonversion.py @@ -146,7 +146,7 @@ def from_zipname(cls, zip_name): raise ValueError('fagment[0] != "python"') if not fragments[2] == "embed": raise ValueError('fagment[2] != "embed"') - if not fragments[3] in ("win32", "amd64"): + if not fragments[3] in ("win32", "amd64", "arm64"): raise ValueError('fagment[3] not in in ("win32", "amd64")') if not fragments[4] == "zip": raise ValueError('fagment[4] != "zip"') From 2ce881975cdfa569c6b00831d1fc537cee2c64c8 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 16:35:01 +0100 Subject: [PATCH 035/131] log --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 26e0a39..8b83e2f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,7 @@ - FEATURE: Dropped support for Python 3.4 and 3.5. - FIX: Switched from unsupported `python-language-server` to supported `python-lsp-server`. - FIX: Error handling in package listing for Wine Python environments was broken. +- FIX: Python version parser could not handle Windows ARM64 builds. - API: New makefile structure for developers. ## 0.2.1 (2020-07-10) From ba0eaf5d17b5ed4604f038f2840538e7ed44d1c7 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 16:38:02 +0100 Subject: [PATCH 036/131] make linter happy and tell user what went wrong --- src/wenv/_core/pythonversion.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/wenv/_core/pythonversion.py b/src/wenv/_core/pythonversion.py index 8882b68..ec81425 100644 --- a/src/wenv/_core/pythonversion.py +++ b/src/wenv/_core/pythonversion.py @@ -157,9 +157,13 @@ def from_zipname(cls, zip_name): if isinstance(release[2], str): if not len(release[2]) > 0: raise ValueError("broken maintenance/build fragment") - for pos, char in enumerate(release[2]): + pos = None + for pos_idx, char in enumerate(release[2]): if not char.isdigit(): + pos = pos_idx break + if pos is None: + raise ValueError("maintenance fragment could not be identified") release.append(release[2][pos:]) release[2] = release[2][:pos] if not release[2].isnumeric(): From 7c8f8009fef5d3cea62f9561dce630fc9d3085ce Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 17:44:04 +0100 Subject: [PATCH 037/131] find latest maintenance release --- src/wenv/_core/source.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/wenv/_core/source.py b/src/wenv/_core/source.py index 7f3e65e..9cbf1f4 100644 --- a/src/wenv/_core/source.py +++ b/src/wenv/_core/source.py @@ -53,6 +53,18 @@ def download(down_url, mode="binary"): assert mode == "binary" and isinstance(r.content, bytes) return r.content +def get_latest_maintenance_release(arch: str, major: int, minor: int, sorted_versions = None): + + if sorted_versions is None: + sorted_versions = get_available_python_versions() + + arch_versions = sorted_versions[arch] + maintenance_versions = { + k[2]: v # maintenance + for k, v in arch_versions.items() + if k[0] == major and k[1] == minor + } + return maintenance_versions[max(maintenance_versions.keys())][-1] def get_available_python_versions(): From 2280f5f839bdd07c5aa62a12e1f8f370809fb92c Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 21:47:03 +0100 Subject: [PATCH 038/131] wine 4 or better --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3a88f1b..03c750b 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ About Wine (from [winehq.org](https://www.winehq.org/)): *Wine (originally an ac | prerequisite | version | | --- | --- | | [CPython](https://www.python.org/) | 3.x (tested with 3.{6,7,8,9,10}) | -| [Wine](https://www.winehq.org/) | 4.x (tested with regular & [staging](https://wine-staging.com/)) - expected to be in the user's [`PATH`](https://en.wikipedia.org/wiki/PATH_(variable)) | +| [Wine](https://www.winehq.org/) | >= 4.x (tested with regular & [staging](https://wine-staging.com/)) - expected to be in the user's [`PATH`](https://en.wikipedia.org/wiki/PATH_(variable)) | If you are limited to an older version of Wine such as 2.x or 3.x, see `wenv`'s [installation instructions](https://wenv.readthedocs.io/en/latest/installation.html) for details and workarounds. From ab51bdc4c0145cd977c45e7bf0f9fe0d58778bff Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 21:47:20 +0100 Subject: [PATCH 039/131] error fix --- src/wenv/_core/pythonversion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wenv/_core/pythonversion.py b/src/wenv/_core/pythonversion.py index ec81425..d89e249 100644 --- a/src/wenv/_core/pythonversion.py +++ b/src/wenv/_core/pythonversion.py @@ -147,7 +147,7 @@ def from_zipname(cls, zip_name): if not fragments[2] == "embed": raise ValueError('fagment[2] != "embed"') if not fragments[3] in ("win32", "amd64", "arm64"): - raise ValueError('fagment[3] not in in ("win32", "amd64")') + raise ValueError('fagment[3] not in in ("win32", "amd64", "arm64")') if not fragments[4] == "zip": raise ValueError('fagment[4] != "zip"') From baa901745249152de34ccc94f0702af195929117 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 21:55:44 +0100 Subject: [PATCH 040/131] support for arm64 --- src/wenv/_core/pythonversion.py | 4 ++-- src/wenv/_core/source.py | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/wenv/_core/pythonversion.py b/src/wenv/_core/pythonversion.py index d89e249..12d448d 100644 --- a/src/wenv/_core/pythonversion.py +++ b/src/wenv/_core/pythonversion.py @@ -35,7 +35,7 @@ def __init__(self, arch, major, minor, maintenance, build="stable"): if not isinstance(arch, str): raise TypeError("arch must be str") - if arch not in ("win32", "win64"): + if arch not in ("win32", "win64", "arm64"): raise ValueError("Unknown arch: " + arch) if any((not isinstance(item, int) for item in (major, minor, maintenance))): raise TypeError("Unknown type for major, minor or maintenance") @@ -151,7 +151,7 @@ def from_zipname(cls, zip_name): if not fragments[4] == "zip": raise ValueError('fagment[4] != "zip"') - arch = "win32" if fragments[3] == "win32" else "win64" + arch = "win64" if fragments[3] == "amd64" else fragments[3] release = [int(f) if f.isnumeric() else f for f in fragments[1].split(".")] if isinstance(release[2], str): diff --git a/src/wenv/_core/source.py b/src/wenv/_core/source.py index 9cbf1f4..df5a9ad 100644 --- a/src/wenv/_core/source.py +++ b/src/wenv/_core/source.py @@ -122,6 +122,12 @@ def get_available_python_versions(): ) for version_tuple, versions in embedded_versions.items() }, + "arm64": { + version_tuple: sorted( + [version for version in versions if version.arch == "arm64"] + ) + for version_tuple, versions in embedded_versions.items() + }, } return sorted_versions From 5ac24434a5cc9f36ac349cebe11dab95b74e2606 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 21:57:08 +0100 Subject: [PATCH 041/131] fix #15 --- src/wenv/_core/config.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/wenv/_core/config.py b/src/wenv/_core/config.py index b1474fa..57a3ec7 100644 --- a/src/wenv/_core/config.py +++ b/src/wenv/_core/config.py @@ -129,6 +129,7 @@ def _get_config_from_files(self): for fn in [ "/etc/wenv", os.path.join("/etc", CONFIG_FN), + os.path.join("/etc", CONFIG_FN[1:]), os.path.join(os.path.expanduser("~"), CONFIG_FN), os.environ.get("WENV"), os.path.join(os.environ.get("WENV"), CONFIG_FN) From 521fa6fb1ace48fe35131d1a4426283a232bd96b Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 21:58:25 +0100 Subject: [PATCH 042/131] log --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 8b83e2f..bed97c1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,7 @@ - FIX: Switched from unsupported `python-language-server` to supported `python-lsp-server`. - FIX: Error handling in package listing for Wine Python environments was broken. - FIX: Python version parser could not handle Windows ARM64 builds. +- FIX: Configuration expected in `/etc/.wenv.json` and `/etc/wenv.json`, see #15. The support for `/etc/.wenv.json` will be removed in a future release. - API: New makefile structure for developers. ## 0.2.1 (2020-07-10) From dce58ae37f103e77497436b549a85d1bb8e75beb Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 22:00:09 +0100 Subject: [PATCH 043/131] full support for arm64 --- src/wenv/_core/env.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/wenv/_core/env.py b/src/wenv/_core/env.py index 2cb5417..892dfd4 100644 --- a/src/wenv/_core/env.py +++ b/src/wenv/_core/env.py @@ -82,7 +82,11 @@ def _init_dicts(self): """ # Init Wine cmd names - self._wine_dict = {"win32": "wine", "win64": "wine64"} + self._wine_dict = { + "win32": "wine", + "win64": "wine64", + "arm64": "wine", + } # Init Python environment paths self._path_dict = Paths( From b58b4b04b13cd276170cc7dac9d39a487b4d8547 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 22:00:47 +0100 Subject: [PATCH 044/131] log --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index bed97c1..875732b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,7 @@ ## 0.3.0 (2021-XX-XX) +- FEATURE: Experimental ARM64 support added. - FEATURE: Added support for Python 3.9 and 3.10. - FEATURE: Dropped support for Python 3.4 and 3.5. - FIX: Switched from unsupported `python-language-server` to supported `python-lsp-server`. From 190e3684a1bfed287d4a793b486e82ca17423d59 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 22:14:22 +0100 Subject: [PATCH 045/131] custom wine commands --- CHANGES.md | 1 + docs/configuration.rst | 7 +++++++ src/wenv/_core/config.py | 6 ++++++ src/wenv/_core/env.py | 6 +++--- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 875732b..6481d21 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,7 @@ - FIX: Error handling in package listing for Wine Python environments was broken. - FIX: Python version parser could not handle Windows ARM64 builds. - FIX: Configuration expected in `/etc/.wenv.json` and `/etc/wenv.json`, see #15. The support for `/etc/.wenv.json` will be removed in a future release. +- FIX: The names of wine binaries/commands can be configured for special cases like RedHat/Fedora/CentOS wine packages, see zugbruecke#70. - API: New makefile structure for developers. ## 0.2.1 (2020-07-10) diff --git a/docs/configuration.rst b/docs/configuration.rst index 6cbb980..d590bfc 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -51,6 +51,13 @@ Release candidates, alpha and beta versions can be accessed in the following for .. _python.org: https://www.python.org/downloads/windows/ +``wine_bin_win32``, ``wine_bin_win64``, ``wine_bin_arm64`` (str) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This field configures the name of the ``wine`` binary/command. It usually depends on packaging and differs across Linux distributions. On Debian- & Ubuntu-based systems, ``wine`` points to Wine 32 bit while ``wine64`` points to Wine 64 bit. Those are also the default settings for ``wenv``. On RedHat/Fedora/CentOS, ``wine`` is by default an alias for Wine 64 bit, however, see `forums at WineHQ`_, and may require additional configuration for ``wenv``. + +.. _forums at WineHQ: https://forum.winehq.org/viewtopic.php?t=29567 + ``prefix`` (str) ^^^^^^^^^^^^^^^^ diff --git a/src/wenv/_core/config.py b/src/wenv/_core/config.py index 57a3ec7..82aa7fb 100644 --- a/src/wenv/_core/config.py +++ b/src/wenv/_core/config.py @@ -76,6 +76,12 @@ def __getitem__(self, key): return "win32" # Define Wine & Wine-Python architecture if key == "pythonversion": return "3.7.4" # Define Wine-Python version + if key == "wine_bin_win32": + return "wine" + if key == "wine_bin_win64": + return "wine64" + if key == "wine_bin_arm64": + return "wine" if key == "winedebug": return "-all" # Wine debug output off if key == "wineinstallprefix": diff --git a/src/wenv/_core/env.py b/src/wenv/_core/env.py index 892dfd4..e730878 100644 --- a/src/wenv/_core/env.py +++ b/src/wenv/_core/env.py @@ -83,9 +83,9 @@ def _init_dicts(self): # Init Wine cmd names self._wine_dict = { - "win32": "wine", - "win64": "wine64", - "arm64": "wine", + "win32": self._p["wine_bin_win32"], + "win64": self._p["wine_bin_win64"], + "arm64": self._p["wine_bin_arm32"], } # Init Python environment paths From 605a1fe54e345daa7ce637b25294a1cca138f1a5 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 22:29:57 +0100 Subject: [PATCH 046/131] black --- src/wenv/_core/config.py | 4 ++- src/wenv/_core/env.py | 61 +++++++++++++++++---------------- src/wenv/_core/errors.py | 1 + src/wenv/_core/paths.py | 1 + src/wenv/_core/pythonversion.py | 1 + src/wenv/_core/source.py | 9 +++-- tests/test_pip.py | 1 + tests/test_python.py | 1 + 8 files changed, 47 insertions(+), 32 deletions(-) diff --git a/src/wenv/_core/config.py b/src/wenv/_core/config.py index 82aa7fb..29634ca 100644 --- a/src/wenv/_core/config.py +++ b/src/wenv/_core/config.py @@ -164,7 +164,9 @@ def _load_config_from_file(self, try_path): with open(try_path, "r", encoding="utf-8") as f: cnt = f.read() except Exception as e: - raise EnvConfigParserError('Config file could not be read: "%s"' % try_path) from e + raise EnvConfigParserError( + 'Config file could not be read: "%s"' % try_path + ) from e # Try to parse it try: diff --git a/src/wenv/_core/env.py b/src/wenv/_core/env.py index e730878..bc6e2e3 100644 --- a/src/wenv/_core/env.py +++ b/src/wenv/_core/env.py @@ -44,11 +44,12 @@ # CLASS # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + class Env: -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -# INIT -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + # INIT + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ def __init__(self, **kwargs): @@ -100,7 +101,6 @@ def _init_dicts(self): self._init_envvar_dict() def _init_cmd_dict(self): - def ls_exe(directory): if not os.path.isdir(directory): return @@ -153,9 +153,9 @@ def _init_envvar_dict(self): ) ) # https://wiki.winehq.org/FAQ#Can_I_install_more_than_one_Wine_version_on_my_system.3F -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -# WINE BUG #47766 / ZUGBRUECKE BUG #49 -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + # WINE BUG #47766 / ZUGBRUECKE BUG #49 + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ def wine_47766_workaround(self): """ @@ -201,9 +201,9 @@ def wine_47766_workaround_uninstall(self): if os.path.lexists(self._p["pythonprefix"]): os.unlink(self._p["pythonprefix"]) -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -# ENSURE ENVIRONMENT -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + # ENSURE ENVIRONMENT + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ def ensure(self): @@ -212,9 +212,9 @@ def ensure(self): self.wine_47766_workaround() # must run after setup_pythonprefix and before setup_pip self.setup_pip() -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -# DESTROY / UNINSTALL ENVIRONMENT -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + # DESTROY / UNINSTALL ENVIRONMENT + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ def uninstall(self): @@ -230,9 +230,9 @@ def uninstall(self): self.wine_47766_workaround_uninstall() -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -# CACHE INSTALLATION FILES LOCALLY -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + # CACHE INSTALLATION FILES LOCALLY + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ def cache(self): @@ -262,9 +262,9 @@ def cache_package(self, name): with open(os.path.join(self._p["packages"], item["filename"]), "wb") as f: f.write(download(item["url"], mode="binary")) -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -# Fetch installer data -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + # Fetch installer data + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ def _get_python(self, offline=False): @@ -286,9 +286,9 @@ def _get_pip(self, offline=False): "utf-8" ) -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -# SETUP -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + # SETUP + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ def setup_wineprefix(self, overwrite=False): @@ -405,9 +405,9 @@ def setup_coverage_activate(self): with open(self._path_dict["sitecustomize"], "w") as f: f.write(siteconfig_cnt + "\n" + COVERAGE_STARTUP) -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -# PACKAGE MANAGEMENT -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + # PACKAGE MANAGEMENT + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ def install_package(self, name, update=False): """ @@ -475,13 +475,13 @@ def list_packages(self): ) outs, errs = proc.communicate() if proc.returncode != 0: - raise SystemError('listing packages failed', outs, errs) + raise SystemError("listing packages failed", outs, errs) return json.loads(outs.decode("utf-8")) -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -# CLI -# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + # CLI + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ def _cli_init(self): "sets up an environment (Wine prefix, Python interpreter, pip, setuptools, wheel)" @@ -603,14 +603,17 @@ def shebang(self): wine, (wine, self._cmd_dict["python"], sys.argv[1]), self._envvar_dict ) + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # CLI EXPORT # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + def cli(): Env().cli() + def shebang(): Env().shebang() diff --git a/src/wenv/_core/errors.py b/src/wenv/_core/errors.py index 7d501d8..799bcb6 100644 --- a/src/wenv/_core/errors.py +++ b/src/wenv/_core/errors.py @@ -26,5 +26,6 @@ # TYPES # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + class EnvConfigParserError(Exception): pass diff --git a/src/wenv/_core/paths.py b/src/wenv/_core/paths.py index bd71391..fb864ec 100644 --- a/src/wenv/_core/paths.py +++ b/src/wenv/_core/paths.py @@ -34,6 +34,7 @@ # PATHS # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + class Paths: """ Wine Python environment paths diff --git a/src/wenv/_core/pythonversion.py b/src/wenv/_core/pythonversion.py index 12d448d..c5a7933 100644 --- a/src/wenv/_core/pythonversion.py +++ b/src/wenv/_core/pythonversion.py @@ -26,6 +26,7 @@ # CLASS # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + class PythonVersion: """ Parse and handle Python versions diff --git a/src/wenv/_core/source.py b/src/wenv/_core/source.py index df5a9ad..0c14724 100644 --- a/src/wenv/_core/source.py +++ b/src/wenv/_core/source.py @@ -36,6 +36,7 @@ # ROUTINES # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + def download(down_url, mode="binary"): assert mode in ("text", "binary") @@ -53,19 +54,23 @@ def download(down_url, mode="binary"): assert mode == "binary" and isinstance(r.content, bytes) return r.content -def get_latest_maintenance_release(arch: str, major: int, minor: int, sorted_versions = None): + +def get_latest_maintenance_release( + arch: str, major: int, minor: int, sorted_versions=None +): if sorted_versions is None: sorted_versions = get_available_python_versions() arch_versions = sorted_versions[arch] maintenance_versions = { - k[2]: v # maintenance + k[2]: v # maintenance for k, v in arch_versions.items() if k[0] == major and k[1] == minor } return maintenance_versions[max(maintenance_versions.keys())][-1] + def get_available_python_versions(): versions = [ diff --git a/tests/test_pip.py b/tests/test_pip.py index a7be415..b996fd5 100644 --- a/tests/test_pip.py +++ b/tests/test_pip.py @@ -36,6 +36,7 @@ # TEST(s) # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @pytest.mark.parametrize("arch", get_context()) def test_pip(arch): diff --git a/tests/test_python.py b/tests/test_python.py index 53c9003..5dba3e1 100644 --- a/tests/test_python.py +++ b/tests/test_python.py @@ -34,6 +34,7 @@ # TEST(s) # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + @pytest.mark.parametrize("arch", get_context()) def test_python(arch): From 537689780fd5b631f147e25d3d1249b209ae35e7 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 22:31:42 +0100 Subject: [PATCH 047/131] export internal apis --- src/wenv/__init__.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/wenv/__init__.py b/src/wenv/__init__.py index 23795ea..a5f46e9 100644 --- a/src/wenv/__init__.py +++ b/src/wenv/__init__.py @@ -37,5 +37,12 @@ from ._core.errors import ( EnvConfigParserError, ) +from ._core.pythonversion import ( + PythonVersion, +) +from ._core.source import ( + get_available_python_versions, + get_latest_maintenance_release, +) env = Env # legacy From d0a8621cc20ba07c066ed601c501c4ab1e09d318 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 22:39:45 +0100 Subject: [PATCH 048/131] black --- docs/conf.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 085fe37..229ed92 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -2,7 +2,6 @@ # -*- coding: utf-8 -*- """ - WENV Running Python on Wine https://github.com/pleiszenburg/wenv @@ -22,7 +21,6 @@ WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. - """ From c0b6645916e4e9b5339ae44ab83e5568b767bd83 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 22:40:25 +0100 Subject: [PATCH 049/131] error handling --- docs/conf.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/conf.py b/docs/conf.py index 229ed92..c4d72e6 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -37,6 +37,9 @@ def fetch_version_string(): version = line.split("'")[1].split("'")[0] break + if len(version) == 0: + raise ValueError('failed to parse version') + return version From 08b7a3e31f56a8a181e72a7e81d5f48464048b67 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 22:50:21 +0100 Subject: [PATCH 050/131] modern docs --- docs/_templates/layout.html | 31 ++++++++++++++++++++++++++++++ docs/conf.py | 38 ++++++++++++++++++++++++++++--------- docs/index.rst | 11 +++++++++++ docs/introduction.rst | 4 ++-- setup.py | 2 ++ 5 files changed, 75 insertions(+), 11 deletions(-) create mode 100644 docs/_templates/layout.html diff --git a/docs/_templates/layout.html b/docs/_templates/layout.html new file mode 100644 index 0000000..ff3a445 --- /dev/null +++ b/docs/_templates/layout.html @@ -0,0 +1,31 @@ +{% extends "!layout.html" %} + +{% block menu %} + + {{ super() }} + + {% if sidebar_external_links %} + +

+ + {% if sidebar_external_links_caption %} + {{ sidebar_external_links_caption }} + {% else %} + External links + {% endif %} + +

+ +
+ + {% endif %} + +{% endblock %} diff --git a/docs/conf.py b/docs/conf.py index c4d72e6..60432ce 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -34,7 +34,7 @@ def fetch_version_string(): setup_py_lines = setup_py.split("\n") for line in setup_py_lines: if "_version_" in line: - version = line.split("'")[1].split("'")[0] + version = line.split('"')[1].split('"')[0] break if len(version) == 0: @@ -61,7 +61,13 @@ def fetch_version_string(): # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = [] +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.napoleon', + 'sphinx_autodoc_typehints', + 'sphinx_rtd_theme', + 'myst_parser', +] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] @@ -77,8 +83,8 @@ def fetch_version_string(): # General information about the project. project = "wenv" -copyright = "2017-2019 Sebastian M. Ernst" author = "Sebastian M. Ernst" +copyright = f"2017-2021 {author:s}" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -122,6 +128,20 @@ def fetch_version_string(): # # html_theme_options = {} +# Values to pass into the template engine's context for all pages. +html_context = { + 'sidebar_external_links_caption': 'Links', + 'sidebar_external_links': [ + # (' Blog', 'https://www.000'), + (' Source Code', 'https://github.com/pleiszenburg/wenv'), + (' Issue Tracker', 'https://github.com/pleiszenburg/wenv/issues'), + ### (' Mailing List', 'https://groups.io/g/zugbruecke-dev'), + ### (' Chat', 'https://matrix.to/#/#zugbruecke:matrix.org'), + # (' Citation', 'https://doi.org/000'), + (' pleiszenburg.de', 'http://www.pleiszenburg.de/'), + ], +} + # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". @@ -146,7 +166,7 @@ def fetch_version_string(): # -- Options for HTMLHelp output ------------------------------------------ # Output file base name for HTML help builder. -htmlhelp_basename = "wenvdoc" +htmlhelp_basename = f"{project:s}doc" # -- Options for LaTeX output --------------------------------------------- @@ -170,7 +190,7 @@ def fetch_version_string(): # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, "wenv.tex", "wenv Documentation", "Sebastian M. Ernst", "manual"), + (master_doc, f"{project:s}.tex", f"{project:s} Documentation", author, "manual"), ] @@ -178,7 +198,7 @@ def fetch_version_string(): # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [(master_doc, "wenv", "wenv Documentation", [author], 1)] +man_pages = [(master_doc, project, f"{project:s} Documentation", [author], 1)] # -- Options for Texinfo output ------------------------------------------- @@ -189,10 +209,10 @@ def fetch_version_string(): texinfo_documents = [ ( master_doc, - "wenv", - "wenv Documentation", + project, + f"{project:s} Documentation", author, - "wenv", + project, "One line description of project.", "Miscellaneous", ), diff --git a/docs/index.rst b/docs/index.rst index 5439e72..38e1034 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -12,13 +12,24 @@ User's guide .. toctree:: :maxdepth: 2 + :caption: Introduction introduction installation examples + +.. toctree:: + :maxdepth: 2 + :caption: Reference + configuration usage api + +.. toctree:: + :maxdepth: 2 + :caption: Advanced + security bugs faq diff --git a/docs/introduction.rst b/docs/introduction.rst index 46ed0f5..b906069 100644 --- a/docs/introduction.rst +++ b/docs/introduction.rst @@ -7,8 +7,8 @@ single: implementation single: use cases -Introduction -============ +About ``wenv`` +============== .. _synopsis: diff --git a/setup.py b/setup.py index fd8f61c..28418eb 100644 --- a/setup.py +++ b/setup.py @@ -82,6 +82,8 @@ "setuptools", "Sphinx", "sphinx_rtd_theme", + "sphinx-autodoc-typehints", + "myst-parser", "twine", "wheel", ] From d69c06bef7f2c47e8a98695fb01d4b0a4e169c86 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 23:14:08 +0100 Subject: [PATCH 051/131] add changes --- docs/changes.md | 1 + docs/index.rst | 1 + 2 files changed, 2 insertions(+) create mode 120000 docs/changes.md diff --git a/docs/changes.md b/docs/changes.md new file mode 120000 index 0000000..cf54708 --- /dev/null +++ b/docs/changes.md @@ -0,0 +1 @@ +../CHANGES.md \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index 38e1034..4f9afd7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -32,6 +32,7 @@ User's guide security bugs + changes faq `Interested in contributing?`_ From 25205f45dceb609a96f7154de23982ce73f43538 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 23:14:57 +0100 Subject: [PATCH 052/131] clarify versioning --- docs/installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation.rst b/docs/installation.rst index 5f6a018..15739c9 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -43,7 +43,7 @@ If you are interested in testing the latest work from the **development branch** After installing the package with ``pip``, you must initialize the "Wine Python environment" by running ``wenv init``. -If you are relying on *wenv*, please notice that it uses semantic versioning. Breaking changes are indicated by increasing the first version number, the major version. Going for example from 0.0.x to 1.0.0 or going from 0.1.y to 1.0.0 therefore indicates a breaking change. +If you are relying on *wenv*, please notice that it uses semantic versioning. Breaking changes are indicated by increasing the first version number, the major version. Going for example from 0.0.x to 0.1.y or going from 0.1.x to 0.1.y therefore indicates a breaking change. Possible problem: ``OSError: [WinError 6] Invalid handle`` ---------------------------------------------------------- From 81094562b493353d0f8f9550cdb255073794ae01 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 23:19:35 +0100 Subject: [PATCH 053/131] highlight command --- docs/installation.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/installation.rst b/docs/installation.rst index 15739c9..2d4db4c 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -41,7 +41,11 @@ If you are interested in testing the latest work from the **development branch** pip install git+https://github.com/pleiszenburg/wenv.git@develop -After installing the package with ``pip``, you must initialize the "Wine Python environment" by running ``wenv init``. +After installing the package with ``pip``, you must **initialize** the "Wine Python environment" by running: + +.. code:: bash + + wenv init If you are relying on *wenv*, please notice that it uses semantic versioning. Breaking changes are indicated by increasing the first version number, the major version. Going for example from 0.0.x to 0.1.y or going from 0.1.x to 0.1.y therefore indicates a breaking change. From 6ad8616965afecd554f783d860f05d9bf28d93e9 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 23:21:42 +0100 Subject: [PATCH 054/131] spelling --- docs/introduction.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/introduction.rst b/docs/introduction.rst index b906069..3b25ce6 100644 --- a/docs/introduction.rst +++ b/docs/introduction.rst @@ -36,7 +36,7 @@ While researching options for developing *zugbruecke*, *CPython*'s `embeddable p Implementation -------------- -*wenv* has two roles. First, it downloads, installs and configures both *CPython* and *pip*. The process is based on *CPython*'s embeddable package distribution for *Windows*. Second, *wenv* provides a thin launcher for staring *Python* (or just any *Python* application) on *Wine*. The installer and launcher themselves are also written in *Python* and run on any *Unix*-version of *Python*. The launcher sets the stage in *Unix Python* before using an `exec syscall`_ to replace itself with *Windows Python*. +*wenv* has two roles. First, it downloads, installs and configures both *CPython* and *pip*. The process is based on *CPython*'s embeddable package distribution for *Windows*. Second, *wenv* provides a thin launcher for starting *Python* (or just any *Python* application) on *Wine*. The installer and launcher themselves are also written in *Python* and run on any *Unix*-version of *Python*. The launcher sets the stage in *Unix Python* before using an `exec syscall`_ to replace itself with *Windows Python*. .. _exec syscall: https://en.wikipedia.org/wiki/Exec_(system_call) From 204ec43901e2b56c482f3aafcbcc7493b296817f Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 23:23:09 +0100 Subject: [PATCH 055/131] show wrong key --- src/wenv/_core/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wenv/_core/config.py b/src/wenv/_core/config.py index 29634ca..05ae274 100644 --- a/src/wenv/_core/config.py +++ b/src/wenv/_core/config.py @@ -108,7 +108,7 @@ def __getitem__(self, key): if key == "_issues_50_workaround": return False # Workaround for zugbruecke issue #50 (symlinks ...) - raise KeyError("not a valid configuration key") + raise KeyError("not a valid configuration key", key) def export_envvar_dict(self): From bb624d47a5eb516c21d7860ea6a143616b31f300 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 23:23:16 +0100 Subject: [PATCH 056/131] fix arm arch --- src/wenv/_core/env.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wenv/_core/env.py b/src/wenv/_core/env.py index bc6e2e3..317db5c 100644 --- a/src/wenv/_core/env.py +++ b/src/wenv/_core/env.py @@ -86,7 +86,7 @@ def _init_dicts(self): self._wine_dict = { "win32": self._p["wine_bin_win32"], "win64": self._p["wine_bin_win64"], - "arm64": self._p["wine_bin_arm32"], + "arm64": self._p["wine_bin_arm64"], } # Init Python environment paths From 4b55b0befa91ffba0a69f0c31edb823d82a5c7bb Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 23:31:48 +0100 Subject: [PATCH 057/131] docs are phony --- makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/makefile b/makefile index 2a4f9de..676d012 100644 --- a/makefile +++ b/makefile @@ -73,3 +73,5 @@ upload: for filename in $$(ls dist/*.tar.gz dist/*.whl) ; do \ twine upload $$filename $$filename.asc ; \ done + +.PHONY: docs From eca8e6fa13a47d0103bdff0d699c9c069fe5221e Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 23:53:54 +0100 Subject: [PATCH 058/131] versioning fix --- CHANGES.md | 2 +- docs/installation.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 6481d21..0ec820c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -57,7 +57,7 @@ Wine 2.x and 3.x are no longer supported. Please use Wine 4.x or later. On older versions of Linux such as *Ubuntu 14.04* alias *Trusty Tahr* (released 2014), you may observe errors when running ``wenv python``. Most commonly, they will present themselves as ``OSError: [WinError 6] Invalid handle: 'z:\\...`` triggered by calling ``os.listdir`` on a symbolic link ("symlink") to a folder. -*wenv* will use semantic versioning. Breaking changes will be indicated by increasing the first version number, the major version. Going for example from 0.x.0 to 1.0.0 or going from 1.y.0 to 2.0.0 therefore indicates a breaking change. +*wenv* will use semantic versioning. Breaking changes will be indicated by increasing the first version number, the major version. Going for example from 0.0.x to 0.1.y or going from 0.1.x to 0.2.y therefore indicates a breaking change. * FEATURE: Allow `-h` and `--help` as alternatives to `help`. * FEATURE: ``wineprefix``, ``winedebug`` and ``pythonprefix`` become configuration parameters definable by users allowing custom wine prefixes, wine debug levels and Python installation paths, see issue zugbruecke#44. diff --git a/docs/installation.rst b/docs/installation.rst index 2d4db4c..c0057b2 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -47,7 +47,7 @@ After installing the package with ``pip``, you must **initialize** the "Wine Pyt wenv init -If you are relying on *wenv*, please notice that it uses semantic versioning. Breaking changes are indicated by increasing the first version number, the major version. Going for example from 0.0.x to 0.1.y or going from 0.1.x to 0.1.y therefore indicates a breaking change. +If you are relying on *wenv*, please notice that it uses semantic versioning. Breaking changes are indicated by increasing the first version number, the major version. Going for example from 0.0.x to 0.1.y or going from 0.1.x to 0.2.y therefore indicates a breaking change. Possible problem: ``OSError: [WinError 6] Invalid handle`` ---------------------------------------------------------- From 1819a197ef325696554f1f9fd0a5c59f20eaf138 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 23:55:19 +0100 Subject: [PATCH 059/131] many more examples --- docs/examples.rst | 120 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 116 insertions(+), 4 deletions(-) diff --git a/docs/examples.rst b/docs/examples.rst index 3d86b42..3a89f57 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -8,10 +8,16 @@ single: shebang single: pip -Examples -======== +Getting Started +=============== -Fire up a shell and try the following: +Fire up a shell. If you have not done so already, initialize a Wine Python environment first: + +.. code:: bash + + (env) user@comp:~> wenv init + +Now you can try the following: .. code:: bash @@ -21,8 +27,114 @@ Fire up a shell and try the following: Linux (env) user@comp:~> wenv python -m platform Windows + (env) user@comp:~> python --version + Python 3.9.6 + (env) user@comp:~> wenv python --version + Python 3.7.4 + +``wenv pip`` works just like one would expect: + +.. code:: bash + + (env) user@comp:~> wenv pip list + Package Version + ---------- ------- + pip 21.3.1 + setuptools 59.2.0 + wheel 0.37.0 + (env) user@comp:~> wenv pip install requests + Collecting requests + Downloading requests-2.26.0-py2.py3-none-any.whl (62 kB) + |████████████████████████████████| 62 kB 411 kB/s + Collecting idna<4,>=2.5 + Downloading idna-3.3-py3-none-any.whl (61 kB) + |████████████████████████████████| 61 kB 1.3 MB/s + Collecting urllib3<1.27,>=1.21.1 + Downloading urllib3-1.26.7-py2.py3-none-any.whl (138 kB) + |████████████████████████████████| 138 kB 1.9 MB/s + Collecting certifi>=2017.4.17 + Downloading certifi-2021.10.8-py2.py3-none-any.whl (149 kB) + |████████████████████████████████| 149 kB 2.5 MB/s + Collecting charset-normalizer~=2.0.0 + Downloading charset_normalizer-2.0.7-py3-none-any.whl (38 kB) + Installing collected packages: urllib3, idna, charset-normalizer, certifi, requests + Successfully installed certifi-2021.10.8 charset-normalizer-2.0.7 idna-3.3 requests-2.26.0 urllib3-1.26.7 + +Have a look at the output of ``wenv help`` for more commands and information: + +.. code:: bash + + (env) user@comp:~> wenv help + wenv - the Wine Python environment + + - wenv cache: fetches installation files and caches them for offline usage (Python interpreter, pip, setuptools, wheel) + - wenv clean: removes current environment (Python interpreter, pip, setuptools, wheel, all installed packages) + - wenv help: prints this help text + - wenv init: sets up an environment (Wine prefix, Python interpreter, pip, setuptools, wheel) + - wenv init_coverage: enables coverage analysis inside wenv + + The following interpreters, scripts and modules are installed and available: + + - wenv pip + - wenv pip3 + - wenv pip3.7 + - wenv python + - wenv pythonw + - wenv wheel + +If you install a package that includes new commands, they become available via ``wenv`` and will be shown in its help: + +.. code:: bash + + (env) user@comp:~> wenv help | grep pytest + (env) user@comp:~> wenv pip install pytest > /dev/null + (env) user@comp:~> wenv help | grep pytest + - wenv pytest + (env) user@comp:~> wenv pytest --version + pytest 6.2.5 -``wenv pip`` works just like one would expect. Have a look at the output of ``wenv help`` for more commands and information. For use as a shebang, ``wenv python`` has an alias: One can write ``#!/usr/bin/env _wenv_python`` at the top of scripts. +The ``wenv python`` command behaves just like the regular ``python`` command on Unix: + +.. code:: bash + + (env) user@comp:~> wenv python + Python 3.7.4 (tags/v3.7.4:e09359112e, Jul 8 2019, 19:29:22) [MSC v.1916 32 bit (Intel)] on win32 + Type "help", "copyright", "credits" or "license" for more information. + >>> import platform + >>> platform.uname().system + 'Windows' + >>> exit() + (env) user@comp:~> wenv python -c "from platform import uname; print(uname().system)" + Windows + +Thanks to Wine, the handling of paths is seamless and transparent: + +.. code:: bash + + (env) user@comp:~> python -c "import os; print(os.getcwd())" + /home/user + (env) user@comp:~> wenv python -c "import os; print(os.getcwd())" + Z:\home\user + +For use as a shebang, ``wenv python`` has an alias. One can write ``#!/usr/bin/env _wenv_python`` at the top of scripts: + +.. code:: python + + #!/usr/bin/env _wenv_python + + import platform + if __name__ == '__main__': + print(f'Hello from {platform.uname().system:s}!') + +If the above script was named ``hello_from_platform.py``, one could run it easily as follows: + +.. code:: bash + + (env) user@comp:~> uname + Linux + (env) user@comp:~> chmod +x hello_from_platform.py + (env) user@comp:~> ./hello_from_platform.py + Hello from Windows! ``wenv python`` can also be used as a **Jupyter kernel**, side-by-side with a Unix-version of Python. Have a look at the `wenv-kernel project`_. From 5013889b57fa38d855cc90e7bf23366fab03512f Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sat, 20 Nov 2021 23:55:53 +0100 Subject: [PATCH 060/131] log --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 0ec820c..9eb24b5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,6 +11,7 @@ - FIX: Configuration expected in `/etc/.wenv.json` and `/etc/wenv.json`, see #15. The support for `/etc/.wenv.json` will be removed in a future release. - FIX: The names of wine binaries/commands can be configured for special cases like RedHat/Fedora/CentOS wine packages, see zugbruecke#70. - API: New makefile structure for developers. +- DOCS: Hugely improved. ## 0.2.1 (2020-07-10) From 77721e4e6111bda9dbdc3843c70b56133d2235db Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sun, 21 Nov 2021 00:02:54 +0100 Subject: [PATCH 061/131] better etc name --- docs/configuration.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index d590bfc..0e9ae28 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -21,7 +21,7 @@ Configuration files * The current working directory * A directory specified in the ``WENV`` environment variable * The user profile folder (``~`` / ``/home/{username}``) -* ``/etc`` +* ``/etc`` (here, the file is simply expected to be named ``wenv.json``) There is one optional addition to the above rules: The path specified in the ``WENV`` environment variable can directly point to a configuration file. I.e. the ``WENV`` environment variable can also contain a path similar to ``/path/to/some/file.json``. From 2d24f9de315fc3a6b3ec1ed1453a17f97bc3d643 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sun, 21 Nov 2021 00:03:19 +0100 Subject: [PATCH 062/131] arm support --- docs/configuration.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index 0e9ae28..c3ce1e9 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -38,7 +38,7 @@ Configurable parameters ``arch`` (str) ^^^^^^^^^^^^^^ -Defines the architecture of *Wine* & *Wine* *Python*. It can be set to ``win32`` or ``win64``. Default is ``win32``, even on 64-bit systems. It appears to be a more stable configuration. +Defines the architecture of *Wine* & *Wine* *Python*. It can be set to ``win32``, ``win64`` or ``arm64``. Default is ``win32``, even on 64-bit systems. It appears to be a more stable configuration. ``pythonversion`` (str) ^^^^^^^^^^^^^^^^^^^^^^^ From 07eb3a7314b1959e79862ad57a7461d5a41c33f6 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sun, 21 Nov 2021 00:12:16 +0100 Subject: [PATCH 063/131] grammar --- docs/configuration.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index c3ce1e9..9036361 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -54,14 +54,14 @@ Release candidates, alpha and beta versions can be accessed in the following for ``wine_bin_win32``, ``wine_bin_win64``, ``wine_bin_arm64`` (str) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -This field configures the name of the ``wine`` binary/command. It usually depends on packaging and differs across Linux distributions. On Debian- & Ubuntu-based systems, ``wine`` points to Wine 32 bit while ``wine64`` points to Wine 64 bit. Those are also the default settings for ``wenv``. On RedHat/Fedora/CentOS, ``wine`` is by default an alias for Wine 64 bit, however, see `forums at WineHQ`_, and may require additional configuration for ``wenv``. +These fields configure the name of the ``wine`` binary/command. They usually depend on packaging and differ across Linux distributions. On Debian- & Ubuntu-based systems, ``wine`` points to Wine 32 bit while ``wine64`` points to Wine 64 bit. Those are also the default settings for ``wenv``. On RedHat/Fedora/CentOS, ``wine`` is by default an alias for Wine 64 bit, however, see `forums at WineHQ`_, and may require additional configuration for ``wenv``. .. _forums at WineHQ: https://forum.winehq.org/viewtopic.php?t=29567 ``prefix`` (str) ^^^^^^^^^^^^^^^^ -If ``wenv`` is installed into a *Python* virtual environment or system-wide, this option's defaults is ``sys.prefix``. If ``wenv`` is installed user-wide, its default is ``site.USER_BASE`` (typically ``~/.local``). +If ``wenv`` is installed into a *Python* virtual environment or system-wide, this option's default is ``sys.prefix``. If ``wenv`` is installed user-wide, its default is ``site.USER_BASE`` (typically ``~/.local``). ``wineprefix`` (str) ^^^^^^^^^^^^^^^^^^^^ From 851516b41598e86a5a7486cafbe2e4497e1824a9 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sun, 21 Nov 2021 00:29:57 +0100 Subject: [PATCH 064/131] one more example --- docs/examples.rst | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/docs/examples.rst b/docs/examples.rst index 3a89f57..faeaa6e 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -116,6 +116,35 @@ Thanks to Wine, the handling of paths is seamless and transparent: (env) user@comp:~> wenv python -c "import os; print(os.getcwd())" Z:\home\user +``wenv`` can be heavily configured via configuration files and/or environment variables. In the following example, two Wine Python environments are initialized. The first environment is using the ``wenv`` default Python version, 3.7.4. The second environment is using a custom Python version, 3.9.6: + +.. code:: bash + + (env) user@comp:~> wenv init 2> /dev/null + Collecting pip + Downloading pip-21.3.1-py3-none-any.whl (1.7 MB) + |################################| 1.7 MB 1.1 MB/s + Collecting setuptools + Downloading setuptools-59.2.0-py3-none-any.whl (952 kB) + |################################| 952 kB 1.7 MB/s + Collecting wheel + Downloading wheel-0.37.0-py2.py3-none-any.whl (35 kB) + Installing collected packages: wheel, setuptools, pip + Successfully installed pip-21.3.1 setuptools-59.2.0 wheel-0.37.0 + (env) user@comp:~> WENV_PYTHONVERSION=3.9.6 wenv init 2> /dev/null + Collecting pip + Using cached pip-21.3.1-py3-none-any.whl (1.7 MB) + Collecting setuptools + Using cached setuptools-59.2.0-py3-none-any.whl (952 kB) + Collecting wheel + Using cached wheel-0.37.0-py2.py3-none-any.whl (35 kB) + Installing collected packages: wheel, setuptools, pip + Successfully installed pip-21.3.1 setuptools-59.2.0 wheel-0.37.0 + (env) user@comp:~> wenv python --version + Python 3.7.4 + (env) user@comp:~> WENV_PYTHONVERSION=3.9.6 wenv python --version + Python 3.9.6 + For use as a shebang, ``wenv python`` has an alias. One can write ``#!/usr/bin/env _wenv_python`` at the top of scripts: .. code:: python From 9fcc2029916622627cd13f2fc5235b91927dcb32 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sun, 21 Nov 2021 00:32:26 +0100 Subject: [PATCH 065/131] younger python version --- docs/examples.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/examples.rst b/docs/examples.rst index faeaa6e..2f1145f 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -116,7 +116,7 @@ Thanks to Wine, the handling of paths is seamless and transparent: (env) user@comp:~> wenv python -c "import os; print(os.getcwd())" Z:\home\user -``wenv`` can be heavily configured via configuration files and/or environment variables. In the following example, two Wine Python environments are initialized. The first environment is using the ``wenv`` default Python version, 3.7.4. The second environment is using a custom Python version, 3.9.6: +``wenv`` can be heavily configured via configuration files and/or environment variables. In the following example, two Wine Python environments are initialized. The first environment is using the ``wenv`` default Python version, 3.7.4. The second environment is using a custom Python version, 3.10.0: .. code:: bash @@ -131,7 +131,7 @@ Thanks to Wine, the handling of paths is seamless and transparent: Downloading wheel-0.37.0-py2.py3-none-any.whl (35 kB) Installing collected packages: wheel, setuptools, pip Successfully installed pip-21.3.1 setuptools-59.2.0 wheel-0.37.0 - (env) user@comp:~> WENV_PYTHONVERSION=3.9.6 wenv init 2> /dev/null + (env) user@comp:~> WENV_PYTHONVERSION=3.10.0 wenv init 2> /dev/null Collecting pip Using cached pip-21.3.1-py3-none-any.whl (1.7 MB) Collecting setuptools @@ -142,8 +142,8 @@ Thanks to Wine, the handling of paths is seamless and transparent: Successfully installed pip-21.3.1 setuptools-59.2.0 wheel-0.37.0 (env) user@comp:~> wenv python --version Python 3.7.4 - (env) user@comp:~> WENV_PYTHONVERSION=3.9.6 wenv python --version - Python 3.9.6 + (env) user@comp:~> WENV_PYTHONVERSION=3.10.0 wenv python --version + Python 3.10.0 For use as a shebang, ``wenv python`` has an alias. One can write ``#!/usr/bin/env _wenv_python`` at the top of scripts: From af58e804e9e8d27e2d898b3850614f1f0d4b5c3b Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sun, 21 Nov 2021 00:34:31 +0100 Subject: [PATCH 066/131] change headline --- docs/usage.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/usage.rst b/docs/usage.rst index 5220362..aea35d8 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -10,8 +10,8 @@ triple: wine; python; environment module: wenv._core.env -Usage -===== +Commands +======== This chapter covers the main modes of usage of ``wenv``. From 532c3460fa5d6c40e226dd0d6fbb1226bd4815b7 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sun, 21 Nov 2021 00:38:43 +0100 Subject: [PATCH 067/131] stye --- docs/configuration.rst | 6 +++--- docs/installation.rst | 2 +- docs/introduction.rst | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index 9036361..21bb3be 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -13,7 +13,7 @@ Configuration *wenv* can configure itself automatically or can be configured with files and environment variables manually. -Configuration files +Configuration Files ------------------- *wenv* uses ``JSON`` configuration files named ``.wenv.json``. They are expected in the following locations (in that order): @@ -27,12 +27,12 @@ There is one optional addition to the above rules: The path specified in the ``W Configuration options are being looked for location after location in the above listed places. If, after checking for configuration files in all those locations, there are still configuration options left undefined, *wenv* will fill them with its defaults. A configuration option found in a location higher in the list will always be given priority over a the same configuration option with different content found in a location further down the list. -Configuration environment variables +Configuration Environment Variables ----------------------------------- Independently of the ``WENV`` environment variable, all configurable parameters of ``wenv`` can directly be overridden with environment variables. All values coming from configuration files will then be ignored for this particular parameter. Take the name of any configurable parameter, convert it to upper case and prefix it with ``WENV``. Example: The ``arch`` parameter can be overridden by declaring the ``WENV_ARCH`` environment variable. -Configurable parameters +Configurable Parameters ----------------------- ``arch`` (str) diff --git a/docs/installation.rst b/docs/installation.rst index c0057b2..60262a2 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -56,7 +56,7 @@ On older versions of Linux such as *Ubuntu 14.04* alias *Trusty Tahr* (released A **clean solution** is to upgrade to a younger version of Linux. E.g. *Ubuntu 16.04* alias *Xenial Xerus* (released 2016) is known to work. -Installing *wenv* in development mode +Installing *wenv* in Development Mode ------------------------------------- If you are interested in contributing to *wenv*, you might want to install it in development mode. You can find the latest instructions on how to do this in the `CONTRIBUTING file`_ of this project on *Github*. diff --git a/docs/introduction.rst b/docs/introduction.rst index 3b25ce6..7d579d1 100644 --- a/docs/introduction.rst +++ b/docs/introduction.rst @@ -42,7 +42,7 @@ Implementation .. _usecases: -Use cases +Use Cases --------- - Running *Python* apps & scripts written for *Windows* on *Unix*. From 146252c4034bed780c9d213796b4154499ff1637 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sun, 21 Nov 2021 00:39:54 +0100 Subject: [PATCH 068/131] cleanup --- docs/usage.rst | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/usage.rst b/docs/usage.rst index aea35d8..0ae21c7 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -15,34 +15,34 @@ Commands This chapter covers the main modes of usage of ``wenv``. -Command: ``wenv init`` ----------------------- +``wenv init`` +------------- If you have not initialized ``wenv`` before, you must initialize the *Wine Python environment* first by running ``wenv init``. This will configure *Wine*, install a *Windows* version of *Python* and fetch *pip*. -Command: ``wenv clean`` ------------------------ +``wenv clean`` +-------------- This command is useful if you want to remove your current *Wine Python environment* and all related data (including the relevant *Wine* prefix). ``wenv``'s configuration is left untouched. -Command: ``wenv help`` ----------------------- +``wenv help`` +------------- This command provides help and lists all currently available sub-commands (such as ``init`` or ``python``). -Command: ``wenv python`` ------------------------- +``wenv python`` +--------------- This command behaves just like the regular ``python`` command in a *Unix* shell, except that it fires up a *Windows* *Python* interpreter on top of *Wine*. It works with all regular parameters and switches, accepts pipes and can be launched in interactive mode. You can also use it for creating executable *Python* scripts by adding the following at their top: ``#!/usr/bin/env _wenv_python``. Do not forget ``chmod +x your_script.py``. Notice that there is a difference between the more general ``wenv python`` command and its alias ``_wenv_python``, which is meant to be used only with a shebang. -Command: ``wenv pip`` ---------------------- +``wenv pip`` +------------ This command behaves just like the regular ``pip`` command on *Unix*, except that it attempts to install *Python* packages into a dedicated *Python* environment under *Wine*. So if you need any specific packages in ``wenv python``, this is how you install them. Most packages written in pure *Python* should work just fine. Anything requiring a compiler during installation does not work. Packages / wheels with pre-compiled binary components in them might work, although this is largely untested territory. Feel free to report any (positive or negative) results. -Command: ``wenv init_coverage`` -------------------------------- +``wenv init_coverage`` +---------------------- This command enables coverage analysis across the entire *Wine Python environment*. The ``coverage`` package must have been installed before running this command. From 585f5ab29e3a5a6339e29dcdb5b229b0054708fa Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sun, 21 Nov 2021 00:48:14 +0100 Subject: [PATCH 069/131] debugging with typeguard --- setup.py | 1 + src/wenv/_core/env.py | 3 ++- src/wenv/_core/typeguard.py | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 src/wenv/_core/typeguard.py diff --git a/setup.py b/setup.py index 28418eb..02cccd7 100644 --- a/setup.py +++ b/setup.py @@ -83,6 +83,7 @@ "Sphinx", "sphinx_rtd_theme", "sphinx-autodoc-typehints", + "typeguard", "myst-parser", "twine", "wheel", diff --git a/src/wenv/_core/env.py b/src/wenv/_core/env.py index 317db5c..a7850b5 100644 --- a/src/wenv/_core/env.py +++ b/src/wenv/_core/env.py @@ -39,12 +39,13 @@ from .paths import Paths from .pythonversion import PythonVersion from .source import download +from .typeguard import typechecked # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # CLASS # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - +@typechecked class Env: # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/src/wenv/_core/typeguard.py b/src/wenv/_core/typeguard.py new file mode 100644 index 0000000..1025e3e --- /dev/null +++ b/src/wenv/_core/typeguard.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- + +""" +WENV +Running Python on Wine +https://github.com/pleiszenburg/wenv + + src/wenv/_core/typeguard.py: Wrapper for typeguard for debugging + + Copyright (C) 2017-2020 Sebastian M. Ernst + + +The contents of this file are subject to the GNU Lesser General Public License +Version 2.1 ("LGPL" or "License"). You may not use this file except in +compliance with the License. You may obtain a copy of the License at +https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt +https://github.com/pleiszenburg/wenv/blob/master/LICENSE + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +""" + +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# WRAPPER +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +import os +import warnings + +if os.environ.get('WENV_DEBUG', '0') == '1': + from typeguard import typechecked + warnings.warn("running in debug mode with activated run-time type checks", RuntimeWarning) +else: + typechecked = lambda x: x From c60ba894b767afe939f07cef02c77f5071870093 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sun, 21 Nov 2021 00:53:18 +0100 Subject: [PATCH 070/131] annotated env class --- src/wenv/_core/env.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/wenv/_core/env.py b/src/wenv/_core/env.py index a7850b5..76a9705 100644 --- a/src/wenv/_core/env.py +++ b/src/wenv/_core/env.py @@ -32,6 +32,7 @@ import shutil import subprocess import sys +from typing import Any, Generator import zipfile from .config import EnvConfig @@ -52,7 +53,7 @@ class Env: # INIT # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - def __init__(self, **kwargs): + def __init__(self, **kwargs: Any): if "parameter" in kwargs.keys(): # legacy API if len(kwargs) > 1: @@ -102,7 +103,9 @@ def _init_dicts(self): self._init_envvar_dict() def _init_cmd_dict(self): - def ls_exe(directory): + + @typechecked + def ls_exe(directory: str) -> Generator: if not os.path.isdir(directory): return for item in os.listdir(directory): @@ -249,7 +252,7 @@ def cache(self): for package in ("pip", "setuptools", "wheel"): self.cache_package(package) - def cache_package(self, name): + def cache_package(self, name: str): os.makedirs(self._p["packages"], exist_ok=True) @@ -267,7 +270,7 @@ def cache_package(self, name): # Fetch installer data # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - def _get_python(self, offline=False): + def _get_python(self, offline: bool = False) -> bytes: version = PythonVersion.from_config(self._p["arch"], self._p["pythonversion"]) @@ -277,7 +280,7 @@ def _get_python(self, offline=False): else: return download(version.as_url(), mode="binary") - def _get_pip(self, offline=False): + def _get_pip(self, offline: bool = False) -> bytes: if offline: with open(os.path.join(self._p["cache"], "get-pip.py"), "rb") as f: @@ -291,7 +294,7 @@ def _get_pip(self, offline=False): # SETUP # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - def setup_wineprefix(self, overwrite=False): + def setup_wineprefix(self, overwrite: bool = False): if not isinstance(overwrite, bool): raise TypeError("overwrite is not a boolean") @@ -316,7 +319,7 @@ def setup_wineprefix(self, overwrite=False): if proc.returncode != 0: sys.exit(1) - def setup_pythonprefix(self, overwrite=False): + def setup_pythonprefix(self, overwrite: bool = False): if not isinstance(overwrite, bool): raise TypeError("overwrite is not a boolean") @@ -410,7 +413,7 @@ def setup_coverage_activate(self): # PACKAGE MANAGEMENT # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - def install_package(self, name, update=False): + def install_package(self, name: str, update: bool = False): """ Thin wrapper for `wenv pip install` """ @@ -437,7 +440,7 @@ def install_package(self, name, update=False): if proc.returncode != 0: raise SystemError('installing package "%s" failed' % name, outs, errs) - def uninstall_package(self, name): + def uninstall_package(self, name: str): """ Thin wrapper for `wenv pip uninstall -y` """ From cfbb7ed079c5254558a4a3cc12a7c02622b6accb Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sun, 21 Nov 2021 01:06:36 +0100 Subject: [PATCH 071/131] auto doc step 1 --- docs/api.rst | 27 ++++++++++++++++++--------- docs/env.rst | 19 +++++++++++++++++++ docs/envconfig.rst | 16 ++++++++++++++++ docs/pythonversion.rst | 15 +++++++++++++++ 4 files changed, 68 insertions(+), 9 deletions(-) create mode 100644 docs/env.rst create mode 100644 docs/envconfig.rst create mode 100644 docs/pythonversion.rst diff --git a/docs/api.rst b/docs/api.rst index 22cc1ba..e0a979d 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -3,17 +3,26 @@ .. _wineenv: .. index:: - single: env python - single: env pip - single: env pytest - single: env init - triple: wine; python; environment - module: wenv._core.env + single: API + single: classes + single: functions + single: methods + +API +=== + +``wenv`` can not only be used from the command line. It also offers an API and is therefore fully programmable. + +.. toctree:: + :maxdepth: 2 + :caption: The API in detail + + env + envconfig + pythonversion + -API: class ``Env`` -================== -``wenv`` can not only be used from the command line. It also offers an API through its ``Env`` class. Constructor: ``Env(**kwargs)`` ------------------------------ diff --git a/docs/env.rst b/docs/env.rst new file mode 100644 index 0000000..2722598 --- /dev/null +++ b/docs/env.rst @@ -0,0 +1,19 @@ +:github_url: + +.. _wineenv: + +.. index:: + single: env python + single: env pip + single: env pytest + single: env init + triple: wine; python; environment + module: wenv.Env + +Environment +=========== + +foo bar + +.. autoclass:: wenv.Env + :members: diff --git a/docs/envconfig.rst b/docs/envconfig.rst new file mode 100644 index 0000000..3a6e023 --- /dev/null +++ b/docs/envconfig.rst @@ -0,0 +1,16 @@ +:github_url: + +.. _wineenv: + +.. index:: + single: configuration + triple: wine; python; environment + module: wenv.EnvConfig + +Configuration +============= + +foo bar + +.. autoclass:: wenv.EnvConfig + :members: diff --git a/docs/pythonversion.rst b/docs/pythonversion.rst new file mode 100644 index 0000000..97cf928 --- /dev/null +++ b/docs/pythonversion.rst @@ -0,0 +1,15 @@ +:github_url: + +.. _wineenv: + +.. index:: + single: Python version + module: wenv.PythonVersion + +Python Version Processing +========================= + +foo bar + +.. autoclass:: wenv.PythonVersion + :members: From e3ee20336b30d6950899a17385248d15a7105ead Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sun, 21 Nov 2021 01:08:48 +0100 Subject: [PATCH 072/131] style --- docs/configuration.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index 21bb3be..6c407b6 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -13,8 +13,8 @@ Configuration *wenv* can configure itself automatically or can be configured with files and environment variables manually. -Configuration Files -------------------- +Files +----- *wenv* uses ``JSON`` configuration files named ``.wenv.json``. They are expected in the following locations (in that order): @@ -27,13 +27,13 @@ There is one optional addition to the above rules: The path specified in the ``W Configuration options are being looked for location after location in the above listed places. If, after checking for configuration files in all those locations, there are still configuration options left undefined, *wenv* will fill them with its defaults. A configuration option found in a location higher in the list will always be given priority over a the same configuration option with different content found in a location further down the list. -Configuration Environment Variables ------------------------------------ +Environment Variables +--------------------- Independently of the ``WENV`` environment variable, all configurable parameters of ``wenv`` can directly be overridden with environment variables. All values coming from configuration files will then be ignored for this particular parameter. Take the name of any configurable parameter, convert it to upper case and prefix it with ``WENV``. Example: The ``arch`` parameter can be overridden by declaring the ``WENV_ARCH`` environment variable. -Configurable Parameters ------------------------ +Parameters +---------- ``arch`` (str) ^^^^^^^^^^^^^^ From 14e3fee6ff1828d08e02e4fd6d7cf516fe714d6f Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sun, 21 Nov 2021 11:45:20 +0100 Subject: [PATCH 073/131] copyright 2022 --- .travis.yml | 2 +- docs/conf.py | 4 ++-- makefile | 2 +- setup.py | 2 +- src/wenv/__init__.py | 2 +- src/wenv/__main__.py | 2 +- src/wenv/_core/__init__.py | 2 +- src/wenv/_core/config.py | 2 +- src/wenv/_core/const.py | 2 +- src/wenv/_core/env.py | 2 +- src/wenv/_core/errors.py | 2 +- src/wenv/_core/paths.py | 2 +- src/wenv/_core/pythonversion.py | 2 +- src/wenv/_core/source.py | 2 +- src/wenv/_core/typeguard.py | 2 +- tests/__init__.py | 2 +- tests/lib/__init__.py | 2 +- tests/lib/const.py | 2 +- tests/lib/param.py | 2 +- tests/test_init.py | 2 +- tests/test_pip.py | 2 +- tests/test_python.py | 2 +- 22 files changed, 23 insertions(+), 23 deletions(-) diff --git a/.travis.yml b/.travis.yml index c7df1e1..52145d7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ # # .travis.yml: Configuration for Travis CI build test # -# Copyright (C) 2017-2019 Sebastian M. Ernst +# Copyright (C) 2017-2022 Sebastian M. Ernst # # # The contents of this file are subject to the GNU Lesser General Public License diff --git a/docs/conf.py b/docs/conf.py index 60432ce..6bb943f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -8,7 +8,7 @@ docs/conf.py: Configures the documentation build process - Copyright (C) 2017-2020 Sebastian M. Ernst + Copyright (C) 2017-2022 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License @@ -84,7 +84,7 @@ def fetch_version_string(): # General information about the project. project = "wenv" author = "Sebastian M. Ernst" -copyright = f"2017-2021 {author:s}" +copyright = f"2017-2022 {author:s}" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/makefile b/makefile index 676d012..90b0dff 100644 --- a/makefile +++ b/makefile @@ -4,7 +4,7 @@ # # makefile: GNU makefile for project management # -# Copyright (C) 2017-2020 Sebastian M. Ernst +# Copyright (C) 2017-2022 Sebastian M. Ernst # # # The contents of this file are subject to the GNU Lesser General Public License diff --git a/setup.py b/setup.py index 02cccd7..229313c 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup.py: Used for package distribution - Copyright (C) 2017-2020 Sebastian M. Ernst + Copyright (C) 2017-2022 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License diff --git a/src/wenv/__init__.py b/src/wenv/__init__.py index a5f46e9..7497660 100644 --- a/src/wenv/__init__.py +++ b/src/wenv/__init__.py @@ -7,7 +7,7 @@ src/wenv/__init__.py: Package init file - Copyright (C) 2017-2020 Sebastian M. Ernst + Copyright (C) 2017-2022 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License diff --git a/src/wenv/__main__.py b/src/wenv/__main__.py index 58e6be5..248c702 100644 --- a/src/wenv/__main__.py +++ b/src/wenv/__main__.py @@ -7,7 +7,7 @@ src/wenv/__init__.py: Package entry point - Copyright (C) 2017-2020 Sebastian M. Ernst + Copyright (C) 2017-2022 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License diff --git a/src/wenv/_core/__init__.py b/src/wenv/_core/__init__.py index 21522fd..7b605bd 100644 --- a/src/wenv/_core/__init__.py +++ b/src/wenv/_core/__init__.py @@ -7,7 +7,7 @@ src/wenv/_core/__init__.py: Core module - Copyright (C) 2017-2020 Sebastian M. Ernst + Copyright (C) 2017-2022 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License diff --git a/src/wenv/_core/config.py b/src/wenv/_core/config.py index 05ae274..e0b4cc6 100644 --- a/src/wenv/_core/config.py +++ b/src/wenv/_core/config.py @@ -7,7 +7,7 @@ src/wenv/_core/config.py: Handles the modules configuration - Copyright (C) 2017-2020 Sebastian M. Ernst + Copyright (C) 2017-2022 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License diff --git a/src/wenv/_core/const.py b/src/wenv/_core/const.py index aaec3ca..a1a8a70 100644 --- a/src/wenv/_core/const.py +++ b/src/wenv/_core/const.py @@ -7,7 +7,7 @@ src/wenv/_core/const.py: Holds constant values, flags, types - Copyright (C) 2017-2020 Sebastian M. Ernst + Copyright (C) 2017-2022 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License diff --git a/src/wenv/_core/env.py b/src/wenv/_core/env.py index 76a9705..3ef62e7 100644 --- a/src/wenv/_core/env.py +++ b/src/wenv/_core/env.py @@ -7,7 +7,7 @@ src/wenv/_core/env.py: Managing a Wine-Python environment - Copyright (C) 2017-2020 Sebastian M. Ernst + Copyright (C) 2017-2022 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License diff --git a/src/wenv/_core/errors.py b/src/wenv/_core/errors.py index 799bcb6..3b8697a 100644 --- a/src/wenv/_core/errors.py +++ b/src/wenv/_core/errors.py @@ -7,7 +7,7 @@ src/wenv/_core/errors.py: Exceptions - Copyright (C) 2017-2020 Sebastian M. Ernst + Copyright (C) 2017-2022 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License diff --git a/src/wenv/_core/paths.py b/src/wenv/_core/paths.py index fb864ec..e9d68d5 100644 --- a/src/wenv/_core/paths.py +++ b/src/wenv/_core/paths.py @@ -7,7 +7,7 @@ src/wenv/_core/path.py: Wine-Python environment paths - Copyright (C) 2017-2020 Sebastian M. Ernst + Copyright (C) 2017-2022 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License diff --git a/src/wenv/_core/pythonversion.py b/src/wenv/_core/pythonversion.py index c5a7933..a7b1d43 100644 --- a/src/wenv/_core/pythonversion.py +++ b/src/wenv/_core/pythonversion.py @@ -7,7 +7,7 @@ src/wenv/_core/pythonversion.py: Parse and handle Python versions - Copyright (C) 2017-2020 Sebastian M. Ernst + Copyright (C) 2017-2022 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License diff --git a/src/wenv/_core/source.py b/src/wenv/_core/source.py index 0c14724..c0ee2a8 100644 --- a/src/wenv/_core/source.py +++ b/src/wenv/_core/source.py @@ -7,7 +7,7 @@ src/wenv/_core/source.py: Obtaining Python and pip locally or remotely - Copyright (C) 2017-2020 Sebastian M. Ernst + Copyright (C) 2017-2022 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License diff --git a/src/wenv/_core/typeguard.py b/src/wenv/_core/typeguard.py index 1025e3e..049a8ef 100644 --- a/src/wenv/_core/typeguard.py +++ b/src/wenv/_core/typeguard.py @@ -7,7 +7,7 @@ src/wenv/_core/typeguard.py: Wrapper for typeguard for debugging - Copyright (C) 2017-2020 Sebastian M. Ernst + Copyright (C) 2017-2022 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License diff --git a/tests/__init__.py b/tests/__init__.py index 04657b0..9e07590 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -7,7 +7,7 @@ tests/__init__.py: Test module - Copyright (C) 2017-2020 Sebastian M. Ernst + Copyright (C) 2017-2022 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License diff --git a/tests/lib/__init__.py b/tests/lib/__init__.py index 01a1f4c..c39dad5 100644 --- a/tests/lib/__init__.py +++ b/tests/lib/__init__.py @@ -7,7 +7,7 @@ tests/lib/__init__.py: Test library module - Copyright (C) 2017-2020 Sebastian M. Ernst + Copyright (C) 2017-2022 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License diff --git a/tests/lib/const.py b/tests/lib/const.py index d6d434d..02e2f00 100644 --- a/tests/lib/const.py +++ b/tests/lib/const.py @@ -7,7 +7,7 @@ tests/lib/const.py: Holds constant values, flags, types - Copyright (C) 2017-2020 Sebastian M. Ernst + Copyright (C) 2017-2022 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License diff --git a/tests/lib/param.py b/tests/lib/param.py index c30b5bb..47e820f 100644 --- a/tests/lib/param.py +++ b/tests/lib/param.py @@ -7,7 +7,7 @@ tests/lib/const.py: Holds constant values, flags, types - Copyright (C) 2017-2020 Sebastian M. Ernst + Copyright (C) 2017-2022 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License diff --git a/tests/test_init.py b/tests/test_init.py index aa202b3..f445fac 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -7,7 +7,7 @@ tests/test_init.py: Testing clean & init - Copyright (C) 2017-2020 Sebastian M. Ernst + Copyright (C) 2017-2022 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License diff --git a/tests/test_pip.py b/tests/test_pip.py index b996fd5..4b23f26 100644 --- a/tests/test_pip.py +++ b/tests/test_pip.py @@ -7,7 +7,7 @@ tests/test_util.py: Testing pip - Copyright (C) 2017-2020 Sebastian M. Ernst + Copyright (C) 2017-2022 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License diff --git a/tests/test_python.py b/tests/test_python.py index 5dba3e1..1a22d00 100644 --- a/tests/test_python.py +++ b/tests/test_python.py @@ -7,7 +7,7 @@ tests/test_util.py: Testing Python interpreter - Copyright (C) 2017-2020 Sebastian M. Ernst + Copyright (C) 2017-2022 Sebastian M. Ernst The contents of this file are subject to the GNU Lesser General Public License From 851f0670aeed8ec83129207ac73624f01989796a Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sun, 21 Nov 2021 12:35:43 +0100 Subject: [PATCH 074/131] github actions test --- .github/workflows/test.yaml | 50 +++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 .github/workflows/test.yaml diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..ce68262 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,50 @@ +name: zugbruecke test suite + +on: [push] + +jobs: + build: + + strategy: + matrix: + os: ["ubuntu-18.04", "ubuntu-20.04"] + python-version: [3.6, 3.7, 3.8, 3.9, "3.10"] + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v2 + - name: Install Wine + run: | + sudo dpkg --add-architecture i386 + wget https://dl.winehq.org/wine-builds/Release.key + sudo apt-key add Release.key + wget https://dl.winehq.org/wine-builds/winehq.key + sudo apt-key add winehq.key + sudo apt-add-repository 'https://dl.winehq.org/wine-builds/ubuntu/' + sudo apt-get -qq update + sudo apt-get install -y wine-staging + echo "/opt/wine-staging/bin" >> $GITHUB_PATH + - name: Install Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Update Python infrastructure + run: | + python -m pip install --upgrade pip + pip install -U setuptools + - name: Install wenv package + run: | + make install + - name: Pre-check + run: | + wine --version + wine64 --version + python --version + pytest --version + uname -a + lsb_release -a + - name: Build docs and run tests + run: | + make docs + make test From 9b565d3a3f6fab4ed6316335ccb864cb159a8c13 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sun, 21 Nov 2021 13:14:50 +0100 Subject: [PATCH 075/131] fix download encoding issue --- src/wenv/_core/source.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/wenv/_core/source.py b/src/wenv/_core/source.py index c0ee2a8..747c012 100644 --- a/src/wenv/_core/source.py +++ b/src/wenv/_core/source.py @@ -47,12 +47,9 @@ def download(down_url, mode="binary"): assert r.ok r.raise_for_status() - if r.encoding is not None: - assert mode == "text" and isinstance(r.text, str) + if mode == 'text': return r.text - else: - assert mode == "binary" and isinstance(r.content, bytes) - return r.content + return r.content # mode == 'binary' def get_latest_maintenance_release( From f25896c7c85af6f9224374565daafe1c09ba499e Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sun, 21 Nov 2021 13:15:36 +0100 Subject: [PATCH 076/131] break test section down --- .github/workflows/test.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index ce68262..8e25d5b 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -44,7 +44,9 @@ jobs: pytest --version uname -a lsb_release -a - - name: Build docs and run tests + - name: Build docs run: | make docs + - name: Run tests + run: | make test From aa290aeb4829a5dbc5f3efe8c5a4f19b9c6d01b9 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sun, 21 Nov 2021 13:40:12 +0100 Subject: [PATCH 077/131] removed travis testing --- .travis.yml | 82 ----------------------------------------------------- 1 file changed, 82 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 52145d7..0000000 --- a/.travis.yml +++ /dev/null @@ -1,82 +0,0 @@ -# WENV -# Running Python on Wine -# https://github.com/pleiszenburg/wenv -# -# .travis.yml: Configuration for Travis CI build test -# -# Copyright (C) 2017-2022 Sebastian M. Ernst -# -# -# The contents of this file are subject to the GNU Lesser General Public License -# Version 2.1 ("LGPL" or "License"). You may not use this file except in -# compliance with the License. You may obtain a copy of the License at -# https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt -# https://github.com/pleiszenburg/wenv/blob/master/LICENSE -# -# Software distributed under the License is distributed on an "AS IS" basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the -# specific language governing rights and limitations under the License. -# - - -# Check this file at: -# http://lint.travis-ci.org/ - -# A virtual machine is required to run wine - is it? -sudo: enabled - -# Repository language -language: python - -# Python 3.4 is availabe for trusty and xenial, Python >= 3.5 xenial only -matrix: - include: - - dist: trusty - python: "3.4" - - dist: xenial - python: "3.5" - - dist: xenial - python: "3.6" - - dist: xenial - python: "3.7" - - dist: xenial - python: "3.8" - -# Get wine -# http://ubuntuhandbook.org/index.php/2017/01/install-wine-2-0-ubuntu-16-04-14-04-16-10/ -# https://launchpad.net/~wine/+archive/ubuntu/wine-builds?field.series_filter=trusty -# https://dl.winehq.org/wine-builds/ubuntu/ -before_install: - - sudo dpkg --add-architecture i386 - - wget https://dl.winehq.org/wine-builds/Release.key - - sudo apt-key add Release.key - - wget https://dl.winehq.org/wine-builds/winehq.key - - sudo apt-key add winehq.key - - sudo apt-add-repository 'https://dl.winehq.org/wine-builds/ubuntu/' - - sudo apt-get -qq update - - sudo apt-get install -y wine-staging - # - export PATH=/opt/wine-staging/bin:$PATH - - pip install -U pip - - pip install -U setuptools - - pip install -U pytest - - PATH=/opt/wine-staging/bin:$PATH wine --version - - PATH=/opt/wine-staging/bin:$PATH wine64 --version - - python --version - - pytest --version - - uname -a - - lsb_release -a - -# command to install dependencies and module -install: - - make install - -# command to run tests -script: WENV_WINEINSTALLPREFIX=/opt/wine-staging make test - -# Notify developers -notifications: - email: - recipients: - - ernst@pleiszenburg.de - on_success: always - on_failure: always From 47d5fb8db96f53377c84e0311d965c77abc8f75b Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sun, 21 Nov 2021 13:49:44 +0100 Subject: [PATCH 078/131] rename test suite --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 8e25d5b..ceb80a9 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -1,4 +1,4 @@ -name: zugbruecke test suite +name: wenv test suite on: [push] From 7f808a89d037b9697656faa36075dab4834dbb40 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Sun, 21 Nov 2021 13:49:54 +0100 Subject: [PATCH 079/131] switch badges to gha --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 03c750b..412853e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -[![build_master](https://img.shields.io/travis/pleiszenburg/wenv/master.svg?style=flat-square "Build Status: master / release")](https://travis-ci.org/pleiszenburg/wenv) +[![build_master](https://github.com/pleiszenburg/wenv/actions/workflows/test.yaml/badge.svg?branch=master "Build Status: master / release")](https://travis-ci.org/pleiszenburg/wenv) [![docs_master](https://readthedocs.org/projects/wenv/badge/?version=latest&style=flat-square "Documentation Status: master / release")](https://wenv.readthedocs.io/en/latest/) -[![build_develop](https://img.shields.io/travis/pleiszenburg/wenv/develop.svg?style=flat-square "Build Status: development branch")](https://travis-ci.org/pleiszenburg/wenv) +[![build_develop](https://github.com/pleiszenburg/wenv/actions/workflows/test.yaml/badge.svg?branch=develop "Build Status: development branch")](https://travis-ci.org/pleiszenburg/wenv) [![docs_develop](https://readthedocs.org/projects/wenv/badge/?version=develop&style=flat-square "Documentation Status: development branch")](https://wenv.readthedocs.io/en/develop/) [![license](https://img.shields.io/pypi/l/wenv.svg?style=flat-square "GNU Lesser General Public License v2.1")](https://github.com/pleiszenburg/wenv/blob/master/LICENSE) [![status](https://img.shields.io/pypi/status/wenv.svg?style=flat-square "Project Development Status")](https://github.com/pleiszenburg/wenv/issues) From a52f1e2d808610abcafdb3f89049592e4f30119d Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Tue, 23 Nov 2021 14:08:49 +0100 Subject: [PATCH 080/131] intro --- docs/env.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/env.rst b/docs/env.rst index 2722598..6716656 100644 --- a/docs/env.rst +++ b/docs/env.rst @@ -13,7 +13,7 @@ Environment =========== -foo bar +Wine Python environments can be created, managed and destroyed using this API. .. autoclass:: wenv.Env :members: From e63783ede3600f67002bad4a28a052c5a6fef999 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Tue, 23 Nov 2021 14:09:14 +0100 Subject: [PATCH 081/131] moved api documentation to Env class --- docs/api.rst | 72 ---------------------------------- src/wenv/_core/env.py | 91 +++++++++++++++++++++++++++++++++++++------ 2 files changed, 80 insertions(+), 83 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index e0a979d..907c7f4 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -20,75 +20,3 @@ API env envconfig pythonversion - - - - -Constructor: ``Env(**kwargs)`` ------------------------------- - -The constructor expects an arbitrary number of keyword arguments matching valid configuration options. - -In previous releases, the constructor expected one optional argument, ``parameter``. It should either be ``None`` or a dictionary. In the latter case, the dictionary may contain all valid configuration options. ``parameter`` can still be used but is deprecated. - -Method: ``ensure()`` --------------------- - -Equivalent of ``wenv init``. Intended to be used by 3rd-party packages which want to "ensure" that ``wenv`` has been initialized (i.e. *Python* and *pip* are present and working). ``ensure()`` calls ``wine_47766_workaround()`` internally. - -Method: ``wine_47766_workaround()`` ------------------------------------ - -Due to *Wine*'s bug #47766, *Wine* crashes if **any** folder in the path to ``pythonprefix`` is hidden Unix-style (i.e. prefixed with a dot / ``.``). This workaround creates a symlink directly pointing to ``pythonprefix`` into ``/tmp``, which is (more or less) guaranteed to be visible. It is then used instead of the actual ``pythonprefix``. - -Run ``setup_wineprefix`` and ``setup_pythonprefix`` **before** calling ``wine_47766_workaround``. Any subsequent action such as installing or using ``pip`` must happen **after** calling ``wine_47766_workaround``. - -Method: ``setup_wineprefix(overwrite = False)`` ------------------------------------------------ - -Part of the initialization process, but can be triggered on its own if required. It creates a *Wine* prefix according to *wenv*'s configuration. If ``overwrite`` is set to ``True``, a pre-existing *Wine* prefix is removed before a new one is created. - -Method: ``setup_pythonprefix(overwrite = False)`` -------------------------------------------------- - -Part of the initialization process, but can be triggered on its own if required. It installs the *CPython* interpreter into the *Python* prefix. If ``overwrite`` is set to ``True``, a pre-existing *Python* prefix is removed before a new one is created. - -Method: ``setup_pip()`` ------------------------ - -Part of the initialization process, but can be triggered on its own if required. It installs ``pip``, assuming that both the ``wineprefix`` and ``pythonprefix`` are already present. - -Method: ``install_package(name, update = False)`` -------------------------------------------------- - -Thin wrapper around ``wenv pip install [-U] {name}``. - -Method: ``uninstall_package(name)`` ------------------------------------ - -Thin wrapper around ``wenv pip uninstall -y {name}``. - -Method: ``list_packages()`` ---------------------------- - -Thin wrapper around ``wenv pip list --format json``. Returns a list of dictionaries. - -Method: ``shebang()`` ---------------------- - -Shebang entry point. - -Method: ``cli()`` ------------------ - -Command line interface entry point. - -Method: ``uninstall()`` ------------------------ - -Equivalent of ``wenv clear``. Removes the current Wine Python environment. - -Method: ``setup_coverage_activate()`` -------------------------------------- - -Equivalent of ``wenv init_coverage``. Activates coverage analysis throughout the Wine Python environment. diff --git a/src/wenv/_core/env.py b/src/wenv/_core/env.py index 3ef62e7..ebbe5d0 100644 --- a/src/wenv/_core/env.py +++ b/src/wenv/_core/env.py @@ -32,7 +32,7 @@ import shutil import subprocess import sys -from typing import Any, Generator +from typing import Any, Dict, Generator, List import zipfile from .config import EnvConfig @@ -48,6 +48,14 @@ @typechecked class Env: + """ + Represents one Wine Python environment. Mutable. + + The constructor expects + + args: + kwargs : An arbitrary number of keyword arguments matching valid configuration options. In previous releases, the constructor expected one optional argument, ``parameter``. It should either be ``None`` or a dictionary. In the latter case, the dictionary may contain all valid configuration options. ``parameter`` can still be used but is deprecated. + """ # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # INIT @@ -163,8 +171,11 @@ def _init_envvar_dict(self): def wine_47766_workaround(self): """ - PathAllocCanonicalize treats path segments start with dots wrong. - https://bugs.winehq.org/show_bug.cgi?id=47766 + Due to `Wine bug #47766`_ (in ``PathAllocCanonicalize``), *Wine* crashes if **any** folder in the path to ``pythonprefix`` is hidden Unix-style (i.e. prefixed with a dot / ``.``). This workaround creates a symlink directly pointing to ``pythonprefix`` into ``/tmp``, which is (more or less) guaranteed to be visible. It is then used instead of the actual ``pythonprefix``. + + Run ``setup_wineprefix`` and ``setup_pythonprefix`` **before** calling ``wine_47766_workaround``. Any subsequent action such as installing or using ``pip`` must happen **after** calling ``wine_47766_workaround``. + + .. _Wine bug #47766: https://bugs.winehq.org/show_bug.cgi?id=47766 """ is_clean = lambda path: not any( @@ -199,6 +210,9 @@ def wine_47766_workaround(self): self._init_dicts() def wine_47766_workaround_uninstall(self): + """ + Reverts the `Wine bug #47766`_ workaround, i.e. it removes the symlink. + """ self.wine_47766_workaround() @@ -210,6 +224,14 @@ def wine_47766_workaround_uninstall(self): # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ def ensure(self): + """ + Equivalent to ``wenv init``. Intended to be used by 3rd-party packages which want to "ensure" that ``wenv`` has been initialized (i.e. *Python* and *pip* are present and working). ``ensure()`` calls the following methods: + + - :meth:`wenv.Env.setup_wineprefix` + - :meth:`wenv.Env.setup_pythonprefix` + - :meth:`wenv.Env.wine_47766_workaround` + - :meth:`wenv.Env.setup_pip` + """ self.setup_wineprefix() self.setup_pythonprefix() @@ -221,6 +243,9 @@ def ensure(self): # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ def uninstall(self): + """ + Equivalent to ``wenv clean``. It removes the current Wine Python environment, i.e. Python interpreter, pip, setuptools, wheel and all installed packages. + """ # Does Wine prefix exist? if os.path.exists(self._p["wineprefix"]): @@ -239,6 +264,9 @@ def uninstall(self): # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ def cache(self): + """ + Equivalent to ``wenv cache``. It fetches installation files and caches them for offline usage, including the Python interpreter, pip, setuptools and wheel. + """ version = PythonVersion.from_config(self._p["arch"], self._p["pythonversion"]) @@ -253,6 +281,12 @@ def cache(self): self.cache_package(package) def cache_package(self, name: str): + """ + Caches a specific package by nameself. + + Args: + name : Name of PyPI package + """ os.makedirs(self._p["packages"], exist_ok=True) @@ -295,6 +329,12 @@ def _get_pip(self, offline: bool = False) -> bytes: # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ def setup_wineprefix(self, overwrite: bool = False): + """ + Part of the initialization process, but can be triggered on its own if required. It creates a *Wine* prefix according to *wenv*'s configuration. + + Args: + overwrite : If set to ``True``, a pre-existing *Wine* prefix is removed before a new one is created. + """ if not isinstance(overwrite, bool): raise TypeError("overwrite is not a boolean") @@ -320,6 +360,12 @@ def setup_wineprefix(self, overwrite: bool = False): sys.exit(1) def setup_pythonprefix(self, overwrite: bool = False): + """ + Part of the initialization process, but can be triggered on its own if required. It installs the *CPython* interpreter into the *Python* prefix. + + Args: + overwrite : If set to ``True``, a pre-existing *Python* prefix is removed before a new one is created. + """ if not isinstance(overwrite, bool): raise TypeError("overwrite is not a boolean") @@ -370,6 +416,9 @@ def setup_pythonprefix(self, overwrite: bool = False): os.makedirs(self._path_dict["sitepackages"]) def setup_pip(self): + """ + Part of the initialization process, but can be triggered on its own if required. It installs ``pip``, assuming that both the ``wineprefix`` and ``pythonprefix`` are already present. + """ # Exit if it exists if os.path.isfile(self._path_dict["pip"]): @@ -398,6 +447,9 @@ def setup_pip(self): proc.communicate(input=getpip) def setup_coverage_activate(self): + """ + Equivalent to ``wenv init_coverage``. It enables coverage analysis inside wenv. + """ # Ensure that coverage is started with the Python interpreter siteconfig_cnt = "" @@ -415,7 +467,11 @@ def setup_coverage_activate(self): def install_package(self, name: str, update: bool = False): """ - Thin wrapper for `wenv pip install` + Thin wrapper for ``wenv pip install {-U} {name}``. Installs and/or updates a package. + + Args: + name : Name of PyPI package + update : Update flag """ if not isinstance(name, str): @@ -442,7 +498,10 @@ def install_package(self, name: str, update: bool = False): def uninstall_package(self, name: str): """ - Thin wrapper for `wenv pip uninstall -y` + Thin wrapper for ``wenv pip uninstall -y {name}``. Removes a package. + + Args: + name : Name of PyPI package """ if not isinstance(name, str): @@ -463,9 +522,12 @@ def uninstall_package(self, name: str): if proc.returncode != 0: raise SystemError('uninstalling package "%s" failed' % name, outs, errs) - def list_packages(self): + def list_packages(self) -> List[Dict[str, str]]: """ - Thin wrapper for `wenv pip list --format json` + Thin wrapper for ``wenv pip list --format json``. + + Returns: + A list of dictionaries of format ``{"name": "Name of PyPI package ", "version": "package version"}``. """ envvar_dict = {k: os.environ[k] for k in os.environ.keys()} @@ -547,6 +609,9 @@ def colorize(text): sys.stdout.flush() def cli(self): + """ + Command line interface entry point. Equivalent to ``wenv [...]``. Looks for sub-commands and parameters in ``sys.argv``. + """ # No command passed if len(sys.argv) < 2: @@ -586,10 +651,14 @@ def cli(self): ) def shebang(self): - """Working around a lack of Unix specification ... - https://stackoverflow.com/q/4303128/1672565 - https://unix.stackexchange.com/q/63979/28301 - https://lists.gnu.org/archive/html/bug-sh-utils/2002-04/msg00020.html + """ + shebang entry point for Wine Python interpreter. Equivalent to ``_wenv_python [...]``. Does not look at ``sys.argv``. It only passes ``sys.argv[1]`` on to the Wine Python interpreter. + + This interface is working around a lack of Unix specification, see: + + - https://stackoverflow.com/q/4303128/1672565 + - https://unix.stackexchange.com/q/63979/28301 + - https://lists.gnu.org/archive/html/bug-sh-utils/2002-04/msg00020.html """ if len(sys.argv) < 2: From bf7bfd825a71357640fd8e8d53901c6298841720 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Tue, 23 Nov 2021 14:37:30 +0100 Subject: [PATCH 082/131] make private methods show up --- docs/conf.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/conf.py b/docs/conf.py index 6bb943f..e755a4d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -217,3 +217,10 @@ def fetch_version_string(): "Miscellaneous", ), ] + + +always_document_param_types = True # sphinx_autodoc_typehints + +napoleon_include_special_with_doc = True # napoleon +# napoleon_use_param = True +# napoleon_type_aliases = True From 08b8f4761bd21712a1f7fc9a98c73aedb549d52d Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Tue, 23 Nov 2021 14:37:47 +0100 Subject: [PATCH 083/131] annotated and documented --- src/wenv/_core/config.py | 77 +++++++++++++++++++++++++++++----------- 1 file changed, 56 insertions(+), 21 deletions(-) diff --git a/src/wenv/_core/config.py b/src/wenv/_core/config.py index e0b4cc6..a473648 100644 --- a/src/wenv/_core/config.py +++ b/src/wenv/_core/config.py @@ -30,21 +30,46 @@ import json import site import sys +from typing import Any, Dict, Generator, Optional from .const import CONFIG_FN from .errors import EnvConfigParserError +from .typeguard import typechecked # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # CONFIGURATION CLASS # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +@typechecked class EnvConfig(dict): """ - Wine Python environment configuration + Wine Python environment configuration. Subclass of ``dict``. + It holds default values and overwrites them with values found in configuration files and environment variables. + Usually, one should not work with this class directly - unless an access to current configuration parameters is desired. + + Args: + override : Specify custom values for configuration parameters via keyword arguments. """ - def __init__(self, **override_dict): + _KEYS = ( + "arch", + "pythonversion", + "wine_bin_win32" + "wine_bin_win64", + "wine_bin_arm64", + "winedebug", + "wineinstallprefix", + "prefix", + "wineprefix", + "pythonprefix", + "offline", + "cache", + "packages", + "_issues_50_workaround", + ) + + def __init__(self, **override: Any): # Call parent constructur, just in case super().__init__() @@ -54,10 +79,22 @@ def __init__(self, **override_dict): self.update(config) # Add override parameters - if len(override_dict) > 0: - self.update(override_dict) + if len(override) > 0: + self.update(override) + + def __getitem__(self, key: str) -> Any: + """ + Returns values from the following sources in the following order: + + - Environment variables + - Internal storage, i.e. changed in the dictionary or read from configuration files. + - Default values. - def __getitem__(self, key): + Args: + key : Name of configuration value. + Returns: + Arbitrary configuration value. + """ env_var = "WENV_{NAME:s}".format(NAME=key.upper()) if env_var in os.environ.keys(): @@ -110,26 +147,24 @@ def __getitem__(self, key): raise KeyError("not a valid configuration key", key) - def export_envvar_dict(self): + def export_dict(self) -> Dict[str, str]: + """ + Exports a dictionary. + """ + + return {self[field] for field in self._KEYS} + + def export_envvar_dict(self) -> Dict[str, str]: + """ + Exports a dictionary which can passed to the OS as a set of environment variables for ``wenv`` itself. + """ return { "WENV_" + field.upper(): "" if field is None else str(self[field]) - for field in ( - "arch", - "pythonversion", - "winedebug", - "wineinstallprefix", - "prefix", - "wineprefix", - "pythonprefix", - "offline", - "cache", - "packages", - "_issues_50_workaround", - ) + for field in self._KEYS } - def _get_config_from_files(self): + def _get_config_from_files(self) -> Generator: # Look for config in the usual spots for fn in [ @@ -149,7 +184,7 @@ def _get_config_from_files(self): if cnt_dict is not None: yield cnt_dict - def _load_config_from_file(self, try_path): + def _load_config_from_file(self, try_path: Optional[str] = None) -> Dict[str, Any]: # If there is a path ... if try_path is None: From 16940e556c8ca8b4993e7f1274e47b7c34c9e423 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Tue, 23 Nov 2021 14:40:41 +0100 Subject: [PATCH 084/131] paths annotated --- src/wenv/_core/paths.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/wenv/_core/paths.py b/src/wenv/_core/paths.py index e9d68d5..4060b50 100644 --- a/src/wenv/_core/paths.py +++ b/src/wenv/_core/paths.py @@ -29,25 +29,27 @@ import os from .pythonversion import PythonVersion +from .typeguard import typechecked # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # PATHS # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +@typechecked class Paths: """ Wine Python environment paths """ - def __init__(self, pythonprefix, arch, pythonversion): + def __init__(self, pythonprefix: str, arch: str, pythonversion: str): self._pythonprefix = pythonprefix self._pythonversion_block = PythonVersion.from_config( arch, pythonversion ).as_block() - def __getitem__(self, key): + def __getitem__(self, key: str) -> str: if key == "pythonprefix": return self._pythonprefix @@ -75,7 +77,7 @@ def __getitem__(self, key): raise KeyError("not a valid path key") @staticmethod - def symlink(src, dest): + def symlink(src, dest: str): """ Generates a symlink and checks result """ From 30539ef4b89e59482d42bc3a8b563d883b39ee45 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Tue, 23 Nov 2021 15:01:06 +0100 Subject: [PATCH 085/131] annomated --- src/wenv/_core/pythonversion.py | 119 ++++++++++++++++++++++++++------ 1 file changed, 96 insertions(+), 23 deletions(-) diff --git a/src/wenv/_core/pythonversion.py b/src/wenv/_core/pythonversion.py index a7b1d43..db3e4d9 100644 --- a/src/wenv/_core/pythonversion.py +++ b/src/wenv/_core/pythonversion.py @@ -22,34 +22,52 @@ """ +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# IMPORT +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +from typing import Any, Union + +try: + from typing import NotImplementedType # re-introduced in Python 3.10 +except ImportError: + NotImplementedType = type(NotImplemented) + +from .typeguard import typechecked + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # CLASS # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +@typechecked class PythonVersion: """ - Parse and handle Python versions + Parse and handle Python versions. Immutable. + + Args: + arch : Build architecture. Can be ``win32``, ``win64`` or ``arm64``. + major : Python major version, i.e. ``X`` from ``X.0.0``. + minor : Python minor version, i.e. ``X`` from ``0.X.0``. + maintenance : Python maintenance version, i.e. ``X`` from ``0.0.X``. + build : Type of build, e.g. ``alpha``, ``beta``, ``rcX``, etc. If left empty or set to ``stable``, the build is considered stable. """ - def __init__(self, arch, major, minor, maintenance, build="stable"): + def __init__(self, arch: str, major: int, minor: int, maintenance: int, build: str = "stable"): - if not isinstance(arch, str): - raise TypeError("arch must be str") if arch not in ("win32", "win64", "arm64"): raise ValueError("Unknown arch: " + arch) - if any((not isinstance(item, int) for item in (major, minor, maintenance))): - raise TypeError("Unknown type for major, minor or maintenance") if major <= 2: raise ValueError("Only Python 3 and newer supported") - if not isinstance(build, str): - raise TypeError("build must be str") self._arch = arch self._major, self._minor, self._maintenance = major, minor, maintenance self._build = "stable" if build == "" else build - def __str__(self): + def __str__(self) -> str: + """ + Converts version to string. + """ return "%d.%d.%d.%s" % ( self._major, @@ -58,7 +76,10 @@ def __str__(self): self._build, ) - def __repr__(self): + def __repr__(self) -> str: + """ + Converts version to interactive string representation. + """ return "" % ( self._major, @@ -68,16 +89,38 @@ def __repr__(self): self._arch, ) - def __eq__(self, other): + def __eq__(self, other: Any) -> Union[bool, NotImplementedType]: + """ + Equality operator. + """ + + if not isinstance(other, type(self)): + return NotImplemented + return self._as_sort() == other._as_sort() - def __gt__(self, other): + def __gt__(self, other: Any) -> Union[bool, NotImplementedType]: + """ + Greater than operator. + """ + + if not isinstance(other, type(self)): + return NotImplemented + return self._as_sort() > other._as_sort() - def __lt__(self, other): + def __lt__(self, other: Any) -> Union[bool, NotImplementedType]: + """ + Lesser than operator. + """ + + if not isinstance(other, type(self)): + return NotImplemented + return self._as_sort() < other._as_sort() - def _as_sort(self): + def _as_sort(self) -> str: + return "%04d-%04d-%04d-%s" % ( self._major, self._minor, @@ -86,19 +129,34 @@ def _as_sort(self): ) @property - def arch(self): + def arch(self) -> str: + """ + Build architecture. + """ return self._arch - def as_block(self): + def as_block(self) -> str: + """ + Returns: + "Block", i.e. combination of major and minor version as string. + """ return "%d%d" % (self._major, self._minor) - def as_config(self): + def as_config(self) -> str: + """ + Returns: + String for use in configuration. + """ return str(self) - def as_url(self): + def as_url(self) -> str: + """ + Returns: + Download URL for Windows Embedded Build ZIP file. + """ return ( "https://www.python.org/ftp/python/%d.%d.%d/" @@ -106,7 +164,11 @@ def as_url(self): + self.as_zipname() ) - def as_zipname(self): + def as_zipname(self) -> str: + """ + Returns: + Name of Windows Embedded Build ZIP file. + """ build = "" if self._build == "stable" else self._build arch = "amd64" if self._arch == "win64" else self._arch @@ -118,10 +180,15 @@ def as_zipname(self): return "python-%d.%d.%d%s-embed-%s.zip" % sub_tuple @classmethod - def from_config(cls, arch, version): + def from_config(cls, arch: str, version: str): + """ + Parses version from configuration value. + + Args: + arch : Build architecture. + version : Full version string, including type of build. + """ - if not isinstance(version, str): - raise TypeError("version must be str") segments = version.split(".") if not len(segments) in (3, 4): raise ValueError("wrong number of version segments") @@ -134,7 +201,13 @@ def from_config(cls, arch, version): return cls(arch, *segments) @classmethod - def from_zipname(cls, zip_name): + def from_zipname(cls, zip_name: str): + """ + Parse version from Windows Embedded Build ZIP file name. + + Args: + zip_name : Name of file + """ if not isinstance(zip_name, str): raise TypeError("zip_name must be str") From 36c851ffb22a8e1f9bfce7c69f09b19821dd678e Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Tue, 23 Nov 2021 15:02:15 +0100 Subject: [PATCH 086/131] text --- docs/pythonversion.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/pythonversion.rst b/docs/pythonversion.rst index 97cf928..70a57de 100644 --- a/docs/pythonversion.rst +++ b/docs/pythonversion.rst @@ -9,7 +9,7 @@ Python Version Processing ========================= -foo bar +``wenv`` allows to handle Python versions for configuration purposes. .. autoclass:: wenv.PythonVersion :members: From f0f008b9ac14ddb44453f02ff2d6308ff937ff06 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Tue, 23 Nov 2021 15:02:24 +0100 Subject: [PATCH 087/131] better doc string --- src/wenv/_core/pythonversion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wenv/_core/pythonversion.py b/src/wenv/_core/pythonversion.py index db3e4d9..6187320 100644 --- a/src/wenv/_core/pythonversion.py +++ b/src/wenv/_core/pythonversion.py @@ -43,7 +43,7 @@ @typechecked class PythonVersion: """ - Parse and handle Python versions. Immutable. + Parse, compare and export Python versions. Immutable. Args: arch : Build architecture. Can be ``win32``, ``win64`` or ``arm64``. From 15532b8ac8f421272ca98411c66f7acbce535d69 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Tue, 23 Nov 2021 15:06:14 +0100 Subject: [PATCH 088/131] expose everything --- src/wenv/_core/pythonversion.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/wenv/_core/pythonversion.py b/src/wenv/_core/pythonversion.py index 6187320..11225a2 100644 --- a/src/wenv/_core/pythonversion.py +++ b/src/wenv/_core/pythonversion.py @@ -136,6 +136,38 @@ def arch(self) -> str: return self._arch + @property + def major(self) -> int: + """ + Python major version. + """ + + return self._major + + @property + def minor(self) -> int: + """ + Python minor version. + """ + + return self._minor + + @property + def maintenance(self) -> int: + """ + Python maintenance version. + """ + + return self._maintenance + + @property + def build(self) -> str: + """ + Type of build. + """ + + return self._build + def as_block(self) -> str: """ Returns: From ad07ddb029ec0cc30c898e781e4c0f7eafb6b359 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Tue, 23 Nov 2021 15:15:17 +0100 Subject: [PATCH 089/131] annotations, 1 --- src/wenv/_core/source.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/wenv/_core/source.py b/src/wenv/_core/source.py index 747c012..1fb54b6 100644 --- a/src/wenv/_core/source.py +++ b/src/wenv/_core/source.py @@ -27,17 +27,20 @@ # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ from multiprocessing.pool import ThreadPool +from typing import Union import requests from .pythonversion import PythonVersion +from .typeguard import typechecked # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # ROUTINES # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -def download(down_url, mode="binary"): +@typechecked +def download(down_url: str, mode: str = "binary") -> Union[str, bytes]: assert mode in ("text", "binary") assert isinstance(down_url, str) From 0405ff56c62196d44faa185013e5cca19684a0b3 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Tue, 23 Nov 2021 15:45:39 +0100 Subject: [PATCH 090/131] build queries --- docs/pythonversion.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/pythonversion.rst b/docs/pythonversion.rst index 70a57de..5c3c274 100644 --- a/docs/pythonversion.rst +++ b/docs/pythonversion.rst @@ -11,5 +11,9 @@ Python Version Processing ``wenv`` allows to handle Python versions for configuration purposes. +.. autofunction:: wenv.get_available_python_builds + +.. autofunction:: wenv.get_latest_python_build + .. autoclass:: wenv.PythonVersion :members: From f56d2b83a3127f264e0e5c854c617ec43043731b Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Tue, 23 Nov 2021 15:46:00 +0100 Subject: [PATCH 091/131] cleanup and annotations --- src/wenv/__init__.py | 4 +- src/wenv/_core/source.py | 127 +++++++++++++++++++++------------------ 2 files changed, 71 insertions(+), 60 deletions(-) diff --git a/src/wenv/__init__.py b/src/wenv/__init__.py index 7497660..fe20b4c 100644 --- a/src/wenv/__init__.py +++ b/src/wenv/__init__.py @@ -41,8 +41,8 @@ PythonVersion, ) from ._core.source import ( - get_available_python_versions, - get_latest_maintenance_release, + get_available_python_builds, + get_latest_python_build, ) env = Env # legacy diff --git a/src/wenv/_core/source.py b/src/wenv/_core/source.py index 1fb54b6..ec9ee51 100644 --- a/src/wenv/_core/source.py +++ b/src/wenv/_core/source.py @@ -27,7 +27,7 @@ # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ from multiprocessing.pool import ThreadPool -from typing import Union +from typing import List, Optional, Union import requests @@ -55,23 +55,56 @@ def download(down_url: str, mode: str = "binary") -> Union[str, bytes]: return r.content # mode == 'binary' -def get_latest_maintenance_release( - arch: str, major: int, minor: int, sorted_versions=None -): +@typechecked +def get_latest_python_build( + arch: str, + major: int, + minor: int, + builds: Optional[List[PythonVersion]] = None, +) -> Optional[PythonVersion]: + """ + Find the latest build of a given Python major and minor version for a given architecture. + Returns ``None`` if none can be found. + + Args: + arch : Build architecture. + major : Python major version. + minor : Python minor version. + builds : A list of :class:`wenv.PythonVersion` objects. If left empty, ``python.org`` will be queried. + Returns: + A :class:`wenv.PythonVersion` object or ``None``. + """ + + if builds is None: + builds = get_available_python_builds() + + filtered_build = [ + build + for build in builds + if all(( + build.arch == arch, + build.major == major, + minor.minor == minor, + )) + ] - if sorted_versions is None: - sorted_versions = get_available_python_versions() + if len(filtered_build) == 0: + return None - arch_versions = sorted_versions[arch] - maintenance_versions = { - k[2]: v # maintenance - for k, v in arch_versions.items() - if k[0] == major and k[1] == minor - } - return maintenance_versions[max(maintenance_versions.keys())][-1] + filtered_build.sort() + return filtered_build[-1] -def get_available_python_versions(): +@typechecked +def get_available_python_builds(parallel: int = 8) -> List[PythonVersion]: + """ + Queries ``python.org`` for Windows Embedded Builds. + + Args: + parallel : Number of parallel queries to ``python.org``. + Returns: + All available Windows Embedded Builds of CPython 3. + """ versions = [ tuple(int(nr) for nr in line.split('"')[1][:-1].split(".")) @@ -79,60 +112,38 @@ def get_available_python_versions(): "\n" ) if all( - [ + ( line.startswith(' 0} - - sorted_versions = { - "win32": { - version_tuple: sorted( - [version for version in versions if version.arch == "win32"] - ) - for version_tuple, versions in embedded_versions.items() - }, - "win64": { - version_tuple: sorted( - [version for version in versions if version.arch == "win64"] - ) - for version_tuple, versions in embedded_versions.items() - }, - "arm64": { - version_tuple: sorted( - [version for version in versions if version.arch == "arm64"] + for line in version_download.split("\n") + if all( + ( + line.startswith(' Date: Tue, 23 Nov 2021 15:51:33 +0100 Subject: [PATCH 092/131] headline --- docs/pythonversion.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/pythonversion.rst b/docs/pythonversion.rst index 5c3c274..5056f48 100644 --- a/docs/pythonversion.rst +++ b/docs/pythonversion.rst @@ -6,8 +6,8 @@ single: Python version module: wenv.PythonVersion -Python Version Processing -========================= +Python Versions +=============== ``wenv`` allows to handle Python versions for configuration purposes. From 2c79301972ae3e53cd26a1158fe33a3fdad9c50a Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Tue, 23 Nov 2021 17:44:46 +0100 Subject: [PATCH 093/131] fix annotation --- src/wenv/_core/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wenv/_core/config.py b/src/wenv/_core/config.py index a473648..238ae7e 100644 --- a/src/wenv/_core/config.py +++ b/src/wenv/_core/config.py @@ -184,7 +184,7 @@ def _get_config_from_files(self) -> Generator: if cnt_dict is not None: yield cnt_dict - def _load_config_from_file(self, try_path: Optional[str] = None) -> Dict[str, Any]: + def _load_config_from_file(self, try_path: Optional[str] = None) -> Optional[Dict[str, Any]]: # If there is a path ... if try_path is None: From 7f02f8a29d1612c8adc2055b28ed1146813e40cb Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Tue, 23 Nov 2021 17:45:42 +0100 Subject: [PATCH 094/131] adding type checks to tests --- makefile | 2 +- tests/lib/__init__.py | 1 + tests/lib/output.py | 59 +++++++++++++++++++++++++++++++++++++++++++ tests/test_init.py | 16 ++++++------ tests/test_pip.py | 12 ++++----- tests/test_python.py | 6 ++--- 6 files changed, 78 insertions(+), 18 deletions(-) create mode 100644 tests/lib/output.py diff --git a/makefile b/makefile index 90b0dff..312413b 100644 --- a/makefile +++ b/makefile @@ -67,7 +67,7 @@ release: test: make clean - pytest + WENV_DEBUG=1 pytest upload: for filename in $$(ls dist/*.tar.gz dist/*.whl) ; do \ diff --git a/tests/lib/__init__.py b/tests/lib/__init__.py index c39dad5..13aea89 100644 --- a/tests/lib/__init__.py +++ b/tests/lib/__init__.py @@ -27,3 +27,4 @@ # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ from .param import get_context, run_process +from .output import no_errors_in diff --git a/tests/lib/output.py b/tests/lib/output.py new file mode 100644 index 0000000..9d99ce2 --- /dev/null +++ b/tests/lib/output.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- + +""" +WENV +Running Python on Wine +https://github.com/pleiszenburg/wenv + + tests/lib/output.py: Looking at output + + Copyright (C) 2017-2022 Sebastian M. Ernst + + +The contents of this file are subject to the GNU Lesser General Public License +Version 2.1 ("LGPL" or "License"). You may not use this file except in +compliance with the License. You may obtain a copy of the License at +https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt +https://github.com/pleiszenburg/wenv/blob/master/LICENSE + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +""" + +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# IMPORT +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +from .param import get_context, run_process + + +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# ROUTINES +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +def no_errors_in(output: str) -> bool: + + lines = output.split('\n') + + for line in lines: + + line = line.strip() + + if len(line) == 0: + continue + + if all( + fragment in line.lower() + for fragment in ( + 'debug mode', + 'run-time type checks', + 'runtimewarning', + ) + ): + continue + + return False + + return True diff --git a/tests/test_init.py b/tests/test_init.py index f445fac..fa50f67 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -26,7 +26,7 @@ # IMPORT # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -from .lib import get_context, run_process +from .lib import get_context, run_process, no_errors_in import pytest @@ -41,12 +41,12 @@ def test_init(arch): out, err, code = run_process(["wenv", "clean"], env={"WENV_ARCH": arch}) assert code == 0 - assert len(err.strip()) == 0 + assert no_errors_in(err) out, err, code = run_process(["wenv", "help"], env={"WENV_ARCH": arch}) assert code == 0 - assert len(err.strip()) == 0 + assert no_errors_in(err) assert "wenv pip" not in out # assert 'wenv python' not in out # TODO @@ -58,7 +58,7 @@ def test_init(arch): out, err, code = run_process(["wenv", "help"], env={"WENV_ARCH": arch}) assert code == 0 - assert len(err.strip()) == 0 + assert no_errors_in(err) assert "wenv pip" in out # assert 'wenv python' in out # TODO @@ -69,19 +69,19 @@ def test_init_offline(arch): out, err, code = run_process(["wenv", "clean"], env={"WENV_ARCH": arch}) assert code == 0 - assert len(err.strip()) == 0 + assert no_errors_in(err) out, err, code = run_process(["wenv", "help"], env={"WENV_ARCH": arch}) assert code == 0 - assert len(err.strip()) == 0 + assert no_errors_in(err) assert "wenv pip" not in out # assert 'wenv python' not in out # TODO out, err, code = run_process(["wenv", "cache"], env={"WENV_ARCH": arch}) assert code == 0 - assert len(err.strip()) == 0 + assert no_errors_in(err) assert "wenv pip" not in out out, err, code = run_process( @@ -94,6 +94,6 @@ def test_init_offline(arch): out, err, code = run_process(["wenv", "help"], env={"WENV_ARCH": arch}) assert code == 0 - assert len(err.strip()) == 0 + assert no_errors_in(err) assert "wenv pip" in out # assert 'wenv python' in out # TODO diff --git a/tests/test_pip.py b/tests/test_pip.py index 4b23f26..c60d7fc 100644 --- a/tests/test_pip.py +++ b/tests/test_pip.py @@ -26,7 +26,7 @@ # IMPORT # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -from .lib import get_context, run_process +from .lib import get_context, run_process, no_errors_in from wenv import Env @@ -42,7 +42,7 @@ def test_pip(arch): out, err, code = run_process(["wenv", "pip", "list"], env={"WENV_ARCH": arch}) assert code == 0 - assert len(err.strip()) == 0 + assert no_errors_in(err) assert "pip" in out assert "setuptools" in out assert "pytest" not in out @@ -55,7 +55,7 @@ def test_pip(arch): out, err, code = run_process(["wenv", "pip", "list"], env={"WENV_ARCH": arch}) assert code == 0 - assert len(err.strip()) == 0 + assert no_errors_in(err) assert "pip" in out assert "setuptools" in out assert "pytest" in out @@ -68,7 +68,7 @@ def test_pip_api(arch): out, err, code = run_process(["wenv", "pip", "list"], env={"WENV_ARCH": arch}) assert code == 0 - assert len(err.strip()) == 0 + assert no_errors_in(err) assert "pip" in out assert "setuptools" in out assert "requests" not in out @@ -82,7 +82,7 @@ def test_pip_api(arch): out, err, code = run_process(["wenv", "pip", "list"], env={"WENV_ARCH": arch}) assert code == 0 - assert len(err.strip()) == 0 + assert no_errors_in(err) assert "pip" in out assert "setuptools" in out assert "requests" in out @@ -96,7 +96,7 @@ def test_pip_api(arch): out, err, code = run_process(["wenv", "pip", "list"], env={"WENV_ARCH": arch}) assert code == 0 - assert len(err.strip()) == 0 + assert no_errors_in(err) assert "pip" in out assert "setuptools" in out assert "requests" not in out diff --git a/tests/test_python.py b/tests/test_python.py index 1a22d00..9d9c33b 100644 --- a/tests/test_python.py +++ b/tests/test_python.py @@ -26,7 +26,7 @@ # IMPORT # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -from .lib import get_context, run_process +from .lib import get_context, run_process, no_errors_in import pytest @@ -43,7 +43,7 @@ def test_python(arch): ) assert code == 0 - assert len(err.strip()) == 0 + assert no_errors_in(err) assert "Windows" in out out, err, code = run_process( @@ -51,6 +51,6 @@ def test_python(arch): env={"WENV_ARCH": arch}, ) assert code == 0 - assert len(err.strip()) == 0 + assert no_errors_in(err) out = out.strip() assert (arch == "win32" and out == "x86") ^ (arch == "win64" and out == "AMD64") From 0ac786374266255baead5761c2075f93c9f665ef Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Tue, 23 Nov 2021 17:52:13 +0100 Subject: [PATCH 095/131] fix assert --- tests/test_pip.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_pip.py b/tests/test_pip.py index c60d7fc..d6186d1 100644 --- a/tests/test_pip.py +++ b/tests/test_pip.py @@ -51,7 +51,7 @@ def test_pip(arch): ["wenv", "pip", "install", "pytest"], env={"WENV_ARCH": arch} ) assert code == 0 - assert len(err.strip()) == 0 + assert no_errors_in(err) out, err, code = run_process(["wenv", "pip", "list"], env={"WENV_ARCH": arch}) assert code == 0 From e3b80ef5318450b1b34a8e2db1e3b13a4612d74d Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Tue, 23 Nov 2021 17:52:21 +0100 Subject: [PATCH 096/131] fix comma --- src/wenv/_core/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wenv/_core/config.py b/src/wenv/_core/config.py index 238ae7e..93972c9 100644 --- a/src/wenv/_core/config.py +++ b/src/wenv/_core/config.py @@ -55,7 +55,7 @@ class EnvConfig(dict): _KEYS = ( "arch", "pythonversion", - "wine_bin_win32" + "wine_bin_win32", "wine_bin_win64", "wine_bin_arm64", "winedebug", From e290fef616b2e6f2d74caaa0f06bd3d98cfebb97 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Tue, 23 Nov 2021 18:24:55 +0100 Subject: [PATCH 097/131] used futures as a more stable api --- src/wenv/_core/source.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/wenv/_core/source.py b/src/wenv/_core/source.py index ec9ee51..22bfc1e 100644 --- a/src/wenv/_core/source.py +++ b/src/wenv/_core/source.py @@ -26,7 +26,7 @@ # IMPORT # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -from multiprocessing.pool import ThreadPool +from concurrent.futures import ThreadPoolExecutor, as_completed from typing import List, Optional, Union import requests @@ -127,10 +127,16 @@ def get_available_python_builds(parallel: int = 8) -> List[PythonVersion]: for version in versions ] # some URLs in early Python 3.X releases are following `major.minor` pattern - with ThreadPool(parallel) as p: - version_downloads = p.imap( - lambda x: download(x, mode="text"), version_urls - ) # get inventory of all maintenance version downloads + with ThreadPoolExecutor(max_workers = parallel) as p: + version_futures = [ + p.submit( + lambda x: download(x, mode="text"), version_url + ) for version_url in version_urls + ] # get inventory of all maintenance version downloads + version_downloads = [ + future.result() + for future in as_completed(version_futures) + ] return [ PythonVersion.from_zipname(line.split('"')[1]) From 42eb4b4dba0802692ed848c84c8cbced07fe30e0 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Tue, 23 Nov 2021 18:36:04 +0100 Subject: [PATCH 098/131] name fix --- src/wenv/_core/source.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wenv/_core/source.py b/src/wenv/_core/source.py index 22bfc1e..909c76a 100644 --- a/src/wenv/_core/source.py +++ b/src/wenv/_core/source.py @@ -84,7 +84,7 @@ def get_latest_python_build( if all(( build.arch == arch, build.major == major, - minor.minor == minor, + build.minor == minor, )) ] From 8abb2e9f348bcebb3f39ff310031a6bf5abdce64 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Tue, 23 Nov 2021 18:36:19 +0100 Subject: [PATCH 099/131] testing build / version parser --- tests/test_versions.py | 69 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 tests/test_versions.py diff --git a/tests/test_versions.py b/tests/test_versions.py new file mode 100644 index 0000000..3da76b1 --- /dev/null +++ b/tests/test_versions.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- + +""" +WENV +Running Python on Wine +https://github.com/pleiszenburg/wenv + + tests/test_versions.py: Test version parser and queries + + Copyright (C) 2017-2022 Sebastian M. Ernst + + +The contents of this file are subject to the GNU Lesser General Public License +Version 2.1 ("LGPL" or "License"). You may not use this file except in +compliance with the License. You may obtain a copy of the License at +https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt +https://github.com/pleiszenburg/wenv/blob/master/LICENSE + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +""" + +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# IMPORT +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +from wenv import PythonVersion, get_available_python_builds, get_latest_python_build + +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# TEST(s) +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +def test_available_builds(): + + builds = get_available_python_builds() + + assert len(builds) > 0 + assert all(isinstance(build, PythonVersion) for build in builds) + + for arch in ('win32', 'win64', 'arm64'): + assert arch in (build.arch for build in builds) + + +def tests_latest_build(): + + a = get_latest_python_build('win64', 3, 5) + + assert a == PythonVersion('win64', 3, 5, 4) + + +def test_stable_versions(): + + a = PythonVersion('win64', 3, 9, 8, 'stable') + b = PythonVersion('win64', 3, 10, 1, 'stable') + + assert a != b + assert a < b + assert b > a + +def test_unstable_versions(): + + a = PythonVersion('win64', 3, 9, 0, 'alpha') + b = PythonVersion('win64', 3, 9, 0, 'rc1') + + assert a != b + assert a < b + assert b > a From ce25b00b58c7901c7680284fd80c756abe34ab67 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Tue, 23 Nov 2021 19:21:45 +0100 Subject: [PATCH 100/131] run entirely on pythonversion class --- src/wenv/_core/config.py | 39 ++++++++++++++++++++++++++------------- src/wenv/_core/env.py | 15 ++++----------- src/wenv/_core/paths.py | 6 ++---- 3 files changed, 32 insertions(+), 28 deletions(-) diff --git a/src/wenv/_core/config.py b/src/wenv/_core/config.py index 93972c9..14f36dd 100644 --- a/src/wenv/_core/config.py +++ b/src/wenv/_core/config.py @@ -30,10 +30,11 @@ import json import site import sys -from typing import Any, Dict, Generator, Optional +from typing import Any, Dict, Optional from .const import CONFIG_FN from .errors import EnvConfigParserError +from .pythonversion import PythonVersion from .typeguard import typechecked # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -74,14 +75,20 @@ def __init__(self, **override: Any): # Call parent constructur, just in case super().__init__() - # Get config from files as a prioritized list - for config in self._get_config_from_files(): - self.update(config) + # Get config from files + self.update(self._get_config_from_files()) # Add override parameters if len(override) > 0: self.update(override) + # Version type cleanup + if not isinstance(self['pythonversion'], PythonVersion): + self['pythonversion'] = PythonVersion.from_config( + arch = self['arch'], + version = self['pythonversion'], + ) + def __getitem__(self, key: str) -> Any: """ Returns values from the following sources in the following order: @@ -100,6 +107,8 @@ def __getitem__(self, key: str) -> Any: if env_var in os.environ.keys(): value = os.environ[env_var] if len(value) > 0: + if key == "pythonversion": + return PythonVersion.from_config(self['arch'], value) if value.isnumeric(): return int(value) if value.strip().lower() in ("true", "false"): @@ -112,7 +121,7 @@ def __getitem__(self, key: str) -> Any: if key == "arch": return "win32" # Define Wine & Wine-Python architecture if key == "pythonversion": - return "3.7.4" # Define Wine-Python version + return PythonVersion(self['arch'], 3, 7, 4, 'stable') # Define Wine-Python version if key == "wine_bin_win32": return "wine" if key == "wine_bin_win64": @@ -164,12 +173,14 @@ def export_envvar_dict(self) -> Dict[str, str]: for field in self._KEYS } - def _get_config_from_files(self) -> Generator: + def _get_config_from_files(self) -> Dict: + + base = {} # Look for config in the usual spots for fn in [ "/etc/wenv", - os.path.join("/etc", CONFIG_FN), + os.path.join("/etc", CONFIG_FN), # TODO deprecated os.path.join("/etc", CONFIG_FN[1:]), os.path.join(os.path.expanduser("~"), CONFIG_FN), os.environ.get("WENV"), @@ -179,10 +190,12 @@ def _get_config_from_files(self) -> Generator: os.path.join(os.getcwd(), CONFIG_FN), ]: - cnt_dict = self._load_config_from_file(fn) + cnt = self._load_config_from_file(fn) + + if cnt is not None: + base.update(cnt) - if cnt_dict is not None: - yield cnt_dict + return base def _load_config_from_file(self, try_path: Optional[str] = None) -> Optional[Dict[str, Any]]: @@ -205,14 +218,14 @@ def _load_config_from_file(self, try_path: Optional[str] = None) -> Optional[Dic # Try to parse it try: - cnt_dict = json.loads(cnt) + cnt = json.loads(cnt) except Exception as e: raise EnvConfigParserError( 'Config file could not be parsed: "%s"' % try_path ) from e # Ensure that config has the right format - if not isinstance(cnt_dict, dict): + if not isinstance(cnt, dict): raise EnvConfigParserError('Config file is malformed: "%s"' % try_path) - return cnt_dict + return cnt diff --git a/src/wenv/_core/env.py b/src/wenv/_core/env.py index ebbe5d0..71dd96e 100644 --- a/src/wenv/_core/env.py +++ b/src/wenv/_core/env.py @@ -38,7 +38,6 @@ from .config import EnvConfig from .const import c, COVERAGE_STARTUP, HELP_STR from .paths import Paths -from .pythonversion import PythonVersion from .source import download from .typeguard import typechecked @@ -100,9 +99,7 @@ def _init_dicts(self): } # Init Python environment paths - self._path_dict = Paths( - self._p["pythonprefix"], self._p["arch"], self._p["pythonversion"] - ) + self._path_dict = Paths(self._p["pythonprefix"], self._p["pythonversion"]) # Init Python commands and scripts self._init_cmd_dict() # Init internal CLI commands @@ -268,11 +265,9 @@ def cache(self): Equivalent to ``wenv cache``. It fetches installation files and caches them for offline usage, including the Python interpreter, pip, setuptools and wheel. """ - version = PythonVersion.from_config(self._p["arch"], self._p["pythonversion"]) - os.makedirs(self._p["cache"], exist_ok=True) - with open(os.path.join(self._p["cache"], version.as_zipname()), "wb") as f: + with open(os.path.join(self._p["cache"], self._p["pythonversion"].as_zipname()), "wb") as f: f.write(self._get_python(offline=False)) with open(os.path.join(self._p["cache"], "get-pip.py"), "wb") as f: f.write(self._get_pip(offline=False)) @@ -306,13 +301,11 @@ def cache_package(self, name: str): def _get_python(self, offline: bool = False) -> bytes: - version = PythonVersion.from_config(self._p["arch"], self._p["pythonversion"]) - if offline: - with open(os.path.join(self._p["cache"], version.as_zipname()), "rb") as f: + with open(os.path.join(self._p["cache"], self._p["pythonversion"].as_zipname()), "rb") as f: return f.read() else: - return download(version.as_url(), mode="binary") + return download(self._p["pythonversion"].as_url(), mode="binary") def _get_pip(self, offline: bool = False) -> bytes: diff --git a/src/wenv/_core/paths.py b/src/wenv/_core/paths.py index 4060b50..053f5d8 100644 --- a/src/wenv/_core/paths.py +++ b/src/wenv/_core/paths.py @@ -42,12 +42,10 @@ class Paths: Wine Python environment paths """ - def __init__(self, pythonprefix: str, arch: str, pythonversion: str): + def __init__(self, pythonprefix: str, pythonversion: PythonVersion): self._pythonprefix = pythonprefix - self._pythonversion_block = PythonVersion.from_config( - arch, pythonversion - ).as_block() + self._pythonversion_block = pythonversion.as_block() def __getitem__(self, key: str) -> str: From 99a0ea413cfc20db0355038dfc6f1fa8e9e410f7 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Tue, 23 Nov 2021 19:38:39 +0100 Subject: [PATCH 101/131] log --- CHANGES.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 9eb24b5..3ad1641 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,12 +5,16 @@ - FEATURE: Experimental ARM64 support added. - FEATURE: Added support for Python 3.9 and 3.10. - FEATURE: Dropped support for Python 3.4 and 3.5. +- FEATURE: Exposed parser for CPython versions, see `wenv.PythonVersion`. +- FEATURE: New functions for querying available Windows Embeddable Python builds from [python.org/downloads](https://www.python.org/downloads/), see `wenv.get_available_python_builds` and `wenv.get_latest_python_build`. +- FIX: Wine Python can distinctly refer to and handle alpha, beta, release-candidate and stable builds of CPython. - FIX: Switched from unsupported `python-language-server` to supported `python-lsp-server`. - FIX: Error handling in package listing for Wine Python environments was broken. - FIX: Python version parser could not handle Windows ARM64 builds. - FIX: Configuration expected in `/etc/.wenv.json` and `/etc/wenv.json`, see #15. The support for `/etc/.wenv.json` will be removed in a future release. - FIX: The names of wine binaries/commands can be configured for special cases like RedHat/Fedora/CentOS wine packages, see zugbruecke#70. -- API: New makefile structure for developers. +- FIX: Configuration module, `wenv.EnvConfig`, is now properly documented and actually usable from outside `wenv`. +- DEV: New `makefile` structure for developers. - DOCS: Hugely improved. ## 0.2.1 (2020-07-10) From 0c5677ac383fe7b6a55b15f75cf2d90163855f1d Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Tue, 23 Nov 2021 19:40:22 +0100 Subject: [PATCH 102/131] fix rtd builds --- .readthedocs.yml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .readthedocs.yml diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 0000000..542a175 --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,9 @@ +version: 2 + +python: + version: 3.7 + install: + - method: pip + path: . + extra_requirements: + - dev From 7e3f9b3acbbb164ee928a1a56947a2ccf7bca445 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Tue, 23 Nov 2021 19:44:35 +0100 Subject: [PATCH 103/131] clarification --- docs/security.rst | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/security.rst b/docs/security.rst index a2783d1..1d9c6a3 100644 --- a/docs/security.rst +++ b/docs/security.rst @@ -8,7 +8,11 @@ Security ======== -*wenv* should be used with **caution**. +.. warning:: + + *wenv* must be used with **caution**. + +Beware: - **DO NOT** run untrusted code (or DLLs)! - **DO NOT** run it with root / super users privileges! @@ -21,4 +25,6 @@ The following problems also directly apply to *wenv*: .. _Windows malware: https://en.wikipedia.org/wiki/Wine_(software)#Security .. _FAQ at WineHQ: https://wiki.winehq.org/FAQ#Should_I_run_Wine_as_root.3F -*wenv* does not actively prohibit its use with root privileges. +.. warning:: + + *wenv* does not actively prohibit its use with root privileges. From e389386f8b22829286d3c622328ea0833dd9785a Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Tue, 23 Nov 2021 19:46:39 +0100 Subject: [PATCH 104/131] clarification --- docs/installation.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 60262a2..1dcbb9f 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -47,14 +47,18 @@ After installing the package with ``pip``, you must **initialize** the "Wine Pyt wenv init -If you are relying on *wenv*, please notice that it uses semantic versioning. Breaking changes are indicated by increasing the first version number, the major version. Going for example from 0.0.x to 0.1.y or going from 0.1.x to 0.2.y therefore indicates a breaking change. +.. note:: + + If you are relying on *wenv*, please notice that it uses semantic versioning. Breaking changes are indicated by increasing the first version number, the major version. Going for example from 0.0.x to 0.1.y or going from 0.1.x to 0.2.y therefore indicates a breaking change. Possible problem: ``OSError: [WinError 6] Invalid handle`` ---------------------------------------------------------- On older versions of Linux such as *Ubuntu 14.04* alias *Trusty Tahr* (released 2014), you may observe errors when running ``wenv python``. Most commonly, they will present themselves as ``OSError: [WinError 6] Invalid handle: 'z:\\...`` triggered by calling ``os.listdir`` on a symbolic link ("symlink") to a folder. -A **clean solution** is to upgrade to a younger version of Linux. E.g. *Ubuntu 16.04* alias *Xenial Xerus* (released 2016) is known to work. +.. note:: + + A **clean solution** is to upgrade to a younger version of Linux. E.g. *Ubuntu 16.04* alias *Xenial Xerus* (released 2016) is known to work. Installing *wenv* in Development Mode ------------------------------------- From b7e4bf1be8b80b6021483eeaa03c1a13cd88468a Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Tue, 23 Nov 2021 19:53:25 +0100 Subject: [PATCH 105/131] link to api, clarification --- docs/configuration.rst | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index 6c407b6..6e92561 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -32,6 +32,11 @@ Environment Variables Independently of the ``WENV`` environment variable, all configurable parameters of ``wenv`` can directly be overridden with environment variables. All values coming from configuration files will then be ignored for this particular parameter. Take the name of any configurable parameter, convert it to upper case and prefix it with ``WENV``. Example: The ``arch`` parameter can be overridden by declaring the ``WENV_ARCH`` environment variable. +Configuration via API +--------------------- + +When using ``wenv`` via its API, it can be configured through its :class:`wenv.EnvConfig` class. + Parameters ---------- @@ -45,7 +50,9 @@ Defines the architecture of *Wine* & *Wine* *Python*. It can be set to ``win32`` The ``pythonversion`` parameter tells *wenv* what version of the *Windows* *CPython* interpreter it should use. By default, it is set to ``3.7.4``. -Please note that 3.4 and earlier are not supported. In the opposite direction, at the time of writing, 3.6 (and later) do require *Wine* 4.0 or later. If you are forced to use *Wine* 2.0 or 3.0, you may try to set this parameter to ``3.5.4``. Note that you can only specify versions for which an "*Windows* embeddable zip file" is available, see `python.org`_. +.. note:: + + Windows of Python versions of 3.4 and earlier are not supported. In the opposite direction, at the time of writing, 3.6 (and later) do require *Wine* 4.0 or later. If you are forced to use *Wine* 2.0 or 3.0, you may try to set this parameter to ``3.5.4``. You can only specify versions for which an "*Windows* embeddable zip file" is available, see `python.org`_. Release candidates, alpha and beta versions can be accessed in the following form: ``3.7.0.rc1``. ``3.7.0.a1`` or ``3.7.0.b1``. From 1e434c3a0ae2c31b7f29483790320e57cb88d038 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Tue, 23 Nov 2021 19:55:26 +0100 Subject: [PATCH 106/131] clarification --- docs/usage.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/usage.rst b/docs/usage.rst index 0ae21c7..446e5cf 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -35,7 +35,9 @@ This command provides help and lists all currently available sub-commands (such This command behaves just like the regular ``python`` command in a *Unix* shell, except that it fires up a *Windows* *Python* interpreter on top of *Wine*. It works with all regular parameters and switches, accepts pipes and can be launched in interactive mode. -You can also use it for creating executable *Python* scripts by adding the following at their top: ``#!/usr/bin/env _wenv_python``. Do not forget ``chmod +x your_script.py``. Notice that there is a difference between the more general ``wenv python`` command and its alias ``_wenv_python``, which is meant to be used only with a shebang. +.. note:: + + You can also use it for creating executable *Python* scripts by adding the following at their top: ``#!/usr/bin/env _wenv_python``. Do not forget ``chmod +x your_script.py``. Notice that there is a difference between the more general ``wenv python`` command and its alias ``_wenv_python``, which is meant to be used only with a **shebang**. ``wenv pip`` ------------ From 12beb51e14d75ab3db2cf636082dc70fe1df3d97 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Tue, 23 Nov 2021 20:05:43 +0100 Subject: [PATCH 107/131] warning --- docs/faq.rst | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/faq.rst b/docs/faq.rst index 50dfd0e..8bd962e 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -15,12 +15,19 @@ What are actual use cases for this project? Read the section on :ref:`use cases ` in the introduction. -How does it work? ------------------ +How does ``wenv`` work? +----------------------- Have a closer look at the section describing the :ref:`implementation ` in the introduction. -Is it secure? -------------- +Is ``wenv`` secure? +------------------- Yes - limitations apply, though. See :ref:`chapter on security `. + +Can I use ``wenv`` for X/Y/Z fancy use case? +-------------------------------------------- + +`In principle`_, yes. A lot of crazy and outright amazing stuff has been attempted on top of ``wenv``, but not every idea succeeded. Solutions based on ``wenv`` or Wine for that matter tend to be a nightmare to maintain in the longer term. It is usually a good idea to stick to known-good versions of Python, Wine and related tools once you made your idea work. Plan ahead if you need to upgrade any of the involved components. + +.. _In principle: https://en.wikipedia.org/wiki/Radio_Yerevan_jokes From 7dac5ee7831bcb0a33d468c4dad82fb6036e840c Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Tue, 23 Nov 2021 20:34:00 +0100 Subject: [PATCH 108/131] prepare tests to handle multiple versions of wine python --- tests/lib/__init__.py | 2 +- tests/lib/output.py | 8 ++++++++ tests/test_init.py | 27 ++++++++++++++++++++------- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/tests/lib/__init__.py b/tests/lib/__init__.py index 13aea89..6b46d97 100644 --- a/tests/lib/__init__.py +++ b/tests/lib/__init__.py @@ -27,4 +27,4 @@ # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ from .param import get_context, run_process -from .output import no_errors_in +from .output import no_errors_in, remove_colors diff --git a/tests/lib/output.py b/tests/lib/output.py index 9d99ce2..586e64e 100644 --- a/tests/lib/output.py +++ b/tests/lib/output.py @@ -26,6 +26,8 @@ # IMPORT # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +import re + from .param import get_context, run_process @@ -57,3 +59,9 @@ def no_errors_in(output: str) -> bool: return False return True + + +def remove_colors(output: str) -> str: + + # https://stackoverflow.com/a/14693789/1672565 + return re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])').sub('', output) diff --git a/tests/test_init.py b/tests/test_init.py index fa50f67..e3c5fc5 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -26,7 +26,7 @@ # IMPORT # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -from .lib import get_context, run_process, no_errors_in +from .lib import get_context, run_process, no_errors_in, remove_colors import pytest @@ -36,19 +36,23 @@ @pytest.mark.parametrize("arch", get_context()) -def test_init(arch): +def test_1_clean(arch): out, err, code = run_process(["wenv", "clean"], env={"WENV_ARCH": arch}) assert code == 0 assert no_errors_in(err) + +@pytest.mark.parametrize("arch", get_context()) +def test_2_init(arch): + out, err, code = run_process(["wenv", "help"], env={"WENV_ARCH": arch}) assert code == 0 assert no_errors_in(err) assert "wenv pip" not in out - # assert 'wenv python' not in out # TODO + assert 'wenv python' not in remove_colors(out) out, err, code = run_process(["wenv", "init"], env={"WENV_ARCH": arch}) @@ -60,11 +64,20 @@ def test_init(arch): assert code == 0 assert no_errors_in(err) assert "wenv pip" in out - # assert 'wenv python' in out # TODO + assert 'wenv python' in remove_colors(out) + + +@pytest.mark.parametrize("arch", get_context()) +def test_3_clean(arch): + + out, err, code = run_process(["wenv", "clean"], env={"WENV_ARCH": arch}) + + assert code == 0 + assert no_errors_in(err) @pytest.mark.parametrize("arch", get_context()) -def test_init_offline(arch): +def test_4_offline(arch): out, err, code = run_process(["wenv", "clean"], env={"WENV_ARCH": arch}) @@ -76,7 +89,7 @@ def test_init_offline(arch): assert code == 0 assert no_errors_in(err) assert "wenv pip" not in out - # assert 'wenv python' not in out # TODO + assert 'wenv python' not in remove_colors(out) out, err, code = run_process(["wenv", "cache"], env={"WENV_ARCH": arch}) @@ -96,4 +109,4 @@ def test_init_offline(arch): assert code == 0 assert no_errors_in(err) assert "wenv pip" in out - # assert 'wenv python' in out # TODO + assert 'wenv python' in remove_colors(out) From ed930e8a999b1ddaea91442895d2713ac8c55ae6 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Tue, 23 Nov 2021 21:28:12 +0100 Subject: [PATCH 109/131] fix docstring --- src/wenv/_core/pythonversion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wenv/_core/pythonversion.py b/src/wenv/_core/pythonversion.py index 11225a2..5acb369 100644 --- a/src/wenv/_core/pythonversion.py +++ b/src/wenv/_core/pythonversion.py @@ -50,7 +50,7 @@ class PythonVersion: major : Python major version, i.e. ``X`` from ``X.0.0``. minor : Python minor version, i.e. ``X`` from ``0.X.0``. maintenance : Python maintenance version, i.e. ``X`` from ``0.0.X``. - build : Type of build, e.g. ``alpha``, ``beta``, ``rcX``, etc. If left empty or set to ``stable``, the build is considered stable. + build : Type of build, e.g. ``aX``, ``bX``, ``rcX``, etc. If left empty or set to ``stable``, the build is considered stable. """ def __init__(self, arch: str, major: int, minor: int, maintenance: int, build: str = "stable"): From e38a2e732b3c066563c1e594f44cbd9665e83007 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Tue, 23 Nov 2021 21:29:04 +0100 Subject: [PATCH 110/131] clarification on minimum wine and python versions; adjust pythonversion docs accordingly --- README.md | 4 +--- docs/configuration.rst | 16 +++++++++++----- docs/envconfig.rst | 2 +- docs/installation.rst | 2 +- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 412853e..b94f50f 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,7 @@ About Wine (from [winehq.org](https://www.winehq.org/)): *Wine (originally an ac | prerequisite | version | | --- | --- | | [CPython](https://www.python.org/) | 3.x (tested with 3.{6,7,8,9,10}) | -| [Wine](https://www.winehq.org/) | >= 4.x (tested with regular & [staging](https://wine-staging.com/)) - expected to be in the user's [`PATH`](https://en.wikipedia.org/wiki/PATH_(variable)) | - -If you are limited to an older version of Wine such as 2.x or 3.x, see `wenv`'s [installation instructions](https://wenv.readthedocs.io/en/latest/installation.html) for details and workarounds. +| [Wine](https://www.winehq.org/) | >= 6.x (tested with regular & [staging](https://wine-staging.com/)) - expected to be in the user's [`PATH`](https://en.wikipedia.org/wiki/PATH_(variable)) | ## Installation diff --git a/docs/configuration.rst b/docs/configuration.rst index 6e92561..a8f8ab6 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -37,6 +37,8 @@ Configuration via API When using ``wenv`` via its API, it can be configured through its :class:`wenv.EnvConfig` class. +.. _parameters: + Parameters ---------- @@ -45,16 +47,20 @@ Parameters Defines the architecture of *Wine* & *Wine* *Python*. It can be set to ``win32``, ``win64`` or ``arm64``. Default is ``win32``, even on 64-bit systems. It appears to be a more stable configuration. -``pythonversion`` (str) -^^^^^^^^^^^^^^^^^^^^^^^ +``pythonversion`` (:class:`wenv.PythonVersion` or str) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -The ``pythonversion`` parameter tells *wenv* what version of the *Windows* *CPython* interpreter it should use. By default, it is set to ``3.7.4``. +The ``pythonversion`` parameter tells *wenv* what version of the *Windows* *CPython* interpreter it should use. By default, it is set to ``PythonVersion('{arch}', 3, 7, 4)``. .. note:: - Windows of Python versions of 3.4 and earlier are not supported. In the opposite direction, at the time of writing, 3.6 (and later) do require *Wine* 4.0 or later. If you are forced to use *Wine* 2.0 or 3.0, you may try to set this parameter to ``3.5.4``. You can only specify versions for which an "*Windows* embeddable zip file" is available, see `python.org`_. + Windows versions of Python of 3.5 and earlier are not supported. You can only specify versions for which an "*Windows* embeddable zip file" is available, see `python.org`_. Available versions/builds can be queried for using :func:`wenv.get_available_python_builds` and :func:`wenv.get_latest_python_build`. + +Release candidate, alpha and beta versions can be accessed in the following form: ``PythonVersion('{arch}', 3, 7, 0, 'rc1')`` (first release candidate). ``PythonVersion('{arch}', 3, 7, 0, 'a1')`` (first alpha version) or ``PythonVersion('{arch}', 3, 7, 0, 'b1')`` (first beta version). + +.. note:: -Release candidates, alpha and beta versions can be accessed in the following form: ``3.7.0.rc1``. ``3.7.0.a1`` or ``3.7.0.b1``. + The ``pythonversion`` parameter can also be specified as a string, e.g. ``3.7.4``, ``3.7.0.rc1``, ``3.7.0.a1`` or ``3.7.0.b1``. The architecture is then taken from the ``arch`` field. .. _python.org: https://www.python.org/downloads/windows/ diff --git a/docs/envconfig.rst b/docs/envconfig.rst index 3a6e023..7bd7525 100644 --- a/docs/envconfig.rst +++ b/docs/envconfig.rst @@ -10,7 +10,7 @@ Configuration ============= -foo bar +The class ``EnvConfig`` is meant to initialize :class:`wenv.Env`. For available configuration options, see :ref:`chapter on configuration parameters `. .. autoclass:: wenv.EnvConfig :members: diff --git a/docs/installation.rst b/docs/installation.rst index 1dcbb9f..f130d7b 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -24,7 +24,7 @@ For using *wenv*, you need to install **Wine** first. Depending on your platform .. _Installation instructions for Mac OS X: https://wiki.winehq.org/MacOS .. _Installation instructions for FreeBSD: https://wiki.winehq.org/FreeBSD -Currently, Wine >= 4.x is supported (tested). If you are limited to an older version of Wine such as 2.x or 3.x, you have one option: Try to set the ``pythonversion`` configuration parameter to ``3.5.4``. +Currently, Wine >= 6.x is supported (tested). Getting *wenv* -------------- From f8c4b2d29ddeef50e833687d2eb1da84855b7707 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Tue, 23 Nov 2021 21:48:36 +0100 Subject: [PATCH 111/131] experimental arm support --- CHANGES.md | 1 + docs/configuration.rst | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 3ad1641..f7a21e2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,7 @@ - FEATURE: Dropped support for Python 3.4 and 3.5. - FEATURE: Exposed parser for CPython versions, see `wenv.PythonVersion`. - FEATURE: New functions for querying available Windows Embeddable Python builds from [python.org/downloads](https://www.python.org/downloads/), see `wenv.get_available_python_builds` and `wenv.get_latest_python_build`. +- FEATURE: Experimental support for ARM64. - FIX: Wine Python can distinctly refer to and handle alpha, beta, release-candidate and stable builds of CPython. - FIX: Switched from unsupported `python-language-server` to supported `python-lsp-server`. - FIX: Error handling in package listing for Wine Python environments was broken. diff --git a/docs/configuration.rst b/docs/configuration.rst index a8f8ab6..8126b84 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -47,6 +47,10 @@ Parameters Defines the architecture of *Wine* & *Wine* *Python*. It can be set to ``win32``, ``win64`` or ``arm64``. Default is ``win32``, even on 64-bit systems. It appears to be a more stable configuration. +.. warning:: + + The support for ``arm64`` is experimental and does not receive testing. + ``pythonversion`` (:class:`wenv.PythonVersion` or str) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From ba4c79f442c5c42a565be86acbcc05caab65a160 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Tue, 23 Nov 2021 21:52:23 +0100 Subject: [PATCH 112/131] testing through python for windows, 3.5 to 3.10 --- tests/lib/param.py | 24 +++++++++++++++++++++++- tests/test_init.py | 39 +++++++++++++++++---------------------- tests/test_pip.py | 22 +++++++++++----------- tests/test_python.py | 8 ++++---- 4 files changed, 55 insertions(+), 38 deletions(-) diff --git a/tests/lib/param.py b/tests/lib/param.py index 47e820f..ce020eb 100644 --- a/tests/lib/param.py +++ b/tests/lib/param.py @@ -33,6 +33,27 @@ from .const import ARCHS, DEFAULT_TIMEOUT +from wenv import get_available_python_builds, get_latest_python_build + +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# BUILDS +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +_builds = get_available_python_builds() + +BUILDS = { + arch: [ + get_latest_python_build(arch, 3, minor, builds = _builds) + for minor in range( + 6, # min minor version + 10 + 1, # max major version + ) + ] + for arch in ARCHS +} +for _value in BUILDS.values(): + _value.sort() + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # ROUTINES # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -41,7 +62,8 @@ def get_context(): for arch in ARCHS: - yield arch + for build in BUILDS[arch]: + yield arch, build # https://stackoverflow.com/a/14693789/1672565 diff --git a/tests/test_init.py b/tests/test_init.py index e3c5fc5..c62307a 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -35,31 +35,31 @@ # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -@pytest.mark.parametrize("arch", get_context()) -def test_1_clean(arch): +@pytest.mark.parametrize("arch,build", get_context()) +def test_1_clean(arch, build): - out, err, code = run_process(["wenv", "clean"], env={"WENV_ARCH": arch}) + out, err, code = run_process(["wenv", "clean"], env={"WENV_ARCH": arch, "WENV_PYTHONVERSION": str(build)}) assert code == 0 assert no_errors_in(err) -@pytest.mark.parametrize("arch", get_context()) -def test_2_init(arch): +@pytest.mark.parametrize("arch,build", get_context()) +def test_2_init(arch, build): - out, err, code = run_process(["wenv", "help"], env={"WENV_ARCH": arch}) + out, err, code = run_process(["wenv", "help"], env={"WENV_ARCH": arch, "WENV_PYTHONVERSION": str(build)}) assert code == 0 assert no_errors_in(err) assert "wenv pip" not in out assert 'wenv python' not in remove_colors(out) - out, err, code = run_process(["wenv", "init"], env={"WENV_ARCH": arch}) + out, err, code = run_process(["wenv", "init"], env={"WENV_ARCH": arch, "WENV_PYTHONVERSION": str(build)}) assert code == 0 # assert len(err.strip()) == 0 # pip output goes to stderr - out, err, code = run_process(["wenv", "help"], env={"WENV_ARCH": arch}) + out, err, code = run_process(["wenv", "help"], env={"WENV_ARCH": arch, "WENV_PYTHONVERSION": str(build)}) assert code == 0 assert no_errors_in(err) @@ -67,44 +67,39 @@ def test_2_init(arch): assert 'wenv python' in remove_colors(out) -@pytest.mark.parametrize("arch", get_context()) -def test_3_clean(arch): +@pytest.mark.parametrize("arch,build", get_context()) +def test_3_clean(arch, build): - out, err, code = run_process(["wenv", "clean"], env={"WENV_ARCH": arch}) + out, err, code = run_process(["wenv", "clean"], env={"WENV_ARCH": arch, "WENV_PYTHONVERSION": str(build)}) assert code == 0 assert no_errors_in(err) -@pytest.mark.parametrize("arch", get_context()) -def test_4_offline(arch): +@pytest.mark.parametrize("arch,build", get_context()) +def test_4_offline(arch, build): - out, err, code = run_process(["wenv", "clean"], env={"WENV_ARCH": arch}) - - assert code == 0 - assert no_errors_in(err) - - out, err, code = run_process(["wenv", "help"], env={"WENV_ARCH": arch}) + out, err, code = run_process(["wenv", "help"], env={"WENV_ARCH": arch, "WENV_PYTHONVERSION": str(build)}) assert code == 0 assert no_errors_in(err) assert "wenv pip" not in out assert 'wenv python' not in remove_colors(out) - out, err, code = run_process(["wenv", "cache"], env={"WENV_ARCH": arch}) + out, err, code = run_process(["wenv", "cache"], env={"WENV_ARCH": arch, "WENV_PYTHONVERSION": str(build)}) assert code == 0 assert no_errors_in(err) assert "wenv pip" not in out out, err, code = run_process( - ["wenv", "init"], env={"WENV_ARCH": arch, "WENV_OFFLINE": "true"} + ["wenv", "init"], env={"WENV_ARCH": arch, "WENV_PYTHONVERSION": str(build), "WENV_OFFLINE": "true"} ) assert code == 0 # assert len(err.strip()) == 0 # pip output goes to stderr - out, err, code = run_process(["wenv", "help"], env={"WENV_ARCH": arch}) + out, err, code = run_process(["wenv", "help"], env={"WENV_ARCH": arch, "WENV_PYTHONVERSION": str(build)}) assert code == 0 assert no_errors_in(err) diff --git a/tests/test_pip.py b/tests/test_pip.py index d6186d1..d8e90d2 100644 --- a/tests/test_pip.py +++ b/tests/test_pip.py @@ -37,10 +37,10 @@ # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -@pytest.mark.parametrize("arch", get_context()) -def test_pip(arch): +@pytest.mark.parametrize("arch,build", get_context()) +def test_pip(arch, build): - out, err, code = run_process(["wenv", "pip", "list"], env={"WENV_ARCH": arch}) + out, err, code = run_process(["wenv", "pip", "list"], env={"WENV_ARCH": arch, "WENV_PYTHONVERSION": str(build)}) assert code == 0 assert no_errors_in(err) assert "pip" in out @@ -48,12 +48,12 @@ def test_pip(arch): assert "pytest" not in out out, err, code = run_process( - ["wenv", "pip", "install", "pytest"], env={"WENV_ARCH": arch} + ["wenv", "pip", "install", "pytest"], env={"WENV_ARCH": arch, "WENV_PYTHONVERSION": str(build)} ) assert code == 0 assert no_errors_in(err) - out, err, code = run_process(["wenv", "pip", "list"], env={"WENV_ARCH": arch}) + out, err, code = run_process(["wenv", "pip", "list"], env={"WENV_ARCH": arch, "WENV_PYTHONVERSION": str(build)}) assert code == 0 assert no_errors_in(err) assert "pip" in out @@ -61,12 +61,12 @@ def test_pip(arch): assert "pytest" in out -@pytest.mark.parametrize("arch", get_context()) -def test_pip_api(arch): +@pytest.mark.parametrize("arch,build", get_context()) +def test_pip_api(arch, build): - env = Env(arch=arch) + env = Env(arch=arch, pythonversion=build) - out, err, code = run_process(["wenv", "pip", "list"], env={"WENV_ARCH": arch}) + out, err, code = run_process(["wenv", "pip", "list"], env={"WENV_ARCH": arch, "WENV_PYTHONVERSION": str(build)}) assert code == 0 assert no_errors_in(err) assert "pip" in out @@ -80,7 +80,7 @@ def test_pip_api(arch): env.install_package("requests") - out, err, code = run_process(["wenv", "pip", "list"], env={"WENV_ARCH": arch}) + out, err, code = run_process(["wenv", "pip", "list"], env={"WENV_ARCH": arch, "WENV_PYTHONVERSION": str(build)}) assert code == 0 assert no_errors_in(err) assert "pip" in out @@ -94,7 +94,7 @@ def test_pip_api(arch): env.uninstall_package("requests") - out, err, code = run_process(["wenv", "pip", "list"], env={"WENV_ARCH": arch}) + out, err, code = run_process(["wenv", "pip", "list"], env={"WENV_ARCH": arch, "WENV_PYTHONVERSION": str(build)}) assert code == 0 assert no_errors_in(err) assert "pip" in out diff --git a/tests/test_python.py b/tests/test_python.py index 9d9c33b..e2a2d9d 100644 --- a/tests/test_python.py +++ b/tests/test_python.py @@ -35,11 +35,11 @@ # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -@pytest.mark.parametrize("arch", get_context()) -def test_python(arch): +@pytest.mark.parametrize("arch,build", get_context()) +def test_python(arch, build): out, err, code = run_process( - ["wenv", "python", "-m", "platform"], env={"WENV_ARCH": arch} + ["wenv", "python", "-m", "platform"], env={"WENV_ARCH": arch, "WENV_PYTHONVERSION": str(build)} ) assert code == 0 @@ -48,7 +48,7 @@ def test_python(arch): out, err, code = run_process( ["wenv", "python", "-c", "import platform; print(platform.machine())"], - env={"WENV_ARCH": arch}, + env={"WENV_ARCH": arch, "WENV_PYTHONVERSION": str(build)}, ) assert code == 0 assert no_errors_in(err) From b68e98ef27a872467e7eefdfcb1a5f1dbe1a4941 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Tue, 23 Nov 2021 22:44:34 +0100 Subject: [PATCH 113/131] support section --- docs/index.rst | 1 + docs/support.rst | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 docs/support.rst diff --git a/docs/index.rst b/docs/index.rst index 4f9afd7..4d96016 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -34,6 +34,7 @@ User's guide bugs changes faq + support `Interested in contributing?`_ diff --git a/docs/support.rst b/docs/support.rst new file mode 100644 index 0000000..f12b0cc --- /dev/null +++ b/docs/support.rst @@ -0,0 +1,36 @@ +.. _support: + +Getting Help +============ + +There are multiple ways of getting help. + +Mailing List +------------ + +A public mailing list for the `zugbruecke project`_ and closely related projects - including *wenv* and `wenv-kernel`_ - for both users and developers is available at `groups.io`_. + +.. _groups.io: https://groups.io/g/zugbruecke-dev +.. _zugbruecke project: https://github.com/pleiszenburg/zugbruecke +.. _wenv-kernel: https://github.com/pleiszenburg/wenv-kernel + +Chat +---- + +There is a `dedicated public Matrix chat`_. + +.. _dedicated public Matrix chat: https://matrix.to/#/#zugbruecke:matrix.org + +Reporting Bugs / Issues +----------------------- + +Bugs and any kind of issues should be reported in the project's `Github issue tracker`_, also see :ref:`chapter on bugs `. + +.. _Github issue tracker: https://github.com/pleiszenburg/wenv/issues + +Paid Support +------------ + +In addition to the previous options, paid support is available from `pleiszenburg.de - Independent Scientific Services`_. + +.. _pleiszenburg.de - Independent Scientific Services: http://www.pleiszenburg.de From 72125e840fb0093bc5582013d0f5d0021ce07bca Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Tue, 23 Nov 2021 22:46:27 +0100 Subject: [PATCH 114/131] cleanup --- CHANGES.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index f7a21e2..e9b7efb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,21 +2,20 @@ ## 0.3.0 (2021-XX-XX) -- FEATURE: Experimental ARM64 support added. - FEATURE: Added support for Python 3.9 and 3.10. - FEATURE: Dropped support for Python 3.4 and 3.5. - FEATURE: Exposed parser for CPython versions, see `wenv.PythonVersion`. - FEATURE: New functions for querying available Windows Embeddable Python builds from [python.org/downloads](https://www.python.org/downloads/), see `wenv.get_available_python_builds` and `wenv.get_latest_python_build`. -- FEATURE: Experimental support for ARM64. +- FEATURE: Experimental support for ARM64 added. - FIX: Wine Python can distinctly refer to and handle alpha, beta, release-candidate and stable builds of CPython. -- FIX: Switched from unsupported `python-language-server` to supported `python-lsp-server`. - FIX: Error handling in package listing for Wine Python environments was broken. - FIX: Python version parser could not handle Windows ARM64 builds. - FIX: Configuration expected in `/etc/.wenv.json` and `/etc/wenv.json`, see #15. The support for `/etc/.wenv.json` will be removed in a future release. - FIX: The names of wine binaries/commands can be configured for special cases like RedHat/Fedora/CentOS wine packages, see zugbruecke#70. - FIX: Configuration module, `wenv.EnvConfig`, is now properly documented and actually usable from outside `wenv`. -- DEV: New `makefile` structure for developers. - DOCS: Hugely improved. +- DEV: New `makefile` structure for developers. +- DEV: Switched from unsupported `python-language-server` to supported `python-lsp-server`. ## 0.2.1 (2020-07-10) From e3450fef224c3320b86878b3c49c5d74742efca5 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Tue, 23 Nov 2021 22:48:28 +0100 Subject: [PATCH 115/131] enable links --- docs/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index e755a4d..6f577ac 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -135,8 +135,8 @@ def fetch_version_string(): # (' Blog', 'https://www.000'), (' Source Code', 'https://github.com/pleiszenburg/wenv'), (' Issue Tracker', 'https://github.com/pleiszenburg/wenv/issues'), - ### (' Mailing List', 'https://groups.io/g/zugbruecke-dev'), - ### (' Chat', 'https://matrix.to/#/#zugbruecke:matrix.org'), + (' Mailing List', 'https://groups.io/g/zugbruecke-dev'), + (' Chat', 'https://matrix.to/#/#zugbruecke:matrix.org'), # (' Citation', 'https://doi.org/000'), (' pleiszenburg.de', 'http://www.pleiszenburg.de/'), ], From fd5df861a3ef775d6b257251c74c81bdf6d30316 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Tue, 23 Nov 2021 23:03:08 +0100 Subject: [PATCH 116/131] shebang test --- tests/shebang.py | 38 +++++++++++++++++++++++++++++ tests/test_shebang.py | 56 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100755 tests/shebang.py create mode 100644 tests/test_shebang.py diff --git a/tests/shebang.py b/tests/shebang.py new file mode 100755 index 0000000..e62c4a8 --- /dev/null +++ b/tests/shebang.py @@ -0,0 +1,38 @@ +#!/usr/bin/env _wenv_python +# -*- coding: utf-8 -*- + +""" +WENV +Running Python on Wine +https://github.com/pleiszenburg/wenv + + tests/shebang.py: Test script for Python interpreter shebang alias + + Copyright (C) 2017-2022 Sebastian M. Ernst + + +The contents of this file are subject to the GNU Lesser General Public License +Version 2.1 ("LGPL" or "License"). You may not use this file except in +compliance with the License. You may obtain a copy of the License at +https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt +https://github.com/pleiszenburg/wenv/blob/master/LICENSE + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +""" + +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# IMPORT +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +import platform + +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# ENTRY POINT +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +if __name__ == '__main__': + + print(f'Hello from {platform.uname().system:s}!') diff --git a/tests/test_shebang.py b/tests/test_shebang.py new file mode 100644 index 0000000..ce1acc5 --- /dev/null +++ b/tests/test_shebang.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- + +""" +WENV +Running Python on Wine +https://github.com/pleiszenburg/wenv + + tests/test_shebang.py: Testing Python interpreter shebang alias + + Copyright (C) 2017-2022 Sebastian M. Ernst + + +The contents of this file are subject to the GNU Lesser General Public License +Version 2.1 ("LGPL" or "License"). You may not use this file except in +compliance with the License. You may obtain a copy of the License at +https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt +https://github.com/pleiszenburg/wenv/blob/master/LICENSE + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +""" + +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# IMPORT +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +from .lib import get_context, run_process, no_errors_in + +import pytest + +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# TEST(s) +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + +def test_without_shebang(): + + out, err, code = run_process(["python", "tests/shebang.py"]) + + assert code == 0 + assert no_errors_in(err) + assert "Hello from Linux!" == out.strip() + + +@pytest.mark.parametrize("arch,build", get_context()) +def test_with_shebang(arch, build): + + out, err, code = run_process( + ["./tests/shebang.py"], env={"WENV_ARCH": arch, "WENV_PYTHONVERSION": str(build)} + ) + + assert code == 0 + assert no_errors_in(err) + assert "Hello from Windows!" == out.strip() From 2e3e75b67ac2363285bc01ab14d53c9469c25732 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Tue, 23 Nov 2021 23:08:22 +0100 Subject: [PATCH 117/131] added arch to shebang tests --- tests/shebang.py | 2 +- tests/test_shebang.py | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/shebang.py b/tests/shebang.py index e62c4a8..bed718a 100755 --- a/tests/shebang.py +++ b/tests/shebang.py @@ -35,4 +35,4 @@ if __name__ == '__main__': - print(f'Hello from {platform.uname().system:s}!') + print(f'Hello from {platform.uname().system:s} on {platform.uname().machine:s}!') diff --git a/tests/test_shebang.py b/tests/test_shebang.py index ce1acc5..1989711 100644 --- a/tests/test_shebang.py +++ b/tests/test_shebang.py @@ -41,7 +41,7 @@ def test_without_shebang(): assert code == 0 assert no_errors_in(err) - assert "Hello from Linux!" == out.strip() + assert "Hello from Linux on x86_64!" == out.strip() @pytest.mark.parametrize("arch,build", get_context()) @@ -53,4 +53,7 @@ def test_with_shebang(arch, build): assert code == 0 assert no_errors_in(err) - assert "Hello from Windows!" == out.strip() + if arch == 'win32': + assert "Hello from Windows on x86!" == out.strip() + else: + assert "Hello from Windows on AMD64!" == out.strip() From f34b5fe16826300730aeeb93332d912fd982f547 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Tue, 23 Nov 2021 23:25:47 +0100 Subject: [PATCH 118/131] badges --- docs/index.rst | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 4d96016..768c0aa 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -3,7 +3,35 @@ .. image:: http://www.pleiszenburg.de/wenv_logo.png :alt: wenv -Running Python on Wine. +wenv - Running Python on Wine +============================= + +.. |build_master| image:: https://github.com/pleiszenburg/wenv/actions/workflows/test.yaml/badge.svg?branch=master + :target: https://github.com/pleiszenburg/wenv/actions/workflows/test.yaml + :alt: Documentation Status: master / release +.. |docs_master| image:: https://readthedocs.org/projects/wenv/badge/?version=latest&style=flat-square + :target: https://wenv.readthedocs.io/en/latest/ + :alt: Documentation Status: master / release +.. |license| image:: https://img.shields.io/pypi/l/wenv.svg?style=flat-square + :target: https://github.com/pleiszenburg/wenv/blob/master/LICENSE + :alt: LGPL 2.1 +.. |status| image:: https://img.shields.io/pypi/status/wenv.svg?style=flat-square + :target: https://github.com/pleiszenburg/wenv/issues + :alt: Project Development Status +.. |pypi_version| image:: https://img.shields.io/pypi/v/wenv.svg?style=flat-square + :target: https://pypi.python.org/pypi/wenv + :alt: pypi version +.. |pypi_versions| image:: https://img.shields.io/pypi/pyversions/wenv.svg?style=flat-square + :target: https://pypi.python.org/pypi/wenv + :alt: Available on PyPi - the Python Package Index +.. |chat| image:: https://img.shields.io/matrix/zugbruecke:matrix.org.svg?style=flat-square + :target: https://matrix.to/#/#zugbruecke:matrix.org + :alt: Matrix Chat Room +.. |mailing_list| image:: https://img.shields.io/badge/mailing%20list-groups.io-8cbcd1.svg?style=flat-square + :target: https://groups.io/g/zugbruecke-dev + :alt: Mailing List + +|build_master| |docs_master| |license| |status| |pypi_version| |pypi_versions| |chat| |mailing_list| User's guide ------------ From f3316c01753227d15629d1050a2bc96383875d1f Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Tue, 23 Nov 2021 23:30:36 +0100 Subject: [PATCH 119/131] badges --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b94f50f..3ef37e7 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,15 @@ -[![build_master](https://github.com/pleiszenburg/wenv/actions/workflows/test.yaml/badge.svg?branch=master "Build Status: master / release")](https://travis-ci.org/pleiszenburg/wenv) +![wenv](http://www.pleiszenburg.de/wenv_logo.png) + +# wenv - Running Python on Wine + +[![build_master](https://github.com/pleiszenburg/wenv/actions/workflows/test.yaml/badge.svg?branch=master "Build Status: master / release")](https://github.com/pleiszenburg/wenv/actions/workflows/test.yaml) [![docs_master](https://readthedocs.org/projects/wenv/badge/?version=latest&style=flat-square "Documentation Status: master / release")](https://wenv.readthedocs.io/en/latest/) -[![build_develop](https://github.com/pleiszenburg/wenv/actions/workflows/test.yaml/badge.svg?branch=develop "Build Status: development branch")](https://travis-ci.org/pleiszenburg/wenv) -[![docs_develop](https://readthedocs.org/projects/wenv/badge/?version=develop&style=flat-square "Documentation Status: development branch")](https://wenv.readthedocs.io/en/develop/) [![license](https://img.shields.io/pypi/l/wenv.svg?style=flat-square "GNU Lesser General Public License v2.1")](https://github.com/pleiszenburg/wenv/blob/master/LICENSE) [![status](https://img.shields.io/pypi/status/wenv.svg?style=flat-square "Project Development Status")](https://github.com/pleiszenburg/wenv/issues) [![pypi_version](https://img.shields.io/pypi/v/wenv.svg?style=flat-square "Project Development Status")](https://pypi.python.org/pypi/wenv) [![pypi_versions](https://img.shields.io/pypi/pyversions/wenv.svg?style=flat-square "Available on PyPi - the Python Package Index")](https://pypi.python.org/pypi/wenv) - -![wenv](http://www.pleiszenburg.de/wenv_logo.png) +[![chat](https://img.shields.io/matrix/zugbruecke:matrix.org.svg?style=flat-square "Matrix Chat Room")](https://matrix.to/#/#zugbruecke:matrix.org) +[![mailing_list](https://img.shields.io/badge/mailing%20list-groups.io-8cbcd1.svg?style=flat-square "Mailing List")](https://groups.io/g/zugbruecke-dev) ## Synopsis From 88d55f22b1f58dc8b46efb5b666cf175430cce51 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Wed, 24 Nov 2021 00:29:08 +0100 Subject: [PATCH 120/131] debug 1 --- tests/lib/output.py | 4 ++++ tests/test_pip.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/lib/output.py b/tests/lib/output.py index 586e64e..8a93e8b 100644 --- a/tests/lib/output.py +++ b/tests/lib/output.py @@ -56,6 +56,10 @@ def no_errors_in(output: str) -> bool: ): continue + if 'wine client error:' in line: + print('IGNORED:', line) + continue + return False return True diff --git a/tests/test_pip.py b/tests/test_pip.py index d8e90d2..95f3679 100644 --- a/tests/test_pip.py +++ b/tests/test_pip.py @@ -55,10 +55,10 @@ def test_pip(arch, build): out, err, code = run_process(["wenv", "pip", "list"], env={"WENV_ARCH": arch, "WENV_PYTHONVERSION": str(build)}) assert code == 0 - assert no_errors_in(err) assert "pip" in out assert "setuptools" in out assert "pytest" in out + assert no_errors_in(err) @pytest.mark.parametrize("arch,build", get_context()) From 0f9888c907a79124081f4a7ef1aa721d3609bfd1 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Wed, 24 Nov 2021 13:21:19 +0100 Subject: [PATCH 121/131] changed order --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 768c0aa..3a97ac1 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -50,8 +50,8 @@ User's guide :maxdepth: 2 :caption: Reference - configuration usage + configuration api .. toctree:: From 00e09a9878accfa1ba48aac103f6231d8847a992 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Wed, 24 Nov 2021 13:24:10 +0100 Subject: [PATCH 122/131] move version string into module; add new subcommand for showing version out of wenv --- docs/__init__.py | 24 ++++++++++++++++ docs/conf.py | 32 ++++++--------------- docs/usage.rst | 5 ++++ docs/version.py | 64 +++++++++++++++++++++++++++++++++++++++++ setup.py | 9 +++--- src/wenv/__init__.py | 2 ++ src/wenv/_core/const.py | 2 +- src/wenv/_core/env.py | 37 +++++++++++++++++------- 8 files changed, 137 insertions(+), 38 deletions(-) create mode 100644 docs/__init__.py create mode 100644 docs/version.py diff --git a/docs/__init__.py b/docs/__init__.py new file mode 100644 index 0000000..d7830ac --- /dev/null +++ b/docs/__init__.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +WENV +Running Python on Wine +https://github.com/pleiszenburg/wenv + + docs/__init__.py: Docs root + + Copyright (C) 2017-2022 Sebastian M. Ernst + + +The contents of this file are subject to the GNU Lesser General Public License +Version 2.1 ("LGPL" or "License"). You may not use this file except in +compliance with the License. You may obtain a copy of the License at +https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt +https://github.com/pleiszenburg/wenv/blob/master/LICENSE + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +""" diff --git a/docs/conf.py b/docs/conf.py index 6f577ac..56dfdd4 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -24,33 +24,19 @@ """ -def fetch_version_string(): - - f = open("../setup.py", "r") - setup_py = f.read() - f.close() - - version = "" - setup_py_lines = setup_py.split("\n") - for line in setup_py_lines: - if "_version_" in line: - version = line.split('"')[1].split('"')[0] - break - - if len(version) == 0: - raise ValueError('failed to parse version') - - return version - +# -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -# -# import os -# import sys + +import os +import sys + # sys.path.insert(0, os.path.abspath('.')) +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) +from docs.version import get_version # -- General configuration ------------------------------------------------ @@ -91,9 +77,9 @@ def fetch_version_string(): # built documents. # # The short X.Y version. -version = fetch_version_string() +version = get_version() # The full version, including alpha/beta/rc tags. -release = fetch_version_string() +release = get_version() # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/usage.rst b/docs/usage.rst index 446e5cf..ad7771e 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -48,3 +48,8 @@ This command behaves just like the regular ``pip`` command on *Unix*, except tha ---------------------- This command enables coverage analysis across the entire *Wine Python environment*. The ``coverage`` package must have been installed before running this command. + +``wenv version`` +------------- + +Shows the version of ``wenv``. diff --git a/docs/version.py b/docs/version.py new file mode 100644 index 0000000..57036e8 --- /dev/null +++ b/docs/version.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- + +""" +WENV +Running Python on Wine +https://github.com/pleiszenburg/wenv + + docs/version.py: Package version parser + + Copyright (C) 2017-2022 Sebastian M. Ernst + + +The contents of this file are subject to the GNU Lesser General Public License +Version 2.1 ("LGPL" or "License"). You may not use this file except in +compliance with the License. You may obtain a copy of the License at +https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt +https://github.com/pleiszenburg/wenv/blob/master/LICENSE + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +""" + +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# IMPORT +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +import ast +import os + +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# CONFIG +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +SRC_DIR = "src" + +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# ROUTINES +# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +def parse_version(code: str) -> str: + + tree = ast.parse(code) + + for item in tree.body: + if not isinstance(item, ast.Assign): + continue + if len(item.targets) != 1: + continue + if item.targets[0].id != "__version__": + continue + return item.value.s + +def get_version() -> str: + + path = os.path.join( + os.path.dirname(__file__), '..', SRC_DIR, "wenv", "__init__.py", + ) + + with open(path, "r", encoding="utf-8") as f: + version = parse_version(f.read()) + + return version diff --git a/setup.py b/setup.py index 229313c..b3a2a0b 100644 --- a/setup.py +++ b/setup.py @@ -31,12 +31,13 @@ from setuptools import find_packages, setup +from docs.version import get_version + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # SETUP # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -# Bump version HERE! -_version_ = "0.3.0" +__version__ = get_version() # List all versions of Python which are supported python_minor_min = 6 @@ -58,14 +59,14 @@ name="wenv", packages=find_packages("src"), package_dir={"": "src"}, - version=_version_, + version=__version__, description="Running Python on Wine", long_description=long_description, long_description_content_type="text/markdown", author="Sebastian M. Ernst", author_email="ernst@pleiszenburg.de", url="https://github.com/pleiszenburg/wenv", - download_url="https://github.com/pleiszenburg/wenv/archive/v%s.tar.gz" % _version_, + download_url="https://github.com/pleiszenburg/wenv/archive/v%s.tar.gz" % __version__, license="LGPLv2", keywords=["wine", "cross platform"], scripts=[], diff --git a/src/wenv/__init__.py b/src/wenv/__init__.py index fe20b4c..fa1d279 100644 --- a/src/wenv/__init__.py +++ b/src/wenv/__init__.py @@ -26,6 +26,8 @@ # IMPORT / EXPORT # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +__version__ = '0.3.0' # Bump version HERE! + from ._core.env import ( cli, Env, diff --git a/src/wenv/_core/const.py b/src/wenv/_core/const.py index a1a8a70..db6d9bd 100644 --- a/src/wenv/_core/const.py +++ b/src/wenv/_core/const.py @@ -41,7 +41,7 @@ # CLI HELP # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -HELP_STR = """wenv - the Wine Python environment +HELP_STR = """wenv {VERSION:s} - the Wine Python environment {CLIS:s} diff --git a/src/wenv/_core/env.py b/src/wenv/_core/env.py index 71dd96e..dfb25c9 100644 --- a/src/wenv/_core/env.py +++ b/src/wenv/_core/env.py @@ -41,6 +41,8 @@ from .source import download from .typeguard import typechecked +import wenv # HACK für version + # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # CLASS # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -576,8 +578,22 @@ def colorize(text): text = text.replace(key, c[color] + key + c["RESET"]) return text + scripts = colorize( + "\n".join( + [ + "- wenv {SCRIPT:s}".format( + SCRIPT=key, + ) + for key in sorted(self._cmd_dict.keys()) + ] + ) + ) + if len(scripts) == 0: + scripts = "(None)" + sys.stdout.write( HELP_STR.format( + VERSION=wenv.__version__, CLIS="\n".join( [ "- wenv {CLI:s}: {HELP:s}".format( @@ -587,20 +603,17 @@ def colorize(text): for key in sorted(self._cli_dict.keys()) ] ), - SCRIPTS=colorize( - "\n".join( - [ - "- wenv {SCRIPT:s}".format( - SCRIPT=key, - ) - for key in sorted(self._cmd_dict.keys()) - ] - ) - ), + SCRIPTS=scripts, ) ) sys.stdout.flush() + def _cli_version(self): + "shows wenv's version (also available as `--version`)" + + sys.stdout.write(wenv.__version__ + '\n') + sys.stdout.flush() + def cli(self): """ Command line interface entry point. Equivalent to ``wenv [...]``. Looks for sub-commands and parameters in ``sys.argv``. @@ -619,6 +632,10 @@ def cli(self): if cmd in ["-h", "--help"]: cmd = "help" + # Version exposed + if cmd == "--version": + cmd = "version" + # Special CLI command if cmd in self._cli_dict.keys(): self._cli_dict[cmd]() From b4f7b646b96428c0f7b059e926c55fd223274fa4 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Wed, 24 Nov 2021 13:24:50 +0100 Subject: [PATCH 123/131] log --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index e9b7efb..1b6186b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,7 @@ - FEATURE: Exposed parser for CPython versions, see `wenv.PythonVersion`. - FEATURE: New functions for querying available Windows Embeddable Python builds from [python.org/downloads](https://www.python.org/downloads/), see `wenv.get_available_python_builds` and `wenv.get_latest_python_build`. - FEATURE: Experimental support for ARM64 added. +- FEATURE: New command for showing wenv's version: ``wenv version`` - FIX: Wine Python can distinctly refer to and handle alpha, beta, release-candidate and stable builds of CPython. - FIX: Error handling in package listing for Wine Python environments was broken. - FIX: Python version parser could not handle Windows ARM64 builds. From 0230855128038c0b4fc4f26ccb94760541e2d950 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Wed, 24 Nov 2021 13:42:03 +0100 Subject: [PATCH 124/131] clarification on wenv subcommands --- docs/usage.rst | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/usage.rst b/docs/usage.rst index ad7771e..5d61086 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -44,12 +44,17 @@ This command behaves just like the regular ``python`` command in a *Unix* shell, This command behaves just like the regular ``pip`` command on *Unix*, except that it attempts to install *Python* packages into a dedicated *Python* environment under *Wine*. So if you need any specific packages in ``wenv python``, this is how you install them. Most packages written in pure *Python* should work just fine. Anything requiring a compiler during installation does not work. Packages / wheels with pre-compiled binary components in them might work, although this is largely untested territory. Feel free to report any (positive or negative) results. +``wenv {*}`` +------------ + +``wenv`` will automatically detect commands installed in the present environment, e.g. by third-party Python packages. Installing ``pytest`` (``wenv pip install pytest``) for instance will make it available via ``wenv pytest``. It will also be listed in the output of ``wenv help``. + ``wenv init_coverage`` ---------------------- This command enables coverage analysis across the entire *Wine Python environment*. The ``coverage`` package must have been installed before running this command. ``wenv version`` -------------- +---------------- -Shows the version of ``wenv``. +Shows the version of ``wenv``. Can also be used as follows: ``wenv --version``. From 9a6ad513e640007b60d425808ad2ffafd1e904c7 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Wed, 24 Nov 2021 14:22:43 +0100 Subject: [PATCH 125/131] fix missing key in dict export --- src/wenv/_core/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wenv/_core/config.py b/src/wenv/_core/config.py index 14f36dd..61579ab 100644 --- a/src/wenv/_core/config.py +++ b/src/wenv/_core/config.py @@ -161,7 +161,7 @@ def export_dict(self) -> Dict[str, str]: Exports a dictionary. """ - return {self[field] for field in self._KEYS} + return {field: self[field] for field in self._KEYS} def export_envvar_dict(self) -> Dict[str, str]: """ From 0c19b311214b4814e23c18106287c3b8786e1bdd Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Wed, 24 Nov 2021 14:23:25 +0100 Subject: [PATCH 126/131] clarification --- docs/examples.rst | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/examples.rst b/docs/examples.rst index 2f1145f..f256781 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -107,7 +107,7 @@ The ``wenv python`` command behaves just like the regular ``python`` command on (env) user@comp:~> wenv python -c "from platform import uname; print(uname().system)" Windows -Thanks to Wine, the handling of paths is seamless and transparent: +Thanks to Wine, the **handling of paths** is seamless and transparent: .. code:: bash @@ -116,7 +116,7 @@ Thanks to Wine, the handling of paths is seamless and transparent: (env) user@comp:~> wenv python -c "import os; print(os.getcwd())" Z:\home\user -``wenv`` can be heavily configured via configuration files and/or environment variables. In the following example, two Wine Python environments are initialized. The first environment is using the ``wenv`` default Python version, 3.7.4. The second environment is using a custom Python version, 3.10.0: +``wenv`` can be heavily configured via **configuration files and/or environment variables**. In the following example, two **Wine Python environments** are initialized. The first environment is using the ``wenv`` default Python version, 3.7.4. The second environment is using a custom Python version, 3.10.0: .. code:: bash @@ -145,7 +145,11 @@ Thanks to Wine, the handling of paths is seamless and transparent: (env) user@comp:~> WENV_PYTHONVERSION=3.10.0 wenv python --version Python 3.10.0 -For use as a shebang, ``wenv python`` has an alias. One can write ``#!/usr/bin/env _wenv_python`` at the top of scripts: +.. note:: + + *wenv* uses a somewhat unusual definition of "virtual environments" for its "Wine/Windows Python environments". *wenv* itself resides as a normal Python package within a regular Python virtual environment on the "Unix side". When ``wenv init`` is invoked, *wenv* will create a special kind of environment **underneath** the Unix Python virtual environment, by default in ``{prefix}/share/wenv/{arch}/drive_c/python-{pythonversion}/``. The parameters ``prefix``, ``arch`` and ``pythonversion`` can be :ref:`configured `. The ``prefix`` parameter defaults to ``sys.prefix``, the root of the Unix Python environment. The location can also be overwritten as a whole by setting the ``pythonprefix`` parameter. In the above example, two "Wine Python environments" are created by altering the ``pythonversion`` parameter: The first one is based on the default ``pythonversion`` of 3.7.4, the second one is user-defined ``pythonversion`` of 3.10.0. + +For use as a **shebang**, ``wenv python`` has an alias. One can write ``#!/usr/bin/env _wenv_python`` at the top of scripts: .. code:: python From 54b81da05d75e1a48058ee3305781e39bee8b9f6 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Wed, 24 Nov 2021 14:28:26 +0100 Subject: [PATCH 127/131] show env version --- .github/workflows/test.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index ceb80a9..d013fd1 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -44,6 +44,7 @@ jobs: pytest --version uname -a lsb_release -a + wenv --version - name: Build docs run: | make docs From 0d896db4a2c522a3614cf2b6b6cdfd9f95350e91 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Wed, 24 Nov 2021 14:31:08 +0100 Subject: [PATCH 128/131] make tests run monthly --- .github/workflows/test.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index d013fd1..c0fd4a2 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -1,6 +1,9 @@ name: wenv test suite -on: [push] +on: + - push + - schedule: + - cron: '30 9 7 * *' jobs: build: From 072181022a87fa5c20a8e09c275c87f6372321fc Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Wed, 24 Nov 2021 14:32:27 +0100 Subject: [PATCH 129/131] syntax --- .github/workflows/test.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index c0fd4a2..e24c1a8 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -1,8 +1,8 @@ name: wenv test suite on: - - push - - schedule: + push + schedule: - cron: '30 9 7 * *' jobs: From 933f91e2a1353e280070d0692b2bfb8d4d4f0c61 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Wed, 24 Nov 2021 14:35:20 +0100 Subject: [PATCH 130/131] syntax 2 --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index e24c1a8..6b0b1be 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -1,7 +1,7 @@ name: wenv test suite on: - push + push: {} schedule: - cron: '30 9 7 * *' From 010eee9baa91c1bd66f3c3ccf009452901f0f016 Mon Sep 17 00:00:00 2001 From: "Sebastian M. Ernst" Date: Wed, 24 Nov 2021 15:09:08 +0100 Subject: [PATCH 131/131] prepare release --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 1b6186b..7792648 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## 0.3.0 (2021-XX-XX) +## 0.3.0 (2021-11-24) - FEATURE: Added support for Python 3.9 and 3.10. - FEATURE: Dropped support for Python 3.4 and 3.5.