├── .github └── workflows │ ├── fedora-legacy.yml │ └── tox.yml ├── .gitignore ├── CHANGELOG ├── Dockerfile ├── LICENSE ├── MANIFEST.in ├── README.md ├── mybin.py ├── pyp2rpm.1 ├── pyp2rpm ├── __init__.py ├── archive.py ├── bin.py ├── command │ ├── __init__.py │ └── extract_dist.py ├── convertor.py ├── dependency_convert.py ├── dependency_parser.py ├── exceptions.py ├── filters.py ├── logger.py ├── metadata_extractors.py ├── module_runners.py ├── name_convertor.py ├── package_data.py ├── package_getters.py ├── settings.py ├── templates │ ├── epel6.spec │ ├── epel7.spec │ ├── fedora.spec │ ├── macros.spec │ ├── mageia.spec │ └── pld.spec ├── utils.py ├── version.py └── virtualenv.py ├── setup.cfg ├── setup.py ├── tests ├── test_archive.py ├── test_convertor.py ├── test_data │ ├── LICENSE │ ├── Sphinx-1.1.3-py2.6.egg │ ├── bitarray-0.8.0.tar.gz │ ├── coverage_pth-0.0.1.tar.gz │ ├── django.json │ ├── isholiday-0.1 │ │ ├── PKG-INFO │ │ ├── isholiday.py │ │ ├── setup.cfg │ │ └── setup.py │ ├── netjsonconfig-0.5.1.tar.gz │ ├── pkginfo-1.2b1.tar.gz │ ├── plumbum-0.9.0.tar.gz │ ├── py2exe-0.9.2.2-py33.py34-none-any.whl │ ├── pytest-2.2.3.zip │ ├── python-Jinja2_dnfnc.spec │ ├── python-Jinja2_epel6_dnfnc.spec │ ├── python-Jinja2_epel6_nc.spec │ ├── python-Jinja2_epel7_dnfnc.spec │ ├── python-Jinja2_epel7_nc.spec │ ├── python-Jinja2_mageia_py23.spec │ ├── python-Jinja2_nc.spec │ ├── python-Jinja2_py23_autonc.spec │ ├── python-Jinja2_py2_autonc.spec │ ├── python-Jinja2_py3_autonc.spec │ ├── python-paperwork-backend.spec │ ├── python-sphinx_autonc.spec │ ├── python-utest.spec │ ├── python-utest_epel7.spec │ ├── restsh-0.1.tar.gz │ ├── setuptools-19.6-py2.py3-none-any.whl │ ├── unextractable-1.tar │ ├── utest │ │ ├── setup.py │ │ └── utest │ │ │ └── __init__.py │ └── versiontools-1.9.1.tar.gz ├── test_dependency_parser.py ├── test_extract_distribution.py ├── test_filters.py ├── test_integration.py ├── test_metadata_extractors.py ├── test_name_convertor.py ├── test_package_data.py ├── test_package_getters.py ├── test_utils.py └── test_virtualenv.py └── tox.ini /.github/workflows/fedora-legacy.yml: -------------------------------------------------------------------------------- 1 | name: Run tests in a legacy environment 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | fedora_test: 9 | runs-on: ubuntu-latest 10 | container: 11 | image: fedora:${{ matrix.release }} 12 | steps: 13 | - name: Check out repository code 14 | uses: actions/checkout@v2 15 | 16 | - name: Install dependencies 17 | run: | 18 | dnf install -y --setopt=install_weak_deps=false --setopt=tsflags=nodocs --setopt=deltarpm=false --disablerepo=\*modular gcc tox python27 python34 python3-setuptools 19 | 20 | - name: Test older python releases 21 | run: | 22 | PIP_NO_CACHE_DIR=off LC_ALL=C.UTF-8 LANG=C.UTF-8 tox -e py27,py34 23 | 24 | strategy: 25 | matrix: 26 | release: [30] 27 | -------------------------------------------------------------------------------- /.github/workflows/tox.yml: -------------------------------------------------------------------------------- 1 | name: Run tox with various Python versions 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | tox_test: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Check out repository code 12 | uses: actions/checkout@v2 13 | 14 | - name: Run Tox tests 15 | uses: fedora-python/tox-github-action@main 16 | with: 17 | tox_env: ${{ matrix.tox_env }} 18 | dnf_install: python3-wheel ${{ matrix.dnf_install }} 19 | 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | tox_env: [py36, py37, py38, py39, py310] 24 | include: 25 | # Python 3.10 needs to compile cffi 26 | - tox_env: py310 27 | dnf_install: libffi-devel 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.mo 2 | *.pyc 3 | *.swo 4 | *.swp 5 | *.swn 6 | *~ 7 | build/ 8 | dist/ 9 | pyp2rpm.egg-info 10 | MANIFEST 11 | .tox/ 12 | .cache/ 13 | .eggs/ 14 | tests/test_output/ 15 | tests/test_data/utest-*.tar.gz 16 | tests/test_data/*/*.egg-info 17 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | 3.3.10 2 | * Fix tests on Python 3.12 3 | 4 | 3.3.9 5 | * Fix tests when using setuptools >= 67 (issue #290) 6 | * Use sphinx-build-%{python3_version} on EPEL7 (issue #272) 7 | 8 | 3.3.8 9 | * Sync dependency conversion with pyreq2rpm 10 | 11 | 3.3.7 12 | * Ensure that all tests pass under Python 3.10 13 | * Include rc/post/dev in version (issue #259) 14 | * Handle all-zero versions without crashing 15 | * Don't process PyPI version list if user specifies a version (issue #243) 16 | * Ensure that sdist is complete, and utest and JSON test data are included 17 | * Wait for rpmdev-setuptree to exit (issue #266) 18 | 19 | 3.3.6 20 | * Improve logging of rpmbuild output (issue #252) 21 | * Sort releases so that the API ordering no longer affects local behavior (issue #251) 22 | 23 | 3.3.5 24 | * Replace PyPI XML-RPC client with JSON client. 25 | 26 | 3.3.4 27 | * Exclude prereleases unless specifically selected (issue #141) 28 | * Improve handling of PEP 440 requirements (issue #182) 29 | * Avoid unnecessary PyPI downloads 30 | * Avoid duplicate packages (issue #178) 31 | * Exclude *.egg_info from the list of packages 32 | * Use the new %pypi_source macro for Source URL (issue #172) 33 | 34 | 3.3.3 35 | * Work around an error that occurred when setup.py contained print() calls 36 | * Use the pythonX_version macro instead of "?.?" in pth and egg-info paths 37 | 38 | 3.3.2 39 | * Make Python 3 the only default Python version for Fedora and Mageia (issue #163) 40 | 41 | 3.3.1 42 | * Rebase Mageia template based on latest Python Packaging Policy (thanks @Conan-Kudo for the PR) 43 | * Bug fixes (issue #159) 44 | 45 | 3.3.0 46 | * Use name convertor based on Automatic PyPI provides as default for Fedora 47 | * Use the warehouse installation URL for XMLRPC client for PyPI 48 | * Make Python 3 default python version for Fedora 49 | * Stop creating -MAJOR.MINOR suffixed executables for each python version 50 | 51 | 3.2.3 52 | * Make order of requires, scripts, modules and packages deterministic 53 | * Add name convertor using standardized name format of the dependencies based on virtual Provides 54 | * Update templates to follow the latest Packaging Guidelines for Python 55 | * Include extras require in metadata extraction 56 | * Enable generating sclized spec files using spec2scl 57 | 58 | 3.2.2 59 | * Fix package rename option (-r), issue #87 (thanks to Sorin Sbarnea for reporting) 60 | * Improve detection of packages and modules, issue #86 (thanks to Joe Mullally for reporting) 61 | * Abide by the latest packaging guidelines (use %{summary} macro and improve creating soft links for executables) 62 | * Use python version provided with -b2 option to extract metadata, issue #90 63 | * Drop Python 2.6 support 64 | * Additional various bug fixes (issues #95, #96, #97) 65 | 66 | 3.2.1 67 | * Update MANIFEST.in 68 | 69 | 3.2.0 70 | * Refactoring of extract_dist command and metadata_extractors 71 | * Drop .egg archives support in SetupPyMetadataExtractor 72 | * Remove redirection of streams to log 73 | * Add epel6 and epel7 templates 74 | 75 | 3.1.3 76 | * Move Licenses from %doc to %license 77 | * Update pld template 78 | * Add webtest marker 79 | * Bugfixes, minor enhancements 80 | 81 | 3.1.2 82 | * Use https in PyPI URL instead of http, issue #54 83 | * Improve description processing 84 | * Add filter of sphinx dependency for non-base versions 85 | 86 | 3.1.1 87 | * Add man page file to MANIFEST.in 88 | * Necessary to bump version because of PyPI release 89 | 90 | 3.1.0 91 | * Add DandifiedNameConvertor based on DNF API queries 92 | * Add support of PyPI's new URL format 93 | * Add man page 94 | * Fix bugs found during the analysis of automated builds from PyPI in Copr build system 95 | 96 | 3.0.2 97 | * Make virtualenv-api an optional require 98 | * Fix console logger issue 99 | 100 | 3.0.1 101 | * Prevent double occurance of python-setuptools in Build Requires 102 | * Fix encoding of converted string in Python 2, issue #28 103 | 104 | 3.0.0 105 | * Metadata extraction from setup.py based on distutils command 106 | * Completely independent wheel metadata extractor 107 | * Improves scripts and site-packages metadata extraction using virtualenv 108 | * Python version extraction 109 | 110 | 2.0.0 111 | * Added default python3 subpackage, testing tools tox and travis 112 | * Small change in command line switches 113 | * Improved documentation 114 | * Updated fedora template to comply with newest packaging guidelines 115 | * Old template renamed to fedora_subdirs.spec 116 | 117 | 1.1.2 118 | * Use python2-devel instead of python-devel 119 | * Support Python2.6 120 | * Correctly handle specialcasing; pyfoo packages are now python-pyfoo in fedora 121 | * Auto add setuptools as requires if entry points are used 122 | * Additional various bug fixes 123 | 124 | 1.1.1 125 | * Brings back Python 2 support issue #12. 126 | * Change python-sphinx-build to sphinx-build. 127 | * Add support for build SRPMs (see --help). 128 | * Logging redone. 129 | * Introduce basic support for http proxy (see --help) #14. 130 | 131 | 1.1.0 132 | * New maintainer 133 | * Pyp2rpm now only supports Python 3 134 | * Few changes to code, refactoring 135 | * Added logging 136 | * Experimental support for Wheel metadata extraction in pyp2rpm.metadata_extractors._WheelMetadataExtractor 137 | * Fixed issues #8, #7, #6, #4 and also rhbzs #1056800, #1036046, #1079576 138 | 139 | 1.0.1 140 | * Handle docs in subdirectories. (thanks to Joseph Wang) 141 | 142 | 1.0.0 143 | * When we cannot figure the version of a license (such as ASL), make it more obvious, not looking like forgotten interpolation. (thanks to Pádraig Brady for suggestion) 144 | * Rework of some internals, pyp2rpmlib moved to pyp2rpm, auto-generated binary /usr/bin/pyp2rpm is now used. 145 | * Truncate description, when it is too long. 146 | * Allow specifying according to which distro rules to convert. 147 | * Some mageia specific name conversions. 148 | * More robustness for cases when the distribution is not available on pypi in the same versionas the provided source archive is. (thanks to Tomas Mlcoch for reporting) 149 | 150 | - 0.5.2 151 | * Mageia spec template (thanks to Joseph Wang) 152 | * Various fixes to import robustness (thanks to Joseph Wang) 153 | * The URL tag in specfile should in fact be home_page from pypi xmlrpc. 154 | * Catch exception when list_argument in setup.py is unparsable. 155 | 156 | 0.5.1 157 | * Use %{pypi_name} and %{version} in Source URL. 158 | * Handle 'scripts' setup.py argument. 159 | * pyp2rpm is now compatible with Python 3. 160 | * If original package name contains '-', it is usually replaced by '_' in directory name, fixed in %files section. 161 | 162 | - 0.5.0 163 | * Various small bug fixes. 164 | * List __pycache__ with pure Python 3 packages that contain py_modules. 165 | * Add support for pure Python 3 specfiles. 166 | * Refactor templates using macros. 167 | * Add listing py_modules from setup.py setup() in %files (thanks to Pádraig Brady for pointing this direction). 168 | * Fix encoding issues (thanks to Pádraig Brady). 169 | 170 | - 0.4.2 171 | * Enable searching for sphinx documentation and generating it. 172 | * Enable searching archive for directories according to given re. 173 | * Moved monkey patching of ZipFile to not interfere with setup.py install (thanks to 174 | Konstantin Zemlyak). 175 | 176 | - 0.4.1 177 | * Some minor fixes. 178 | * Temporary use distutils in setup.py to overcome issues with installation. 179 | 180 | - 0.4.0 181 | * Add functionality to search for doc files and put them into %doc. 182 | * Enable searching archive for files according to given re. 183 | * Extracted archive handling to a class. 184 | * Archives can now be searched for files by full path (internal thing only). 185 | 186 | - 0.3.1 187 | * Put the 'Created ...' header in templates - taking some credits here ;) (Added version file for that.) 188 | * Some minor fixes in templates (no functionality affected). 189 | 190 | - 0.3.0 191 | * More tests, mainly for PyPI functionality (mocked, do not need net connection). 192 | * The -n parameter is no longer required for local sources. 193 | * Choosing templates now work (either relative or absolute path). 194 | 195 | - 0.2.0 196 | * Minor bug fixes. 197 | * Support multiple python versions in one template (e.g. %{?with_python3}). 198 | * Check that the package exists on PyPI when getting source. 199 | 200 | - 0.1.0 201 | * Initial release. 202 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM fedora:rawhide 2 | 3 | ENV PIP_NO_CACHE_DIR=off \ 4 | LC_ALL=C.UTF-8 \ 5 | LANG=C.UTF-8 6 | 7 | LABEL summary="Image for running automatic tests of pyp2rpm in Travis CI" \ 8 | name="pyp2rpm-tests" \ 9 | maintainer="Michal Cyprian " 10 | 11 | RUN INSTALL_PKGS="gcc tox python3.6 python3.7 python3.8 python3.9 python3.10 \ 12 | python3-setuptools python3-pip" && \ 13 | dnf -y install --setopt=install_weak_deps=false --setopt=tsflags=nodocs \ 14 | --setopt=deltarpm=false $INSTALL_PKGS && \ 15 | dnf clean all 16 | 17 | CMD ["/usr/bin/tox"] 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Bohuslav Kabrda 2 | Copyright (c) 2012 Red Hat, Inc. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included 13 | in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 19 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 20 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 21 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | exclude .git* 2 | exclude .git*/** 3 | include CHANGELOG 4 | include LICENSE 5 | include README.md 6 | include pyp2rpm/templates/* 7 | include tests/*.py 8 | include tests/test_data/*.spec 9 | include tests/test_data/*.gz 10 | include tests/test_data/*.whl 11 | include tests/test_data/*.zip 12 | include tests/test_data/*.egg 13 | include tests/test_data/*.json 14 | exclude tests/test_data/isholiday*.gz 15 | include tests/test_data/isholiday-0.1/*.py 16 | include tests/test_data/isholiday-0.1/setup.cfg 17 | include tests/test_data/isholiday-0.1/PKG-INFO 18 | exclude tests/test_data/utest*.gz 19 | include tests/test_data/utest/*.py 20 | include tests/test_data/utest/utest/*.py 21 | include tests/test_data/LICENSE 22 | include pyp2rpm.1 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ![Logo](https://rkuska.fedorapeople.org/pyp2rpm_large.png) 3 | 4 | pyp2rpm 5 | ======= 6 | 7 | A tool to convert a PyPI package to an RPM `SPECFILE` or to generate an SRPM. 8 | Under heavy development, see the TODO file for a list of planned features. 9 | pyp2rpm currently ships with Fedora and Mageia specific templates. 10 | 11 | ## Usage 12 | 13 | The simplest use case is to run: 14 | ```sh 15 | pyp2rpm package_name 16 | ``` 17 | 18 | This downloads the package from PyPI and outputs the RPM `SPECFILE`. 19 | 20 | Or: 21 | ```sh 22 | pyp2rpm package_name --srpm 23 | ``` 24 | 25 | This downloads the package from PyPI and creates a SRPM file. 26 | 27 | All of the `pyp2rpm` options are: 28 | 29 | $ pyp2rpm -h 30 | 31 | usage: pyp2rpm [-h] [-v VERSION] [-d SAVE_DIR] [-r RPM_NAME] 32 | [-t TEMPLATE] [-o DISTRO] [-b BASE_PYTHON] 33 | [-p PYTHON_VERSION] [--srpm] [--proxy PROXY] PACKAGE 34 | 35 | Convert PyPI package to RPM specfile or SRPM. 36 | 37 | Arguments: 38 | PACKAGE Provide PyPI name of the package or path to compressed 39 | source file. 40 | 41 | Options: 42 | -t TEMPLATE Template file (jinja2 format) to render 43 | (default: "fedora").Search order is 1) 44 | filesystem, 2) default templates. 45 | -o [fedora|epel7|epel6|mageia|pld] 46 | Default distro whose conversion rules to use 47 | (default:"fedora"). Default templates have 48 | their rules associated and ignore this. 49 | -b BASE_PYTHON Base Python version to package for (fedora 50 | default: "3"). 51 | -p PYTHON_VERSIONS Additional Python versions to include in the 52 | specfile (e.g -p2 for python2 subpackage). 53 | Can be specified multiple times. Specify 54 | additional version or use -b explicitly to 55 | disable default. 56 | -s Spec file ~/rpmbuild/SPECS/python-package_name.spec 57 | will be created (default: 58 | prints spec file to stdout). 59 | --srpm When used pyp2rpm will produce srpm instead 60 | of printing specfile into stdout. 61 | --proxy PROXY Specify proxy in the form proxy.server:port. 62 | -r RPM_NAME Name of rpm package (overrides calculated 63 | name). 64 | -d SAVE_PATH Specify where to save package file, specfile 65 | and generated SRPM (default: 66 | "/home/mcyprian/rpmbuild"). 67 | -v VERSION Version of the package to download (ignored 68 | for local files). 69 | --venv / --no-venv Enable / disable metadata extraction from 70 | virtualenv (default: enabled). 71 | --autonc / --no-autonc Enable / disable using automatic provides 72 | with a standardized name in dependencies 73 | declaration (default: disabled). 74 | --sclize Convert tags and macro definitions to SCL-style 75 | using `spec2scl` module. NOTE: SCL 76 | related options can be provided alongside 77 | this option. 78 | -h, --help Show this message and exit. 79 | 80 | SCL related options: 81 | --no-meta-runtime-dep Don't add the runtime dependency on the scl 82 | runtime package. 83 | --no-meta-buildtime-dep Don't add the buildtime dependency on the scl 84 | runtime package. 85 | --skip-functions FUNCTIONS Comma separated list of transformer functions to 86 | skip. 87 | --no-deps-convert Don't convert dependency tags (mutually 88 | exclusive with --list-file). 89 | --list-file FILE_NAME List of the packages/provides, that will be in 90 | the SCL (to convert Requires/BuildRequires 91 | properly). Lines in the file are in form of 92 | "pkg-name %%{?custom_prefix}", where the prefix 93 | part is optional. 94 | 95 | 96 | To run the unit tests, cd into the checked out directory and run: 97 | ```sh 98 | PYTHONPATH="$(pwd)" py.test 99 | ``` 100 | 101 | or run: 102 | ```sh 103 | python setup.py test 104 | ``` 105 | 106 | 107 | ## Example usage 108 | 109 | ![alt tag](https://mcyprian.fedorapeople.org/pyp2rpm_guide.gif 110 | "Record of pyp2rpm usage") 111 | 112 | ## Contributing 113 | 114 | We will gladly accept any pull request or feature request. 115 | With complex pull requests, please include unit tests in *pytest* and use *flexmock* if you need mocking. 116 | 117 | Tests can be run in a local container: 118 | 119 | ```sh 120 | docker build -t pyp2rpm-test . 121 | docker run -v "$(pwd):$(pwd):z" -w "$(pwd)" -it pyp2rpm-test 122 | ``` 123 | 124 | pyp2rpm is licensed under the MIT/Expat license. 125 | -------------------------------------------------------------------------------- /mybin.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | from pyp2rpm.bin import main 4 | 5 | main() 6 | -------------------------------------------------------------------------------- /pyp2rpm.1: -------------------------------------------------------------------------------- 1 | .TH pyp2rpm(1) 2 | 3 | .SH NAME 4 | .B pyp2rpm 5 | Tool which converts a package from PyPI to RPM specfile or SRPM. 6 | 7 | .SH SYNOPSIS 8 | .B mybin.py 9 | [\fI\,OPTIONS\/\fR] \fI\,PACKAGE\/\fR 10 | 11 | 12 | .SH ARGUMENTS 13 | .B PACKAGE 14 | PyPI name of the package or path to compressed source file. 15 | 16 | .SH DESCRIPTION 17 | Tool convert Python package to RPM SPECFILES. The package can be download from PyPI or from the local filesystem and the produced SPEC is in line with Fedora packaging Guidelines or Mageia Python Policy. 18 | .PP 19 | Users can provide their own templates for rendering the package metadata. Both the package source and metadata can be extracted from PyPI or from local filesystem (local file doesn't provide that much information though). 20 | 21 | .SH OPTIONS 22 | .TP 23 | .B "\-t \-\-TEMPLATE" 24 | Template file (jinja2 format) to render (default: "fedora"). 25 | Search order is: 1) filesystem, 2) default templates. 26 | .TP 27 | .B "\-o \-\-DISTRO" 28 | Default distro whose conversion rules to use (default: "fedora"). Default templates have their rules associated and ignore this. 29 | .TP 30 | .B "\-b \-\-BASE_PYTHON" 31 | Base Python version to package for (fedora default: "3"). 32 | .TP 33 | .B "\-p \-\-PYTHON_VERSIONS" 34 | Additional Python versions to include in the specfile (e.g. -p2 for python2 subpackage). Can be specified multiple times. Specify additional version or use -b explicitly to disable default. 35 | .TP 36 | .B "\-s \" 37 | Spec file ~/rpmbuild/SPECS/python-package_name.spec will be created (default: prints spec file to stdout). 38 | .TP 39 | .B "\--srpm \ " 40 | When used pyp2rpm will produce srpm instead of printing specfile into stdout. 41 | .TP 42 | .B "\--proxy \-\-PROXY" 43 | Specify proxy in the form proxy.server:port. 44 | .TP 45 | .B "\-r \-\-RPM_NAME" 46 | Name of rpm package (overrides calculated name). 47 | .TP 48 | .B "\-d \-\-SAVE_DIR" 49 | Specify where to save package file, specfile and generated SRPM (default: "~/rpmbuild"). 50 | .TP 51 | .B "\-v \-\-VERSION" 52 | Version of the package to download (ignored for local files). 53 | .TP 54 | .B "\--venv / --no-venv \" 55 | Enable / disable metadata extraction from virtualenv. 56 | .TP 57 | .B "\--autonc/ --no-autonc\" 58 | Enable / disable using automatic provides with a standardized name in dependencies declaration. 59 | .TP 60 | \fB\-\-sclize\fR 61 | Convert tags and macro definitions to SCL\-style 62 | using `spec2scl` module. NOTE: SCL related options 63 | can be provided alongside this option. 64 | .TP 65 | .B "\-h , --help\" 66 | show this help message and exit. 67 | .SS "SCL related options:" 68 | .TP 69 | \fB\-\-no\-meta\-runtime\-dep\fR 70 | Don't add the runtime dependency on the scl 71 | runtime package. 72 | .TP 73 | \fB\-\-no\-meta\-buildtime\-dep\fR 74 | Don't add the buildtime dependency on the scl 75 | runtime package. 76 | .TP 77 | \fB\-\-skip\-functions\fR FUNCTIONS 78 | Comma separated list of transformer functions to 79 | skip. 80 | .TP 81 | \fB\-\-no\-deps\-convert\fR 82 | Don't convert dependency tags (mutually 83 | exclusive with \fB\-\-list\-file\fR). 84 | .TP 85 | \fB\-\-list\-file\fR FILE_NAME 86 | List of the packages/provides, that will be in 87 | the SCL (to convert Requires/BuildRequires 88 | properly). Lines in the file are in form of 89 | "pkg\-name %%{?custom_prefix}", where the prefix 90 | part is optional. 91 | 92 | 93 | .SH EXAMPLES 94 | .TP 95 | .B pyp2rpm [PACKAGE] 96 | Create the SPECFILE 97 | .TP 98 | .B pyp2rpm --srpm [PACKAGE] 99 | Create source RPM package 100 | .TP 101 | .B pyp2rpm -b 2 -p 3 [PACKAGE] 102 | Generate a SPECFILE for python2 and python3 103 | .TP 104 | .B pyp2rpm -b 3 [PACKAGE] 105 | Generate a SPECFILE for python3 106 | .TP 107 | .B pyp2rpm ~/rpmbuild/SOURCES/python_package_name.tar.gz 108 | Pick up package from the local filesystem. 109 | 110 | 111 | 112 | 113 | 114 | .SH LANGUAGE 115 | PYTHON 116 | 117 | .SH LICENCE 118 | MIT 119 | 120 | .SH AUTHOR 121 | Writen by Bohuslav "Slavek" Kabrda, Robert Kuska, Michal Cyprian, Iryna Shcherbina 122 | -------------------------------------------------------------------------------- /pyp2rpm/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | main_dir = os.path.dirname(os.path.dirname(__file__)) 4 | -------------------------------------------------------------------------------- /pyp2rpm/command/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-python/pyp2rpm/92399daa509d2769433f97cf7f07209f738139a2/pyp2rpm/command/__init__.py -------------------------------------------------------------------------------- /pyp2rpm/command/extract_dist.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import json 3 | from distutils.core import Command 4 | 5 | 6 | class extract_dist(Command): 7 | """Custom distutils command to extract metadata form setup function.""" 8 | description = ("Assigns self.distribution to class attribute to make " 9 | "it accessible from outside a class.") 10 | user_options = [('stdout', None, 11 | 'print metadata in json format to stdout')] 12 | class_metadata = None 13 | 14 | def __init__(self, *args, **kwargs): 15 | """Metadata dictionary is created, all the metadata attributes, 16 | that were not found are set to default empty values. Checks of data 17 | types are performed. 18 | """ 19 | Command.__init__(self, *args, **kwargs) 20 | 21 | self.metadata = {} 22 | 23 | for attr in ['setup_requires', 'tests_require', 'install_requires', 24 | 'packages', 'py_modules', 'scripts']: 25 | self.metadata[attr] = to_list(getattr(self.distribution, attr, [])) 26 | 27 | try: 28 | for k, v in getattr( 29 | self.distribution, 'extras_require', {}).items(): 30 | if k in ['test, docs', 'doc', 'dev']: 31 | attr = 'setup_requires' 32 | else: 33 | attr = 'install_requires' 34 | self.metadata[attr] += to_list(v) 35 | except (AttributeError, ValueError): 36 | # extras require are skipped in case of wrong data format 37 | # can't log here, because this file is executed in a subprocess 38 | pass 39 | 40 | for attr in ['url', 'long_description', 'description', 'license']: 41 | self.metadata[attr] = to_str( 42 | getattr(self.distribution.metadata, attr, None)) 43 | 44 | self.metadata['classifiers'] = to_list( 45 | getattr(self.distribution.metadata, 'classifiers', [])) 46 | 47 | if isinstance(getattr(self.distribution, "entry_points", None), dict): 48 | self.metadata['entry_points'] = self.distribution.entry_points 49 | else: 50 | self.metadata['entry_points'] = None 51 | 52 | self.metadata['test_suite'] = getattr( 53 | self.distribution, "test_suite", None) is not None 54 | 55 | def initialize_options(self): 56 | """Sets default value of the stdout option.""" 57 | self.stdout = False 58 | 59 | def finalize_options(self): 60 | """Abstract method of Command class have to be overridden.""" 61 | pass 62 | 63 | def run(self): 64 | """Sends extracted metadata in json format to stdout if stdout 65 | option is specified, assigns metadata dictionary to class_metadata 66 | variable otherwise. 67 | """ 68 | if self.stdout: 69 | sys.stdout.write("extracted json data:\n" + json.dumps( 70 | self.metadata, default=to_str) + "\n") 71 | else: 72 | extract_dist.class_metadata = self.metadata 73 | 74 | 75 | def to_list(var): 76 | """Checks if given value is a list, tries to convert, if it is not.""" 77 | if var is None: 78 | return [] 79 | if isinstance(var, str): 80 | var = var.split('\n') 81 | elif not isinstance(var, list): 82 | try: 83 | var = list(var) 84 | except TypeError: 85 | raise ValueError("{} cannot be converted to the list.".format(var)) 86 | return var 87 | 88 | 89 | def to_str(var): 90 | """Similar to to_list function, but for string attributes.""" 91 | try: 92 | return str(var) 93 | except TypeError: 94 | raise ValueError("{} cannot be converted to string.".format(var)) 95 | -------------------------------------------------------------------------------- /pyp2rpm/dependency_convert.py: -------------------------------------------------------------------------------- 1 | from pkg_resources import parse_version 2 | 3 | class RpmVersion(): 4 | def __init__(self, version_id): 5 | version = parse_version(version_id) 6 | if isinstance(version._version, str): 7 | self.version = version._version 8 | else: 9 | self.epoch = version._version.epoch 10 | self.version = list(version._version.release) 11 | self.pre = version._version.pre 12 | self.dev = version._version.dev 13 | self.post = version._version.post 14 | # version.local is ignored as it is not expected to appear 15 | # in public releases 16 | # https://www.python.org/dev/peps/pep-0440/#local-version-identifiers 17 | 18 | def is_legacy(self): 19 | return isinstance(self.version, str) 20 | 21 | def increment(self): 22 | self.version[-1] += 1 23 | self.pre = None 24 | self.dev = None 25 | self.post = None 26 | return self 27 | 28 | def __str__(self): 29 | if self.is_legacy(): 30 | return self.version 31 | if self.epoch: 32 | rpm_epoch = str(self.epoch) + ':' 33 | else: 34 | rpm_epoch = '' 35 | while len(self.version) > 1 and self.version[-1] == 0: 36 | self.version.pop() 37 | rpm_version = '.'.join(str(x) for x in self.version) 38 | if self.pre: 39 | rpm_suffix = '~{}'.format(''.join(str(x) for x in self.pre)) 40 | elif self.dev: 41 | rpm_suffix = '~~{}'.format(''.join(str(x) for x in self.dev)) 42 | elif self.post: 43 | rpm_suffix = '^post{}'.format(self.post[1]) 44 | else: 45 | rpm_suffix = '' 46 | return '{}{}{}'.format(rpm_epoch, rpm_version, rpm_suffix) 47 | 48 | def convert_compatible(name, operator, version_id): 49 | if version_id.endswith('.*'): 50 | return 'Invalid version' 51 | version = RpmVersion(version_id) 52 | if version.is_legacy(): 53 | # LegacyVersions are not supported in this context 54 | return 'Invalid version' 55 | if len(version.version) == 1: 56 | return 'Invalid version' 57 | upper_version = RpmVersion(version_id) 58 | upper_version.version.pop() 59 | upper_version.increment() 60 | return '({{name}} >= {} with {{name}} < {})'.format( 61 | version, upper_version) 62 | 63 | def convert_equal(name, operator, version_id): 64 | if version_id.endswith('.*'): 65 | version_id = version_id[:-2] + '.0' 66 | return convert_compatible(name, '~=', version_id) 67 | version = RpmVersion(version_id) 68 | return '{{name}} = {}'.format(version) 69 | 70 | def convert_arbitrary_equal(name, operator, version_id): 71 | if version_id.endswith('.*'): 72 | return 'Invalid version' 73 | version = RpmVersion(version_id) 74 | return '{{name}} = {}'.format(version) 75 | 76 | def convert_not_equal(name, operator, version_id): 77 | if version_id.endswith('.*'): 78 | version_id = version_id[:-2] 79 | version = RpmVersion(version_id) 80 | if version.is_legacy(): 81 | # LegacyVersions are not supported in this context 82 | return 'Invalid version' 83 | version_gt = RpmVersion(version_id).increment() 84 | version_gt_operator = '>=' 85 | # Prevent dev and pre-releases from satisfying a < requirement 86 | version = '{}~~'.format(version) 87 | else: 88 | version = RpmVersion(version_id) 89 | version_gt = version 90 | version_gt_operator = '>' 91 | return '({{name}} < {} or {{name}} {} {})'.format( 92 | version, version_gt_operator, version_gt) 93 | 94 | def convert_ordered(name, operator, version_id): 95 | if version_id.endswith('.*'): 96 | # PEP 440 does not define semantics for prefix matching 97 | # with ordered comparisons 98 | # see: https://github.com/pypa/packaging/issues/320 99 | # and: https://github.com/pypa/packaging/issues/321 100 | # This style of specifier is officially "unsupported", 101 | # even though it is processed. Support may be removed 102 | # in version 21.0. 103 | version_id = version_id[:-2] 104 | version = RpmVersion(version_id) 105 | if operator == '>': 106 | # distutils will allow a prefix match with '>' 107 | operator = '>=' 108 | if operator == '<=': 109 | # distutils will not allow a prefix match with '<=' 110 | operator = '<' 111 | else: 112 | version = RpmVersion(version_id) 113 | # For backwards compatibility, fallback to previous behavior with LegacyVersions 114 | if not version.is_legacy(): 115 | # Prevent dev and pre-releases from satisfying a < requirement 116 | if operator == '<' and not version.pre and not version.dev and not version.post: 117 | version = '{}~~'.format(version) 118 | # Prevent post-releases from satisfying a > requirement 119 | if operator == '>' and not version.pre and not version.dev and not version.post: 120 | version = '{}.0'.format(version) 121 | return '{{name}} {} {}'.format(operator, version) 122 | 123 | def legacy_convert_compatible(name, operator, version_id): 124 | if version_id.endswith('.*'): 125 | return 'Invalid version' 126 | version = RpmVersion(version_id) 127 | if version.is_legacy(): 128 | # LegacyVersions are not supported in this context 129 | return 'Invalid version' 130 | if len(version.version) == 1: 131 | return 'Invalid version' 132 | upper_version = RpmVersion(version_id) 133 | upper_version.version.pop() 134 | upper_version.increment() 135 | return ('{{name}} >= {}'.format(version), 136 | '{{name}} < {}'.format(upper_version)) 137 | 138 | OPERATORS = {'~=': convert_compatible, 139 | '==': convert_equal, 140 | '===': convert_arbitrary_equal, 141 | '!=': convert_not_equal, 142 | '<=': convert_ordered, 143 | '<': convert_ordered, 144 | '>=': convert_ordered, 145 | '>': convert_ordered} 146 | 147 | LEGACY_OPERATORS = {'~=': legacy_convert_compatible, 148 | '==': convert_equal, 149 | '===': convert_arbitrary_equal, 150 | '!=': convert_equal, # legacy_convert_requirement will add this to Conflicts 151 | '<=': convert_ordered, 152 | '<': convert_ordered, 153 | '>=': convert_ordered, 154 | '>': convert_ordered} 155 | 156 | def convert(name, operator, version_id): 157 | return OPERATORS[operator](name, operator, version_id) 158 | 159 | def legacy_convert(name, operator, version_id): 160 | return LEGACY_OPERATORS[operator](name, operator, version_id) 161 | 162 | def legacy_convert_requirement(parsed_req): 163 | reqs = [] 164 | conflicts = [] 165 | for spec in parsed_req.specs: 166 | req = legacy_convert(parsed_req.project_name, spec[0], spec[1]) 167 | if spec[0] == '~=': 168 | reqs.append(req[0]) 169 | reqs.append(req[1]) 170 | elif spec[0] == '!=': 171 | conflicts.append(req) 172 | else: 173 | reqs.append(req) 174 | if len(reqs) == 0: 175 | reqs.append('{name}') 176 | conflicts.sort(reverse=True) 177 | reqs.sort(reverse=True) 178 | return [['Conflicts', parsed_req.project_name, r] for r in conflicts] + \ 179 | [['Requires', parsed_req.project_name, r] for r in reqs] 180 | 181 | def convert_requirement(parsed_req, use_rich_deps=True): 182 | if not use_rich_deps: 183 | return legacy_convert_requirement(parsed_req) 184 | reqs = [] 185 | for spec in parsed_req.specs: 186 | reqs.append(convert(parsed_req.project_name, spec[0], spec[1])) 187 | if len(reqs) == 0: 188 | return [['Requires', parsed_req.project_name, '{name}']] 189 | if len(reqs) == 1: 190 | return [['Requires', parsed_req.project_name, reqs[0]]] 191 | reqs.sort(reverse=True) 192 | return [['Requires', parsed_req.project_name, 193 | '({})'.format(' with '.join(reqs))]] 194 | -------------------------------------------------------------------------------- /pyp2rpm/dependency_parser.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import re 3 | 4 | from pkg_resources import Requirement 5 | 6 | from pyp2rpm.dependency_convert import convert_requirement 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | 11 | def dependency_to_rpm(dep, runtime, use_rich_deps=True): 12 | """Converts a dependency got by pkg_resources.Requirement.parse() 13 | to RPM format. 14 | Args: 15 | dep - a dependency retrieved by pkg_resources.Requirement.parse() 16 | runtime - whether the returned dependency should be runtime (True) 17 | or build time (False) 18 | Returns: 19 | List of semi-SPECFILE dependencies (package names are not properly 20 | converted yet). 21 | For example: [['Requires', 'jinja2', '{name}'], 22 | ['Conflicts', 'jinja2', '{name} = 2.0.1']] 23 | """ 24 | logger.debug('Dependencies provided: {0} runtime: {1}.'.format( 25 | dep, runtime)) 26 | converted = convert_requirement(dep, use_rich_deps) 27 | 28 | if not runtime: 29 | for conv in converted: 30 | conv[0] = "Build" + conv[0] 31 | logger.debug('Converted dependencies: {0}.'.format(converted)) 32 | 33 | return converted 34 | 35 | 36 | def deps_from_pyp_format(requires, runtime=True, use_rich_deps=True): 37 | """Parses dependencies extracted from setup.py. 38 | Args: 39 | requires: list of dependencies as written in setup.py of the package. 40 | runtime: are the dependencies runtime (True) or build time (False)? 41 | Returns: 42 | List of semi-SPECFILE dependencies (see dependency_to_rpm for format). 43 | """ 44 | parsed = [] 45 | logger.debug("Dependencies from setup.py: {0} runtime: {1}.".format( 46 | requires, runtime)) 47 | 48 | for req in requires: 49 | try: 50 | parsed.append(Requirement.parse(req)) 51 | except ValueError: 52 | logger.warn("Unparsable dependency {0}.".format(req), 53 | exc_info=True) 54 | 55 | in_rpm_format = [] 56 | for dep in parsed: 57 | in_rpm_format.extend(dependency_to_rpm(dep, runtime, use_rich_deps)) 58 | logger.debug("Dependencies from setup.py in rpm format: {0}.".format( 59 | in_rpm_format)) 60 | 61 | return in_rpm_format 62 | 63 | 64 | def deps_from_pydit_json(requires, runtime=True): 65 | """Parses dependencies returned by pydist.json, since versions 66 | uses brackets we can't use pkg_resources to parse and we need a separate 67 | method 68 | Args: 69 | requires: list of dependencies as written in pydist.json of the package 70 | runtime: are the dependencies runtime (True) or build time (False) 71 | Returns: 72 | List of semi-SPECFILE dependecies (see dependency_to_rpm for format) 73 | """ 74 | parsed = [] 75 | for req in requires: 76 | # req looks like 'some-name (>=X.Y,!=Y.X)' or 'someme-name' where 77 | # 'some-name' is the name of required package and '(>=X.Y,!=Y.X)' 78 | # are specs 79 | name, specs = None, None 80 | # len(reqs) == 1 if there are not specified versions, 2 otherwise 81 | reqs = req.split(' ') 82 | name = reqs[0] 83 | if len(reqs) == 2: 84 | specs = reqs[1] 85 | # try if there are more specs in spec part of the requires 86 | specs = specs.split(",") 87 | # strip brackets 88 | specs = [re.sub('[()]', '', spec) for spec in specs] 89 | # this will divide (>=0.1.2) to ['>=', '0', '.1.2'] 90 | # or (0.1.2) into ['', '0', '.1.2'] 91 | specs = [re.split('([0-9])', spec, 1) for spec in specs] 92 | # we have separated specs based on number as delimiter 93 | # so we need to join it back to rest of version number 94 | # e.g ['>=', '0', '.1.2'] to ['>=', '0.1.2'] 95 | for spec in specs: 96 | spec[1:3] = [''.join(spec[1:3])] 97 | if specs: 98 | for spec in specs: 99 | if '!' in spec[0]: 100 | parsed.append(['Conflicts', name, '{{name}} = {}'.format(spec[1])]) 101 | elif specs[0] == '==': 102 | parsed.append(['Requires', name, '{{name}} = {}'.format(spec[1])]) 103 | else: 104 | parsed.append(['Requires', name, '{{name}} {} {}'.format( 105 | spec[0], spec[1])]) 106 | else: 107 | parsed.append(['Requires', name, '{name}']) 108 | 109 | if not runtime: 110 | for pars in parsed: 111 | pars[0] = 'Build' + pars[0] 112 | 113 | return parsed 114 | -------------------------------------------------------------------------------- /pyp2rpm/exceptions.py: -------------------------------------------------------------------------------- 1 | class UnknownArchiveFormatException(BaseException): 2 | pass 3 | 4 | 5 | class BadFilenameException(BaseException): 6 | pass 7 | 8 | 9 | class NameNotSpecifiedException(BaseException): 10 | pass 11 | 12 | 13 | class NoSuchPackageException(BaseException): 14 | pass 15 | 16 | 17 | class NoSuchSourceException(BaseException): 18 | pass 19 | 20 | 21 | class VirtualenvFailException(BaseException): 22 | pass 23 | 24 | 25 | class ExtractionError(BaseException): 26 | pass 27 | 28 | 29 | class MissingUrlException(BaseException): 30 | pass 31 | -------------------------------------------------------------------------------- /pyp2rpm/filters.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | 4 | from pyp2rpm import settings 5 | from pyp2rpm import name_convertor 6 | 7 | 8 | def name_for_python_version(name, version, default_number=False): 9 | return name_convertor.NameConvertor.rpm_versioned_name( 10 | name, version, default_number, True) 11 | 12 | 13 | def script_name_for_python_version(name, version, minor=False, 14 | default_number=True): 15 | if not default_number: 16 | if version == settings.DEFAULT_PYTHON_VERSIONS[ 17 | name_convertor.NameConvertor.distro][0]: 18 | return name 19 | if minor: 20 | if len(version) > 1: 21 | return '{0}-{1}'.format(name, '.'.join(list(version))) 22 | else: 23 | return '{0}-%{{python{1}_version}}'.format(name, version) 24 | else: 25 | return '{0}-{1}'.format(name, version[0]) 26 | 27 | 28 | def sitedir_for_python_version(name, version, default_string='python2'): 29 | if version == settings.DEFAULT_PYTHON_VERSION: 30 | return name 31 | else: 32 | return name.replace(default_string, 'python{0}'.format(version)) 33 | 34 | 35 | def python_bin_for_python_version(name, version, default_string='__python2'): 36 | if version == settings.DEFAULT_PYTHON_VERSION: 37 | return name 38 | else: 39 | return name.replace(default_string, '__python{0}'.format(version)) 40 | 41 | 42 | def macroed_pkg_name(pkg_name, srcname): 43 | macro = '%{srcname}' if srcname else '%{pypi_name}' 44 | if pkg_name.startswith('python-'): 45 | return 'python-{0}'.format(macro) 46 | else: 47 | return macro 48 | 49 | 50 | def module_to_path(name, module): 51 | module = module.replace(".", "/") 52 | if name == module: 53 | return "%{pypi_name}" 54 | else: 55 | return module 56 | 57 | 58 | def package_to_path(package, module): 59 | # this is used only on items in data.packages 60 | # if package name differs from module name than it is a subpackage 61 | # and we have to list it in %files section 62 | if package == module: 63 | return "%{pypi_name}" 64 | else: 65 | return package 66 | 67 | 68 | def macroed_url(url): 69 | if url.startswith('https://files.pythonhosted.org/packages/source/'): 70 | if url.endswith('/%{pypi_name}/%{pypi_name}-%{pypi_version}.tar.gz'): 71 | return '%{pypi_source}' 72 | elif url.endswith('/%{pypi_name}/%{pypi_name}-%{pypi_version}.zip'): 73 | return '%{pypi_source %{pypi_name} %{version} zip}' 74 | return url 75 | 76 | 77 | def rpm_version_410(version, use_macro=True): 78 | """Converts a Python version into rpm equivalent or the named variable. 79 | 80 | rpm versions 4.10 - 4.14 supported only the tilde pre-release separator. 81 | This variant of the rpm_version filter is provided for compatibility 82 | with older releases. 83 | """ 84 | if use_macro: 85 | default = '%{pypi_version}' 86 | else: 87 | default = version 88 | re_match = re.compile( 89 | r"(\d+\.?\d*\.?\d*\.?\d*)\.?((?:a|b|rc|dev)\d*)").search( 90 | version) 91 | if not re_match: 92 | return default 93 | rpm_version, rpm_suffix = re_match.groups() 94 | if rpm_suffix.startswith("dev"): 95 | return '{}~~{}'.format(rpm_version, rpm_suffix) 96 | else: 97 | return '{}~{}'.format(rpm_version, rpm_suffix) 98 | 99 | 100 | def rpm_version(version, use_macro=True): 101 | """Converts a Python version into rpm equivalent or the named variable. 102 | 103 | Python versions may contain a suffix indicating that it is a pre- 104 | release or post-release version. RPM implements pre-relase 105 | versions with a tilde separator, and post-release versions with a 106 | caret separator. 107 | 108 | If use_macro is True, then the string "%{pypi_version}" will be 109 | returned when there is no pre or post-release suffix. 110 | """ 111 | if use_macro: 112 | default = '%{pypi_version}' 113 | else: 114 | default = version 115 | re_match = re.compile( 116 | r"(\d+\.?\d*\.?\d*\.?\d*)\.?((?:a|b|rc|post|dev)\d*)").search( 117 | version) 118 | if not re_match: 119 | return default 120 | rpm_version, rpm_suffix = re_match.groups() 121 | if rpm_suffix.startswith("post"): 122 | return '{}^{}'.format(rpm_version, rpm_suffix) 123 | if rpm_suffix.startswith("dev"): 124 | return '{}~~{}'.format(rpm_version, rpm_suffix) 125 | else: 126 | return '{}~{}'.format(rpm_version, rpm_suffix) 127 | 128 | 129 | __all__ = [name_for_python_version, 130 | script_name_for_python_version, 131 | sitedir_for_python_version, 132 | python_bin_for_python_version, 133 | macroed_pkg_name, 134 | module_to_path, 135 | package_to_path, 136 | macroed_url, 137 | rpm_version_410, 138 | rpm_version] 139 | -------------------------------------------------------------------------------- /pyp2rpm/logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import sys 4 | 5 | 6 | logger = logging.getLogger('pyp2rpm') 7 | logger.setLevel(logging.DEBUG) 8 | 9 | file_formatter = logging.Formatter( 10 | u'%(asctime)s::%(name)s::%(levelname)s::%(message)s') 11 | console_formatter = logging.Formatter(u'%(levelname)s %(message)s') 12 | 13 | destinations = [] 14 | 15 | 16 | class LoggerWriter(object): 17 | """Allows to redirect stream to logger""" 18 | 19 | def __init__(self, level): 20 | self.level = level 21 | self.errors = None 22 | 23 | def write(self, message): 24 | if message not in ('\n', ''): 25 | self.level(message.rstrip('\n')) 26 | 27 | def flush(self): 28 | pass 29 | 30 | 31 | class LevelFilter(logging.Filter): 32 | 33 | def __init__(self, level): 34 | super(LevelFilter, self).__init__(level) 35 | self.level = level 36 | 37 | def filter(self, record): 38 | return record.levelno == self.level 39 | 40 | 41 | def register_file_log_handler(log_file, level=logging.DEBUG, 42 | fmt=file_formatter): 43 | destinations.append(log_file) 44 | dirname = os.path.dirname(log_file) 45 | try: 46 | if not os.path.exists(dirname): 47 | os.makedirs(dirname) 48 | except (OSError, IOError): 49 | return False 50 | try: 51 | file_handler = logging.FileHandler(log_file, 'a') 52 | file_handler.setLevel(level) 53 | file_handler.setFormatter(fmt) 54 | logger.addHandler(file_handler) 55 | except (OSError, IOError): 56 | return False 57 | return True 58 | 59 | 60 | def register_console_log_handler(level=logging.INFO, fmt=console_formatter): 61 | destinations.append('stdout') 62 | console_handler = logging.StreamHandler(sys.stdout) 63 | console_handler.setLevel(level) 64 | console_handler.setFormatter(fmt) 65 | logger.addHandler(console_handler) 66 | -------------------------------------------------------------------------------- /pyp2rpm/module_runners.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import logging 4 | import json 5 | import runpy 6 | from subprocess import Popen, PIPE 7 | from abc import ABCMeta 8 | 9 | from pyp2rpm import utils 10 | from pyp2rpm import main_dir 11 | from pyp2rpm.exceptions import ExtractionError 12 | from pyp2rpm.command import extract_dist 13 | 14 | logger = logging.getLogger(__name__) 15 | 16 | 17 | class ModuleRunner(object): 18 | """Abstract base class for module runners.""" 19 | 20 | __metaclass__ = ABCMeta 21 | 22 | def __init__(self, module, *args): 23 | self.dirname = os.path.dirname(module) 24 | self.filename = os.path.basename(module) 25 | self.args = args 26 | 27 | 28 | class RunpyModuleRunner(ModuleRunner): 29 | """Runs given module in current interpreter using runpy.""" 30 | @staticmethod 31 | def not_suffixed(module): 32 | if module.endswith('py'): 33 | return module[:-3] 34 | 35 | def run(self): 36 | """Executes the code of the specified module.""" 37 | with utils.ChangeDir(self.dirname): 38 | sys.path.insert(0, self.dirname) 39 | sys.argv[1:] = self.args 40 | runpy.run_module(self.not_suffixed(self.filename), 41 | run_name='__main__', 42 | alter_sys=True) 43 | 44 | @property 45 | def results(self): 46 | return extract_dist.extract_dist.class_metadata 47 | 48 | 49 | class SubprocessModuleRunner(ModuleRunner): 50 | """Runs module in external interpreter using subprocess.""" 51 | 52 | def run(self, interpreter): 53 | """Executes the code of the specified module. Deserializes captured 54 | json data. 55 | """ 56 | with utils.ChangeDir(self.dirname): 57 | command_list = ['PYTHONPATH=' + main_dir, interpreter, 58 | self.filename] + list(self.args) 59 | try: 60 | proc = Popen(' '.join(command_list), stdout=PIPE, stderr=PIPE, 61 | shell=True) 62 | stream_data = proc.communicate() 63 | except Exception as e: 64 | logger.error( 65 | "Error {0} while executing extract_dist command.".format(e)) 66 | raise ExtractionError 67 | stream_data = [utils.console_to_str(s) for s in stream_data] 68 | if proc.returncode: 69 | logger.error( 70 | "Subprocess failed, working dir: {}".format(os.getcwd())) 71 | logger.error( 72 | "Subprocess failed, command: {}".format(command_list)) 73 | logger.error( 74 | "Subprocess failed, stdout: {0[0]}, stderr: {0[1]}".format( 75 | stream_data)) 76 | self._result = json.loads(stream_data[0].split( 77 | "extracted json data:\n")[-1].split("\n")[0]) 78 | 79 | @property 80 | def results(self): 81 | try: 82 | return self._result 83 | except AttributeError: 84 | return None 85 | -------------------------------------------------------------------------------- /pyp2rpm/package_data.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import time 3 | import locale 4 | import logging 5 | 6 | from pyp2rpm import version 7 | from pyp2rpm import utils 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | 12 | def get_deps_names(runtime_deps_list): 13 | ''' 14 | data['runtime_deps'] has format: 15 | [['Requires', 'name', '{name} >= version'], ...] 16 | this function creates list of lowercase deps names 17 | ''' 18 | return [x[1].lower() for x in runtime_deps_list] 19 | 20 | 21 | class PackageData(object): 22 | credit_line = '# Created by pyp2rpm-{0}'.format(version.version) 23 | 24 | """A simple object that carries data about a package.""" 25 | 26 | def __init__(self, local_file, name, pkg_name, version, 27 | md5='', source0='', srcname=None): 28 | object.__setattr__(self, 'data', {}) 29 | self.data['local_file'] = local_file 30 | self.data['name'] = name 31 | self.data['srcname'] = srcname 32 | self.data['pkg_name'] = pkg_name 33 | self.data['version'] = version 34 | self.data['python_versions'] = [] 35 | self.data['md5'] = md5 36 | self.data['source0'] = source0 37 | self.data['sphinx_dir'] = None 38 | 39 | def __getattr__(self, name): 40 | if name == 'underscored_name': 41 | return self.data['name'].replace('-', '_') 42 | elif name == 'changelog_date_packager': 43 | return self.get_changelog_date_packager() 44 | elif name in ['runtime_deps', 'build_deps', 'classifiers', 45 | 'doc_files', 'doc_license']: 46 | return self.data.get(name, []) 47 | elif name in ['packages', 'py_modules', 'scripts']: 48 | return self.data.get(name, []) 49 | elif name in ['has_egg_info', 'has_test_suite', 50 | 'has_pth', 'has_extension']: 51 | return self.data.get(name, False) 52 | elif name == 'sorted_python_versions': 53 | return sorted([self.data.get('base_python_version')] + 54 | self.data.get('python_versions', [])) 55 | return self.data.get(name, 'TODO:') 56 | 57 | def __setattr__(self, name, value): 58 | if name == 'summary' and isinstance(value, utils.str_classes): 59 | value = value.rstrip('.').replace('\n', ' ') 60 | if value is not None: 61 | self.data[name] = value 62 | 63 | def update_attr(self, name, value): 64 | if name in self.data and value: 65 | if name in ['runtime_deps', 'build_deps']: 66 | for item in value: 67 | if not item[1].lower() in get_deps_names(self.data[name]): 68 | self.data[name].append(item) 69 | elif isinstance(self.data[name], list): 70 | for item in value: 71 | if item not in self.data[name]: 72 | self.data[name].append(item) 73 | elif isinstance(self.data[name], set): 74 | if not isinstance(value, set): 75 | value = set(value) 76 | self.data[name] |= value 77 | elif not self.data[name] and self.data[name] is not False: 78 | self.data[name] = value 79 | elif name not in self.data and value is not None: 80 | self.data[name] = value 81 | 82 | def set_from(self, data_dict, update=False): 83 | for k, v in data_dict.items(): 84 | if update: 85 | self.update_attr(k, v) 86 | else: 87 | setattr(self, k, v) 88 | 89 | def get_changelog_date_packager(self): 90 | """Returns part of the changelog entry, containing date and packager. 91 | """ 92 | try: 93 | packager = subprocess.Popen( 94 | 'rpmdev-packager', stdout=subprocess.PIPE).communicate( 95 | )[0].strip() 96 | except OSError: 97 | # Hi John Doe, you should install rpmdevtools 98 | packager = "John Doe " 99 | logger.warn("Package rpmdevtools is missing, using default " 100 | "name: {0}.".format(packager)) 101 | with utils.c_time_locale(): 102 | date_str = time.strftime('%a %b %d %Y', time.gmtime()) 103 | encoding = locale.getpreferredencoding() 104 | return u'{0} {1}'.format(date_str, packager.decode(encoding)) 105 | -------------------------------------------------------------------------------- /pyp2rpm/package_getters.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import sys 4 | import subprocess 5 | import tempfile 6 | import shutil 7 | import re 8 | try: 9 | import urllib.request as request 10 | except ImportError: 11 | import urllib as request 12 | from pkg_resources import parse_version 13 | 14 | 15 | from pyp2rpm import settings 16 | from pyp2rpm import exceptions 17 | 18 | 19 | logger = logger = logging.getLogger(__name__) 20 | 21 | 22 | def get_url(client, name, version, wheel=False, hashed_format=False): 23 | """Retrieves list of package URLs using PyPI's XML-RPC. Chooses URL 24 | of prefered archive and md5_digest. 25 | """ 26 | try: 27 | release_urls = client.release_urls(name, version) 28 | release_data = client.release_data(name, version) 29 | except BaseException: # some kind of error with client 30 | logger.debug('Client: {0} Name: {1} Version: {2}.'.format( 31 | client, name, version)) 32 | raise exceptions.MissingUrlException( 33 | "Some kind of error while communicating with client: {0}.".format( 34 | client)) 35 | 36 | url = '' 37 | md5_digest = None 38 | 39 | if not wheel: 40 | # Prefered archive is tar.gz 41 | if len(release_urls): 42 | zip_url = zip_md5 = '' 43 | for release_url in release_urls: 44 | if release_url['url'].endswith("tar.gz"): 45 | url = release_url['url'] 46 | md5_digest = release_url['md5_digest'] 47 | if release_url['url'].endswith(".zip"): 48 | zip_url = release_url['url'] 49 | zip_md5 = release_url['md5_digest'] 50 | if url == '': 51 | url = zip_url or release_urls[0]['url'] 52 | md5_digest = zip_md5 or release_urls[0]['md5_digest'] 53 | elif release_data: 54 | url = release_data['download_url'] 55 | else: 56 | # Only wheel is acceptable 57 | for release_url in release_urls: 58 | if release_url['url'].endswith("none-any.whl"): 59 | url = release_url['url'] 60 | md5_digest = release_url['md5_digest'] 61 | break 62 | if not url: 63 | raise exceptions.MissingUrlException( 64 | "Url of source archive not found.") 65 | 66 | if url == 'UNKNOWN': 67 | raise exceptions.MissingUrlException( 68 | "{0} package has no sources on PyPI, Please ask the maintainer " 69 | "to upload sources.".format(release_data['name'])) 70 | 71 | if not hashed_format: 72 | url = ("https://files.pythonhosted.org/packages/source" 73 | "/{0[0]}/{0}/{1}").format(name, url.split("/")[-1]) 74 | 75 | return (url, md5_digest) 76 | 77 | 78 | class PackageGetter(object): 79 | 80 | """Base class for package getters""" 81 | 82 | def get(self): 83 | pass 84 | 85 | def get_name_version(self): 86 | """Returns (name, version) tuple. 87 | Returns: 88 | (name, version) tuple of the package. 89 | """ 90 | pass 91 | 92 | def __del__(self): 93 | if hasattr(self, "temp_dir") and os.path.exists(self.temp_dir): 94 | shutil.rmtree(self.temp_dir) 95 | 96 | def save_dir_init(self, save_dir): 97 | self.save_dir = save_dir or settings.DEFAULT_PKG_SAVE_PATH 98 | if self.save_dir == settings.DEFAULT_PKG_SAVE_PATH: 99 | self.save_dir += '/SOURCES' 100 | 101 | if not os.path.exists(self.save_dir): 102 | if self.save_dir != (settings.DEFAULT_PKG_SAVE_PATH + '/SOURCES'): 103 | os.makedirs(self.save_dir) 104 | else: 105 | try: 106 | subprocess.Popen( 107 | 'rpmdev-setuptree', stdout=subprocess.PIPE).wait() 108 | logger.info("Using rpmdevtools package to make rpmbuild " 109 | "folders tree.") 110 | except OSError: 111 | self.save_dir = '/tmp' 112 | # pyp2rpm can work without rpmdevtools 113 | logger.warning("Package rpmdevtools is missing , using " 114 | "default folder: {0} to store {1}.".format( 115 | self.save_dir, self.name)) 116 | logger.warning("Specify folder to store a file (SAVE_DIR) " 117 | "or install rpmdevtools.") 118 | logger.info("Using {0} as directory to save source.".format( 119 | self.save_dir)) 120 | 121 | 122 | class PypiDownloader(PackageGetter): 123 | 124 | """Class for downloading the package from PyPI.""" 125 | 126 | def __init__(self, client, name, version=None, prerelease=False, 127 | save_dir=None): 128 | self.client = client 129 | self.name = name 130 | if version: 131 | # if version is specified, will check if such version exists 132 | if not self.client.release_urls(name, version): 133 | raise exceptions.NoSuchPackageException( 134 | 'Package with name "{0}" and version "{1}" could not be ' 135 | 'found on PyPI.'.format(name, version)) 136 | 137 | self.version = version 138 | else: 139 | self.versions = sorted(self.client.package_releases(self.name, True), 140 | reverse=True, key=parse_version) 141 | 142 | # Use only stable versions, unless --pre was specified 143 | if not prerelease: 144 | self.versions = [candidate for candidate in self.versions 145 | if not parse_version(candidate).is_prerelease] 146 | 147 | # If versions is empty list then there is no such package on PyPI 148 | if not self.versions: 149 | raise exceptions.NoSuchPackageException( 150 | 'Package "{0}" could not be found on PyPI.'.format(name)) 151 | 152 | self.version = self.versions[0] 153 | self.save_dir_init(save_dir) 154 | 155 | def get(self, wheel=False): 156 | """Downloads the package from PyPI. 157 | Returns: 158 | Full path of the downloaded file. 159 | Raises: 160 | PermissionError if the save_dir is not writable. 161 | """ 162 | try: 163 | url = get_url(self.client, self.name, self.version, 164 | wheel, hashed_format=True)[0] 165 | except exceptions.MissingUrlException as e: 166 | raise SystemExit(e) 167 | if wheel: 168 | self.temp_dir = tempfile.mkdtemp() 169 | save_dir = self.temp_dir 170 | else: 171 | save_dir = self.save_dir 172 | 173 | save_file = '{0}/{1}'.format(save_dir, url.split('/')[-1]) 174 | request.urlretrieve(url, save_file) 175 | logger.info('Downloaded package from PyPI: {0}.'.format(save_file)) 176 | return save_file 177 | 178 | def get_name_version(self): 179 | """Try to normalize unusual version string, 180 | Returns name and version of the package. 181 | """ 182 | return (self.name, self.version) 183 | 184 | 185 | class LocalFileGetter(PackageGetter): 186 | 187 | def __init__(self, local_file, save_dir=None): 188 | self.local_file = local_file 189 | self.name_version_pattern = re.compile( 190 | r"(^.*?)-(\d+\.?\d*\.?\d*\.?\d*\.?(?:a|b|rc|post|dev)?\d*).*$") 191 | self.save_dir_init(save_dir) 192 | 193 | def get(self): 194 | """Copies file from local filesystem to self.save_dir. 195 | Returns: 196 | Full path of the copied file. 197 | Raises: 198 | EnvironmentError if the file can't be found or the save_dir 199 | is not writable. 200 | """ 201 | if self.local_file.endswith('.whl'): 202 | self.temp_dir = tempfile.mkdtemp() 203 | save_dir = self.temp_dir 204 | else: 205 | save_dir = self.save_dir 206 | 207 | save_file = '{0}/{1}'.format(save_dir, os.path.basename( 208 | self.local_file)) 209 | if not os.path.exists(save_file) or not os.path.samefile( 210 | self.local_file, save_file): 211 | shutil.copy2(self.local_file, save_file) 212 | logger.info('Local file: {0} copied to {1}.'.format( 213 | self.local_file, save_file)) 214 | 215 | return save_file 216 | 217 | @property 218 | def _stripped_name_version(self): 219 | """Returns filename stripped of the suffix. 220 | Returns: 221 | Filename stripped of the suffix (extension). 222 | """ 223 | # we don't use splitext, because on "a.tar.gz" it returns ("a.tar", 224 | # "gz") 225 | filename = os.path.basename(self.local_file) 226 | for archive_suffix in settings.ARCHIVE_SUFFIXES: 227 | if filename.endswith(archive_suffix): 228 | return filename.rstrip('{0}'.format(archive_suffix)) 229 | # if for cycle is exhausted it means no suffix was found 230 | else: 231 | raise exceptions.UnknownArchiveFormatException( 232 | 'Unkown archive format of file {0}.'.format(filename)) 233 | 234 | def get_name_version(self): 235 | try: 236 | name, version = self.name_version_pattern.search( 237 | self._stripped_name_version).groups() 238 | except AttributeError: 239 | raise SystemExit("Failed to get name and version of the package, " 240 | "check if name of the archive is in format: " 241 | "name-version.suffix.") 242 | if version[-1] == '.': 243 | version = version[:-1] 244 | return (name, version) 245 | 246 | @property 247 | def name(self): 248 | return self.get_name_version()[0] 249 | -------------------------------------------------------------------------------- /pyp2rpm/settings.py: -------------------------------------------------------------------------------- 1 | from pyp2rpm import utils 2 | 3 | DEFAULT_TEMPLATE = 'fedora' 4 | DEFAULT_PYTHON_VERSIONS = { 5 | 'fedora': ['3'], 6 | 'epel7': ['2', '3'], 7 | 'epel6': ['2'], 8 | 'mageia': ['3'], 9 | 'pld': ['2', '3'] 10 | } 11 | DEFAULT_PYTHON_VERSION = DEFAULT_PYTHON_VERSIONS[DEFAULT_TEMPLATE][0] 12 | DEFAULT_PKG_SOURCE = 'pypi' 13 | DEFAULT_METADATA_SOURCE = 'pypi' 14 | DEFAULT_DISTRO = 'fedora' 15 | DEFAULT_PKG_SAVE_PATH = utils.get_default_save_path() 16 | KNOWN_DISTROS = DEFAULT_PYTHON_VERSIONS.keys() 17 | ARCHIVE_SUFFIXES = ['.tar', '.tgz', '.tar.gz', '.tar.bz2', 18 | '.gz', '.bz2', '.xz', '.zip', '.egg', '.whl'] 19 | EXTENSION_SUFFIXES = ['.c', '.cpp'] 20 | MODULE_SUFFIXES = ('.py', '.pyc') 21 | DOC_FILES_RE = [r'readme.+', r'licens.+', r'copying.+'] 22 | LICENSE_FILES = ['license', 'copyright', 'copying'] 23 | SPHINX_DIR_RE = r'[^/]+/doc.?' 24 | PYPI_URL = 'https://pypi.org/pypi' 25 | PYPI_USABLE_DATA = ['description', 'summary', 'license', 26 | 'home_page', 'requires'] 27 | PYTHON_INTERPRETER = '/usr/bin/python' 28 | EXTRACT_DIST_COMMAND_ARGS = ['--quiet', '--command-packages', 29 | 'pyp2rpm.command', 'extract_dist'] 30 | RPM_RICH_DEP_BLACKLIST = ['epel6', 'epel7'] 31 | 32 | TROVE_LICENSES = { 33 | 'License :: OSI Approved :: Academic Free License (AFL)': 'AFL', 34 | 'License :: OSI Approved :: Apache Software License': 'ASL %(TODO: version)s', 35 | 'License :: OSI Approved :: Apple Public Source License': 'APSL %(TODO: version)s', 36 | 'License :: OSI Approved :: Artistic License': 'Artistic %(TODO: version)s', 37 | 'License :: OSI Approved :: Attribution Assurance License': 'AAL', 38 | 'License :: OSI Approved :: BSD License': 'BSD', 39 | 'License :: OSI Approved :: Common Public License': 'CPL', 40 | 'License :: OSI Approved :: Eiffel Forum License': 'EFL %(TODO: version)s', 41 | 'License :: OSI Approved :: European Union Public Licence 1.0 (EUPL 1.0)': 'EUPL 1.0', 42 | 'License :: OSI Approved :: European Union Public Licence 1.1 (EUPL 1.1)': 'EUPL 1.1', 43 | 'License :: OSI Approved :: GNU Affero General Public License v3': 'AGPLv3', 44 | 'License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)': 'AGPLv3+', 45 | 'License :: OSI Approved :: GNU Free Documentation License (FDL)': 'GFDL', 46 | 'License :: OSI Approved :: GNU General Public License (GPL)': 'GPL', 47 | 'License :: OSI Approved :: GNU General Public License v2 (GPLv2)': 'GPLv2', 48 | 'License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)': 'GPLv2+', 49 | 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)': 'GPLv3', 50 | 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)': 'GPLv3+', 51 | 'License :: OSI Approved :: GNU Lesser General Public License v2 (LGPLv2)': 'LGPLv2', 52 | 'License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+)': 'LGPLv2+', 53 | 'License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)': 'LGPLv3', 54 | 'License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)': 'LGPLv3+', 55 | 'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)': 'LGPL', 56 | 'License :: OSI Approved :: IBM Public License': 'IBM', 57 | 'License :: OSI Approved :: Intel Open Source License': 'Intel Open Source License - Deprecated (BAD)', 58 | 'License :: OSI Approved :: ISC License (ISCL)': 'ISC', 59 | 'License :: OSI Approved :: Jabber Open Source License': 'Jabber', 60 | 'License :: OSI Approved :: MIT License': 'MIT', 61 | 'License :: OSI Approved :: MITRE Collaborative Virtual Workspace License (CVW)': 'MITRE - Deprecated (BAD)', 62 | 'License :: OSI Approved :: Motosoto License': 'Motosoto', 63 | 'License :: OSI Approved :: Mozilla Public License 1.0 (MPL)': 'MPLv1.0', 64 | 'License :: OSI Approved :: Mozilla Public License 1.1 (MPL 1.1)': 'MPLv1.1', 65 | 'License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)': 'MPLv2.0', 66 | 'License :: OSI Approved :: Nethack General Public License': 'NGPL', 67 | 'License :: OSI Approved :: Nokia Open Source License': 'Nokia', 68 | 'License :: OSI Approved :: Open Group Test Suite License': 'Open Group Test Suite License - Flawed (BAD)', 69 | 'License :: OSI Approved :: Python License (CNRI Python License)': 'CNRI', 70 | 'License :: OSI Approved :: Python Software Foundation License': 'Python', 71 | 'License :: OSI Approved :: Qt Public License (QPL)': 'QPL', 72 | 'License :: OSI Approved :: Ricoh Source Code Public License': 'Ricoh Source Code Public License - (BAD)', 73 | 'License :: OSI Approved :: Sleepycat License': 'Sleepycat', 74 | 'License :: OSI Approved :: Sun Industry Standards Source License (SISSL)': 'SISSL', 75 | 'License :: OSI Approved :: Sun Public License': 'SPL', 76 | 'License :: OSI Approved :: University of Illinois/NCSA Open Source License': 'NCSA', 77 | 'License :: OSI Approved :: Vovida Software License 1.0': 'VSL', 78 | 'License :: OSI Approved :: W3C License': 'W3C - not sure', 79 | 'License :: OSI Approved :: X.Net License': 'X.Net License - Deprecated (BAD)', 80 | 'License :: OSI Approved :: zlib/libpng License': 'zlib', 81 | 'License :: OSI Approved :: Zope Public License': 'ZPLv%(TODO: version)s', 82 | 'License :: Other/Proprietary License': 'Proprietary shit - BAD', 83 | 'License :: Public Domain': 'Public Domain', 84 | 'License :: Repoze Public License': 'Repoze Public License - ???'} 85 | -------------------------------------------------------------------------------- /pyp2rpm/templates/epel6.spec: -------------------------------------------------------------------------------- 1 | {{ data.credit_line }} 2 | {% from 'macros.spec' import dependencies, for_python_versions, underscored_or_pypi -%} 3 | %global pypi_name {{ data.name }} 4 | %global pypi_version {{ data.version }} 5 | {%- if data.srcname %} 6 | %global srcname {{ data.srcname }} 7 | {%- endif %} 8 | {%- for pv in data.python_versions %} 9 | %global with_python{{ pv }} 1 10 | {%- endfor %} 11 | 12 | Name: {{ data.pkg_name|macroed_pkg_name(data.srcname) }} 13 | Version: {{ data.version|rpm_version_410 }} 14 | Release: 1%{?dist} 15 | Summary: {{ data.summary }} 16 | 17 | License: {{ data.license }} 18 | URL: {{ data.home_page }} 19 | Source0: {{ data.source0|replace(data.name, '%{pypi_name}')|replace(data.version, '%{pypi_version}') }} 20 | 21 | {%- if not data.has_extension %} 22 | BuildArch: noarch 23 | {%- endif %} 24 | {%- for pv in data.sorted_python_versions %} 25 | {{ dependencies(data.build_deps, False, pv, data.base_python_version) }} 26 | {%- endfor %} 27 | {{ dependencies(data.runtime_deps, True, data.base_python_version, data.base_python_version) }} 28 | 29 | %description 30 | {{ data.description|truncate(400)|wordwrap }} 31 | {% call(pv) for_python_versions(data.python_versions) -%} 32 | %package -n {{ data.pkg_name|macroed_pkg_name(data.srcname)|name_for_python_version(pv) }} 33 | Summary: {{ data.summary }} 34 | {{ dependencies(data.runtime_deps, True, pv, pv) }} 35 | 36 | %description -n {{ data.pkg_name|macroed_pkg_name(data.srcname)|name_for_python_version(pv) }} 37 | {{ data.description|truncate(400)|wordwrap }} 38 | {%- endcall %} 39 | {%- if data.sphinx_dir %} 40 | %package -n {{ data.pkg_name|macroed_pkg_name(data.srcname)|name_for_python_version(None, True) }}-doc 41 | Summary: {{ data.name }} documentation 42 | %description -n {{ data.pkg_name|macroed_pkg_name(data.srcname)|name_for_python_version(None, True) }}-doc 43 | Documentation for {{ data.name }} 44 | {%- endif %} 45 | 46 | %prep 47 | %setup -q -n %{pypi_name}-%{pypi_version} 48 | {%- if data.has_bundled_egg_info %} 49 | # Remove bundled egg-info 50 | rm -rf %{pypi_name}.egg-info 51 | {%- endif %} 52 | {% call(pv) for_python_versions(data.sorted_python_versions, data.base_python_version) -%} 53 | {%- if pv != data.base_python_version -%} 54 | rm -rf python{{pv}} 55 | cp -a . python{{pv}} 56 | find python{{pv}} -name '*.py' | xargs sed -i '1s|^#!python|#!%{__python{{pv}}}|' 57 | {%- endif %} 58 | {%- if data.sphinx_dir %} 59 | # generate html docs {# TODO: generate properly for other versions (pushd/popd into their dirs...) 60 | # #} 61 | PYTHONPATH=${PWD} {{ "sphinx-build"|script_name_for_python_version(data.base_python_version, False, False) }} {{ data.sphinx_dir }} html 62 | # remove the sphinx-build leftovers 63 | rm -rf html/.{doctrees,buildinfo} 64 | {%- endif %} 65 | {%- endcall %} 66 | 67 | %build 68 | {%- call(pv) for_python_versions(data.sorted_python_versions, data.base_python_version) -%} 69 | {%- if pv != data.base_python_version -%} 70 | pushd python{{ pv }} 71 | {%- endif %} 72 | {% if data.has_extension %}CFLAGS="$RPM_OPT_FLAGS" {% endif %}%{__python{{ pv }}} setup.py build 73 | {% if pv != data.base_python_version -%} 74 | popd 75 | {%- endif %} 76 | {%- endcall %} 77 | 78 | %install 79 | {%- if data.python_versions|length > 0 %} 80 | # Must do the subpackages' install first because the scripts in /usr/bin are 81 | # overwritten with every setup.py install (and we want the python2 version 82 | # to be the default for now). 83 | {%- endif -%} 84 | {%- call(pv) for_python_versions(data.python_versions + [data.base_python_version], data.base_python_version) -%} 85 | {%- if pv != data.base_python_version -%} 86 | pushd python{{ pv }} 87 | {%- elif data.python_versions and data.scripts %} 88 | rm -rf %{buildroot}%{_bindir}/* 89 | {%- endif %} 90 | %{__python{{ pv }}} setup.py install --skip-build --root %{buildroot} 91 | {%- if pv != data.base_python_version %} 92 | popd 93 | {%- endif %} 94 | {%- endcall %} 95 | 96 | {%- if data.has_test_suite %} 97 | %check 98 | {%- call(pv) for_python_versions(data.sorted_python_versions, data.base_python_version) -%} 99 | {%- if pv != data.base_python_version -%} 100 | pushd python{{ pv }} 101 | {%- endif %} 102 | %{__python{{ pv }}} setup.py test 103 | {% if pv != data.base_python_version -%} 104 | popd 105 | {%- endif %} 106 | {%- endcall %} 107 | {%- endif %} 108 | 109 | {% call(pv) for_python_versions(data.sorted_python_versions, data.base_python_version) -%} 110 | %files{% if pv != data.base_python_version %} -n {{ data.pkg_name|macroed_pkg_name(data.srcname)|name_for_python_version(pv) }}{% endif %} 111 | {%- if data.doc_files %} 112 | %doc {{ data.doc_files|join(' ') }} 113 | {%- endif %} 114 | {%- if pv == data.base_python_version %} 115 | {%- for script in data.scripts %} 116 | %{_bindir}/{{ script }} 117 | {%- endfor %} 118 | {%- endif %} 119 | {%- if data.py_modules %} 120 | {%- for module in data.py_modules -%} 121 | {%- if pv == '3' %} 122 | %dir %{python{{ pv }}_sitelib}/__pycache__/ 123 | %{python{{ pv }}_sitelib}/__pycache__/* 124 | {%- endif %} 125 | %{python{{ pv }}_sitelib}/{{ data.name | module_to_path(module) }}.py{% if pv != '3'%}*{% endif %} 126 | {%- endfor %} 127 | {%- endif %} 128 | {%- if data.has_extension %} 129 | {%- if data.has_packages %} 130 | {%- for package in data.packages %} 131 | %{python{{ pv }}_sitearch}/{{ package | package_to_path(data.name) }} 132 | {%- endfor %} 133 | {%- endif %} 134 | {%- if data.has_pth %} 135 | %{python{{ pv }}_sitearch}/{{ underscored_or_pypi(data.name, data.underscored_name) }}-%{pypi_version}-py%{python{{ pv }}_version}-*.pth 136 | {%- endif %} 137 | %{python{{ pv }}_sitearch}/{{ underscored_or_pypi(data.name, data.underscored_name) }}-%{pypi_version}-py%{python{{ pv }}_version}.egg-info 138 | {%- else %} 139 | {%- if data.has_packages %} 140 | {%- for package in data.packages %} 141 | %{python{{ pv }}_sitelib}/{{ package | package_to_path(data.name) }} 142 | {%- endfor %} 143 | {%- endif %} 144 | {%- if data.has_pth %} 145 | %{python{{ pv }}_sitelib}/{{ underscored_or_pypi(data.name, data.underscored_name) }}-%{pypi_version}-py%{python{{ pv }}_version}-*.pth 146 | {%- endif %} 147 | %{python{{ pv }}_sitelib}/{{ underscored_or_pypi(data.name, data.underscored_name) }}-%{pypi_version}-py%{python{{ pv }}_version}.egg-info 148 | {%- endif %} 149 | {% endcall %} 150 | {%- if data.sphinx_dir %} 151 | %files -n {{ data.pkg_name|macroed_pkg_name(data.srcname)|name_for_python_version(pv, True) }}-doc 152 | %doc html 153 | {%- if data.doc_license %} 154 | %license {{data.doc_license|join(' ')}} 155 | {%- endif %} 156 | {% endif %} 157 | %changelog 158 | * {{ data.changelog_date_packager }} - {{ data.version|rpm_version_410(False) }}-1 159 | - Initial package. 160 | -------------------------------------------------------------------------------- /pyp2rpm/templates/epel7.spec: -------------------------------------------------------------------------------- 1 | {{ data.credit_line }} 2 | {% from 'macros.spec' import dependencies, for_python_versions, underscored_or_pypi -%} 3 | %global pypi_name {{ data.name }} 4 | %global pypi_version {{ data.version }} 5 | {%- if data.srcname %} 6 | %global srcname {{ data.srcname }} 7 | {%- endif %} 8 | 9 | Name: {{ data.pkg_name|macroed_pkg_name(data.srcname) }} 10 | Version: {{ data.version|rpm_version_410 }} 11 | Release: 1%{?dist} 12 | Summary: {{ data.summary }} 13 | 14 | License: {{ data.license }} 15 | URL: {{ data.home_page }} 16 | Source0: {{ data.source0|replace(data.name, '%{pypi_name}')|replace(data.version, '%{pypi_version}') }} 17 | 18 | {%- if not data.has_extension %} 19 | BuildArch: noarch 20 | {%- endif %} 21 | {%- for pv in data.sorted_python_versions %} 22 | {{ dependencies(data.build_deps, False, pv, data.base_python_version, use_with=False) }} 23 | {%- endfor %} 24 | 25 | %description 26 | {{ data.description|truncate(400)|wordwrap }} 27 | {% for pv in data.sorted_python_versions %} 28 | %package -n {{data.pkg_name|macroed_pkg_name(data.srcname)|name_for_python_version(pv, True)}} 29 | Summary: {{ data.summary }} 30 | {{ dependencies(data.runtime_deps, True, pv, pv, use_with=False) }} 31 | %description -n {{data.pkg_name|macroed_pkg_name(data.srcname)|name_for_python_version(pv, True)}} 32 | {{ data.description|truncate(400)|wordwrap }} 33 | {% endfor -%} 34 | {%- if data.sphinx_dir %} 35 | %package -n python-%{pypi_name}-doc 36 | Summary: {{ data.name }} documentation 37 | %description -n python-%{pypi_name}-doc 38 | Documentation for {{ data.name }} 39 | {%- endif %} 40 | 41 | %prep 42 | %autosetup -n {{ data.dirname|replace(data.name, '%{pypi_name}')|replace(data.version, '%{pypi_version}')|default('%{pypi_name}-%{pypi_version}', true) }} 43 | {%- if data.has_bundled_egg_info %} 44 | # Remove bundled egg-info 45 | rm -rf %{pypi_name}.egg-info 46 | {%- endif %} 47 | 48 | %build 49 | {%- for pv in data.sorted_python_versions %} 50 | {% if data.has_extension %}CFLAGS="$RPM_OPT_FLAGS" {% endif %}%{__python{{ pv }}} setup.py build 51 | {%- endfor %} 52 | {%- if data.sphinx_dir %} 53 | # generate html docs 54 | PYTHONPATH=${PWD} {{ "sphinx-build"|script_name_for_python_version(data.base_python_version, True, False) }} {{ data.sphinx_dir }} html 55 | # remove the sphinx-build leftovers 56 | rm -rf html/.{doctrees,buildinfo} 57 | {%- endif %} 58 | 59 | %install 60 | {%- if data.python_versions|length > 0 %} 61 | # Must do the default python version install last because 62 | # the scripts in /usr/bin are overwritten with every setup.py install. 63 | {%- endif %} 64 | {%- for pv in data.python_versions + [data.base_python_version] %} 65 | {%- if pv == data.base_python_version and data.python_versions and data.scripts %} 66 | rm -rf %{buildroot}%{_bindir}/* 67 | {%- endif %} 68 | %{__python{{ pv }}} setup.py install --skip-build --root %{buildroot} 69 | {%- endfor -%} 70 | {% if data.has_test_suite %} 71 | 72 | %check 73 | {%- for pv in data.sorted_python_versions %} 74 | %{__python{{ pv }}} setup.py test 75 | {%- endfor %} 76 | {%- endif %} 77 | {% for pv in data.sorted_python_versions %} 78 | %files -n {{ data.pkg_name|macroed_pkg_name(data.srcname)|name_for_python_version(pv, True) }} 79 | {%- if data.doc_files %} 80 | %doc {{data.doc_files|join(' ') }} 81 | {%- endif %} 82 | {%- if pv == data.base_python_version %} 83 | {%- for script in data.scripts %} 84 | %{_bindir}/{{ script }} 85 | {%- endfor %} 86 | {%- endif %} 87 | {%- if data.py_modules %} 88 | {%- for module in data.py_modules -%} 89 | {%- if pv == '3' %} 90 | %dir %{python{{ pv }}_sitelib}/__pycache__/ 91 | %{python{{ pv }}_sitelib}/__pycache__/* 92 | {%- endif %} 93 | %{python{{ pv }}_sitelib}/{{ data.name | module_to_path(module) }}.py{% if pv != '3'%}*{% endif %} 94 | {%- endfor %} 95 | {%- endif %} 96 | {%- if data.has_extension %} 97 | {%- if data.has_packages %} 98 | {%- for package in data.packages %} 99 | %{python{{ pv }}_sitearch}/{{ package | package_to_path(data.name) }} 100 | {%- endfor %} 101 | {%- endif %} 102 | {%- if data.has_pth %} 103 | %{python{{ pv }}_sitearch}/{{ underscored_or_pypi(data.name, data.underscored_name) }}-%{pypi_version}-py%{python{{ pv }}_version}-*.pth 104 | {%- endif %} 105 | %{python{{ pv }}_sitearch}/{{ underscored_or_pypi(data.name, data.underscored_name) }}-%{pypi_version}-py%{python{{ pv }}_version}.egg-info 106 | {%- else %} 107 | {%- if data.has_packages %} 108 | {%- for package in data.packages %} 109 | %{python{{ pv }}_sitelib}/{{ package | package_to_path(data.name) }} 110 | {%- endfor %} 111 | {%- endif %} 112 | {%- if data.has_pth %} 113 | %{python{{ pv }}_sitelib}/{{ underscored_or_pypi(data.name, data.underscored_name) }}-%{pypi_version}-py%{python{{ pv }}_version}-*.pth 114 | {%- endif %} 115 | %{python{{ pv }}_sitelib}/{{ underscored_or_pypi(data.name, data.underscored_name) }}-%{pypi_version}-py%{python{{ pv }}_version}.egg-info 116 | {%- endif %} 117 | {% endfor %} 118 | {%- if data.sphinx_dir %} 119 | %files -n python-%{pypi_name}-doc 120 | %doc html 121 | {%- if data.doc_license %} 122 | %license {{data.doc_license|join(' ')}} 123 | {%- endif %} 124 | {% endif %} 125 | %changelog 126 | * {{ data.changelog_date_packager }} - {{ data.version|rpm_version_410(False) }}-1 127 | - Initial package. 128 | -------------------------------------------------------------------------------- /pyp2rpm/templates/fedora.spec: -------------------------------------------------------------------------------- 1 | {{ data.credit_line }} 2 | {% from 'macros.spec' import dependencies, for_python_versions, underscored_or_pypi, macroed_url -%} 3 | %global pypi_name {{ data.name }} 4 | %global pypi_version {{ data.version }} 5 | {%- if data.srcname %} 6 | %global srcname {{ data.srcname }} 7 | {%- endif %} 8 | 9 | Name: {{ data.pkg_name|macroed_pkg_name(data.srcname) }} 10 | Version: {{ data.version|rpm_version }} 11 | Release: 1%{?dist} 12 | Summary: {{ data.summary }} 13 | 14 | License: {{ data.license }} 15 | URL: {{ data.home_page }} 16 | Source0: {{ data.source0|replace(data.name, '%{pypi_name}')|replace(data.version, '%{pypi_version}')|macroed_url }} 17 | 18 | {%- if not data.has_extension %} 19 | BuildArch: noarch 20 | {%- endif %} 21 | {%- for pv in data.sorted_python_versions %} 22 | {{ dependencies(data.build_deps, False, pv, data.base_python_version, False) }} 23 | {%- endfor %} 24 | 25 | %description 26 | {{ data.description|truncate(400)|wordwrap }} 27 | {% for pv in data.sorted_python_versions %} 28 | %package -n {{data.pkg_name|macroed_pkg_name(data.srcname)|name_for_python_version(pv, True) }} 29 | Summary: %{summary} 30 | %{?python_provide:%python_provide {{data.pkg_name|macroed_pkg_name(data.srcname)|name_for_python_version(pv, True)}}} 31 | {{ dependencies(data.runtime_deps, True, pv, pv) }} 32 | %description -n {{data.pkg_name|macroed_pkg_name(data.srcname)|name_for_python_version(pv, True) }} 33 | {{ data.description|truncate(400)|wordwrap }} 34 | {% endfor -%} 35 | {%- if data.sphinx_dir %} 36 | %package -n {{ data.pkg_name|macroed_pkg_name(data.srcname)|name_for_python_version(None, True) }}-doc 37 | Summary: {{ data.name }} documentation 38 | %description -n {{ data.pkg_name|macroed_pkg_name(data.srcname)|name_for_python_version(None, True) }}-doc 39 | Documentation for {{ data.name }} 40 | {%- endif %} 41 | 42 | %prep 43 | %autosetup -n {{ data.dirname|replace(data.name, '%{pypi_name}')|replace(data.version, '%{pypi_version}')|default('%{pypi_name}-%{pypi_version}', true) }} 44 | {%- if data.has_bundled_egg_info %} 45 | # Remove bundled egg-info 46 | rm -rf %{pypi_name}.egg-info 47 | {%- endif %} 48 | 49 | %build 50 | {%- for pv in data.sorted_python_versions %} 51 | %py{{ pv }}_build 52 | {%- endfor %} 53 | {%- if data.sphinx_dir %} 54 | # generate html docs 55 | PYTHONPATH=${PWD} {{ "sphinx-build"|script_name_for_python_version(data.base_python_version, False, True) }} {{ data.sphinx_dir }} html 56 | # remove the sphinx-build leftovers 57 | rm -rf html/.{doctrees,buildinfo} 58 | {%- endif %} 59 | 60 | %install 61 | {%- if data.python_versions|length > 0 %} 62 | # Must do the default python version install last because 63 | # the scripts in /usr/bin are overwritten with every setup.py install. 64 | {%- endif %} 65 | {%- for pv in data.python_versions + [data.base_python_version] %} 66 | {%- if pv == data.base_python_version and data.python_versions and data.scripts %} 67 | rm -rf %{buildroot}%{_bindir}/* 68 | {%- endif %} 69 | %py{{ pv }}_install 70 | {%- endfor -%} 71 | {% if data.has_test_suite %} 72 | 73 | %check 74 | {%- for pv in data.sorted_python_versions %} 75 | %{__python{{ pv }}} setup.py test 76 | {%- endfor %} 77 | {%- endif %} 78 | {% for pv in data.sorted_python_versions %} 79 | %files -n {{ data.pkg_name|macroed_pkg_name(data.srcname)|name_for_python_version(pv, True) }} 80 | {%- if data.doc_license %} 81 | %license {{data.doc_license|join(' ')}} 82 | {%- endif %} 83 | {%- if data.doc_files %} 84 | %doc {{data.doc_files|join(' ') }} 85 | {%- endif %} 86 | {%- if pv == data.base_python_version %} 87 | {%- for script in data.scripts %} 88 | %{_bindir}/{{ script }} 89 | {%- endfor %} 90 | {%- endif %} 91 | {%- if data.py_modules %} 92 | {%- for module in data.py_modules -%} 93 | {%- if pv == '3' %} 94 | %{python{{ pv }}_sitelib}/__pycache__/* 95 | {%- endif %} 96 | %{python{{ pv }}_sitelib}/{{ data.name | module_to_path(module) }}.py{% if pv != '3'%}*{% endif %} 97 | {%- endfor %} 98 | {%- endif %} 99 | {%- if data.has_extension %} 100 | {%- if data.has_packages %} 101 | {%- for package in data.packages %} 102 | %{python{{ pv }}_sitearch}/{{ package | package_to_path(data.name) }} 103 | {%- endfor %} 104 | {%- endif %} 105 | {%- if data.has_pth %} 106 | %{python{{ pv }}_sitearch}/{{ underscored_or_pypi(data.name, data.underscored_name) }}-%{pypi_version}-py%{python{{ pv }}_version}-*.pth 107 | {%- endif %} 108 | %{python{{ pv }}_sitearch}/{{ underscored_or_pypi(data.name, data.underscored_name) }}-%{pypi_version}-py%{python{{ pv }}_version}.egg-info 109 | {%- else %} 110 | {%- if data.has_packages %} 111 | {%- for package in data.packages %} 112 | %{python{{ pv }}_sitelib}/{{ package | package_to_path(data.name) }} 113 | {%- endfor %} 114 | {%- endif %} 115 | {%- if data.has_pth %} 116 | %{python{{ pv }}_sitelib}/{{ underscored_or_pypi(data.name, data.underscored_name) }}-%{pypi_version}-py%{python{{ pv }}_version}-*.pth 117 | {%- endif %} 118 | %{python{{ pv }}_sitelib}/{{ underscored_or_pypi(data.name, data.underscored_name) }}-%{pypi_version}-py%{python{{ pv }}_version}.egg-info 119 | {%- endif %} 120 | {% endfor %} 121 | {%- if data.sphinx_dir %} 122 | %files -n {{ data.pkg_name|macroed_pkg_name(data.srcname)|name_for_python_version(None, True) }}-doc 123 | %doc html 124 | {%- if data.doc_license %} 125 | %license {{data.doc_license|join(' ')}} 126 | {%- endif %} 127 | {% endif %} 128 | %changelog 129 | * {{ data.changelog_date_packager }} - {{ data.version|rpm_version(False) }}-1 130 | - Initial package. 131 | -------------------------------------------------------------------------------- /pyp2rpm/templates/macros.spec: -------------------------------------------------------------------------------- 1 | {# prints a single dependency for a specific python version #} 2 | {%- macro one_dep(dep, python_version) %} 3 | {{ dep[0] }}:{{ ' ' * (15 - dep[0]|length) }}{{ dep[2].format(name=dep[1]|name_for_python_version(python_version, True)) }} 4 | {%- endmacro %} 5 | 6 | {# Prints given deps (runtime or buildtime for given python_version, 7 | considering the base_python_version. #} 8 | {# This cannot be implemented by macro for_python_versions because it needs to 9 | decide on its own, whether to even use the %if 0%{?with_pythonX} or not. #} 10 | {%- macro dependencies(deps, runtime, python_version, base_python_version, 11 | use_with=True) %} 12 | {%- if deps|length > 0 or not runtime %} {# for build deps, we always have at least 1 - pythonXX-devel #} 13 | {%- if python_version != base_python_version and use_with %} 14 | %if 0%{?with_python{{ python_version }}} 15 | {%- endif %} 16 | {%- for dep in deps -%} 17 | {%- if python_version == base_python_version or not 'sphinx' in dep[1] -%} 18 | {{ one_dep(dep, python_version) }} 19 | {%- endif -%} 20 | {%- endfor %} 21 | {%- if python_version != base_python_version and use_with %} 22 | %endif # if with_python{{ python_version }} 23 | {%- endif %} 24 | {%- endif %} 25 | {%- endmacro %} 26 | 27 | {# For all python_versions, prints caller content. Content is surrounded by conditionals if 28 | python_version != base_python_version #} 29 | {%- macro for_python_versions(python_versions, base_python_version) %} 30 | {%- for pv in python_versions %} 31 | {%- if pv != base_python_version %} 32 | %if 0%{?with_python{{ pv }}} 33 | {% endif %} 34 | {{- caller(pv) }} 35 | {%- if pv != base_python_version %} 36 | %endif # with_python{{ pv }} 37 | {% endif %} 38 | {%- endfor %} 39 | {%- endmacro %} 40 | 41 | {% macro underscored_or_pypi(original, underscored) -%} 42 | {% if underscored != original %}{{ underscored }}{% else %}%{pypi_name}{% endif %} 43 | {%- endmacro %} 44 | -------------------------------------------------------------------------------- /pyp2rpm/templates/mageia.spec: -------------------------------------------------------------------------------- 1 | {{ data.credit_line }} 2 | {% from 'macros.spec' import dependencies, for_python_versions, underscored_or_pypi -%} 3 | %global pypi_name {{ data.name }} 4 | %global pypi_version {{ data.version }} 5 | {%- if data.srcname %} 6 | %global srcname {{ data.srcname }} 7 | {%- endif %} 8 | 9 | Name: {{ data.pkg_name|macroed_pkg_name(data.srcname) }} 10 | Version: {{ data.version|rpm_version }} 11 | Release: %mkrel 1 12 | Summary: {{ data.summary }} 13 | Group: Development/Python 14 | License: {{ data.license }} 15 | URL: {{ data.home_page }} 16 | Source0: {{ data.source0|replace(data.name, '%{pypi_name}')|replace(data.version, '%{pypi_version}') }} 17 | 18 | {%- if not data.has_extension %} 19 | BuildArch: noarch 20 | {%- endif %} 21 | {%- for pv in data.sorted_python_versions %} 22 | {{ dependencies(data.build_deps, False, pv, data.base_python_version, False) }} 23 | {%- endfor %} 24 | 25 | %description 26 | {{ data.description|truncate(400)|wordwrap }} 27 | {% for pv in data.sorted_python_versions %} 28 | %package -n {{data.pkg_name|macroed_pkg_name(data.srcname)|name_for_python_version(pv, True) }} 29 | Summary: %{summary} 30 | %{?python_provide:%python_provide {{data.pkg_name|macroed_pkg_name(data.srcname)|name_for_python_version(pv, True)}}} 31 | {{ dependencies(data.runtime_deps, True, pv, pv) }} 32 | %description -n {{data.pkg_name|macroed_pkg_name(data.srcname)|name_for_python_version(pv, True) }} 33 | {{ data.description|truncate(400)|wordwrap }} 34 | {% endfor -%} 35 | {%- if data.sphinx_dir %} 36 | %package -n {{ data.pkg_name|macroed_pkg_name(data.srcname)|name_for_python_version(None, True) }}-doc 37 | Summary: {{ data.name }} documentation 38 | %description -n {{ data.pkg_name|macroed_pkg_name(data.srcname)|name_for_python_version(None, True) }}-doc 39 | Documentation for {{ data.name }} 40 | {%- endif %} 41 | 42 | %prep 43 | %autosetup -n {{ data.dirname|replace(data.name, '%{pypi_name}')|replace(data.version, '%{pypi_version}')|default('%{pypi_name}-%{pypi_version}', true) }} 44 | {%- if data.has_bundled_egg_info %} 45 | # Remove bundled egg-info 46 | rm -rf %{pypi_name}.egg-info 47 | {%- endif %} 48 | 49 | %build 50 | {%- for pv in data.sorted_python_versions %} 51 | %py{{ pv }}_build 52 | {%- endfor %} 53 | {%- if data.sphinx_dir %} 54 | # generate html docs 55 | PYTHONPATH=${PWD} {{ "sphinx-build"|script_name_for_python_version(data.base_python_version, False, True) }} {{ data.sphinx_dir }} html 56 | # remove the sphinx-build leftovers 57 | rm -rf html/.{doctrees,buildinfo} 58 | {%- endif %} 59 | 60 | %install 61 | {%- if data.python_versions|length > 0 %} 62 | # Must do the default python version install last because 63 | # the scripts in /usr/bin are overwritten with every setup.py install. 64 | {%- endif %} 65 | {%- for pv in data.python_versions + [data.base_python_version] %} 66 | {%- if pv == data.base_python_version and data.python_versions and data.scripts %} 67 | rm -rf %{buildroot}%{_bindir}/* 68 | {%- endif %} 69 | %py{{ pv }}_install 70 | {%- endfor -%} 71 | {% if data.has_test_suite %} 72 | 73 | %check 74 | {%- for pv in data.sorted_python_versions %} 75 | %{__python{{ pv }}} setup.py test 76 | {%- endfor %} 77 | {%- endif %} 78 | {% for pv in data.sorted_python_versions %} 79 | %files -n {{ data.pkg_name|macroed_pkg_name(data.srcname)|name_for_python_version(pv, True) }} 80 | {%- if data.doc_license %} 81 | %license {{data.doc_license|join(' ')}} 82 | {%- endif %} 83 | {%- if data.doc_files %} 84 | %doc {{data.doc_files|join(' ') }} 85 | {%- endif %} 86 | {%- if pv == data.base_python_version %} 87 | {%- for script in data.scripts %} 88 | %{_bindir}/{{ script }} 89 | {%- endfor %} 90 | {%- endif %} 91 | {%- if data.py_modules %} 92 | {%- for module in data.py_modules -%} 93 | {%- if pv == '3' %} 94 | %{python{{ pv }}_sitelib}/__pycache__/* 95 | {%- endif %} 96 | %{python{{ pv }}_sitelib}/{{ data.name | module_to_path(module) }}.py{% if pv != '3'%}*{% endif %} 97 | {%- endfor %} 98 | {%- endif %} 99 | {%- if data.has_extension %} 100 | {%- if data.has_packages %} 101 | {%- for package in data.packages %} 102 | %{python{{ pv }}_sitearch}/{{ package | package_to_path(data.name) }} 103 | {%- endfor %} 104 | {%- endif %} 105 | {%- if data.has_pth %} 106 | %{python{{ pv }}_sitearch}/{{ underscored_or_pypi(data.name, data.underscored_name) }}-%{pypi_version}-py%{python{{ pv }}_version}-*.pth 107 | {%- endif %} 108 | %{python{{ pv }}_sitearch}/{{ underscored_or_pypi(data.name, data.underscored_name) }}-%{pypi_version}-py%{python{{ pv }}_version}.egg-info 109 | {%- else %} 110 | {%- if data.has_packages %} 111 | {%- for package in data.packages %} 112 | %{python{{ pv }}_sitelib}/{{ package | package_to_path(data.name) }} 113 | {%- endfor %} 114 | {%- endif %} 115 | {%- if data.has_pth %} 116 | %{python{{ pv }}_sitelib}/{{ underscored_or_pypi(data.name, data.underscored_name) }}-%{pypi_version}-py%{python{{ pv }}_version}-*.pth 117 | {%- endif %} 118 | %{python{{ pv }}_sitelib}/{{ underscored_or_pypi(data.name, data.underscored_name) }}-%{pypi_version}-py%{python{{ pv }}_version}.egg-info 119 | {%- endif %} 120 | {% endfor %} 121 | {%- if data.sphinx_dir %} 122 | %files -n {{ data.pkg_name|macroed_pkg_name(data.srcname)|name_for_python_version(None, True) }}-doc 123 | %doc html 124 | {%- if data.doc_license %} 125 | %license {{data.doc_license|join(' ')}} 126 | {%- endif %} 127 | {% endif %} 128 | -------------------------------------------------------------------------------- /pyp2rpm/templates/pld.spec: -------------------------------------------------------------------------------- 1 | {{ data.credit_line }} 2 | {% from 'macros.spec' import dependencies, for_python_versions, underscored_or_pypi -%} 3 | 4 | {# For all python_versions, prints caller content. 5 | Content is surrounded by conditionals if python_version != base_python_version #} 6 | {%- macro for_python_versions(python_versions, base_python_version, use_with=True) %} 7 | {%- for pv in python_versions %} 8 | {%- if pv != base_python_version and use_with %} 9 | %if %{with python{{ pv }}} 10 | {% endif %} 11 | {{- caller(pv) }} 12 | {%- if pv != base_python_version and use_with %} 13 | %endif 14 | {% endif %} 15 | {%- endfor %} 16 | {%- endmacro %} 17 | 18 | {# Foreach all python_versions, prints caller content. 19 | Content is surrounded by conditionals if use_with is True #} 20 | {%- macro foreach_python_versions(use_with=True, v='') %} 21 | {%- for pv in [data.base_python_version] + data.python_versions %} 22 | {%- if use_with %} 23 | %if %{with python{{ pv }}} 24 | {%- endif %} 25 | {# set v variable to be '' for python2, '3' for python3 #} 26 | {%- set v = pv|replace(data.base_python_version, '') %} 27 | {{- caller(pv, v) }} 28 | {%- if use_with %} 29 | %endif 30 | {% endif %} 31 | {%- endfor %} 32 | {%- endmacro %} 33 | 34 | 35 | # 36 | # Conditional build: 37 | %bcond_without doc # don't build doc 38 | %bcond_without tests # do not perform "make test" 39 | {%- for pv in [data.base_python_version] + data.python_versions %} 40 | %bcond_without python{{ pv }} # CPython {{ pv }}.x module 41 | {%- endfor %} 42 | 43 | %define module {{ data.name }} 44 | %define egg_name {{ data.underscored_name }} 45 | %define pypi_name {{ data.name }} 46 | %define pypi_version {{ data.version }} 47 | Summary: {{ data.summary }} 48 | Name: python-%{pypi_name} 49 | Version: {{ data.version|rpm_version }} 50 | Release: 0.1 51 | License: {{ data.license }} 52 | Group: Libraries/Python 53 | Source0: {{ data.source0|replace(data.name, '%{pypi_name}')|replace(data.version, '%{pypi_version}') }} 54 | # Source0-md5: - 55 | URL: {{ data.home_page }} 56 | BuildRequires: rpm-pythonprov 57 | BuildRequires: rpmbuild(macros) >= 1.714 58 | {# build deps for each Python version #} 59 | {%- for pv in [data.base_python_version] + data.python_versions %} 60 | %if %{with python{{ pv }}} 61 | {{ dependencies(data.build_deps, False, pv, data.base_python_version, False) }} 62 | %endif 63 | {%- endfor %} 64 | {%- if not data.has_extension %} 65 | BuildArch: noarch 66 | {%- endif %} 67 | BuildRoot: %{tmpdir}/%{name}-%{pypi_version}-root-%(id -u -n) 68 | 69 | %description 70 | {{ data.description|truncate(400)|wordwrap }} 71 | 72 | {% call(pv) for_python_versions(data.python_versions, use_with=False) -%} 73 | %package -n {{ data.pkg_name|macroed_pkg_name(data.srcname)|name_for_python_version(pv) }} 74 | Summary: {{ data.summary }} 75 | Group: Libraries/Python 76 | 77 | %description -n {{ data.pkg_name|macroed_pkg_name(data.srcname)|name_for_python_version(pv) }} 78 | {{ data.description|truncate(400)|wordwrap }} 79 | {%- endcall %} 80 | 81 | %prep 82 | %setup -q -n %{pypi_name}-%{pypi_version} 83 | {%- if data.has_bundled_egg_info %} 84 | 85 | # Remove bundled egg-info 86 | %{__rm} -r %{egg_name}.egg-info 87 | {%- endif %} 88 | 89 | {% call(pv) for_python_versions([data.base_python_version] + data.python_versions, data.base_python_version, use_with=False) -%} 90 | {%- if data.sphinx_dir %} 91 | # generate html docs {# TODO: generate properly for other versions (pushd/popd into their dirs...) #} 92 | {% if pv != data.base_python_version %}python{{ pv }}-{% endif %}sphinx-build {{ data.sphinx_dir }} html 93 | # remove the sphinx-build leftovers 94 | %{__rm} -r html/.{doctrees,buildinfo} 95 | {%- endif %} 96 | {% endcall %} 97 | 98 | %build 99 | {%- call(pv, v) foreach_python_versions(use_with=True) -%} 100 | 101 | %py{{ v }}_build %{?with_tests:test} 102 | 103 | {%- endcall %} 104 | 105 | %install 106 | rm -rf $RPM_BUILD_ROOT 107 | {%- call(pv, v) foreach_python_versions(use_with=True) -%} 108 | 109 | %py{{ v }}_install 110 | 111 | {%- if pv == data.base_python_version %} 112 | %py_postclean 113 | {%- endif %} 114 | 115 | {%- if data.scripts %} 116 | {%- for script in data.scripts %} 117 | mv $RPM_BUILD_ROOT%{_bindir}/{{ script }} $RPM_BUILD_ROOT%{_bindir}/{{ script|script_name_for_python_version(pv) }} 118 | {%- endfor %} 119 | {%- endif %} 120 | 121 | {%- endcall %} 122 | 123 | %clean 124 | rm -rf $RPM_BUILD_ROOT 125 | 126 | {% call(pv, v) foreach_python_versions(use_with=True) -%} 127 | %files{% if pv != data.base_python_version %} -n {{ data.pkg_name|macroed_pkg_name(data.srcname)|name_for_python_version(pv) }}{% endif %} 128 | %defattr(644,root,root,755) 129 | 130 | %doc {% if data.sphinx_dir %}html {% endif %}{{ data.doc_files|join(' ') }} 131 | 132 | {%- if data.scripts %} 133 | {%- for script in data.scripts %} 134 | %attr(755,root,root) %{_bindir}/{{ script|script_name_for_python_version(pv) }} 135 | {%- endfor %} 136 | {%- endif %} 137 | 138 | {%- if data.py_modules %} 139 | {% for module in data.py_modules -%} 140 | {%- if pv == '2' -%} 141 | %{py{{ v }}_sitescriptdir}/{{ data.name | module_to_path(module) }}.py[co] 142 | {%- endif %} 143 | {%- if pv == '3' -%} 144 | %{py{{ v }}_sitescriptdir}/{{ data.name | module_to_path(module) }}.py 145 | %{py{{ v }}_sitescriptdir}/__pycache__/* 146 | {%- endif %} 147 | {%- endfor %} 148 | {%- endif %} 149 | 150 | {%- if data.has_extension %} 151 | %{py{{ v }}_sitedir/%{module} 152 | {%- if data.has_pth %} 153 | %{py{{ v }}_sitedir/%{egg_name}-%{pypi_version}-py%{python{{ pv }}_version}-*.pth 154 | {%- endif %} 155 | %{py{{ v }}_sitedir/%{egg_name}-%{pypi_version}-py%{python{{ pv }}_version}.egg-info 156 | {%- else %} 157 | {%- if data.has_packages %} 158 | %{py{{ v }}_sitescriptdir}/%{module} 159 | {%- endif %} 160 | 161 | {%- if data.has_pth %} 162 | %{py{{ v }}_sitescriptdir}/%{egg_name}-%{pypi_version}-py%{python{{ pv }}_version}-*.pth 163 | {%- endif %} 164 | %{py{{ v }}_sitescriptdir}/%{egg_name}-%{pypi_version}-py%{python{{ pv }}_version}.egg-info 165 | {%- endif %} 166 | 167 | {%- endcall %} 168 | -------------------------------------------------------------------------------- /pyp2rpm/utils.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | import functools 3 | import locale 4 | import logging 5 | import os 6 | import subprocess 7 | import sys 8 | import re 9 | import copy 10 | import itertools 11 | 12 | try: 13 | import rpm 14 | except ImportError: 15 | rpm = None 16 | 17 | logger = logging.getLogger(__name__) 18 | 19 | PY3 = sys.version > '3' 20 | 21 | if PY3: 22 | str_classes = (str, bytes) 23 | else: 24 | str_classes = (str, unicode) 25 | 26 | 27 | class ChangeDir(object): 28 | """Class to store current directory change cwd to new_path 29 | and return to previous path at exit, must be run using with statement. 30 | """ 31 | 32 | def __init__(self, new_path): 33 | self.primary_path = os.getcwd() 34 | self.new_path = new_path 35 | 36 | def __enter__(self): 37 | os.chdir(self.new_path) 38 | return self 39 | 40 | def __exit__(self, type, value, traceback): # TODO handle exception 41 | os.chdir(self.primary_path) 42 | 43 | 44 | def memoize_by_args(func): 45 | """Memoizes return value of a func based on args.""" 46 | memory = {} 47 | 48 | @functools.wraps(func) 49 | def memoized(*args): 50 | if args not in memory.keys(): 51 | value = func(*args) 52 | memory[args] = value 53 | 54 | return memory[args] 55 | 56 | return memoized 57 | 58 | 59 | def build_srpm(specfile, save_dir): 60 | """Builds a srpm from given specfile using rpmbuild. 61 | Generated srpm is stored in directory specified by save_dir. 62 | 63 | Args: 64 | specfile: path to a specfile 65 | save_dir: path to source and build tree 66 | """ 67 | logger.info('Starting rpmbuild to build: {0} SRPM.'.format(specfile)) 68 | if save_dir != get_default_save_path(): 69 | try: 70 | msg = subprocess.Popen( 71 | ['rpmbuild', 72 | '--define', '_sourcedir {0}'.format(save_dir), 73 | '--define', '_builddir {0}'.format(save_dir), 74 | '--define', '_srcrpmdir {0}'.format(save_dir), 75 | '--define', '_rpmdir {0}'.format(save_dir), 76 | '-bs', specfile], stdout=subprocess.PIPE).communicate( 77 | )[0].strip() 78 | except OSError: 79 | logger.error( 80 | "Rpmbuild failed for specfile: {0} and save_dir: {1}".format( 81 | specfile, save_dir), exc_info=True) 82 | msg = 'Rpmbuild failed. See log for more info.' 83 | return msg 84 | else: 85 | if not os.path.exists(save_dir): 86 | raise IOError("Specify folder to store a file (SAVE_DIR) " 87 | "or install rpmdevtools.") 88 | try: 89 | msg = subprocess.Popen( 90 | ['rpmbuild', 91 | '--define', '_sourcedir {0}'.format(save_dir + '/SOURCES'), 92 | '--define', '_builddir {0}'.format(save_dir + '/BUILD'), 93 | '--define', '_srcrpmdir {0}'.format(save_dir + '/SRPMS'), 94 | '--define', '_rpmdir {0}'.format(save_dir + '/RPMS'), 95 | '-bs', specfile], stdout=subprocess.PIPE).communicate( 96 | )[0].strip() 97 | except OSError: 98 | logger.error("Rpmbuild failed for specfile: {0} and save_dir: " 99 | "{1}".format(specfile, save_dir), exc_info=True) 100 | msg = 'Rpmbuild failed. See log for more info.' 101 | return msg 102 | 103 | 104 | def remove_major_minor_suffix(scripts): 105 | """Checks if executables already contain a "-MAJOR.MINOR" suffix. """ 106 | minor_major_regex = re.compile(r"-\d.?\d?$") 107 | return [x for x in scripts if not minor_major_regex.search(x)] 108 | 109 | 110 | def runtime_to_build(runtime_deps): 111 | """Adds all runtime deps to build deps""" 112 | build_deps = copy.deepcopy(runtime_deps) 113 | for dep in build_deps: 114 | if len(dep) > 0: 115 | dep[0] = 'BuildRequires' 116 | return build_deps 117 | 118 | 119 | def unique_deps(deps): 120 | """Remove duplicities from deps list of the lists""" 121 | deps.sort() 122 | return list(k for k, _ in itertools.groupby(deps)) 123 | 124 | 125 | if PY3: 126 | def console_to_str(s): 127 | try: 128 | return s.decode(sys.__stdout__.encoding) 129 | except UnicodeDecodeError: 130 | return s.decode('utf-8') 131 | else: 132 | def console_to_str(s): 133 | return s 134 | 135 | 136 | @contextlib.contextmanager 137 | def c_time_locale(): 138 | """Context manager with C LC_TIME locale""" 139 | old_time_locale = locale.getlocale(locale.LC_TIME) 140 | locale.setlocale(locale.LC_TIME, 'C') 141 | yield 142 | try: 143 | locale.setlocale(locale.LC_TIME, old_time_locale) 144 | except locale.Error: 145 | # https://bugs.python.org/issue30755 146 | # Python may alias the configured locale to another name, and 147 | # that locale may not be installed. In this case, the locale 148 | # should simply be left in the 'C' locale. 149 | pass 150 | 151 | 152 | def rpm_eval(macro): 153 | """Get value of given macro using rpm tool""" 154 | try: 155 | value = subprocess.Popen( 156 | ['rpm', '--eval', macro], 157 | stdout=subprocess.PIPE).communicate()[0].strip() 158 | except OSError: 159 | logger.error('Failed to get value of {0} rpm macro'.format( 160 | macro), exc_info=True) 161 | value = b'' 162 | return console_to_str(value) 163 | 164 | 165 | def get_default_save_path(): 166 | """Return default save path for the packages""" 167 | macro = '%{_topdir}' 168 | if rpm: 169 | save_path = rpm.expandMacro(macro) 170 | else: 171 | save_path = rpm_eval(macro) 172 | if not save_path: 173 | logger.warning("rpm tools are missing, using default save path " 174 | "~/rpmbuild/.") 175 | save_path = os.path.expanduser('~/rpmbuild') 176 | return save_path 177 | -------------------------------------------------------------------------------- /pyp2rpm/version.py: -------------------------------------------------------------------------------- 1 | version = '3.3.10' 2 | -------------------------------------------------------------------------------- /pyp2rpm/virtualenv.py: -------------------------------------------------------------------------------- 1 | import os 2 | import glob 3 | import logging 4 | import pprint 5 | 6 | from virtualenvapi.manage import VirtualEnvironment 7 | import virtualenvapi.exceptions as ve 8 | 9 | from pyp2rpm.exceptions import VirtualenvFailException 10 | from pyp2rpm.settings import DEFAULT_PYTHON_VERSION, MODULE_SUFFIXES 11 | 12 | logger = logging.getLogger(__name__) 13 | 14 | 15 | def site_packages_filter(site_packages_list): 16 | '''Removes wheel .dist-info files''' 17 | return set([x for x in site_packages_list if not x.endswith( 18 | ('.egg-info', '.dist-info', '.pth', '__pycache__', '.pyc'))]) 19 | 20 | 21 | def scripts_filter(scripts): 22 | ''' 23 | Removes .pyc files and __pycache__ from scripts 24 | ''' 25 | return [x for x in scripts if not x.split('.')[-1] == 'pyc' and 26 | not x == '__pycache__'] 27 | 28 | 29 | class DirsContent(object): 30 | ''' 31 | Object to store and compare directory content before and 32 | after instalation of package. 33 | ''' 34 | 35 | def __init__(self, bindir=None, lib_sitepackages=None): 36 | self.bindir = bindir 37 | self.lib_sitepackages = lib_sitepackages 38 | 39 | def fill(self, path): 40 | ''' 41 | Scans content of directories 42 | ''' 43 | self.bindir = set(os.listdir(path + 'bin/')) 44 | self.lib_sitepackages = set(os.listdir(glob.glob( 45 | path + 'lib/python*.*/site-packages/')[0])) 46 | 47 | def __sub__(self, other): 48 | ''' 49 | Makes differance of DirsContents objects attributes 50 | ''' 51 | if any([self.bindir is None, self.lib_sitepackages is None, 52 | other.bindir is None, other.lib_sitepackages is None]): 53 | raise ValueError("Some of the attributes is uninicialized") 54 | result = DirsContent( 55 | self.bindir - other.bindir, 56 | self.lib_sitepackages - other.lib_sitepackages) 57 | return result 58 | 59 | 60 | class VirtualEnv(object): 61 | 62 | def __init__(self, name, temp_dir, name_convertor, 63 | base_python_version): 64 | self.name = name 65 | self.temp_dir = temp_dir 66 | self.name_convertor = name_convertor 67 | if not base_python_version: 68 | base_python_version = DEFAULT_PYTHON_VERSION 69 | python_version = 'python' + base_python_version 70 | self.env = VirtualEnvironment(temp_dir + '/venv', 71 | python=python_version) 72 | try: 73 | self.env.open_or_create() 74 | except (ve.VirtualenvCreationException, 75 | ve.VirtualenvReadonlyException): 76 | raise VirtualenvFailException('Failed to create virtualenv') 77 | self.dirs_before_install = DirsContent() 78 | self.dirs_after_install = DirsContent() 79 | self.dirs_before_install.fill(temp_dir + '/venv/') 80 | self.data = {} 81 | 82 | def install_package_to_venv(self): 83 | ''' 84 | Installs package given as first argument to virtualenv without 85 | dependencies 86 | ''' 87 | try: 88 | self.env.install((self.name,), force=True, 89 | options=["--no-deps"]) 90 | except (ve.PackageInstallationException, 91 | ve.VirtualenvReadonlyException): 92 | raise VirtualenvFailException( 93 | 'Failed to install package to virtualenv') 94 | self.dirs_after_install.fill(self.temp_dir + '/venv/') 95 | 96 | def get_dirs_differance(self): 97 | ''' 98 | Makes final versions of site_packages and scripts using DirsContent 99 | sub method and filters 100 | ''' 101 | try: 102 | diff = self.dirs_after_install - self.dirs_before_install 103 | except ValueError: 104 | raise VirtualenvFailException( 105 | "Some of the DirsContent attributes is uninicialized") 106 | self.data['has_pth'] = \ 107 | any([x for x in diff.lib_sitepackages if x.endswith('.pth')]) 108 | 109 | site_packages = site_packages_filter(diff.lib_sitepackages) 110 | self.data['packages'] = sorted( 111 | [p for p in site_packages if not p.endswith(MODULE_SUFFIXES)]) 112 | self.data['py_modules'] = sorted(set( 113 | [os.path.splitext(m)[0] for m in site_packages - set( 114 | self.data['packages'])])) 115 | self.data['scripts'] = scripts_filter(sorted(diff.bindir)) 116 | logger.debug('Data from files differance in virtualenv:') 117 | logger.debug(pprint.pformat(self.data)) 118 | 119 | @property 120 | def get_venv_data(self): 121 | self.install_package_to_venv() 122 | self.get_dirs_differance() 123 | return self.data 124 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [aliases] 2 | test=pytest 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys 5 | from pyp2rpm.version import version 6 | 7 | from setuptools import setup 8 | from setuptools.command.build_py import build_py as _build_py 9 | 10 | 11 | description = """Convert Python packages to RPM SPECFILES. The packages can be downloaded from 12 | PyPI and the produced SPEC is in line with Fedora Packaging Guidelines or Mageia Python Policy. 13 | 14 | Users can provide their own templates for rendering the package metadata. Both the package 15 | source and metadata can be extracted from PyPI or from local filesystem (local file doesn't 16 | provide that much information though).""" 17 | 18 | class build_py(_build_py): 19 | def run(self): 20 | # Run the normal build process 21 | _build_py.run(self) 22 | # Build test data 23 | from subprocess import call 24 | from shutil import copy 25 | call([sys.executable, 'setup.py', 'sdist'], 26 | cwd='tests/test_data/isholiday-0.1') 27 | copy('tests/test_data/isholiday-0.1/dist/isholiday-0.1.tar.gz', 28 | 'tests/test_data/') 29 | call([sys.executable, 'setup.py', 'sdist'], 30 | cwd='tests/test_data/utest') 31 | copy('tests/test_data/utest/dist/utest-0.1.0.tar.gz', 32 | 'tests/test_data/') 33 | 34 | setup( 35 | cmdclass={ 36 | 'build_py': build_py, 37 | }, 38 | name='pyp2rpm', 39 | version=version, 40 | description="Convert Python packages to RPM SPECFILES", 41 | long_description=description, 42 | keywords='pypi, rpm, spec, specfile, convert', 43 | author='Bohuslav "Slavek" Kabrda, Robert Kuska, Michal Cyprian, Iryna Shcherbina', 44 | author_email='bkabrda@redhat.com, rkuska@redhat.com, mcyprian@redhat.com, ishcherb@redhat.com', 45 | url='https://github.com/fedora-python/pyp2rpm', 46 | license='MIT', 47 | packages=['pyp2rpm', 'pyp2rpm.command'], 48 | package_data={'pyp2rpm': ['templates/*.spec']}, 49 | entry_points={'console_scripts': ['pyp2rpm = pyp2rpm.bin:main']}, 50 | install_requires=['Jinja2', 51 | 'setuptools', 52 | 'click', 53 | ], 54 | setup_requires=['setuptools', 55 | 'flexmock >= 0.9.3', 56 | 'pytest-runner', 57 | 'click', 58 | 'Jinja2', 59 | ], 60 | tests_require=['packaging < 21;python_version<"3.5"', 'pytest < 5;python_version<"3.5"', 61 | 'pytest < 6.2;python_version=="3.5"', 'pytest < 7.1;python_version=="3.6"', 62 | 'iniconfig < 2.0;python_version=="3.6"', 63 | 'pytest;python_version>="3.7"', 'attrs < 21.1.0;python_version<"3.5"', 64 | 'attrs < 23.1.0;python_version=="3.6"', 65 | 'pluggy < 1.0;python_version<"3.8"', 66 | ], 67 | extras_require={ 68 | 'venv metadata': ['virtualenv-api'], 69 | 'sclize': ['spec2scl >= 1.2.0'] 70 | }, 71 | classifiers=['Development Status :: 4 - Beta', 72 | 'Environment :: Console', 73 | 'Intended Audience :: Developers', 74 | 'Intended Audience :: System Administrators', 75 | 'License :: OSI Approved :: MIT License', 76 | 'Operating System :: POSIX :: Linux', 77 | 'Programming Language :: Python', 78 | 'Programming Language :: Python :: 3', 79 | 'Topic :: Software Development :: Build Tools', 80 | 'Topic :: System :: Software Distribution', 81 | ] 82 | ) 83 | -------------------------------------------------------------------------------- /tests/test_archive.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from tarfile import TarFile 4 | from zipfile import ZipFile 5 | 6 | import pytest 7 | 8 | from pyp2rpm.archive import Archive, flat_list 9 | 10 | 11 | @pytest.mark.parametrize(('arg', 'expected'), [ 12 | (['foo'], ['foo']), 13 | (['foo', ['bar']], ['foo', 'bar']), 14 | ([], []), 15 | (['spam', ['foo', ['bar']]], ['spam', 'foo', 'bar']), 16 | ]) 17 | def test_flat_list_nested(arg, expected): 18 | assert flat_list(arg) == expected 19 | 20 | 21 | tests_dir = os.path.split(os.path.abspath(__file__))[0] 22 | 23 | 24 | class TestArchive(object): 25 | td_dir = '{0}/test_data/'.format(tests_dir) 26 | 27 | def setup_method(self, method): 28 | # create fresh archives for every test 29 | 30 | self.a = [Archive('{0}plumbum-0.9.0.tar.gz'.format(self.td_dir)), 31 | Archive('{0}pytest-2.2.3.zip'.format(self.td_dir)), 32 | Archive('{0}restsh-0.1.tar.gz'.format(self.td_dir)), 33 | Archive('{0}Sphinx-1.1.3-py2.6.egg'.format(self.td_dir)), 34 | Archive('{0}unextractable-1.tar'.format(self.td_dir)), 35 | Archive('{0}bitarray-0.8.0.tar.gz'.format(self.td_dir)), 36 | Archive('{0}pkginfo-1.2b1.tar.gz'.format(self.td_dir)), ] 37 | 38 | @pytest.mark.parametrize(('i', 'expected'), [ 39 | (0, TarFile), 40 | (1, ZipFile), 41 | (2, TarFile), 42 | (3, ZipFile), 43 | (4, TarFile), 44 | ]) 45 | def test_extractor_cls(self, i, expected): 46 | assert self.a[i].extractor_cls == expected 47 | 48 | @pytest.mark.parametrize(('i', 'n', 'abs', 'expected'), [ 49 | (0, 'setup.cfg', False, 50 | '[egg_info]\r\ntag_build = \r\ntag_date = 0\r\ntag_svn_revision = 0\r\n\r\n'), 51 | (1, 'requires.txt', False, 'py>=1.4.7.dev2'), 52 | (1, 'pytest-2.2.3/pytest.egg-info/requires.txt', 53 | True, 'py>=1.4.7.dev2'), 54 | (2, 'does_not_exist.dne', False, None), 55 | (4, 'in_unextractable', False, None), 56 | ]) 57 | def test_get_content_of_file_from_archive(self, i, n, abs, expected): 58 | with self.a[i] as a: 59 | assert a.get_content_of_file(n, abs) == expected 60 | 61 | @pytest.mark.parametrize(('i', 'suf', 'expected'), [ 62 | (0, ['.spamspamspam'], False), 63 | (1, '.py', True), 64 | (4, ['.eggs'], False), 65 | ]) 66 | def test_has_file_with_suffix(self, i, suf, expected): 67 | with self.a[i] as a: 68 | assert a.has_file_with_suffix(suf) == expected 69 | 70 | @pytest.mark.parametrize(('i', 'r', 'f', 'c', 'expected'), [ 71 | (0, r'PKG-INFO', False, False, [ 72 | 'plumbum-0.9.0/PKG-INFO', 73 | 'plumbum-0.9.0/plumbum.egg-info/PKG-INFO']), 74 | (0, r'[a-z]/PKG-INFO', True, False, 75 | ['plumbum-0.9.0/plumbum.egg-info/PKG-INFO']), 76 | (0, r'[a-z]/pKg-InfO', True, True, 77 | ['plumbum-0.9.0/plumbum.egg-info/PKG-INFO']), 78 | (0, r'spam/PKG-INFO', True, False, []), 79 | # should ignore directory in tarfile 80 | (0, r'plumbum-0.9.0$', True, False, []), 81 | ]) 82 | def test_get_files_re(self, i, r, f, c, expected): 83 | with self.a[i] as a: 84 | assert set(a.get_files_re(r, f, c)) == set(expected) 85 | 86 | @pytest.mark.parametrize(('i', 'r', 'f', 'c', 'expected'), [ 87 | (0, r'plumbum.*', False, False, ['plumbum-0.9.0', 88 | 'plumbum-0.9.0/plumbum.egg-info', 89 | 'plumbum-0.9.0/plumbum']), 90 | (0, r'[0-9]/plumbum$', True, False, ['plumbum-0.9.0/plumbum']), 91 | (0, r'[0-9]/pLuMbUm$', True, True, ['plumbum-0.9.0/plumbum']), 92 | (0, r'spam/plumbum.*', True, False, []), 93 | (0, r'setup.py', True, False, []), # should ignore file 94 | # test for zipfile separately - some more logic going on there... 95 | (1, r'pytest.*', False, False, [ 96 | 'pytest-2.2.3', 97 | 'pytest-2.2.3/pytest.egg-info', 'pytest-2.2.3/_pytest']), 98 | (1, r't/assertion$', True, False, ['pytest-2.2.3/_pytest/assertion']), 99 | (1, r'[0-9]/_pYtEsT$', True, True, ['pytest-2.2.3/_pytest']), 100 | (1, r'spam/.*_pytest.*', True, False, []), 101 | (1, r'setup.py', True, False, []), # should ignore file 102 | ]) 103 | def test_get_directories_re(self, i, r, f, c, expected): 104 | with self.a[i] as a: 105 | assert set(a.get_directories_re(r, f, c)) == set(expected) 106 | -------------------------------------------------------------------------------- /tests/test_convertor.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import os 3 | 4 | from flexmock import flexmock 5 | 6 | from pyp2rpm.convertor import Convertor 7 | from pyp2rpm.exceptions import NoSuchPackageException 8 | from pyp2rpm.metadata_extractors import (SetupPyMetadataExtractor, 9 | WheelMetadataExtractor) 10 | from pyp2rpm.package_getters import PypiDownloader, LocalFileGetter 11 | from pyp2rpm.package_data import PackageData 12 | 13 | tests_dir = os.path.split(os.path.abspath(__file__))[0] 14 | 15 | 16 | class TestConvertor(object): 17 | td_dir = '{0}/test_data/'.format(tests_dir) 18 | client = flexmock(package_releases=lambda n, hidden: n == 'spam' and ['0.1'] or []) 19 | Convertor._client = client # flexmock can't mock properties yet 20 | 21 | @pytest.mark.parametrize(('sf', 'g'), [ 22 | ('spam', PypiDownloader), 23 | ('{0}restsh-0.1.tar.gz'.format(td_dir), LocalFileGetter) 24 | ]) 25 | def test_getter_good_data(self, sf, g): 26 | c = Convertor(package=sf) 27 | assert isinstance(c.getter, g) 28 | 29 | @pytest.mark.parametrize(('sf', 'expected'), [ 30 | ('eggs', NoSuchPackageException), 31 | ('/spam/beans/eggs/ham', NoSuchPackageException) 32 | ]) 33 | def test_getter_bad_data(self, sf, expected): 34 | with pytest.raises(expected): 35 | c = Convertor(package=sf) 36 | c.getter 37 | 38 | @pytest.mark.parametrize(('sf', 'expected'), [ 39 | ('{0}plumbum-0.9.0.tar.gz'.format(td_dir), SetupPyMetadataExtractor), 40 | ('{0}setuptools-19.6-py2.py3-none-any.whl'.format(td_dir), 41 | WheelMetadataExtractor) 42 | ]) 43 | def test_get_metadata_extractor(self, sf, expected): 44 | c = Convertor(package=sf) 45 | c.local_file = sf 46 | c.name = 'plumbum' 47 | assert isinstance(c.metadata_extractor, expected) 48 | 49 | @pytest.mark.parametrize(('self_bv', 'self_pv', 'data_pv', 50 | 'expected_bv', 'expected_pv'), [ 51 | (None, [], ['2', '3'], '3', []), 52 | (None, [], ['3', '2'], '3', []), 53 | (None, [], [], '3', []), 54 | (None, ['2'], [], '3', ['2']), 55 | (None, ['2'], ['2'], '2', []), 56 | (None, ['2'], ['3'], '3', ['2']), 57 | ('2', [], ['2', '3'], '2', []), 58 | ('3', [], ['3', '2'], '3', []), 59 | ('3', ['2'], ['2', '3'], '3', ['2']), 60 | ]) 61 | def test_merge_versions_fedora(self, self_bv, self_pv, data_pv, 62 | expected_bv, expected_pv): 63 | c = Convertor(package='pkg', base_python_version=self_bv, 64 | python_versions=self_pv, template='fedora.spec') 65 | data = PackageData('pkg.tar.gz', 'pkg', 'pkg', '0.1') 66 | data.python_versions = data_pv 67 | c.merge_versions(data) 68 | assert data.base_python_version == expected_bv 69 | assert data.python_versions == expected_pv 70 | 71 | @pytest.mark.parametrize(('self_bv', 'self_pv', 'data_bv', 'data_pv', 72 | 'expected_bv', 'expected_pv'), [ 73 | (None, [], '2', ['3'], '2', []), 74 | (None, ['2'], '2', [], '2', []), 75 | ('2', [], '2', ['2'], '2', []), 76 | ('2', '2', '2', ['2'], '2', []), 77 | ]) 78 | def test_merge_versions_epel6(self, self_bv, self_pv, data_bv, data_pv, 79 | expected_bv, expected_pv): 80 | c = Convertor(package='pkg', base_python_version=self_bv, 81 | python_versions=self_pv, template='epel6.spec', 82 | distro='epel6') 83 | data = PackageData('pkg.tar.gz', 'pkg', 'pkg', '0.1') 84 | data.base_python_version = data_bv 85 | data.python_versions = data_pv 86 | c.merge_versions(data) 87 | assert data.base_python_version == expected_bv 88 | assert data.python_versions == expected_pv 89 | 90 | @pytest.mark.parametrize(('self_bv', 'self_pv', 'data_pv', 91 | 'expected_bv', 'expected_pv'), [ 92 | (None, [], ['2', '3'], '2', ['3']), 93 | (None, [], ['3', '2'], '2', ['3']), 94 | (None, [], [], '2', ['3']), 95 | (None, ['2'], [], '2', []), 96 | (None, ['2'], ['2'], '2', []), 97 | (None, ['2'], ['3'], '3', ['2']), 98 | ('2', [], ['2', '3'], '2', ['3']), 99 | ('3', [], ['3', '2'], '3', []), 100 | ('3', ['2'], ['2', '3'], '3', ['2']), 101 | ]) 102 | def test_merge_versions_epel7(self, self_bv, self_pv, data_pv, 103 | expected_bv, expected_pv): 104 | c = Convertor(package='pkg', base_python_version=self_bv, 105 | python_versions=self_pv, template='epel7.spec', 106 | distro='epel7') 107 | data = PackageData('pkg.tar.gz', 'pkg', 'pkg', '0.1') 108 | data.python_versions = data_pv 109 | c.merge_versions(data) 110 | assert data.base_python_version == expected_bv 111 | assert data.python_versions == expected_pv 112 | 113 | @pytest.mark.parametrize(('self_bv', 'self_pv'), [ 114 | ('3', []), 115 | (None, ['3']), 116 | ('3', ['3']) 117 | ]) 118 | def test_bad_versions(self, self_bv, self_pv): 119 | c = Convertor(package='pkg', base_python_version=self_bv, 120 | python_versions=self_pv, template='epel6.spec', 121 | distro='epel6') 122 | data = PackageData('pkg.tar.gz', 'pkg', 'pkg', '0.1') 123 | with pytest.raises(SystemExit): 124 | c.merge_versions(data) 125 | -------------------------------------------------------------------------------- /tests/test_data/LICENSE: -------------------------------------------------------------------------------- 1 | Files in this directory come from different sources and might have different licenses than pyp2rpm. 2 | See inside of the archives for an actual copyright information. 3 | -------------------------------------------------------------------------------- /tests/test_data/Sphinx-1.1.3-py2.6.egg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-python/pyp2rpm/92399daa509d2769433f97cf7f07209f738139a2/tests/test_data/Sphinx-1.1.3-py2.6.egg -------------------------------------------------------------------------------- /tests/test_data/bitarray-0.8.0.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-python/pyp2rpm/92399daa509d2769433f97cf7f07209f738139a2/tests/test_data/bitarray-0.8.0.tar.gz -------------------------------------------------------------------------------- /tests/test_data/coverage_pth-0.0.1.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-python/pyp2rpm/92399daa509d2769433f97cf7f07209f738139a2/tests/test_data/coverage_pth-0.0.1.tar.gz -------------------------------------------------------------------------------- /tests/test_data/isholiday-0.1/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 1.0 2 | Name: isholiday 3 | Version: 0.1 4 | Summary: Finds Czech holiday for given year 5 | Home-page: https://gist.github.com/oskar456/e91ef3ff77476b0dbc4ac19875d0555e 6 | Author: Ondřej Caletka 7 | Author-email: ondrej@caletka.cz 8 | License: Public Domain 9 | Description: UNKNOWN 10 | Platform: UNKNOWN 11 | -------------------------------------------------------------------------------- /tests/test_data/isholiday-0.1/isholiday.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | 4 | def getholidays(year): 5 | """Return a set public holidays in CZ as (day, month) tuples.""" 6 | 7 | holidays = { 8 | (1, 1), # Novy rok 9 | (1, 5), # Svatek prace 10 | (8, 5), # Den vitezstvi 11 | (5, 7), # Cyril a Metodej 12 | (6, 7), # Jan Hus 13 | (28, 9), # Den Ceske statnosti 14 | (28, 10), # Vznik CS statu 15 | (17, 11), # Den boje za svobodu a dem. 16 | (24, 12), # Stedry den 17 | (25, 12), # 1. svatek Vanocni 18 | (26, 12), # 2. svatek Vanocni 19 | } 20 | 21 | # Easter holiday LUT source 22 | # http://www.whyeaster.com/customs/dateofeaster.shtml 23 | easterlut = [(4, 14), (4, 3), (3, 23), (4, 11), (3, 31), (4, 18), (4, 8), 24 | (3, 28), (4, 16), (4, 5), (3, 25), (4, 13), (4, 2), (3, 22), 25 | (4, 10), (3, 30), (4, 17), (4, 7), (3, 27)] 26 | easterday = datetime.date(year, *easterlut[year % 19]) 27 | easterday += datetime.timedelta(6 - easterday.weekday()) 28 | # print("Easter Sunday is on ", easterday) 29 | holidays.update(((d.day, d.month) for d in [easterday - datetime.timedelta(2), 30 | easterday + datetime.timedelta(1)])) 31 | return holidays 32 | 33 | 34 | def isholiday(date): 35 | return (date.day, date.month) in getholidays(date.year) 36 | 37 | 38 | if __name__ == "__main__": 39 | for y in range(2016, 2025): 40 | print("{}: {}".format(y, getholidays(y))) 41 | testdates = [(2016, 3, 28), (2017, 3, 28), (2017, 4, 14)] 42 | for date in testdates: 43 | d = datetime.date(*date) 44 | print(d.ctime(), isholiday(d)) 45 | -------------------------------------------------------------------------------- /tests/test_data/isholiday-0.1/setup.cfg: -------------------------------------------------------------------------------- 1 | [egg_info] 2 | tag_date = 0 3 | tag_build = 4 | tag_svn_revision = 0 5 | 6 | -------------------------------------------------------------------------------- /tests/test_data/isholiday-0.1/setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from setuptools import setup 3 | 4 | setup( 5 | name='isholiday', 6 | version='0.1', 7 | description='Finds Czech holiday for given year', 8 | author='Ondřej Caletka', 9 | author_email='ondrej@caletka.cz', 10 | license='Public Domain', 11 | url='https://gist.github.com/oskar456/e91ef3ff77476b0dbc4ac19875d0555e', 12 | py_modules=['isholiday'], 13 | ) 14 | -------------------------------------------------------------------------------- /tests/test_data/netjsonconfig-0.5.1.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-python/pyp2rpm/92399daa509d2769433f97cf7f07209f738139a2/tests/test_data/netjsonconfig-0.5.1.tar.gz -------------------------------------------------------------------------------- /tests/test_data/pkginfo-1.2b1.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-python/pyp2rpm/92399daa509d2769433f97cf7f07209f738139a2/tests/test_data/pkginfo-1.2b1.tar.gz -------------------------------------------------------------------------------- /tests/test_data/plumbum-0.9.0.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-python/pyp2rpm/92399daa509d2769433f97cf7f07209f738139a2/tests/test_data/plumbum-0.9.0.tar.gz -------------------------------------------------------------------------------- /tests/test_data/py2exe-0.9.2.2-py33.py34-none-any.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-python/pyp2rpm/92399daa509d2769433f97cf7f07209f738139a2/tests/test_data/py2exe-0.9.2.2-py33.py34-none-any.whl -------------------------------------------------------------------------------- /tests/test_data/pytest-2.2.3.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-python/pyp2rpm/92399daa509d2769433f97cf7f07209f738139a2/tests/test_data/pytest-2.2.3.zip -------------------------------------------------------------------------------- /tests/test_data/python-Jinja2_dnfnc.spec: -------------------------------------------------------------------------------- 1 | # Created by pyp2rpm-3.2.3 2 | %global pypi_name Jinja2 3 | %global pypi_version 2.8 4 | 5 | Name: python-%{pypi_name} 6 | Version: %{pypi_version} 7 | Release: 1%{?dist} 8 | Summary: A small but fast and easy to use stand-alone template engine written in pure python 9 | 10 | License: BSD 11 | URL: http://jinja.pocoo.org/ 12 | Source0: %{pypi_source} 13 | BuildArch: noarch 14 | 15 | BuildRequires: python2-devel 16 | BuildRequires: python2-babel >= 0.8 17 | BuildRequires: python2-markupsafe 18 | BuildRequires: python2-setuptools 19 | 20 | BuildRequires: python3-devel 21 | BuildRequires: python3-babel >= 0.8 22 | BuildRequires: python3-markupsafe 23 | BuildRequires: python3-setuptools 24 | BuildRequires: python3-sphinx 25 | 26 | %description 27 | Jinja2 is a template engine written in pure Python. It provides a Django_ 28 | inspired non-XML syntax but supports inline expressions and an optional 29 | sandboxed_ environment.Nutshell Here a small example of a Jinja template:: {% 30 | extends 'base.html' %} {% block title %}Memberlist{% endblock %} {% block 31 | content %}
    {% for user in users %}
  • {{ 32 | user.username }}
  • ... 33 | 34 | %package -n python2-%{pypi_name} 35 | Summary: %{summary} 36 | %{?python_provide:%python_provide python2-%{pypi_name}} 37 | 38 | Requires: python2-babel >= 0.8 39 | Requires: python2-markupsafe 40 | %description -n python2-%{pypi_name} 41 | Jinja2 is a template engine written in pure Python. It provides a Django_ 42 | inspired non-XML syntax but supports inline expressions and an optional 43 | sandboxed_ environment.Nutshell Here a small example of a Jinja template:: {% 44 | extends 'base.html' %} {% block title %}Memberlist{% endblock %} {% block 45 | content %}
      {% for user in users %}
    • {{ 46 | user.username }}
    • ... 47 | 48 | %package -n python3-%{pypi_name} 49 | Summary: %{summary} 50 | %{?python_provide:%python_provide python3-%{pypi_name}} 51 | 52 | Requires: python3-babel >= 0.8 53 | Requires: python3-markupsafe 54 | %description -n python3-%{pypi_name} 55 | Jinja2 is a template engine written in pure Python. It provides a Django_ 56 | inspired non-XML syntax but supports inline expressions and an optional 57 | sandboxed_ environment.Nutshell Here a small example of a Jinja template:: {% 58 | extends 'base.html' %} {% block title %}Memberlist{% endblock %} {% block 59 | content %}
        {% for user in users %}
      • {{ 60 | user.username }}
      • ... 61 | 62 | %package -n python-%{pypi_name}-doc 63 | Summary: Jinja2 documentation 64 | %description -n python-%{pypi_name}-doc 65 | Documentation for Jinja2 66 | 67 | %prep 68 | %autosetup -n %{pypi_name}-%{pypi_version} 69 | # Remove bundled egg-info 70 | rm -rf %{pypi_name}.egg-info 71 | 72 | %build 73 | %py2_build 74 | %py3_build 75 | # generate html docs 76 | PYTHONPATH=${PWD} sphinx-build-3 docs html 77 | # remove the sphinx-build leftovers 78 | rm -rf html/.{doctrees,buildinfo} 79 | 80 | %install 81 | # Must do the default python version install last because 82 | # the scripts in /usr/bin are overwritten with every setup.py install. 83 | %py2_install 84 | %py3_install 85 | 86 | %check 87 | %{__python2} setup.py test 88 | %{__python3} setup.py test 89 | 90 | %files -n python2-%{pypi_name} 91 | %license docs/_themes/LICENSE LICENSE 92 | %doc README.rst 93 | %{python2_sitelib}/jinja2 94 | %{python2_sitelib}/%{pypi_name}-%{pypi_version}-py%{python2_version}.egg-info 95 | 96 | %files -n python3-%{pypi_name} 97 | %license docs/_themes/LICENSE LICENSE 98 | %doc README.rst 99 | %{python3_sitelib}/jinja2 100 | %{python3_sitelib}/%{pypi_name}-%{pypi_version}-py%{python3_version}.egg-info 101 | 102 | %files -n python-%{pypi_name}-doc 103 | %doc html 104 | %license docs/_themes/LICENSE LICENSE 105 | 106 | %changelog 107 | * Wed Dec 06 2017 Michal Cyprian - 2.8-1 108 | - Initial package. 109 | -------------------------------------------------------------------------------- /tests/test_data/python-Jinja2_epel6_dnfnc.spec: -------------------------------------------------------------------------------- 1 | # Created by pyp2rpm-3.2.3 2 | %global pypi_name Jinja2 3 | %global pypi_version 2.8 4 | 5 | Name: python-%{pypi_name} 6 | Version: %{pypi_version} 7 | Release: 1%{?dist} 8 | Summary: A small but fast and easy to use stand-alone template engine written in pure python 9 | 10 | License: BSD 11 | URL: http://jinja.pocoo.org/ 12 | Source0: https://files.pythonhosted.org/packages/source/J/%{pypi_name}/%{pypi_name}-%{pypi_version}.tar.gz 13 | BuildArch: noarch 14 | 15 | BuildRequires: python2-devel 16 | BuildRequires: python2-babel >= 0.8 17 | BuildRequires: python2-MarkupSafe 18 | BuildRequires: python2-setuptools 19 | BuildRequires: python2-sphinx 20 | 21 | Requires: python2-babel >= 0.8 22 | Requires: python2-MarkupSafe 23 | 24 | %description 25 | Jinja2 is a template engine written in pure Python. It provides a Django_ 26 | inspired non-XML syntax but supports inline expressions and an optional 27 | sandboxed_ environment.Nutshell Here a small example of a Jinja template:: {% 28 | extends 'base.html' %} {% block title %}Memberlist{% endblock %} {% block 29 | content %}
          {% for user in users %}
        • {{ 30 | user.username }}
        • ... 31 | 32 | %package -n python-%{pypi_name}-doc 33 | Summary: Jinja2 documentation 34 | %description -n python-%{pypi_name}-doc 35 | Documentation for Jinja2 36 | 37 | %prep 38 | %setup -q -n %{pypi_name}-%{pypi_version} 39 | # Remove bundled egg-info 40 | rm -rf %{pypi_name}.egg-info 41 | 42 | # generate html docs 43 | PYTHONPATH=${PWD} sphinx-build docs html 44 | # remove the sphinx-build leftovers 45 | rm -rf html/.{doctrees,buildinfo} 46 | 47 | %build 48 | %{__python2} setup.py build 49 | 50 | 51 | %install 52 | %{__python2} setup.py install --skip-build --root %{buildroot} 53 | %check 54 | %{__python2} setup.py test 55 | 56 | 57 | %files 58 | %doc README.rst 59 | %{python2_sitelib}/jinja2 60 | %{python2_sitelib}/%{pypi_name}-%{pypi_version}-py%{python2_version}.egg-info 61 | 62 | %files -n python-%{pypi_name}-doc 63 | %doc html 64 | %license docs/_themes/LICENSE LICENSE 65 | 66 | %changelog 67 | * Wed Dec 06 2017 Michal Cyprian - 2.8-1 68 | - Initial package. 69 | -------------------------------------------------------------------------------- /tests/test_data/python-Jinja2_epel6_nc.spec: -------------------------------------------------------------------------------- 1 | # Created by pyp2rpm-3.2.3 2 | %global pypi_name Jinja2 3 | %global pypi_version 2.8 4 | 5 | Name: python-%{pypi_name} 6 | Version: %{pypi_version} 7 | Release: 1%{?dist} 8 | Summary: A small but fast and easy to use stand-alone template engine written in pure python 9 | 10 | License: BSD 11 | URL: http://jinja.pocoo.org/ 12 | Source0: https://files.pythonhosted.org/packages/source/J/%{pypi_name}/%{pypi_name}-%{pypi_version}.tar.gz 13 | BuildArch: noarch 14 | 15 | BuildRequires: python2-devel 16 | BuildRequires: python2-Babel >= 0.8 17 | BuildRequires: python2-MarkupSafe 18 | BuildRequires: python2-setuptools 19 | BuildRequires: python2-sphinx 20 | 21 | Requires: python2-Babel >= 0.8 22 | Requires: python2-MarkupSafe 23 | 24 | %description 25 | Jinja2 is a template engine written in pure Python. It provides a Django_ 26 | inspired non-XML syntax but supports inline expressions and an optional 27 | sandboxed_ environment.Nutshell Here a small example of a Jinja template:: {% 28 | extends 'base.html' %} {% block title %}Memberlist{% endblock %} {% block 29 | content %}
            {% for user in users %}
          • {{ 30 | user.username }}
          • ... 31 | 32 | %package -n python-%{pypi_name}-doc 33 | Summary: Jinja2 documentation 34 | %description -n python-%{pypi_name}-doc 35 | Documentation for Jinja2 36 | 37 | %prep 38 | %setup -q -n %{pypi_name}-%{pypi_version} 39 | # Remove bundled egg-info 40 | rm -rf %{pypi_name}.egg-info 41 | 42 | # generate html docs 43 | PYTHONPATH=${PWD} sphinx-build docs html 44 | # remove the sphinx-build leftovers 45 | rm -rf html/.{doctrees,buildinfo} 46 | 47 | %build 48 | %{__python2} setup.py build 49 | 50 | 51 | %install 52 | %{__python2} setup.py install --skip-build --root %{buildroot} 53 | %check 54 | %{__python2} setup.py test 55 | 56 | 57 | %files 58 | %doc README.rst 59 | %{python2_sitelib}/jinja2 60 | %{python2_sitelib}/%{pypi_name}-%{pypi_version}-py%{python2_version}.egg-info 61 | 62 | %files -n python-%{pypi_name}-doc 63 | %doc html 64 | %license docs/_themes/LICENSE LICENSE 65 | 66 | %changelog 67 | * Wed Dec 06 2017 Michal Cyprian - 2.8-1 68 | - Initial package. 69 | -------------------------------------------------------------------------------- /tests/test_data/python-Jinja2_epel7_dnfnc.spec: -------------------------------------------------------------------------------- 1 | # Created by pyp2rpm-3.2.3 2 | %global pypi_name Jinja2 3 | %global pypi_version 2.8 4 | 5 | Name: python-%{pypi_name} 6 | Version: %{pypi_version} 7 | Release: 1%{?dist} 8 | Summary: A small but fast and easy to use stand-alone template engine written in pure python 9 | 10 | License: BSD 11 | URL: http://jinja.pocoo.org/ 12 | Source0: https://files.pythonhosted.org/packages/source/J/%{pypi_name}/%{pypi_name}-%{pypi_version}.tar.gz 13 | BuildArch: noarch 14 | 15 | BuildRequires: python2-devel 16 | BuildRequires: python2-babel >= 0.8 17 | BuildRequires: python2-markupsafe 18 | BuildRequires: python2-setuptools 19 | 20 | BuildRequires: python%{python3_pkgversion}-devel 21 | BuildRequires: python%{python3_pkgversion}-babel >= 0.8 22 | BuildRequires: python%{python3_pkgversion}-markupsafe 23 | BuildRequires: python%{python3_pkgversion}-setuptools 24 | BuildRequires: python%{python3_pkgversion}-sphinx 25 | 26 | %description 27 | Jinja2 is a template engine written in pure Python. It provides a Django_ 28 | inspired non-XML syntax but supports inline expressions and an optional 29 | sandboxed_ environment.Nutshell Here a small example of a Jinja template:: {% 30 | extends 'base.html' %} {% block title %}Memberlist{% endblock %} {% block 31 | content %}
              {% for user in users %}
            • {{ 32 | user.username }}
            • ... 33 | 34 | %package -n python2-%{pypi_name} 35 | Summary: A small but fast and easy to use stand-alone template engine written in pure python 36 | 37 | Requires: python2-babel >= 0.8 38 | Requires: python2-markupsafe 39 | %description -n python2-%{pypi_name} 40 | Jinja2 is a template engine written in pure Python. It provides a Django_ 41 | inspired non-XML syntax but supports inline expressions and an optional 42 | sandboxed_ environment.Nutshell Here a small example of a Jinja template:: {% 43 | extends 'base.html' %} {% block title %}Memberlist{% endblock %} {% block 44 | content %}
                {% for user in users %}
              • {{ 45 | user.username }}
              • ... 46 | 47 | %package -n python%{python3_pkgversion}-%{pypi_name} 48 | Summary: A small but fast and easy to use stand-alone template engine written in pure python 49 | 50 | Requires: python%{python3_pkgversion}-babel >= 0.8 51 | Requires: python%{python3_pkgversion}-markupsafe 52 | %description -n python%{python3_pkgversion}-%{pypi_name} 53 | Jinja2 is a template engine written in pure Python. It provides a Django_ 54 | inspired non-XML syntax but supports inline expressions and an optional 55 | sandboxed_ environment.Nutshell Here a small example of a Jinja template:: {% 56 | extends 'base.html' %} {% block title %}Memberlist{% endblock %} {% block 57 | content %}
                  {% for user in users %}
                • {{ 58 | user.username }}
                • ... 59 | 60 | %package -n python-%{pypi_name}-doc 61 | Summary: Jinja2 documentation 62 | %description -n python-%{pypi_name}-doc 63 | Documentation for Jinja2 64 | 65 | %prep 66 | %autosetup -n %{pypi_name}-%{pypi_version} 67 | # Remove bundled egg-info 68 | rm -rf %{pypi_name}.egg-info 69 | 70 | %build 71 | %{__python2} setup.py build 72 | %{__python3} setup.py build 73 | # generate html docs 74 | PYTHONPATH=${PWD} sphinx-build-%{python3_version} docs html 75 | # remove the sphinx-build leftovers 76 | rm -rf html/.{doctrees,buildinfo} 77 | 78 | %install 79 | # Must do the default python version install last because 80 | # the scripts in /usr/bin are overwritten with every setup.py install. 81 | %{__python2} setup.py install --skip-build --root %{buildroot} 82 | %{__python3} setup.py install --skip-build --root %{buildroot} 83 | 84 | %check 85 | %{__python2} setup.py test 86 | %{__python3} setup.py test 87 | 88 | %files -n python2-%{pypi_name} 89 | %doc README.rst 90 | %{python2_sitelib}/jinja2 91 | %{python2_sitelib}/%{pypi_name}-%{pypi_version}-py%{python2_version}.egg-info 92 | 93 | %files -n python%{python3_pkgversion}-%{pypi_name} 94 | %doc README.rst 95 | %{python3_sitelib}/jinja2 96 | %{python3_sitelib}/%{pypi_name}-%{pypi_version}-py%{python3_version}.egg-info 97 | 98 | %files -n python-%{pypi_name}-doc 99 | %doc html 100 | %license docs/_themes/LICENSE LICENSE 101 | 102 | %changelog 103 | * Wed Dec 06 2017 Michal Cyprian - 2.8-1 104 | - Initial package. 105 | -------------------------------------------------------------------------------- /tests/test_data/python-Jinja2_epel7_nc.spec: -------------------------------------------------------------------------------- 1 | # Created by pyp2rpm-3.2.3 2 | %global pypi_name Jinja2 3 | %global pypi_version 2.8 4 | 5 | Name: python-%{pypi_name} 6 | Version: %{pypi_version} 7 | Release: 1%{?dist} 8 | Summary: A small but fast and easy to use stand-alone template engine written in pure python 9 | 10 | License: BSD 11 | URL: http://jinja.pocoo.org/ 12 | Source0: https://files.pythonhosted.org/packages/source/J/%{pypi_name}/%{pypi_name}-%{pypi_version}.tar.gz 13 | BuildArch: noarch 14 | 15 | BuildRequires: python2-devel 16 | BuildRequires: python2-Babel >= 0.8 17 | BuildRequires: python2-MarkupSafe 18 | BuildRequires: python2-setuptools 19 | 20 | BuildRequires: python%{python3_pkgversion}-devel 21 | BuildRequires: python%{python3_pkgversion}-Babel >= 0.8 22 | BuildRequires: python%{python3_pkgversion}-MarkupSafe 23 | BuildRequires: python%{python3_pkgversion}-setuptools 24 | BuildRequires: python%{python3_pkgversion}-sphinx 25 | 26 | %description 27 | Jinja2 is a template engine written in pure Python. It provides a Django_ 28 | inspired non-XML syntax but supports inline expressions and an optional 29 | sandboxed_ environment.Nutshell Here a small example of a Jinja template:: {% 30 | extends 'base.html' %} {% block title %}Memberlist{% endblock %} {% block 31 | content %}
                    {% for user in users %}
                  • {{ 32 | user.username }}
                  • ... 33 | 34 | %package -n python2-%{pypi_name} 35 | Summary: A small but fast and easy to use stand-alone template engine written in pure python 36 | 37 | Requires: python2-Babel >= 0.8 38 | Requires: python2-MarkupSafe 39 | %description -n python2-%{pypi_name} 40 | Jinja2 is a template engine written in pure Python. It provides a Django_ 41 | inspired non-XML syntax but supports inline expressions and an optional 42 | sandboxed_ environment.Nutshell Here a small example of a Jinja template:: {% 43 | extends 'base.html' %} {% block title %}Memberlist{% endblock %} {% block 44 | content %}
                      {% for user in users %}
                    • {{ 45 | user.username }}
                    • ... 46 | 47 | %package -n python%{python3_pkgversion}-%{pypi_name} 48 | Summary: A small but fast and easy to use stand-alone template engine written in pure python 49 | 50 | Requires: python%{python3_pkgversion}-Babel >= 0.8 51 | Requires: python%{python3_pkgversion}-MarkupSafe 52 | %description -n python%{python3_pkgversion}-%{pypi_name} 53 | Jinja2 is a template engine written in pure Python. It provides a Django_ 54 | inspired non-XML syntax but supports inline expressions and an optional 55 | sandboxed_ environment.Nutshell Here a small example of a Jinja template:: {% 56 | extends 'base.html' %} {% block title %}Memberlist{% endblock %} {% block 57 | content %}
                        {% for user in users %}
                      • {{ 58 | user.username }}
                      • ... 59 | 60 | %package -n python-%{pypi_name}-doc 61 | Summary: Jinja2 documentation 62 | %description -n python-%{pypi_name}-doc 63 | Documentation for Jinja2 64 | 65 | %prep 66 | %autosetup -n %{pypi_name}-%{pypi_version} 67 | # Remove bundled egg-info 68 | rm -rf %{pypi_name}.egg-info 69 | 70 | %build 71 | %{__python2} setup.py build 72 | %{__python3} setup.py build 73 | # generate html docs 74 | PYTHONPATH=${PWD} sphinx-build-%{python3_version} docs html 75 | # remove the sphinx-build leftovers 76 | rm -rf html/.{doctrees,buildinfo} 77 | 78 | %install 79 | # Must do the default python version install last because 80 | # the scripts in /usr/bin are overwritten with every setup.py install. 81 | %{__python2} setup.py install --skip-build --root %{buildroot} 82 | %{__python3} setup.py install --skip-build --root %{buildroot} 83 | 84 | %check 85 | %{__python2} setup.py test 86 | %{__python3} setup.py test 87 | 88 | %files -n python2-%{pypi_name} 89 | %doc README.rst 90 | %{python2_sitelib}/jinja2 91 | %{python2_sitelib}/%{pypi_name}-%{pypi_version}-py%{python2_version}.egg-info 92 | 93 | %files -n python%{python3_pkgversion}-%{pypi_name} 94 | %doc README.rst 95 | %{python3_sitelib}/jinja2 96 | %{python3_sitelib}/%{pypi_name}-%{pypi_version}-py%{python3_version}.egg-info 97 | 98 | %files -n python-%{pypi_name}-doc 99 | %doc html 100 | %license docs/_themes/LICENSE LICENSE 101 | 102 | %changelog 103 | * Wed Dec 06 2017 Michal Cyprian - 2.8-1 104 | - Initial package. 105 | -------------------------------------------------------------------------------- /tests/test_data/python-Jinja2_mageia_py23.spec: -------------------------------------------------------------------------------- 1 | # Created by pyp2rpm-3.3.1 2 | %global pypi_name Jinja2 3 | %global pypi_version 2.8 4 | 5 | Name: python-%{pypi_name} 6 | Version: %{pypi_version} 7 | Release: %mkrel 1 8 | Summary: A small but fast and easy to use stand-alone template engine written in pure python 9 | Group: Development/Python 10 | License: BSD 11 | URL: http://jinja.pocoo.org/ 12 | Source0: https://files.pythonhosted.org/packages/source/J/%{pypi_name}/%{pypi_name}-%{pypi_version}.tar.gz 13 | BuildArch: noarch 14 | 15 | BuildRequires: python2-devel 16 | BuildRequires: python2dist(babel) >= 0.8 17 | BuildRequires: python2dist(markupsafe) 18 | BuildRequires: python2dist(setuptools) 19 | 20 | BuildRequires: python3-devel 21 | BuildRequires: python3dist(babel) >= 0.8 22 | BuildRequires: python3dist(markupsafe) 23 | BuildRequires: python3dist(setuptools) 24 | BuildRequires: python3dist(sphinx) 25 | 26 | %description 27 | Jinja2 is a template engine written in pure Python. It provides a Django_ 28 | inspired non-XML syntax but supports inline expressions and an optional 29 | sandboxed_ environment.Nutshell Here a small example of a Jinja template:: {% 30 | extends 'base.html' %} {% block title %}Memberlist{% endblock %} {% block 31 | content %}
                          {% for user in users %}
                        • {{ 32 | user.username }}
                        • ... 33 | 34 | %package -n python2-%{pypi_name} 35 | Summary: %{summary} 36 | %{?python_provide:%python_provide python2-%{pypi_name}} 37 | 38 | Requires: python2dist(babel) >= 0.8 39 | Requires: python2dist(markupsafe) 40 | %description -n python2-%{pypi_name} 41 | Jinja2 is a template engine written in pure Python. It provides a Django_ 42 | inspired non-XML syntax but supports inline expressions and an optional 43 | sandboxed_ environment.Nutshell Here a small example of a Jinja template:: {% 44 | extends 'base.html' %} {% block title %}Memberlist{% endblock %} {% block 45 | content %}
                            {% for user in users %}
                          • {{ 46 | user.username }}
                          • ... 47 | 48 | %package -n python3-%{pypi_name} 49 | Summary: %{summary} 50 | %{?python_provide:%python_provide python3-%{pypi_name}} 51 | 52 | Requires: python3dist(babel) >= 0.8 53 | Requires: python3dist(markupsafe) 54 | %description -n python3-%{pypi_name} 55 | Jinja2 is a template engine written in pure Python. It provides a Django_ 56 | inspired non-XML syntax but supports inline expressions and an optional 57 | sandboxed_ environment.Nutshell Here a small example of a Jinja template:: {% 58 | extends 'base.html' %} {% block title %}Memberlist{% endblock %} {% block 59 | content %}
                              {% for user in users %}
                            • {{ 60 | user.username }}
                            • ... 61 | 62 | %package -n python-%{pypi_name}-doc 63 | Summary: Jinja2 documentation 64 | %description -n python-%{pypi_name}-doc 65 | Documentation for Jinja2 66 | 67 | %prep 68 | %autosetup -n %{pypi_name}-%{pypi_version} 69 | # Remove bundled egg-info 70 | rm -rf %{pypi_name}.egg-info 71 | 72 | %build 73 | %py2_build 74 | %py3_build 75 | # generate html docs 76 | PYTHONPATH=${PWD} sphinx-build-3 docs html 77 | # remove the sphinx-build leftovers 78 | rm -rf html/.{doctrees,buildinfo} 79 | 80 | %install 81 | # Must do the default python version install last because 82 | # the scripts in /usr/bin are overwritten with every setup.py install. 83 | %py2_install 84 | %py3_install 85 | 86 | %check 87 | %{__python2} setup.py test 88 | %{__python3} setup.py test 89 | 90 | %files -n python2-%{pypi_name} 91 | %license docs/_themes/LICENSE LICENSE 92 | %doc README.rst 93 | %{python2_sitelib}/jinja2 94 | %{python2_sitelib}/%{pypi_name}-%{pypi_version}-py%{python2_version}.egg-info 95 | 96 | %files -n python3-%{pypi_name} 97 | %license docs/_themes/LICENSE LICENSE 98 | %doc README.rst 99 | %{python3_sitelib}/jinja2 100 | %{python3_sitelib}/%{pypi_name}-%{pypi_version}-py%{python3_version}.egg-info 101 | 102 | %files -n python-%{pypi_name}-doc 103 | %doc html 104 | %license docs/_themes/LICENSE LICENSE 105 | 106 | -------------------------------------------------------------------------------- /tests/test_data/python-Jinja2_nc.spec: -------------------------------------------------------------------------------- 1 | # Created by pyp2rpm-3.2.3 2 | %global pypi_name Jinja2 3 | %global pypi_version 2.8 4 | 5 | Name: python-%{pypi_name} 6 | Version: %{pypi_version} 7 | Release: 1%{?dist} 8 | Summary: A small but fast and easy to use stand-alone template engine written in pure python 9 | 10 | License: BSD 11 | URL: http://jinja.pocoo.org/ 12 | Source0: %{pypi_source} 13 | BuildArch: noarch 14 | 15 | BuildRequires: python2-devel 16 | BuildRequires: python2-Babel >= 0.8 17 | BuildRequires: python2-MarkupSafe 18 | BuildRequires: python2-setuptools 19 | 20 | BuildRequires: python3-devel 21 | BuildRequires: python3-Babel >= 0.8 22 | BuildRequires: python3-MarkupSafe 23 | BuildRequires: python3-setuptools 24 | BuildRequires: python3-sphinx 25 | 26 | %description 27 | Jinja2 is a template engine written in pure Python. It provides a Django_ 28 | inspired non-XML syntax but supports inline expressions and an optional 29 | sandboxed_ environment.Nutshell Here a small example of a Jinja template:: {% 30 | extends 'base.html' %} {% block title %}Memberlist{% endblock %} {% block 31 | content %}
                                {% for user in users %}
                              • {{ 32 | user.username }}
                              • ... 33 | 34 | %package -n python2-%{pypi_name} 35 | Summary: %{summary} 36 | %{?python_provide:%python_provide python2-%{pypi_name}} 37 | 38 | Requires: python2-Babel >= 0.8 39 | Requires: python2-MarkupSafe 40 | %description -n python2-%{pypi_name} 41 | Jinja2 is a template engine written in pure Python. It provides a Django_ 42 | inspired non-XML syntax but supports inline expressions and an optional 43 | sandboxed_ environment.Nutshell Here a small example of a Jinja template:: {% 44 | extends 'base.html' %} {% block title %}Memberlist{% endblock %} {% block 45 | content %}
                                  {% for user in users %}
                                • {{ 46 | user.username }}
                                • ... 47 | 48 | %package -n python3-%{pypi_name} 49 | Summary: %{summary} 50 | %{?python_provide:%python_provide python3-%{pypi_name}} 51 | 52 | Requires: python3-Babel >= 0.8 53 | Requires: python3-MarkupSafe 54 | %description -n python3-%{pypi_name} 55 | Jinja2 is a template engine written in pure Python. It provides a Django_ 56 | inspired non-XML syntax but supports inline expressions and an optional 57 | sandboxed_ environment.Nutshell Here a small example of a Jinja template:: {% 58 | extends 'base.html' %} {% block title %}Memberlist{% endblock %} {% block 59 | content %}
                                    {% for user in users %}
                                  • {{ 60 | user.username }}
                                  • ... 61 | 62 | %package -n python-%{pypi_name}-doc 63 | Summary: Jinja2 documentation 64 | %description -n python-%{pypi_name}-doc 65 | Documentation for Jinja2 66 | 67 | %prep 68 | %autosetup -n %{pypi_name}-%{pypi_version} 69 | # Remove bundled egg-info 70 | rm -rf %{pypi_name}.egg-info 71 | 72 | %build 73 | %py2_build 74 | %py3_build 75 | # generate html docs 76 | PYTHONPATH=${PWD} sphinx-build-3 docs html 77 | # remove the sphinx-build leftovers 78 | rm -rf html/.{doctrees,buildinfo} 79 | 80 | %install 81 | # Must do the default python version install last because 82 | # the scripts in /usr/bin are overwritten with every setup.py install. 83 | %py2_install 84 | %py3_install 85 | 86 | %check 87 | %{__python2} setup.py test 88 | %{__python3} setup.py test 89 | 90 | %files -n python2-%{pypi_name} 91 | %license docs/_themes/LICENSE LICENSE 92 | %doc README.rst 93 | %{python2_sitelib}/jinja2 94 | %{python2_sitelib}/%{pypi_name}-%{pypi_version}-py%{python2_version}.egg-info 95 | 96 | %files -n python3-%{pypi_name} 97 | %license docs/_themes/LICENSE LICENSE 98 | %doc README.rst 99 | %{python3_sitelib}/jinja2 100 | %{python3_sitelib}/%{pypi_name}-%{pypi_version}-py%{python3_version}.egg-info 101 | 102 | %files -n python-%{pypi_name}-doc 103 | %doc html 104 | %license docs/_themes/LICENSE LICENSE 105 | 106 | %changelog 107 | * Wed Dec 06 2017 Michal Cyprian - 2.8-1 108 | - Initial package. 109 | -------------------------------------------------------------------------------- /tests/test_data/python-Jinja2_py23_autonc.spec: -------------------------------------------------------------------------------- 1 | # Created by pyp2rpm-3.2.3 2 | %global pypi_name Jinja2 3 | %global pypi_version 2.8 4 | 5 | Name: python-%{pypi_name} 6 | Version: %{pypi_version} 7 | Release: 1%{?dist} 8 | Summary: A small but fast and easy to use stand-alone template engine written in pure python 9 | 10 | License: BSD 11 | URL: http://jinja.pocoo.org/ 12 | Source0: %{pypi_source} 13 | BuildArch: noarch 14 | 15 | BuildRequires: python2-devel 16 | BuildRequires: python2dist(babel) >= 0.8 17 | BuildRequires: python2dist(markupsafe) 18 | BuildRequires: python2dist(setuptools) 19 | 20 | BuildRequires: python3-devel 21 | BuildRequires: python3dist(babel) >= 0.8 22 | BuildRequires: python3dist(markupsafe) 23 | BuildRequires: python3dist(setuptools) 24 | BuildRequires: python3dist(sphinx) 25 | 26 | %description 27 | Jinja2 is a template engine written in pure Python. It provides a Django_ 28 | inspired non-XML syntax but supports inline expressions and an optional 29 | sandboxed_ environment.Nutshell Here a small example of a Jinja template:: {% 30 | extends 'base.html' %} {% block title %}Memberlist{% endblock %} {% block 31 | content %}
                                      {% for user in users %}
                                    • {{ 32 | user.username }}
                                    • ... 33 | 34 | %package -n python2-%{pypi_name} 35 | Summary: %{summary} 36 | %{?python_provide:%python_provide python2-%{pypi_name}} 37 | 38 | Requires: python2dist(babel) >= 0.8 39 | Requires: python2dist(markupsafe) 40 | %description -n python2-%{pypi_name} 41 | Jinja2 is a template engine written in pure Python. It provides a Django_ 42 | inspired non-XML syntax but supports inline expressions and an optional 43 | sandboxed_ environment.Nutshell Here a small example of a Jinja template:: {% 44 | extends 'base.html' %} {% block title %}Memberlist{% endblock %} {% block 45 | content %}
                                        {% for user in users %}
                                      • {{ 46 | user.username }}
                                      • ... 47 | 48 | %package -n python3-%{pypi_name} 49 | Summary: %{summary} 50 | %{?python_provide:%python_provide python3-%{pypi_name}} 51 | 52 | Requires: python3dist(babel) >= 0.8 53 | Requires: python3dist(markupsafe) 54 | %description -n python3-%{pypi_name} 55 | Jinja2 is a template engine written in pure Python. It provides a Django_ 56 | inspired non-XML syntax but supports inline expressions and an optional 57 | sandboxed_ environment.Nutshell Here a small example of a Jinja template:: {% 58 | extends 'base.html' %} {% block title %}Memberlist{% endblock %} {% block 59 | content %}
                                          {% for user in users %}
                                        • {{ 60 | user.username }}
                                        • ... 61 | 62 | %package -n python-%{pypi_name}-doc 63 | Summary: Jinja2 documentation 64 | %description -n python-%{pypi_name}-doc 65 | Documentation for Jinja2 66 | 67 | %prep 68 | %autosetup -n %{pypi_name}-%{pypi_version} 69 | # Remove bundled egg-info 70 | rm -rf %{pypi_name}.egg-info 71 | 72 | %build 73 | %py2_build 74 | %py3_build 75 | # generate html docs 76 | PYTHONPATH=${PWD} sphinx-build-3 docs html 77 | # remove the sphinx-build leftovers 78 | rm -rf html/.{doctrees,buildinfo} 79 | 80 | %install 81 | # Must do the default python version install last because 82 | # the scripts in /usr/bin are overwritten with every setup.py install. 83 | %py2_install 84 | %py3_install 85 | 86 | %check 87 | %{__python2} setup.py test 88 | %{__python3} setup.py test 89 | 90 | %files -n python2-%{pypi_name} 91 | %license docs/_themes/LICENSE LICENSE 92 | %doc README.rst 93 | %{python2_sitelib}/jinja2 94 | %{python2_sitelib}/%{pypi_name}-%{pypi_version}-py%{python2_version}.egg-info 95 | 96 | %files -n python3-%{pypi_name} 97 | %license docs/_themes/LICENSE LICENSE 98 | %doc README.rst 99 | %{python3_sitelib}/jinja2 100 | %{python3_sitelib}/%{pypi_name}-%{pypi_version}-py%{python3_version}.egg-info 101 | 102 | %files -n python-%{pypi_name}-doc 103 | %doc html 104 | %license docs/_themes/LICENSE LICENSE 105 | 106 | %changelog 107 | * Wed Dec 06 2017 Michal Cyprian - 2.8-1 108 | - Initial package. 109 | -------------------------------------------------------------------------------- /tests/test_data/python-Jinja2_py2_autonc.spec: -------------------------------------------------------------------------------- 1 | # Created by pyp2rpm-3.2.3 2 | %global pypi_name Jinja2 3 | %global pypi_version 2.8 4 | 5 | Name: python-%{pypi_name} 6 | Version: %{pypi_version} 7 | Release: 1%{?dist} 8 | Summary: A small but fast and easy to use stand-alone template engine written in pure python 9 | 10 | License: BSD 11 | URL: http://jinja.pocoo.org/ 12 | Source0: %{pypi_source} 13 | BuildArch: noarch 14 | 15 | BuildRequires: python2-devel 16 | BuildRequires: python2dist(babel) >= 0.8 17 | BuildRequires: python2dist(markupsafe) 18 | BuildRequires: python2dist(setuptools) 19 | BuildRequires: python2dist(sphinx) 20 | 21 | %description 22 | Jinja2 is a template engine written in pure Python. It provides a Django_ 23 | inspired non-XML syntax but supports inline expressions and an optional 24 | sandboxed_ environment.Nutshell Here a small example of a Jinja template:: {% 25 | extends 'base.html' %} {% block title %}Memberlist{% endblock %} {% block 26 | content %}
                                            {% for user in users %}
                                          • {{ 27 | user.username }}
                                          • ... 28 | 29 | %package -n python2-%{pypi_name} 30 | Summary: %{summary} 31 | %{?python_provide:%python_provide python2-%{pypi_name}} 32 | 33 | Requires: python2dist(babel) >= 0.8 34 | Requires: python2dist(markupsafe) 35 | %description -n python2-%{pypi_name} 36 | Jinja2 is a template engine written in pure Python. It provides a Django_ 37 | inspired non-XML syntax but supports inline expressions and an optional 38 | sandboxed_ environment.Nutshell Here a small example of a Jinja template:: {% 39 | extends 'base.html' %} {% block title %}Memberlist{% endblock %} {% block 40 | content %}
                                              {% for user in users %}
                                            • {{ 41 | user.username }}
                                            • ... 42 | 43 | %package -n python-%{pypi_name}-doc 44 | Summary: Jinja2 documentation 45 | %description -n python-%{pypi_name}-doc 46 | Documentation for Jinja2 47 | 48 | %prep 49 | %autosetup -n %{pypi_name}-%{pypi_version} 50 | # Remove bundled egg-info 51 | rm -rf %{pypi_name}.egg-info 52 | 53 | %build 54 | %py2_build 55 | # generate html docs 56 | PYTHONPATH=${PWD} sphinx-build-2 docs html 57 | # remove the sphinx-build leftovers 58 | rm -rf html/.{doctrees,buildinfo} 59 | 60 | %install 61 | %py2_install 62 | 63 | %check 64 | %{__python2} setup.py test 65 | 66 | %files -n python2-%{pypi_name} 67 | %license docs/_themes/LICENSE LICENSE 68 | %doc README.rst 69 | %{python2_sitelib}/jinja2 70 | %{python2_sitelib}/%{pypi_name}-%{pypi_version}-py%{python2_version}.egg-info 71 | 72 | %files -n python-%{pypi_name}-doc 73 | %doc html 74 | %license docs/_themes/LICENSE LICENSE 75 | 76 | %changelog 77 | * Tue Mar 20 2018 Iryna Shcherbina - 2.8-1 78 | - Initial package. 79 | -------------------------------------------------------------------------------- /tests/test_data/python-Jinja2_py3_autonc.spec: -------------------------------------------------------------------------------- 1 | # Created by pyp2rpm-3.2.3 2 | %global pypi_name Jinja2 3 | %global pypi_version 2.8 4 | 5 | Name: python-%{pypi_name} 6 | Version: %{pypi_version} 7 | Release: 1%{?dist} 8 | Summary: A small but fast and easy to use stand-alone template engine written in pure python 9 | 10 | License: BSD 11 | URL: http://jinja.pocoo.org/ 12 | Source0: %{pypi_source} 13 | BuildArch: noarch 14 | 15 | BuildRequires: python3-devel 16 | BuildRequires: python3dist(babel) >= 0.8 17 | BuildRequires: python3dist(markupsafe) 18 | BuildRequires: python3dist(setuptools) 19 | BuildRequires: python3dist(sphinx) 20 | 21 | %description 22 | Jinja2 is a template engine written in pure Python. It provides a Django_ 23 | inspired non-XML syntax but supports inline expressions and an optional 24 | sandboxed_ environment.Nutshell Here a small example of a Jinja template:: {% 25 | extends 'base.html' %} {% block title %}Memberlist{% endblock %} {% block 26 | content %}
                                                {% for user in users %}
                                              • {{ 27 | user.username }}
                                              • ... 28 | 29 | %package -n python3-%{pypi_name} 30 | Summary: %{summary} 31 | %{?python_provide:%python_provide python3-%{pypi_name}} 32 | 33 | Requires: python3dist(babel) >= 0.8 34 | Requires: python3dist(markupsafe) 35 | %description -n python3-%{pypi_name} 36 | Jinja2 is a template engine written in pure Python. It provides a Django_ 37 | inspired non-XML syntax but supports inline expressions and an optional 38 | sandboxed_ environment.Nutshell Here a small example of a Jinja template:: {% 39 | extends 'base.html' %} {% block title %}Memberlist{% endblock %} {% block 40 | content %}
                                                  {% for user in users %}
                                                • {{ 41 | user.username }}
                                                • ... 42 | 43 | %package -n python-%{pypi_name}-doc 44 | Summary: Jinja2 documentation 45 | %description -n python-%{pypi_name}-doc 46 | Documentation for Jinja2 47 | 48 | %prep 49 | %autosetup -n %{pypi_name}-%{pypi_version} 50 | # Remove bundled egg-info 51 | rm -rf %{pypi_name}.egg-info 52 | 53 | %build 54 | %py3_build 55 | # generate html docs 56 | PYTHONPATH=${PWD} sphinx-build-3 docs html 57 | # remove the sphinx-build leftovers 58 | rm -rf html/.{doctrees,buildinfo} 59 | 60 | %install 61 | %py3_install 62 | 63 | %check 64 | %{__python3} setup.py test 65 | 66 | %files -n python3-%{pypi_name} 67 | %license docs/_themes/LICENSE LICENSE 68 | %doc README.rst 69 | %{python3_sitelib}/jinja2 70 | %{python3_sitelib}/%{pypi_name}-%{pypi_version}-py%{python3_version}.egg-info 71 | 72 | %files -n python-%{pypi_name}-doc 73 | %doc html 74 | %license docs/_themes/LICENSE LICENSE 75 | 76 | %changelog 77 | * Wed Dec 06 2017 Michal Cyprian - 2.8-1 78 | - Initial package. 79 | -------------------------------------------------------------------------------- /tests/test_data/python-paperwork-backend.spec: -------------------------------------------------------------------------------- 1 | # Created by pyp2rpm-3.3.2 2 | %global pypi_name paperwork-backend 3 | %global pypi_version 1.2.4 4 | 5 | Name: python-%{pypi_name} 6 | Version: %{pypi_version} 7 | Release: 1%{?dist} 8 | Summary: Paperwork's backend 9 | 10 | License: GPLv3+ 11 | URL: https://github.com/openpaperwork/paperwork-backend 12 | Source0: %{pypi_source} 13 | BuildArch: noarch 14 | 15 | BuildRequires: python3-devel 16 | BuildRequires: python3dist(setuptools) 17 | 18 | %description 19 | Paperwork is a GUI to make papers searchable.This is the backend part of 20 | Paperwork. It manages: - The work directory / Access to the documents - 21 | Indexing - Searching - Suggestions- ExportThere is no GUI here. The GUI is . 22 | 23 | %package -n python3-%{pypi_name} 24 | Summary: %{summary} 25 | %{?python_provide:%python_provide python3-%{pypi_name}} 26 | 27 | Requires: python3dist(natsort) 28 | Requires: python3dist(pillow) 29 | Requires: python3dist(pycountry) 30 | Requires: python3dist(pyenchant) 31 | Requires: python3dist(pyocr) 32 | Requires: python3dist(python-levenshtein) 33 | Requires: python3dist(setuptools) 34 | Requires: python3dist(simplebayes) 35 | Requires: python3dist(termcolor) 36 | Requires: python3dist(whoosh) 37 | %description -n python3-%{pypi_name} 38 | Paperwork is a GUI to make papers searchable.This is the backend part of 39 | Paperwork. It manages: - The work directory / Access to the documents - 40 | Indexing - Searching - Suggestions- ExportThere is no GUI here. The GUI is . 41 | 42 | 43 | %prep 44 | %autosetup -n %{pypi_name}-%{pypi_version} 45 | # Remove bundled egg-info 46 | rm -rf %{pypi_name}.egg-info 47 | 48 | %build 49 | %py3_build 50 | 51 | %install 52 | %py3_install 53 | 54 | %files -n python3-%{pypi_name} 55 | %license LICENSE 56 | %doc README.markdown 57 | %{_bindir}/paperwork-shell 58 | %{python3_sitelib}/paperwork_backend 59 | %{python3_sitelib}/paperwork_backend-%{pypi_version}-py%{python3_version}.egg-info 60 | 61 | %changelog 62 | * Thu Mar 21 2019 Miro Hrončok - 1.2.4-1 63 | - Initial package. 64 | -------------------------------------------------------------------------------- /tests/test_data/python-sphinx_autonc.spec: -------------------------------------------------------------------------------- 1 | # Created by pyp2rpm-3.2.3 2 | %global pypi_name Sphinx 3 | %global pypi_version 1.5 4 | %global srcname sphinx 5 | 6 | Name: python-%{srcname} 7 | Version: %{pypi_version} 8 | Release: 1%{?dist} 9 | Summary: Python documentation generator 10 | 11 | License: BSD 12 | URL: http://sphinx-doc.org/ 13 | Source0: %{pypi_source} 14 | BuildArch: noarch 15 | 16 | BuildRequires: python2-devel 17 | BuildRequires: (python2dist(alabaster) >= 0.7 with python2dist(alabaster) < 0.8~~) 18 | BuildRequires: (python2dist(babel) >= 1.3 with (python2dist(babel) < 2 or python2dist(babel) > 2)) 19 | BuildRequires: python2dist(colorama) >= 0.3.5 20 | BuildRequires: python2dist(docutils) >= 0.11 21 | BuildRequires: python2dist(html5lib) 22 | BuildRequires: python2dist(imagesize) 23 | BuildRequires: python2dist(jinja2) >= 2.3 24 | BuildRequires: python2dist(mock) 25 | BuildRequires: python2dist(nose) 26 | BuildRequires: python2dist(pygments) >= 2 27 | BuildRequires: python2dist(requests) 28 | BuildRequires: python2dist(setuptools) 29 | BuildRequires: python2dist(simplejson) 30 | BuildRequires: python2dist(six) >= 1.5 31 | BuildRequires: python2dist(snowballstemmer) >= 1.1 32 | BuildRequires: python2dist(sqlalchemy) >= 0.9 33 | BuildRequires: python2dist(whoosh) >= 2 34 | 35 | BuildRequires: python3-devel 36 | BuildRequires: (python3dist(alabaster) >= 0.7 with python3dist(alabaster) < 0.8~~) 37 | BuildRequires: (python3dist(babel) >= 1.3 with (python3dist(babel) < 2 or python3dist(babel) > 2)) 38 | BuildRequires: python3dist(colorama) >= 0.3.5 39 | BuildRequires: python3dist(docutils) >= 0.11 40 | BuildRequires: python3dist(html5lib) 41 | BuildRequires: python3dist(imagesize) 42 | BuildRequires: python3dist(jinja2) >= 2.3 43 | BuildRequires: python3dist(mock) 44 | BuildRequires: python3dist(nose) 45 | BuildRequires: python3dist(pygments) >= 2 46 | BuildRequires: python3dist(requests) 47 | BuildRequires: python3dist(setuptools) 48 | BuildRequires: python3dist(simplejson) 49 | BuildRequires: python3dist(six) >= 1.5 50 | BuildRequires: python3dist(snowballstemmer) >= 1.1 51 | BuildRequires: python3dist(sqlalchemy) >= 0.9 52 | BuildRequires: python3dist(whoosh) >= 2 53 | BuildRequires: python3dist(sphinx) 54 | 55 | %description 56 | Sphinx is a tool that makes it easy to create intelligent and beautiful 57 | documentation for Python projects (or other documents consisting of multiple 58 | reStructuredText sources), written by Georg Brandl. It was originally created 59 | for the new Python documentation, and has excellent facilities for Python 60 | project documentation, but C/C++ is supported as well, and more languages are 61 | Sphinx uses... 62 | 63 | %package -n python2-%{srcname} 64 | Summary: %{summary} 65 | %{?python_provide:%python_provide python2-%{srcname}} 66 | 67 | Requires: (python2dist(alabaster) >= 0.7 with python2dist(alabaster) < 0.8~~) 68 | Requires: (python2dist(babel) >= 1.3 with (python2dist(babel) < 2 or python2dist(babel) > 2)) 69 | Requires: python2dist(colorama) >= 0.3.5 70 | Requires: python2dist(docutils) >= 0.11 71 | Requires: python2dist(html5lib) 72 | Requires: python2dist(imagesize) 73 | Requires: python2dist(jinja2) >= 2.3 74 | Requires: python2dist(mock) 75 | Requires: python2dist(nose) 76 | Requires: python2dist(pygments) >= 2 77 | Requires: python2dist(requests) 78 | Requires: python2dist(setuptools) 79 | Requires: python2dist(simplejson) 80 | Requires: python2dist(six) >= 1.5 81 | Requires: python2dist(snowballstemmer) >= 1.1 82 | Requires: python2dist(sqlalchemy) >= 0.9 83 | Requires: python2dist(whoosh) >= 2 84 | %description -n python2-%{srcname} 85 | Sphinx is a tool that makes it easy to create intelligent and beautiful 86 | documentation for Python projects (or other documents consisting of multiple 87 | reStructuredText sources), written by Georg Brandl. It was originally created 88 | for the new Python documentation, and has excellent facilities for Python 89 | project documentation, but C/C++ is supported as well, and more languages are 90 | Sphinx uses... 91 | 92 | %package -n python3-%{srcname} 93 | Summary: %{summary} 94 | %{?python_provide:%python_provide python3-%{srcname}} 95 | 96 | Requires: (python3dist(alabaster) >= 0.7 with python3dist(alabaster) < 0.8~~) 97 | Requires: (python3dist(babel) >= 1.3 with (python3dist(babel) < 2 or python3dist(babel) > 2)) 98 | Requires: python3dist(colorama) >= 0.3.5 99 | Requires: python3dist(docutils) >= 0.11 100 | Requires: python3dist(html5lib) 101 | Requires: python3dist(imagesize) 102 | Requires: python3dist(jinja2) >= 2.3 103 | Requires: python3dist(mock) 104 | Requires: python3dist(nose) 105 | Requires: python3dist(pygments) >= 2 106 | Requires: python3dist(requests) 107 | Requires: python3dist(setuptools) 108 | Requires: python3dist(simplejson) 109 | Requires: python3dist(six) >= 1.5 110 | Requires: python3dist(snowballstemmer) >= 1.1 111 | Requires: python3dist(sqlalchemy) >= 0.9 112 | Requires: python3dist(whoosh) >= 2 113 | %description -n python3-%{srcname} 114 | Sphinx is a tool that makes it easy to create intelligent and beautiful 115 | documentation for Python projects (or other documents consisting of multiple 116 | reStructuredText sources), written by Georg Brandl. It was originally created 117 | for the new Python documentation, and has excellent facilities for Python 118 | project documentation, but C/C++ is supported as well, and more languages are 119 | Sphinx uses... 120 | 121 | %package -n python-%{srcname}-doc 122 | Summary: Sphinx documentation 123 | %description -n python-%{srcname}-doc 124 | Documentation for Sphinx 125 | 126 | %prep 127 | %autosetup -n %{pypi_name}-%{pypi_version} 128 | # Remove bundled egg-info 129 | rm -rf %{pypi_name}.egg-info 130 | 131 | %build 132 | %py2_build 133 | %py3_build 134 | # generate html docs 135 | PYTHONPATH=${PWD} sphinx-build-3 doc html 136 | # remove the sphinx-build leftovers 137 | rm -rf html/.{doctrees,buildinfo} 138 | 139 | %install 140 | # Must do the default python version install last because 141 | # the scripts in /usr/bin are overwritten with every setup.py install. 142 | %py2_install 143 | rm -rf %{buildroot}%{_bindir}/* 144 | %py3_install 145 | 146 | %check 147 | %{__python2} setup.py test 148 | %{__python3} setup.py test 149 | 150 | %files -n python2-%{srcname} 151 | %license LICENSE 152 | %doc README.rst 153 | %{python2_sitelib}/sphinx 154 | %{python2_sitelib}/%{pypi_name}-%{pypi_version}-py%{python2_version}.egg-info 155 | 156 | %files -n python3-%{srcname} 157 | %license LICENSE 158 | %doc README.rst 159 | %{_bindir}/sphinx-apidoc 160 | %{_bindir}/sphinx-autogen 161 | %{_bindir}/sphinx-build 162 | %{_bindir}/sphinx-quickstart 163 | %{python3_sitelib}/sphinx 164 | %{python3_sitelib}/%{pypi_name}-%{pypi_version}-py%{python3_version}.egg-info 165 | 166 | %files -n python-%{srcname}-doc 167 | %doc html 168 | %license LICENSE 169 | 170 | %changelog 171 | * Wed Dec 06 2017 Michal Cyprian - 1.5-1 172 | - Initial package. 173 | -------------------------------------------------------------------------------- /tests/test_data/python-utest.spec: -------------------------------------------------------------------------------- 1 | # Created by pyp2rpm-3.3.3 2 | %global pypi_name utest 3 | %global pypi_version 0.1.0 4 | 5 | Name: python-%{pypi_name} 6 | Version: %{pypi_version} 7 | Release: 1%{?dist} 8 | Summary: Micro test module 9 | 10 | License: GPLv2+ 11 | URL: https://github.com/fedora-python/pyp2rpm 12 | Source0: %{pypi_name}-%{pypi_version}.tar.gz 13 | BuildArch: noarch 14 | 15 | BuildRequires: python3-devel 16 | BuildRequires: python3dist(setuptools) 17 | 18 | %description 19 | 20 | 21 | %package -n python3-%{pypi_name} 22 | Summary: %{summary} 23 | %{?python_provide:%python_provide python3-%{pypi_name}} 24 | 25 | Requires: (python3dist(pyp2rpm) >= 3.3.1 with python3dist(pyp2rpm) < 3.4) 26 | %description -n python3-%{pypi_name} 27 | 28 | 29 | 30 | %prep 31 | %autosetup -n %{pypi_name}-%{pypi_version} 32 | # Remove bundled egg-info 33 | rm -rf %{pypi_name}.egg-info 34 | 35 | %build 36 | %py3_build 37 | 38 | %install 39 | %py3_install 40 | 41 | %files -n python3-%{pypi_name} 42 | %{python3_sitelib}/%{pypi_name} 43 | %{python3_sitelib}/%{pypi_name}-%{pypi_version}-py%{python3_version}.egg-info 44 | 45 | %changelog 46 | * - 0.1.0-1 47 | - Initial package. 48 | -------------------------------------------------------------------------------- /tests/test_data/python-utest_epel7.spec: -------------------------------------------------------------------------------- 1 | # Created by pyp2rpm-3.3.3 2 | %global pypi_name utest 3 | %global pypi_version 0.1.0 4 | 5 | Name: python-%{pypi_name} 6 | Version: %{pypi_version} 7 | Release: 1%{?dist} 8 | Summary: Micro test module 9 | 10 | License: GPLv2+ 11 | URL: https://github.com/fedora-python/pyp2rpm 12 | Source0: %{pypi_name}-%{pypi_version}.tar.gz 13 | BuildArch: noarch 14 | 15 | BuildRequires: python%{python3_pkgversion}-devel 16 | BuildRequires: python%{python3_pkgversion}-setuptools 17 | 18 | %description 19 | 20 | 21 | %package -n python%{python3_pkgversion}-%{pypi_name} 22 | Summary: Micro test module 23 | 24 | Requires: python%{python3_pkgversion}-pyp2rpm < 3.4 25 | Requires: python%{python3_pkgversion}-pyp2rpm >= 3.3.1 26 | %description -n python%{python3_pkgversion}-%{pypi_name} 27 | 28 | 29 | 30 | %prep 31 | %autosetup -n %{pypi_name}-%{pypi_version} 32 | # Remove bundled egg-info 33 | rm -rf %{pypi_name}.egg-info 34 | 35 | %build 36 | %{__python3} setup.py build 37 | 38 | %install 39 | %{__python3} setup.py install --skip-build --root %{buildroot} 40 | 41 | %files -n python%{python3_pkgversion}-%{pypi_name} 42 | %{python3_sitelib}/%{pypi_name} 43 | %{python3_sitelib}/%{pypi_name}-%{pypi_version}-py%{python3_version}.egg-info 44 | 45 | %changelog 46 | * - 0.1.0-1 47 | - Initial package. 48 | -------------------------------------------------------------------------------- /tests/test_data/restsh-0.1.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-python/pyp2rpm/92399daa509d2769433f97cf7f07209f738139a2/tests/test_data/restsh-0.1.tar.gz -------------------------------------------------------------------------------- /tests/test_data/setuptools-19.6-py2.py3-none-any.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-python/pyp2rpm/92399daa509d2769433f97cf7f07209f738139a2/tests/test_data/setuptools-19.6-py2.py3-none-any.whl -------------------------------------------------------------------------------- /tests/test_data/unextractable-1.tar: -------------------------------------------------------------------------------- 1 | fake 2 | -------------------------------------------------------------------------------- /tests/test_data/utest/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from setuptools import setup, find_packages 4 | 5 | requirements = ["pyp2rpm~=3.3.1"] 6 | 7 | setup( 8 | name="utest", 9 | version="0.1.0", 10 | description="Micro test module", 11 | license="GPLv2+", 12 | author="pyp2rpm Developers", 13 | author_email='bkabrda@redhat.com, rkuska@redhat.com, mcyprian@redhat.com, ishcherb@redhat.com', 14 | url='https://github.com/fedora-python/pyp2rpm', 15 | install_requires=requirements, 16 | include_package_data=True, 17 | packages=find_packages(exclude=["test"]), 18 | classifiers=( 19 | "License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)", 20 | "Operating System :: POSIX :: Linux", 21 | "Programming Language :: Python", 22 | "Programming Language :: Python :: 3", 23 | ), 24 | ) 25 | -------------------------------------------------------------------------------- /tests/test_data/utest/utest/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-python/pyp2rpm/92399daa509d2769433f97cf7f07209f738139a2/tests/test_data/utest/utest/__init__.py -------------------------------------------------------------------------------- /tests/test_data/versiontools-1.9.1.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedora-python/pyp2rpm/92399daa509d2769433f97cf7f07209f738139a2/tests/test_data/versiontools-1.9.1.tar.gz -------------------------------------------------------------------------------- /tests/test_dependency_parser.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from pkg_resources import Requirement as R 4 | 5 | from pyp2rpm.dependency_parser import dependency_to_rpm 6 | 7 | 8 | class TestDependencyParser(): 9 | 10 | @pytest.mark.parametrize(('d', 'r', 'rich', 'expected'), [ 11 | ('docutils>=0.3,<1,!=0.5', True, False, 12 | [['Requires', 'docutils', '{name} >= 0.3'], 13 | ['Requires', 'docutils', '{name} < 1~~'], 14 | ['Conflicts', 'docutils', '{name} = 0.5'] 15 | ] 16 | ), 17 | ('pytest>=0.3a5,<1.1.1.1,!=1', False, False, 18 | [['BuildRequires', 'pytest', '{name} >= 0.3~a5'], 19 | ['BuildRequires', 'pytest', '{name} < 1.1.1.1~~'], 20 | ['BuildConflicts', 'pytest', '{name} = 1'] 21 | ] 22 | ), 23 | ('pyp2rpm~=3.3.0rc2', True, False, 24 | [['Requires', 'pyp2rpm', '{name} >= 3.3~rc2'], 25 | ['Requires', 'pyp2rpm', '{name} < 3.4'] 26 | ] 27 | ), 28 | ('pyp2rpm~=0.9.3', True, False, 29 | [['Requires', 'pyp2rpm', '{name} >= 0.9.3'], 30 | ['Requires', 'pyp2rpm', '{name} < 0.10'] 31 | ] 32 | ), 33 | ('pyp2rpm~=0.9.3.1', True, False, 34 | [['Requires', 'pyp2rpm', '{name} >= 0.9.3.1'], 35 | ['Requires', 'pyp2rpm', '{name} < 0.9.4'] 36 | ] 37 | ), 38 | ('docutils>=0.3,<1,!=0.5', True, True, 39 | [['Requires', 'docutils', 40 | '({name} >= 0.3 with {name} < 1~~ with ({name} < 0.5 or {name} > 0.5))'] 41 | ] 42 | ), 43 | ('pytest>=0.3a5,<1.1.1.1,!=1', False, True, 44 | [['BuildRequires', 'pytest', 45 | '({name} >= 0.3~a5 with {name} < 1.1.1.1~~ with ({name} < 1 or {name} > 1))'] 46 | ] 47 | ), 48 | ('pyp2rpm~=3.3.0rc2', True, True, 49 | [['Requires', 'pyp2rpm', '({name} >= 3.3~rc2 with {name} < 3.4)']] 50 | ), 51 | ('pyp2rpm~=0.9.3', True, True, 52 | [['Requires', 'pyp2rpm', '({name} >= 0.9.3 with {name} < 0.10)']] 53 | ), 54 | ('pyp2rpm~=0.9.3.1', True, True, 55 | [['Requires', 'pyp2rpm', '({name} >= 0.9.3.1 with {name} < 0.9.4)']] 56 | ), 57 | 58 | ]) 59 | def test_dependency_to_rpm(self, d, r, rich, expected): 60 | # we can't convert lists of lists into sets => compare len and contents 61 | rpm_deps = dependency_to_rpm(R.parse(d), r, rich) 62 | for dep in expected: 63 | assert dep in rpm_deps 64 | assert len(expected) == len(rpm_deps) 65 | -------------------------------------------------------------------------------- /tests/test_extract_distribution.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from flexmock import flexmock 4 | 5 | from pyp2rpm.command.extract_dist import to_list, extract_dist 6 | 7 | 8 | class TestExtractDistribution(object): 9 | 10 | @pytest.mark.parametrize(('var', 'expected'), [ 11 | (['pkg'], ['pkg']), 12 | (None, []), 13 | ('pkg >= 2.5\npkg2', ['pkg >= 2.5', 'pkg2']), 14 | (('pkg'), ['pkg']), 15 | (('pkg',), ['pkg']), 16 | ((p for p in ('pkg',)), ['pkg']), 17 | ]) 18 | def test_list(self, var, expected): 19 | assert to_list(var) == expected 20 | 21 | @pytest.mark.parametrize(('metadata'), [ 22 | ({'foo': lambda: None}), 23 | ({'foo': ['bar', lambda: None]}), 24 | ]) 25 | def test_serializing_metadata_to_stdout_success(self, metadata, capsys): 26 | flexmock(extract_dist).should_receive('__init__').and_return(None) 27 | command = extract_dist() 28 | command.metadata = metadata 29 | command.stdout = True 30 | command.run() 31 | out, err = capsys.readouterr() 32 | assert not err 33 | -------------------------------------------------------------------------------- /tests/test_filters.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from pyp2rpm.filters import (macroed_pkg_name, 4 | name_for_python_version, 5 | script_name_for_python_version) 6 | 7 | 8 | class TestFilters(object): 9 | 10 | @pytest.mark.parametrize(('pkg_name', 'srcname', 'version', 11 | 'default_number', 'expected'), [ 12 | ('python-Jinja2', None, '2', False, 'python2-%{pypi_name}'), 13 | ('python-Jinja2', None, '2', True, 'python2-%{pypi_name}'), 14 | ('python-Jinja2', None, '3', False, 'python-%{pypi_name}'), 15 | ('python-Jinja2', None, '3', True, 'python3-%{pypi_name}'), 16 | ('python-stdnum', 'stdnum', '2', False, 'python2-%{srcname}'), 17 | ('python-stdnum', 'stdnum', '2', True, 'python2-%{srcname}'), 18 | ('python-stdnum', 'stdnum', '3', False, 'python-%{srcname}'), 19 | ('python-stdnum', 'stdnum', '3', True, 'python3-%{srcname}') 20 | ]) 21 | def test_macroed_pkg_name(self, pkg_name, srcname, version, 22 | default_number, expected): 23 | assert name_for_python_version(macroed_pkg_name( 24 | pkg_name, srcname), version, default_number) == expected 25 | 26 | @pytest.mark.parametrize(('name', 'version', 'minor', 27 | 'default_number', 'expected'), [ 28 | ('foo', '2', False, False, 'foo-2'), 29 | ('foo', '2', False, True, 'foo-2'), 30 | ('foo', '3', False, False, 'foo'), 31 | ('foo', '3', False, True, 'foo-3'), 32 | ('foo', '35', True, True, 'foo-3.5'), 33 | ('foo', '3', True, True, 'foo-%{python3_version}'), 34 | ]) 35 | def test_script_name_for_python_version(self, name, version, minor, 36 | default_number, expected): 37 | assert script_name_for_python_version(name, version, minor, 38 | default_number) == expected 39 | -------------------------------------------------------------------------------- /tests/test_integration.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import os 3 | import tempfile 4 | import shutil 5 | 6 | from flexmock import flexmock 7 | from scripttest import TestFileEnvironment 8 | try: 9 | import dnf 10 | except ImportError: 11 | dnf = None 12 | 13 | from pyp2rpm.bin import Convertor, SclConvertor, main, convert_to_scl 14 | 15 | 16 | tests_dir = os.path.split(os.path.abspath(__file__))[0] 17 | 18 | 19 | class TestSpec(object): 20 | td_dir = '{0}/test_data/'.format(tests_dir) 21 | bin_dir = os.path.split(tests_dir)[0] + '/' 22 | exe = 'python {0}mybin.py'.format(bin_dir) 23 | 24 | def setup_method(self, method): 25 | self.temp_dir = tempfile.mkdtemp() 26 | self.env = TestFileEnvironment(self.temp_dir, start_clear=False) 27 | 28 | def teardown_method(self, method): 29 | shutil.rmtree(self.temp_dir) 30 | 31 | @pytest.mark.parametrize(('package', 'options', 'expected'), [ 32 | ('Jinja2', '-v2.8', 'python-Jinja2_py3_autonc.spec'), 33 | ('Jinja2', '-v2.8 -p2', 'python-Jinja2_py23_autonc.spec'), 34 | ('Jinja2', '-v2.8 -b2', 'python-Jinja2_py2_autonc.spec'), 35 | ('Jinja2', '-v2.8 -b3', 'python-Jinja2_py3_autonc.spec'), 36 | ('Jinja2', '-v2.8 -b3 -p2 -t epel7', 'python-Jinja2_epel7{0}.spec'), 37 | ('Jinja2', '-v2.8 -t epel6', 'python-Jinja2_epel6{0}.spec'), 38 | ('Jinja2', '-v2.8 -p2 -t mageia', 'python-Jinja2_mageia_py23.spec'), 39 | ('Jinja2', '-v2.8 -p2 --no-autonc', 'python-Jinja2{0}.spec'), 40 | ('paperwork-backend', '-v1.2.4', 'python-paperwork-backend.spec'), 41 | ('Sphinx', '-v1.5 -r python-sphinx -p2', 'python-sphinx_autonc.spec'), 42 | ('{0}/test_data/utest-0.1.0.tar.gz'.format(tests_dir), '', 43 | 'python-utest.spec'), 44 | ('{0}/test_data/utest-0.1.0.tar.gz'.format(tests_dir), '-t epel7', 45 | 'python-utest_epel7.spec'), 46 | ]) 47 | @pytest.mark.webtest 48 | def test_spec(self, package, options, expected): 49 | if 'autonc' in expected: 50 | variant = expected 51 | else: 52 | nc = '_dnfnc' if dnf is not None else '_nc' 53 | variant = expected.format(nc) 54 | with open(self.td_dir + variant) as fi: 55 | self.spec_content = fi.read() 56 | res = self.env.run('{0} {1} {2}'.format(self.exe, package, options), 57 | expect_stderr=True) 58 | # changelog have to be cut from spec files 59 | assert res.stdout.split('\n')[1:-4] == self.spec_content.split( 60 | '\n')[1:-4] 61 | 62 | 63 | class TestSrpm(object): 64 | td_dir = '{0}/test_data/'.format(tests_dir) 65 | bin_dir = os.path.split(tests_dir)[0] + '/' 66 | exe = 'python {0}mybin.py'.format(bin_dir) 67 | 68 | def setup_method(self, method): 69 | self.temp_dir = tempfile.mkdtemp() 70 | self.env = TestFileEnvironment(self.temp_dir, start_clear=False) 71 | 72 | def teardown_method(self, method): 73 | shutil.rmtree(self.temp_dir) 74 | 75 | @pytest.mark.webtest 76 | def test_srpm(self): 77 | res = self.env.run('{0} Jinja2 -v2.8 --srpm'.format(self.exe), 78 | expect_stderr=True) 79 | assert res.returncode == 0 80 | 81 | 82 | @pytest.mark.skipif(SclConvertor is None, reason="spec2scl not installed") 83 | class TestSclIntegration(object): 84 | """ 85 | """ 86 | sphinx_spec = '{0}/test_data/python-sphinx_autonc.spec'.format(tests_dir) 87 | 88 | @classmethod 89 | def setup_class(cls): 90 | with open(cls.sphinx_spec, 'r') as spec: 91 | cls.test_spec = spec.read() 92 | 93 | def setup_method(self, method): 94 | self.default_options = { 95 | 'no_meta_runtime_dep': False, 96 | 'no_meta_buildtime_dep': False, 97 | 'skip_functions': [''], 98 | 'no_deps_convert': False, 99 | 'list_file': None, 100 | 'meta_spec': None 101 | } 102 | flexmock(Convertor).should_receive('__init__').and_return(None) 103 | flexmock(Convertor).should_receive('convert').and_return( 104 | self.test_spec) 105 | 106 | @pytest.mark.parametrize(('options', 'expected_options'), [ 107 | (['--no-meta-runtime-dep'], {'no_meta_runtime_dep': True}), 108 | (['--no-meta-buildtime-dep'], {'no_meta_buildtime_dep': True}), 109 | (['--skip-functions=func1,func2'], {'skip_functions': 110 | ['func1', 'func2']}), 111 | (['--no-deps-convert'], {'no_deps_convert': True}), 112 | (['--list-file=file_name'], {'list_file': 'file_name'}), 113 | ]) 114 | def test_scl_convertor_args_correctly_passed(self, options, 115 | expected_options, capsys): 116 | """Test that pyp2rpm command passes correct options to 117 | SCL convertor. 118 | """ 119 | self.default_options.update(expected_options) 120 | flexmock(SclConvertor).should_receive('convert').and_return( 121 | self.test_spec) 122 | flexmock(SclConvertor).should_receive('__init__').with_args( 123 | options=self.default_options, 124 | ).once() 125 | 126 | with pytest.raises(SystemExit): 127 | main(args=['foo_package', '--sclize'] + options) 128 | out, err = capsys.readouterr() 129 | assert out == self.test_spec + '\n' 130 | 131 | @pytest.mark.parametrize(('options', 'omit_from_spec'), [ 132 | ({'no_meta_runtime_dep': True}, '%{?scl:Requires: %{scl}-runtime}'), 133 | ({'no_meta_buildtime_dep': True}, 134 | '{?scl:BuildRequires: %{scl}-runtime}'), 135 | ({'skip_functions': 'handle_python_specific_commands'}, 136 | '%{?scl:scl enable %{scl} - << \\EOF}\nset -e\nsphinx-build doc html\n%{?scl:EOF}'), 137 | ]) 138 | def test_convert_to_scl_options(self, options, omit_from_spec): 139 | """Test integration with SCL options.""" 140 | self.default_options.update({'skip_functions': ''}) 141 | self.default_options.update(options) 142 | converted = convert_to_scl(self.test_spec, self.default_options) 143 | assert omit_from_spec not in converted 144 | -------------------------------------------------------------------------------- /tests/test_name_convertor.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from pyp2rpm.name_convertor import (NameConvertor, DandifiedNameConvertor, 4 | AutoProvidesNameConvertor, NameVariants) 5 | from pyp2rpm import settings 6 | 7 | try: 8 | import dnf 9 | except ImportError: 10 | dnf = None 11 | 12 | 13 | class TestUtils(object): 14 | 15 | def setup_method(self, method): 16 | self.ncf = NameConvertor('fedora') 17 | self.ncm = NameConvertor('mageia') 18 | 19 | @pytest.mark.parametrize(('input', 'expected_f', 'expected_m'), [ 20 | ('python-spam', 'python-spam', 'python-spam'), 21 | ('python-PySpam', 'python-PySpam', 'python-pyspam'), 22 | ('python-spampy', 'python-spampy', 'python-spampy'), 23 | ('spam-python', 'python-spam', 'python-spam'), 24 | ('python26-foo', 'python-foo', 'python-foo'), 25 | ('foo-python26', 'python-foo', 'python-foo'), 26 | ('python3-foo', 'python-foo', 'python-foo'), 27 | ('foo-python3', 'python-foo', 'python-foo'), 28 | ]) 29 | def test_rpm_name(self, input, expected_f, expected_m): 30 | assert self.ncf.rpm_name(input) == expected_f 31 | assert self.ncm.rpm_name(input) == expected_m 32 | 33 | @pytest.mark.parametrize(('name', 'version', 'expected'), [ 34 | ('python-spam', None, 'python-spam'), 35 | ('pyspam', None, 'pyspam'), 36 | ('python-spam', '3', 'python-spam'), 37 | ('pyspam', '26', 'python26-pyspam'), 38 | ('pyspam', settings.DEFAULT_PYTHON_VERSION, 'pyspam'), 39 | ('python-foo', '26', 'python26-foo'), 40 | ('python-foo', '3', 'python-foo'), 41 | ('python2-foo', None, 'python-foo'), 42 | ('python2-foo', '3', 'python-foo'), 43 | ('python26-foo', '3', 'python-foo'), 44 | ('python26-foo', None, 'python-foo'), 45 | ('python-foo', '25', 'python25-foo'), 46 | ('python2-devel', 3, 'python3-devel'), 47 | ('python2-devel', None, 'python2-devel'), 48 | ('python2dist(spam)', 3, 'python3dist(spam)'), 49 | ('python2dist(spam)', 2, 'python2dist(spam)'), 50 | ]) 51 | def test_rpm_versioned_name(self, name, version, expected): 52 | assert NameConvertor.rpm_versioned_name(name, version) == expected 53 | 54 | 55 | class TestDandifiedNameConvertor(object): 56 | 57 | def setup_method(self, method): 58 | self.dnc = DandifiedNameConvertor('fedora') 59 | 60 | @pytest.mark.parametrize(('pypi_name', 'version', 'expected'), [ 61 | ('Babel', '2', 'babel'), # Bad result; Present in repo 62 | ('Babel', '3', 'python3-babel'), # Present in repo 63 | ('MarkupSafe', '2', 'python2-MarkupSafe'), # Not present in repo 64 | ('MarkupSafe', '3', 'python3-markupsafe'), # Present in repo 65 | ('Jinja2', '2', 'python2-Jinja2'), # Not present in repo 66 | ('Jinja2', '3', 'python3-jinja2'), # Present in repo 67 | ('Sphinx', '3', 'python3-sphinx'), 68 | ('Cython', '2', 'python2-Cython'), 69 | ('Cython', '3', 'python3-Cython'), 70 | ('pytest', '2', 'python2-pytest'), 71 | ('pytest', '3', 'python3-pytest'), 72 | ('vertica', '2', 'python2-vertica'), 73 | ('oslosphinx', '3', 'python3-oslo-sphinx'), 74 | ('mock', '3', 'python3-mock'), 75 | ]) 76 | @pytest.mark.skipif(dnf is None, reason="Optional dependency DNF required") 77 | def test_rpm_name(self, pypi_name, version, expected): 78 | assert self.dnc.rpm_name(pypi_name, version) == expected 79 | 80 | 81 | class TestNameVariants(object): 82 | 83 | def setup_method(self, method): 84 | self.nv = NameVariants('foo', '3') 85 | self.mv = NameVariants('foo', '') 86 | 87 | @pytest.mark.parametrize(('version', 'input_list', 'expected'), [ 88 | ('3', ['python3-Foo', 'foo-python', 'python-foo'], 'python3-Foo'), 89 | ('', ['python3-Foo', 'foo-python', 'python-foo'], 'python-foo'), 90 | ('2', ['python3-Foo', 'foo-python', 'foo'], 'foo'), 91 | ]) 92 | def test_best_matching(self, version, input_list, expected): 93 | self.nv.version = version 94 | self.nv.names_init() 95 | self.nv.variants_init() 96 | for name in input_list: 97 | self.nv.find_match(name) 98 | assert self.nv.best_matching == expected 99 | 100 | @pytest.mark.parametrize(('first_list', 'second_list', 'expected'), [ 101 | (['python3-foo', 'py3foo'], 102 | ['foo', 'python-foo'], 103 | {'python_ver_name': 'python3-foo', 104 | 'pyver_name': 'py3foo', 105 | 'name_python_ver': None, 106 | 'raw_name': 'foo'}) 107 | ]) 108 | def test_merge(self, first_list, second_list, expected): 109 | for first, second in zip(first_list, second_list): 110 | self.nv.find_match(first) 111 | self.mv.find_match(second) 112 | assert self.nv.merge(self.mv).variants == expected 113 | 114 | 115 | class TestAutoProvidesNameConvertor(object): 116 | 117 | def setup_method(self, method): 118 | self.anc = AutoProvidesNameConvertor('fedora') 119 | 120 | @pytest.mark.parametrize(('input', 'py_ver', 'expected'), [ 121 | ('spam', None, 'python{0}dist(spam)'.format( 122 | settings.DEFAULT_PYTHON_VERSION)), 123 | ('spam', '2', 'python2dist(spam)'), 124 | ('SPAM', '2', 'python2dist(spam)'), 125 | ('Spam$SPam', '2', 'python2dist(spam-spam)'), 126 | ('spam', '3', 'python3dist(spam)'), 127 | ('spam.spam', '3', 'python3dist(spam.spam)'), 128 | ]) 129 | def test_rpm_name(self, input, py_ver, expected): 130 | if py_ver: 131 | assert self.anc.rpm_name(input, py_ver) == expected 132 | else: 133 | assert self.anc.rpm_name(input) == expected 134 | -------------------------------------------------------------------------------- /tests/test_package_data.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from pyp2rpm.package_data import PackageData 4 | 5 | 6 | class TestPackageData(object): 7 | 8 | @pytest.mark.parametrize(('s', 'expected'), [ 9 | ('Spam.', 'Spam'), 10 | ('Spam', 'Spam'), 11 | ]) 12 | def test_summary_with_dot(self, s, expected): 13 | pd = PackageData('spam', 'spam', 'python-spam', 'spam') 14 | pd.summary = s 15 | assert pd.summary == expected 16 | 17 | @pytest.mark.parametrize('name', [ 18 | 'summary', 'description', ]) 19 | def test_set_none_value(self, name): 20 | pd = PackageData('spam', 'spam', 'python-spam', 'spam') 21 | setattr(pd, name, None) 22 | actual = getattr(pd, name) 23 | assert actual == 'TODO:' 24 | 25 | def test_get_nonexistent_attribute(self): 26 | pd = PackageData('spam', 'spam', 'python-spam', 'spam') 27 | assert pd.eggs == 'TODO:' 28 | 29 | @pytest.mark.parametrize(('n', 'expected'), [ 30 | ('py-spam', 'py_spam'), 31 | ('py_spam', 'py_spam'), 32 | ('spam', 'spam'), 33 | ]) 34 | def test_underscored_name(self, n, expected): 35 | pd = PackageData('spam', n, 'python-spam', 'spam') 36 | assert pd.underscored_name == expected 37 | 38 | @pytest.mark.parametrize(('key', 'init', 'update_data', 'expected'), [ 39 | ('name', 'Spam', {'name': 'Spam'}, 'Spam'), 40 | ('name', ['Spam'], {'name': ['Spam2']}, ['Spam', 'Spam2']), 41 | ('name', set(['Spam']), {'name': set(['Spam2'])}, 42 | set(['Spam', 'Spam2'])), 43 | ('name', [], {'name': ['Spam', 'Spam2']}, ['Spam', 'Spam2']), 44 | ('name', False, {'name': True}, False), 45 | ('name', 'Spam', {'name': ''}, 'Spam'), 46 | ('name', 'Spam', {'name': 'Spam2'}, 'Spam'), 47 | ('doc_files', 'Spam', {'doc_files': set(['README'])}, set(['README'])), 48 | ]) 49 | def test_update_attr(self, key, init, update_data, expected): 50 | pd = PackageData('spam', init, 'python-spam', 'spam') 51 | pd.set_from(update_data, update=True) 52 | assert pd.data[key] == expected 53 | -------------------------------------------------------------------------------- /tests/test_package_getters.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import tempfile 4 | import shutil 5 | 6 | import pytest 7 | 8 | from flexmock import flexmock 9 | 10 | from pyp2rpm.convertor import PyPIClient 11 | from pyp2rpm.package_getters import LocalFileGetter, PypiDownloader, get_url 12 | from pyp2rpm.exceptions import MissingUrlException, NoSuchPackageException 13 | 14 | tests_dir = os.path.split(os.path.abspath(__file__))[0] 15 | 16 | 17 | class TestPackageGetters(object): 18 | client = PyPIClient() 19 | 20 | @pytest.mark.parametrize(('name', 'version', 'wheel', 'hf', 'expected_url', 'expected_md5'), [ 21 | ('setuptools', '18.3.1', False, False, 22 | 'https://files.pythonhosted.org/packages/source/s/setuptools/setuptools-18.3.1.tar.gz', 23 | '748187b93152fa60287dfb896837fd7c'), 24 | ('setuptools', '18.3.1', True, False, 25 | 'https://files.pythonhosted.org/packages/source/s/setuptools/setuptools-18.3.1-py2.py3-none-any.whl', 26 | 'a21a4d02d0bab2eac499cca72faeb076'), 27 | ('setuptools', '18.3.1', False, True, 28 | 'https://files.pythonhosted.org/packages/86/8a/c4666b05c74e840eb9b09d28f4e7ae76fc9075e8c653d0eb4d265a5b49d9/setuptools-18.3.1.tar.gz', 29 | '748187b93152fa60287dfb896837fd7c'), 30 | ('pypandoc', '1.1.3', False, False, 31 | 'https://files.pythonhosted.org/packages/source/p/pypandoc/pypandoc-1.1.3.zip', 32 | '771f376bf9c936a90159cd94235998c2'), 33 | ]) 34 | @pytest.mark.webtest 35 | def test_get_url(self, name, version, wheel, hf, 36 | expected_url, expected_md5): 37 | assert (expected_url, expected_md5) == get_url( 38 | self.client, name, version, wheel, hf) 39 | 40 | @pytest.mark.parametrize(('name', 'version', 'wheel', 'hf', 41 | 'exception', 'error_msg'), [ 42 | ('nonexistent_pkg', '0.0.0', False, False, MissingUrlException, 43 | 'Url of source archive not found.'), 44 | ('Pymacs', '0.25', False, False, MissingUrlException, 45 | 'Pymacs package has no sources on PyPI, Please ask the maintainer to upload sources.'), 46 | ]) 47 | @pytest.mark.webtest 48 | def test_get_url_raises(self, name, version, wheel, hf, 49 | exception, error_msg): 50 | with pytest.raises(exception) as exc_info: 51 | get_url(self.client, name, version, wheel, hf) 52 | assert error_msg == str(exc_info.value) 53 | 54 | 55 | class TestPypiDownloader(object): 56 | class StaticPyPIClient(PyPIClient): 57 | def get_json(self, name, version): 58 | with open('{0}/test_data/django.json'.format(tests_dir)) as json_info: 59 | return json.loads(json_info.read()) 60 | client = StaticPyPIClient() 61 | 62 | @pytest.mark.parametrize(('name', 'expected_ver'), [ 63 | ('django', '3.0.11'), 64 | ]) 65 | def test_init_good_data(self, name, expected_ver): 66 | d = PypiDownloader(self.client, name) 67 | assert d.version == expected_ver 68 | 69 | @pytest.mark.parametrize(('name', 'expected_ver'), [ 70 | ('django', '3.1rc1'), 71 | ]) 72 | def test_init_good_data_pre(self, name, expected_ver): 73 | d = PypiDownloader(self.client, name, prerelease=True) 74 | assert d.version == expected_ver 75 | 76 | 77 | class TestPypiFileGetter(object): 78 | client = flexmock( 79 | package_releases=lambda n, hidden: n == 'spam' and ['3.rc1', '2', '1'] or [], 80 | release_urls=lambda n, v: n == 'spam' and v in [ 81 | '3.rc1', '2', '1'] and [{'url': 'spam'}] or [] 82 | ) 83 | 84 | @pytest.mark.parametrize(('name', 'version'), [ 85 | ('eggs', '2'), 86 | ('spam', '3'), 87 | ]) 88 | def test_init_bad_data(self, name, version): 89 | with pytest.raises(NoSuchPackageException): 90 | PypiDownloader(self.client, name, version) 91 | 92 | @pytest.mark.parametrize(('name', 'version', 'expected_ver'), [ 93 | ('spam', '1', '1'), 94 | ('spam', None, '2'), 95 | ]) 96 | def test_init_good_data(self, name, version, expected_ver): 97 | d = PypiDownloader(self.client, name, version) 98 | assert d.version == expected_ver 99 | 100 | @pytest.mark.parametrize(('name', 'version', 'expected_ver'), [ 101 | ('spam', '1', '1'), 102 | ('spam', None, '3.rc1'), 103 | ]) 104 | def test_init_good_data_pre(self, name, version, expected_ver): 105 | d = PypiDownloader(self.client, name, version, prerelease=True) 106 | assert d.version == expected_ver 107 | 108 | 109 | class TestLocalFileGetter(object): 110 | td_dir = '{0}/test_data/'.format(tests_dir) 111 | 112 | def setup_method(self, method): 113 | self.l = [LocalFileGetter('{0}plumbum-0.9.0.tar.gz'.format( 114 | self.td_dir)), 115 | LocalFileGetter('{0}Sphinx-1.1.3-py2.6.egg'.format( 116 | self.td_dir)), 117 | LocalFileGetter('{0}unextractable-1.tar'.format( 118 | self.td_dir)), 119 | LocalFileGetter( 120 | '{0}setuptools-19.6-py2.py3-none-any.whl'.format( 121 | self.td_dir)), 122 | LocalFileGetter( 123 | '{0}py2exe-0.9.2.2-py33.py34-none-any.whl'.format( 124 | self.td_dir)), 125 | LocalFileGetter('python-foo-1.tar'), 126 | LocalFileGetter('python-many-dashes-foo-1.tar'), 127 | ] 128 | 129 | def teardown_method(self, method): 130 | for file_getter in self.l: 131 | if hasattr(file_getter, 'temp_dir'): 132 | shutil.rmtree(file_getter.temp_dir) 133 | 134 | @pytest.mark.parametrize(('i', 'expected'), [ 135 | (0, 'plumbum-0.9.0'), 136 | (1, 'Sphinx-1.1.3-py2.6'), 137 | (2, 'unextractable-1'), 138 | (3, 'setuptools-19.6-py2.py3-none-any'), 139 | (4, 'py2exe-0.9.2.2-py33.py34-none-any'), 140 | ]) 141 | def test_stripped_name_version(self, i, expected): 142 | assert self.l[i]._stripped_name_version == expected 143 | 144 | @pytest.mark.parametrize(('i', 'expected'), [ 145 | (0, ('plumbum', '0.9.0')), 146 | (1, ('Sphinx', '1.1.3')), 147 | (3, ('setuptools', '19.6')), 148 | (4, ('py2exe', '0.9.2.2')), 149 | (5, ('python-foo', '1')), 150 | (6, ('python-many-dashes-foo', '1')), 151 | ]) 152 | def test_get_name_version(self, i, expected): 153 | assert self.l[i].get_name_version() == expected 154 | 155 | def test_get_non_existent_file(self): 156 | with pytest.raises(EnvironmentError): 157 | LocalFileGetter('/this/path/doesnot/exist', 158 | tempfile.gettempdir()).get() 159 | 160 | def test_get_existent_file(self): 161 | tmpdir = tempfile.gettempdir() 162 | in_tmp_dir = os.path.join(tmpdir, 'plumbum-0.9.0.tar.gz') 163 | self.l[0].save_dir = tmpdir 164 | if os.path.exists(in_tmp_dir): 165 | os.unlink(in_tmp_dir) 166 | assert self.l[0].get() == in_tmp_dir 167 | assert os.path.exists(self.l[0].get()) 168 | os.unlink(in_tmp_dir) 169 | 170 | def test_get_to_same_location(self): 171 | tmpdir = tempfile.gettempdir() 172 | self.l[1].save_dir = self.td_dir 173 | assert os.path.samefile(self.l[1].get(), os.path.join( 174 | self.td_dir, 'Sphinx-1.1.3-py2.6.egg')) 175 | assert not os.path.exists(os.path.join(tmpdir, 176 | 'Sphinx-1.1.3-py2.6.egg')) 177 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import os 3 | from flexmock import flexmock 4 | try: 5 | import rpm 6 | except ImportError: 7 | rpm = None 8 | 9 | from pyp2rpm import utils 10 | 11 | 12 | class TestUtils(object): 13 | 14 | def test_memoize_by_args(self): 15 | assert self.memoized(1) == 1 16 | assert hasattr(self, 'memoized_called') 17 | assert self.memoized(1) == 1 18 | 19 | @utils.memoize_by_args 20 | def memoized(self, num): 21 | if hasattr(self, "memoized_called"): 22 | raise BaseException('This should not have been called!') 23 | else: 24 | setattr(self, "memoized_called", True) 25 | 26 | return num 27 | 28 | @pytest.mark.parametrize(('input', 'expected'), [ 29 | (['script', 'script2', 'script-0.1'], ['script', 'script2']), 30 | ([], []), 31 | (['script-a'], ['script-a']), 32 | (['script-3', 'script-3.4'], []), 33 | (['script-3.4'], []), 34 | ]) 35 | def test_remove_major_minor_suffix(self, input, expected): 36 | assert utils.remove_major_minor_suffix(input) == expected 37 | 38 | @pytest.mark.parametrize(('input', 'expected'), [ 39 | ([['Requires', 'pkg'], ['Requires', 'pkg2']], 40 | [['BuildRequires', 'pkg'], ['BuildRequires', 'pkg2']]), 41 | ([['Requires', 'pkg', '>=', '1.4.29'], 42 | ['Requires', 'python-setuptools']], 43 | [['BuildRequires', 'pkg', '>=', '1.4.29'], ['BuildRequires', 44 | 'python-setuptools']]), 45 | ([], []), 46 | ([[], []], [[], []]), 47 | ]) 48 | def test_runtime_to_build(self, input, expected): 49 | assert utils.runtime_to_build(input) == expected 50 | 51 | @pytest.mark.parametrize(('input', 'expected'), [ 52 | ([['Requires', 'pkg'], ['Requires', 'pkg']], [['Requires', 'pkg']]), 53 | ([['Requires', 'pkg']], [['Requires', 'pkg']]), 54 | ([['Requires', 'pkg'], ['Requires', 'pkg2'], ['Requires', 'pkg']], 55 | [['Requires', 'pkg'], ['Requires', 'pkg2']]), 56 | ([], []), 57 | ([[], []], [[]]), 58 | ([[1], [2], [3], [2]], [[1], [2], [3]]), 59 | ]) 60 | def test_unique_deps(self, input, expected): 61 | assert utils.unique_deps(input) == expected 62 | 63 | def test_rpm_eval(self): 64 | if os.path.exists('/usr/bin/rpm'): 65 | assert utils.rpm_eval('macro') == 'macro' 66 | else: 67 | assert utils.rpm_eval('macro') == '' 68 | 69 | def test_get_default_save_path_eval_success(self): 70 | if rpm: 71 | flexmock(rpm).should_receive( 72 | 'expandMacro').once().and_return('foo') 73 | else: 74 | flexmock(utils).should_receive('rpm_eval').once().and_return('foo') 75 | assert utils.get_default_save_path() == 'foo' 76 | 77 | def test_get_default_save_path_eval_fail(self): 78 | if rpm: 79 | flexmock(rpm).should_receive( 80 | 'expandMacro').once().and_return('foo') 81 | else: 82 | flexmock(utils).should_receive('rpm_eval').once().and_return('') 83 | flexmock(os).should_receive('path.expanduser').once( 84 | ).and_return('foo') 85 | assert utils.get_default_save_path() == 'foo' 86 | -------------------------------------------------------------------------------- /tests/test_virtualenv.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pytest 3 | import shutil 4 | import sys 5 | import tempfile 6 | from flexmock import flexmock 7 | try: 8 | from pyp2rpm.virtualenv import (DirsContent, 9 | VirtualEnv, 10 | site_packages_filter, 11 | scripts_filter) 12 | except ImportError: 13 | VirtualEnv = None 14 | from pyp2rpm.name_convertor import NameConvertor 15 | from pyp2rpm.settings import DEFAULT_DISTRO, DEFAULT_PYTHON_VERSION 16 | 17 | pytestmark = pytest.mark.skipif(VirtualEnv is None, 18 | reason="virtualenv-api not installed") 19 | 20 | tests_dir = os.path.split(os.path.abspath(__file__))[0] 21 | 22 | class TestUtils(object): 23 | 24 | @pytest.mark.parametrize(('input', 'expected'), [ 25 | (['foo', 'foo-1.0.0.dist-info'], set(['foo'])), 26 | (['foo', 'foo-1.0.0.dist-info', 'foo2'], set(['foo', 'foo2'])), 27 | (['foo', 'foo-1.0.0.dist-info', 'foo2-1.0.0-py2.7.egg-info'], 28 | set(['foo'])), 29 | (['foo', 'foo2-1.0.0-py2.7.egg-info'], 30 | set(['foo'])), 31 | ([], set()), 32 | ]) 33 | def test_site_packages_filter(self, input, expected): 34 | assert site_packages_filter(input) == expected 35 | 36 | @pytest.mark.parametrize(('input', 'expected'), [ 37 | (['script', 'script2'], ['script', 'script2']), 38 | (['script.py', 'script2'], ['script.py', 'script2']), 39 | (['script.pyc', 'script2'], ['script2']), 40 | (['script.pyc'], []), 41 | ([], []), 42 | ]) 43 | def test_scripts_filter(self, input, expected): 44 | assert scripts_filter(input) == expected 45 | 46 | 47 | class TestDirsContent(object): 48 | 49 | @pytest.mark.parametrize(('before', 'after', 'expected'), [ 50 | (set(['activate', 'pip']), set(['activate', 'pip', 'foo']), 51 | set(['foo'])), 52 | (set(['activate', 'pip']), set(['activate', 'pip']), set()), 53 | ]) 54 | def test_sub_bin(self, before, after, expected): 55 | result = DirsContent(bindir=after, lib_sitepackages=set([])) -\ 56 | DirsContent(bindir=before, lib_sitepackages=set([])) 57 | assert result.bindir == expected 58 | 59 | @pytest.mark.parametrize(('before', 'after', 'expected'), [ 60 | (set(['setuptools', 'pip']), set(['setuptools', 'pip', 'foo']), 61 | set(['foo'])), 62 | (set(['wheel', 'pip']), set(['foo', 'pip']), set(['foo'])), 63 | (set(['wheel', 'pip']), set(['pip']), set()), 64 | ]) 65 | def test_sub_sitepackages(self, before, after, expected): 66 | result = DirsContent(lib_sitepackages=after, bindir=set([])) -\ 67 | DirsContent(lib_sitepackages=before, bindir=set([])) 68 | assert result.lib_sitepackages == expected 69 | 70 | 71 | class TestVirtualEnvGetData(object): 72 | 73 | def setup_method(self, method): 74 | self.temp_dir = tempfile.mkdtemp() 75 | 76 | def teardown_method(self, method): 77 | shutil.rmtree(self.temp_dir) 78 | 79 | @pytest.mark.parametrize(('file', 'expected'), [ 80 | ('{}/test_data/utest-0.1.0.tar.gz'.format(tests_dir), 81 | {'py_modules': [], 'scripts': [], 'packages': ['utest'], 'has_pth': False}), 82 | ]) 83 | @pytest.mark.skipif(sys.version_info[0] is 2 and 84 | DEFAULT_PYTHON_VERSION is '3', reason="Can't extract virtualenv data") 85 | @pytest.mark.webtest 86 | def test_get_data(self, file, expected): 87 | self.venv = VirtualEnv(name=file, 88 | temp_dir=self.temp_dir, 89 | name_convertor=NameConvertor(DEFAULT_DISTRO), 90 | base_python_version=DEFAULT_PYTHON_VERSION) 91 | assert(self.venv.get_venv_data == expected) 92 | 93 | 94 | class TestVirtualEnv(object): 95 | 96 | def setup_method(self, method): 97 | self.temp_dir = tempfile.mkdtemp() 98 | self.venv = VirtualEnv(name=None, 99 | temp_dir=self.temp_dir, 100 | name_convertor=NameConvertor(DEFAULT_DISTRO), 101 | base_python_version=DEFAULT_PYTHON_VERSION) 102 | 103 | def teardown_method(self, method): 104 | shutil.rmtree(self.temp_dir) 105 | 106 | @pytest.mark.parametrize(('bin_diff', 'package_diff', 'expected'), [ 107 | (set(['foo']), set(['foo']), (['foo'], [], ['foo'], False)), 108 | (set(['foo']), set(['foo.py', 'foo.pyc']), 109 | ([], ['foo'], ['foo'], False)), 110 | (set([]), set(['foo.py']), ([], ['foo'], [], False)), 111 | (set(['foo']), set(["foo-pyX.Y-nspkg.pth"]), 112 | ([], [], ['foo'], True)), 113 | (set(), set(), ([], [], [], False)), 114 | ]) 115 | def test_get_dirs_differance(self, bin_diff, package_diff, expected): 116 | flexmock(DirsContent).should_receive('__sub__').and_return( 117 | DirsContent(bin_diff, package_diff)) 118 | self.venv.get_dirs_differance() 119 | assert ((self.venv.data['packages'], 120 | self.venv.data['py_modules'], 121 | self.venv.data['scripts'], 122 | self.venv.data['has_pth']) == expected) 123 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py36, py37, py38, py39, py310 3 | 4 | [testenv] 5 | deps = 6 | pyparsing 7 | more-itertools <= 7.2.0 8 | setuptools 9 | Jinja2 10 | flexmock 11 | virtualenv-api 12 | virtualenv 13 | scripttest 14 | click 15 | spec2scl >= 1.2.0 16 | allowlist_externals = sh 17 | commands = 18 | sh -c 'cd tests/test_data/utest && python3 setup.py sdist && mv dist/utest-0.1.0.tar.gz ..' 19 | sh -c 'cd tests/test_data/isholiday-0.1 && python3 setup.py sdist && mv dist/isholiday-0.1.tar.gz ..' 20 | python setup.py test --addopts -vv 21 | sitepackages = True 22 | --------------------------------------------------------------------------------