diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..6d67d20 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,161 @@ +# Python CircleCI 2.0 configuration file +# Check https://circleci.com/docs/2.0/language-python/ for more details +# +# References: +# # how to setup multiple python versions +# https://stackoverflow.com/questions/948354/default-behavior-of-git-push-without-a-branch-specified +# https://github.com/adambrenecki/virtualfish/blob/aa3d6271bcb86ad27b6d24f96b5bd386d176f588/.circleci/config.yml +# +# # Multiple files for a checksum +# https://discuss.circleci.com/t/cant-checksum-multiple-files-with-slashes-in-the-file-path/20667/2 +# +# # Auto Cancel Redundant Builds +# https://circleci.com/docs/2.0/skip-build/#steps-to-enable-auto-cancel-for-pipelines-triggered-by-pushes-to-github-or-the-api +# https://app.circleci.com/settings/project/github/pyutils/line_profiler/advanced?return-to=https%3A%2F%2Fapp.circleci.com%2Fpipelines%2Fgithub%2Fpyutils%2FPYPKG + +version: 2 +workflows: + version: 2 + test: + jobs: + - test_full/cp39-cp39-manylinux2010 + - test_full/cp38-cp38-manylinux2010 + - test_full/cp37-cp37m-manylinux2010 + - test_full/cp36-cp36m-manylinux2010 + - test_full/cp35-cp35m-manylinux1 + + +jobs: + + ########### + # TEMPLATES + ########### + + .common_template: &common_template + environment: + # Setting the python executable environ allows template reuse for pypy + - PYTHON_EXE: python + docker: + - image: circleci/python + steps: + - checkout + + + .test_full_template: &test_full_template + <<: + - *common_template + resource_class: small + steps: + - checkout + - run: + name: prepare_env + command: | + $PYTHON_EXE -m venv venv + # $PYTHON_EXE -m pip install --upgrade pip + . venv/bin/activate + # pip install --upgrade pip + - run: + name: build_wheel + command: | + . venv/bin/activate + MB_PYTHON_TAG=$(python -c "import setup; print(setup.MB_PYTHON_TAG)") + VERSION=$(python -c "import setup; print(setup.VERSION)") + _INSIDE_DOCKER=YES REPO_ROOT="." MB_PYTHON_TAG=$MB_PYTHON_TAG ./run_manylinux_build.sh + - persist_to_workspace: + root: . + paths: + - dist + - run: + name: install_wheel + command: | + . venv/bin/activate + # pip install --upgrade pip + MB_PYTHON_TAG=$(python -c "import setup; print(setup.MB_PYTHON_TAG)") + VERSION=$(python -c "import setup; print(setup.VERSION)") + BDIST_WHEEL_PATH=$(ls dist/*-$VERSION-$MB_PYTHON_TAG*.whl) + pip install ${BDIST_WHEEL_PATH}[all] + - run: + name: run_tests + command: | + . venv/bin/activate + python run_tests.py + + + ################################### + ### INHERIT FROM BASE TEMPLATES ### + ################################### + + # Define tests fo the other python verisons using the "test3.6" template + # and indicating what needs to be modified. + # + # All we need to do is change the base docker image so python is the + # version we want we can reuse everything else from the template + + test_full/cp39-cp39-manylinux2010: + <<: *test_full_template + environment: + - PYTHON_EXE: /opt/python/cp39-cp39/bin/python + - MB_PYTHON_TAG=cp39-cp39 + docker: + - image: quay.io/pypa/manylinux2010_x86_64:latest + working_directory: ~/repo-full-cp39 + + test_full/cp38-cp38-manylinux2010: + <<: *test_full_template + environment: + - PYTHON_EXE: /opt/python/cp38-cp38/bin/python + - MB_PYTHON_TAG=cp38-cp38 + docker: + - image: quay.io/pypa/manylinux2010_x86_64:latest + working_directory: ~/repo-full-cp38 + + test_full/cp37-cp37m-manylinux2010: + <<: *test_full_template + environment: + - PYTHON_EXE: /opt/python/cp37-cp37m/bin/python + - MB_PYTHON_TAG=cp37-cp37m + docker: + - image: quay.io/pypa/manylinux2010_x86_64:latest + working_directory: ~/repo-full-cp37 + + test_full/cp36-cp36m-manylinux2010: + <<: *test_full_template + environment: + - PYTHON_EXE: /opt/python/cp36-cp36m/bin/python + - MB_PYTHON_TAG=cp36-cp36m + docker: + - image: quay.io/pypa/manylinux2010_x86_64:latest + working_directory: ~/repo-full-cp36 + + test_full/cp35-cp35m-manylinux1: + <<: *test_full_template + environment: + - PYTHON_EXE: /opt/python/cp35-cp35m/bin/python + - MB_PYTHON_TAG=cp35-cp35m + docker: + - image: quay.io/pypa/manylinux1_x86_64:latest + working_directory: ~/repo-full-cp35 + + +__scratch_work__: + docker: + - image: pypy:3 + working_directory: ~/dev-only-not-a-real-job + steps: + - | + __doc__=" + # Run circleci scripts on a local machine + # snap install circleci + mkdir -p $HOME/Downloads + curl -fLSs https://raw.githubusercontent.com/CircleCI-Public/circleci-cli/master/install.sh | DESTDIR=$HOME/Downloads bash + + circleci update + circleci switch + + circleci config validate + circleci local execute --job test_full/cp39-cp39-manylinux2010 + circleci local execute --config .circleci/config.yml --job test_full/cp38-cp38-manylinux2010 + circleci local execute --config .circleci/config.yml + + circleci local execute --job test_full/cp38-cp38-manylinux2010 + " diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..6a61862 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +version: 2 +updates: + # Maintain dependencies for GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "friday" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..da2449a --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,317 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Tests + +on: + push: + pull_request: + branches: [ master ] + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up Python 3.8 + uses: actions/setup-python@v2 + with: + python-version: 3.8 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install flake8 + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + # flake8 . --count --exit-zero --max-complexity=20 --max-line-length=127 --statistics + + build_and_test_sdist: + name: Test sdist Python 3.8 + runs-on: ubuntu-latest + #needs: [lint] + steps: + - uses: actions/checkout@v2 + - name: Set up Python 3.8 + uses: actions/setup-python@v2 + with: + python-version: 3.8 + - name: Upgrade pip + run: | + python -m pip install --upgrade pip + python -m pip install -r requirements/build.txt + python -m pip install -r requirements/tests.txt + - name: Build sdist + run: | + python setup.py sdist + - name: Install sdist + run: | + cd dist + ls -al + pip install line_profiler*.tar.gz -v + - name: Test sdist + run: | + pwd + ls -al + # Ensure the source doesn't conflict with the test + rm -rf line_profiler + rm -rf kernprof.py + # cd .. + python run_tests.py + + - name: Upload sdist artifact + uses: actions/upload-artifact@v2 + with: + name: wheels + path: ./dist/*.tar.gz + + build_and_test_wheels: + name: ${{ matrix.cibw_build }} on ${{ matrix.os }}, arch=${{ matrix.arch }} + runs-on: ${{ matrix.os }} + #needs: [lint] + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macOS-latest] + arch: [auto] + cibw_build: [cp3*-*] + cibw_skip: ["*-win32"] + # Add additional workers to reduce overall build time + include: + - os: windows-latest + cibw_build: cp3*-win32 + arch: auto + cibw_skip: "" + - os: ubuntu-latest + arch: aarch64 + cibw_build: cp35-* + - os: ubuntu-latest + arch: aarch64 + cibw_build: cp36-* + - os: ubuntu-latest + arch: aarch64 + cibw_build: cp37-* + - os: ubuntu-latest + arch: aarch64 + cibw_build: cp38-* + - os: ubuntu-latest + arch: aarch64 + cibw_build: cp39-* + + + steps: + - name: Checkout source + uses: actions/checkout@v2 + + # Configure compilers for Windows 64bit. + - name: Enable MSVC 64bit + if: matrix.os == 'windows-latest' && matrix.cibw_build != 'cp3*-win32' + uses: ilammy/msvc-dev-cmd@v1 + + # Configure compilers for Windows 32bit. + - name: Enable MSVC 32bit + if: matrix.os == 'windows-latest' && matrix.cibw_build == 'cp3*-win32' + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: x86 + + # Emulate aarch64 ppc64le s390x under linux + - name: Set up QEMU + if: runner.os == 'Linux' && matrix.arch != 'auto' + uses: docker/setup-qemu-action@v1 + with: + platforms: all + + # See: https://github.com/pypa/cibuildwheel/blob/main/action.yml + - name: Build wheels + uses: pypa/cibuildwheel@v1.12.0 + with: + output-dir: wheelhouse + # to supply options, put them in 'env', like: + env: + CIBW_SKIP: ${{ matrix.cibw_skip }} + CIBW_BUILD: ${{ matrix.cibw_build }} + CIBW_TEST_REQUIRES: -r requirements/tests.txt + CIBW_TEST_COMMAND: python {project}/run_tests.py + # configure cibuildwheel to build native archs ('auto'), or emulated ones + CIBW_ARCHS_LINUX: ${{ matrix.arch }} + + - name: Show built files + shell: bash + run: ls -la wheelhouse + + - name: Set up Python 3.8 to combine coverage Linux + if: runner.os == 'Linux' + uses: actions/setup-python@v2 + with: + python-version: 3.8 + + - name: Combine coverage Linux + if: runner.os == 'Linux' + run: | + echo '############ PWD' + pwd + python -m pip install coverage[toml] + echo '############ combine' + coverage combine ./wheelhouse + echo '############ XML' + coverage xml -o ./tests/coverage.xml + echo '############ FIND' + find . -name .coverage.* + find . -name coverage.xml + + - name: Codecov Upload + uses: codecov/codecov-action@v1 + with: + file: ./tests/coverage.xml + + - name: Upload wheels artifact + uses: actions/upload-artifact@v2 + with: + name: wheels + path: ./wheelhouse/*.whl + + deploy: + # Publish on the real PyPI + name: Uploading to PyPi + runs-on: ubuntu-latest + if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') + needs: [build_and_test_wheels, build_and_test_sdist] + steps: + - name: Checkout source + uses: actions/checkout@v2 + + - name: Download wheels and sdist + uses: actions/download-artifact@v2 + with: + name: wheels + path: dist + + - name: Show files to upload + shell: bash + run: ls -la dist + + # Note: + # See ../../dev/setup_secrets.sh for details on how secrets are deployed securely + - name: Sign and Publish + env: + TWINE_REPOSITORY_URL: https://upload.pypi.org/legacy/ + PYUTILS_TWINE_USERNAME: ${{ secrets.PYUTILS_TWINE_USERNAME }} + PYUTILS_TWINE_PASSWORD: ${{ secrets.PYUTILS_TWINE_PASSWORD }} + PYUTILS_CI_SECRET: ${{ secrets.PYUTILS_CI_SECRET }} + run: | + ls -al + GPG_EXECUTABLE=gpg + $GPG_EXECUTABLE --version + openssl version + $GPG_EXECUTABLE --list-keys + echo "Decrypting Keys" + GLKWS=$PYUTILS_CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -d -a -in dev/ci_public_gpg_key.pgp.enc | $GPG_EXECUTABLE --import + $GPG_EXECUTABLE --list-keys || true + GLKWS=$PYUTILS_CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -d -a -in dev/gpg_owner_trust.enc | $GPG_EXECUTABLE --import-ownertrust + $GPG_EXECUTABLE --list-keys || true + GLKWS=$PYUTILS_CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -d -a -in dev/ci_secret_gpg_subkeys.pgp.enc | $GPG_EXECUTABLE --import + + echo "Finish Decrypting Keys" + $GPG_EXECUTABLE --list-keys || true + $GPG_EXECUTABLE --list-keys || echo "first invocation of gpg creates directories and returns 1" + $GPG_EXECUTABLE --list-keys + MB_PYTHON_TAG=$(python -c "import setup; print(setup.MB_PYTHON_TAG)") + VERSION=$(python -c "import setup; print(setup.VERSION)") + pip install twine + pip install six pyopenssl ndg-httpsclient pyasn1 -U --user + pip install requests[security] twine --user + GPG_KEYID=$(cat dev/public_gpg_key) + echo "GPG_KEYID = '$GPG_KEYID'" + MB_PYTHON_TAG=$MB_PYTHON_TAG \ + DO_GPG=True GPG_KEYID=$GPG_KEYID \ + TWINE_REPOSITORY_URL=${TWINE_REPOSITORY_URL} \ + TWINE_PASSWORD=$PYUTILS_TWINE_PASSWORD \ + TWINE_USERNAME=$PYUTILS_TWINE_USERNAME \ + GPG_EXECUTABLE=$GPG_EXECUTABLE \ + DO_UPLOAD=True \ + DO_BUILD=False \ + DO_TAG=False ./publish.sh + + test_deploy: + # Publish on the test PyPI + name: Uploading to Test PyPi + runs-on: ubuntu-latest + #if: github.event_name == 'push' && (startsWith(github.event.ref, 'refs/heads/main') || startsWith(github.event.ref, 'refs/heads/master')) + if: github.event_name == 'push' + needs: [build_and_test_wheels, build_and_test_sdist] + steps: + - name: Checkout source + uses: actions/checkout@v2 + + - name: Download wheels and sdist + uses: actions/download-artifact@v2 + with: + name: wheels + path: dist + + - name: Show files to upload + shell: bash + run: ls -la dist + - name: Sign and Publish + env: + TEST_TWINE_REPOSITORY_URL: https://test.pypi.org/legacy/ + PYUTILS_TEST_TWINE_USERNAME: ${{ secrets.PYUTILS_TEST_TWINE_USERNAME }} + PYUTILS_TEST_TWINE_PASSWORD: ${{ secrets.PYUTILS_TEST_TWINE_PASSWORD }} + PYUTILS_CI_SECRET: ${{ secrets.PYUTILS_CI_SECRET }} + run: | + ls -al + GPG_EXECUTABLE=gpg + $GPG_EXECUTABLE --version + openssl version + $GPG_EXECUTABLE --list-keys + echo "Decrypting Keys" + GLKWS=$PYUTILS_CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -d -a -in dev/ci_public_gpg_key.pgp.enc | $GPG_EXECUTABLE --import + $GPG_EXECUTABLE --list-keys || true + GLKWS=$PYUTILS_CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -d -a -in dev/gpg_owner_trust.enc | $GPG_EXECUTABLE --import-ownertrust + $GPG_EXECUTABLE --list-keys || true + GLKWS=$PYUTILS_CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -d -a -in dev/ci_secret_gpg_subkeys.pgp.enc | $GPG_EXECUTABLE --import + $GPG_EXECUTABLE --list-keys || true + echo "Finish Decrypt Keys" + $GPG_EXECUTABLE --list-keys || echo "first invocation of gpg creates directories and returns 1" + $GPG_EXECUTABLE --list-keys + MB_PYTHON_TAG=$(python -c "import setup; print(setup.MB_PYTHON_TAG)") + VERSION=$(python -c "import setup; print(setup.VERSION)") + pip install twine + pip install six pyopenssl ndg-httpsclient pyasn1 -U --user + pip install requests[security] twine --user + GPG_KEYID=$(cat dev/public_gpg_key) + echo "GPG_KEYID = '$GPG_KEYID'" + MB_PYTHON_TAG=$MB_PYTHON_TAG \ + DO_GPG=True GPG_KEYID=$GPG_KEYID \ + TWINE_REPOSITORY_URL=${TEST_TWINE_REPOSITORY_URL} \ + TWINE_USERNAME=${PYUTILS_TEST_TWINE_USERNAME} \ + TWINE_PASSWORD=${PYUTILS_TEST_TWINE_PASSWORD} \ + GPG_EXECUTABLE=$GPG_EXECUTABLE \ + DO_UPLOAD=True \ + DO_BUILD=False \ + DO_TAG=False ./publish.sh + +### +# Unfortunately we cant (yet) use the yaml docstring trick here +# https://github.community/t/allow-unused-keys-in-workflow-yaml-files/172120 +#__doc__: | +# # How to run locally +# # https://packaging.python.org/guides/using-testpypi/ +# cd $HOME/code +# git clone https://github.com/nektos/act.git $HOME/code/act +# cd $HOME/code/act +# chmod +x install.sh +# ./install.sh -b $HOME/.local/opt/act +# cd $HOME/code/line_profiler + +# load_secrets +# unset GITHUB_TOKEN +# $HOME/.local/opt/act/act \ +# --secret=PYUTILS_TWINE_PASSWORD=$PYUTILS_TWINE_PASSWORD \ +# --secret=PYUTILS_TWINE_USERNAME=$PYUTILS_TWINE_USERNAME \ +# --secret=PYUTILS_CI_SECRET=$PYUTILS_CI_SECRET \ +# --secret=PYUTILS_TEST_TWINE_USERNAME=$PYUTILS_TEST_TWINE_USERNAME \ +# --secret=PYUTILS_TEST_TWINE_PASSWORD=$PYUTILS_TEST_TWINE_PASSWORD diff --git a/.gitignore b/.gitignore index 9ec966d..a7f584d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,14 +5,20 @@ *.pyc *.pyo +*.pyd *.so *.o *.a build/ dist/ +_skbuild _line_profiler.c line_profiler.egg-info/ MANIFEST pypi-site-docs.zip index.html + +.coverage +tests/coverage.xml +tests/htmlcov diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 67a34a0..0000000 --- a/.travis.yml +++ /dev/null @@ -1,26 +0,0 @@ -language: python -python: - - "nightly" - - "3.7-dev" - - "3.6-dev" - - "3.6" - - "3.5" - - "3.4" - - "3.3" - - "2.7" -matrix: - fast_finish: true - allow_failures: - - python: "nightly" - - python: "3.7-dev" - - python: "3.6-dev" -install: - - pip install --install-option='--no-cython-compile' Cython - - pip install -r dev_requirements.txt - - python setup.py develop -script: - - python -m unittest discover -v tests -notifications: - email: - - brett.olsen+travis-ci@gmail.com - - robert.kern+travis-ci@gmail.com diff --git a/CHANGELOG.rst b/CHANGELOG.rst new file mode 100644 index 0000000..ab71d51 --- /dev/null +++ b/CHANGELOG.rst @@ -0,0 +1,108 @@ +Changes +======= + +3.3.1 +~~~~~ +* FIX: Fix bug where lines were not displayed in Jupyter>=6.0 via #93 +* CHANGE: moving forward, new pypi releases will be signed with the GPG key 2A290272C174D28EA9CA48E9D7224DAF0347B114 for PyUtils-CI . For reference, older versions were signed with either 262A1DF005BE5D2D5210237C85CD61514641325F or 1636DAF294BA22B89DBB354374F166CFA2F39C18. + +3.3.0 +~~~~~ +* New CI for building wheels. + +3.2.6 +~~~~~ +* FIX: Update MANIFEST.in to package pyproj.toml and missing pyx file +* CHANGE: Removed version experimental augmentation. + +3.2.5 +~~~~~ +* FIX: Update MANIFEST.in to package nested c source files in the sdist + +3.2.4 +~~~~~ +* FIX: Update MANIFEST.in to package nested CMakeLists.txt in the sdist + +3.2.3 +~~~~~ +* FIX: Use ImportError instead of ModuleNotFoundError while 3.5 is being supported +* FIX: Add MANIFEST.in to package CMakeLists.txt in the sdist + +3.2.2 +~~~~~ +* ENH: Added better error message when c-extension is not compiled. +* FIX: Kernprof no longer imports line_profiler to avoid side effects. + +3.2.0 +~~~~~ +* Dropped 2.7 support, manylinux docker images no longer support 2.7 +* ENH: Add command line option to specify time unit and skip displaying + functions which have not been profiled. +* ENH: Unified versions of line_profiler and kernprof: kernprof version is now + identical to line_profiler version. + +3.1.0 +~~~~~ +* ENH: fix Python 3.9 + +3.0.2 +~~~~~ +* BUG: fix ``__version__`` attribute in Python 2 CLI. + +3.0.1 +~~~~~ +* BUG: fix calling the package from the command line + +3.0.0 +~~~~~ +* ENH: Fix Python 3.7 +* ENH: Restructure into package + +2.1 +~~~ +* ENH: Add support for Python 3.5 coroutines +* ENH: Documentation updates +* ENH: CI for most recent Python versions (3.5, 3.6, 3.6-dev, 3.7-dev, nightly) +* ENH: Add timer unit argument for output time granularity spec + +2.0 +~~~ +* BUG: Added support for IPython 5.0+, removed support for IPython <=0.12 + +1.1 +~~~ +* BUG: Read source files as bytes. + +1.0 +~~~ +* ENH: `kernprof.py` is now installed as `kernprof`. +* ENH: Python 3 support. Thanks to the long-suffering Mikhail Korobov for being + patient. +* Dropped 2.6 as it was too annoying. +* ENH: The `stripzeros` and `add_module` options. Thanks to Erik Tollerud for + contributing it. +* ENH: Support for IPython cell blocks. Thanks to Michael Forbes for adding + this feature. +* ENH: Better warnings when building without Cython. Thanks to David Cournapeau + for spotting this. + +1.0b3 +~~~~~ + +* ENH: Profile generators. +* BUG: Update for compatibility with newer versions of Cython. Thanks to Ondrej + Certik for spotting the bug. +* BUG: Update IPython compatibility for 0.11+. Thanks to Yaroslav Halchenko and + others for providing the updated imports. + +1.0b2 +~~~~~ + +* BUG: fixed line timing overflow on Windows. +* DOC: improved the README. + +1.0b1 +~~~~~ + +* Initial release. + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..8be4f17 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,45 @@ +cmake_minimum_required(VERSION 3.13.0) +project(_line_profiler LANGUAGES C) + +### +# Private helper function to execute `python -c ""` +# +# Runs a python command and populates an outvar with the result of stdout. +# Be careful of indentation if `cmd` is multiline. +# +function(pycmd outvar cmd) + execute_process( + COMMAND "${PYTHON_EXECUTABLE}" -c "${cmd}" + RESULT_VARIABLE _exitcode + OUTPUT_VARIABLE _output) + if(NOT ${_exitcode} EQUAL 0) + message(ERROR "Failed when running python code: \"\"\" +${cmd}\"\"\"") + message(FATAL_ERROR "Python command failed with error code: ${_exitcode}") + endif() + # Remove supurflous newlines (artifacts of print) + string(STRIP "${_output}" _output) + set(${outvar} "${_output}" PARENT_SCOPE) +endfunction() + + +find_package(PythonInterp REQUIRED) + + +### +# Find scikit-build and include its cmake resource scripts +# +if (NOT SKBUILD) + pycmd(skbuild_location "import os, skbuild; print(os.path.dirname(skbuild.__file__))") + set(skbuild_cmake_dir "${skbuild_location}/resources/cmake") + message(STATUS "[LINE_PROFILER] skbuild_cmake_dir = ${skbuild_cmake_dir}") + # If skbuild is not the driver, then we need to include its utilities in our CMAKE_MODULE_PATH + list(APPEND CMAKE_MODULE_PATH ${skbuild_cmake_dir}) +endif() + + +find_package(Cython REQUIRED) +find_package(PythonExtensions REQUIRED) +find_package(PythonLibs REQUIRED) + +add_subdirectory("line_profiler") diff --git a/MANIFEST.in b/MANIFEST.in index a24056f..c9d9793 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,8 +1,14 @@ -include LICENSE.txt -include LICENSE_Python.txt -include README.rst -include python25.pxd -include timers.h -include _line_profiler.c -include unset_trace.h +include *.md +include *.rst +include *.py +include *.txt +include *.toml +include run_tests.sh +recursive-include requirements *.txt recursive-include tests *.py +recursive-include line_profiler *.txt +recursive-include line_profiler *.pyx +recursive-include line_profiler *.pxd +recursive-include line_profiler *.pyd +recursive-include line_profiler *.c +recursive-include line_profiler *.h diff --git a/Makefile b/Makefile deleted file mode 100644 index 7273d4f..0000000 --- a/Makefile +++ /dev/null @@ -1,18 +0,0 @@ -all: - @echo 'Just some tools to help me make releases. Nothing for users.' - -index.html: README.rst - rst2html.py README.rst index.html - -pypi-site-docs.zip: index.html kernprof.py LICENSE.txt - zip -r $@ $? - -site: pypi-site-docs.zip - -# We need to run build_ext first to make sure we have _line_profiler.c. -# However, we can't run both commands in the same run. -sdist: - python setup.py build_ext - python setup.py sdist - -.PHONY: site sdist diff --git a/README.rst b/README.rst index 488f054..682f8bd 100644 --- a/README.rst +++ b/README.rst @@ -1,13 +1,27 @@ line_profiler and kernprof -------------------------- +|Pypi| |Downloads| |CircleCI| |ActionsTest| |Codecov| + + +NOTICE: This is the official `line_profiler` repository. The most recent +version of `line-profiler `_ on pypi +points to this repo. The the original +`line_profiler `_ package by +`@rkern `_ is currently unmaintained. This fork +seeks to simply maintain the original code so it continues to work in new +versions of Python. + +---- + + `line_profiler` is a module for doing line-by-line profiling of functions. kernprof is a convenient script for running either `line_profiler` or the Python standard library's cProfile or profile modules, depending on what is available. They are available under a `BSD license`_. -.. _BSD license: https://raw.githubusercontent.com/rkern/line_profiler/master/LICENSE.txt +.. _BSD license: https://raw.githubusercontent.com/pyutils/line_profiler/master/LICENSE.txt .. contents:: @@ -15,14 +29,6 @@ They are available under a `BSD license`_. Installation ============ -**Note:** As of version 2.1.2, `pip install line_profiler` does not work. -Please install as follows until it is fixed in the next release:: - - git clone https://github.com/rkern/line_profiler.git - find line_profiler -name '*.pyx' -exec cython {} \; - cd line_profiler - pip install . --user - Releases of `line_profiler` can be installed using pip:: $ pip install line_profiler @@ -33,7 +39,7 @@ Source releases and any binaries can be downloaded from the PyPI link. To check out the development sources, you can use Git_:: - $ git clone https://github.com/rkern/line_profiler.git + $ git clone https://github.com/pyutils/line_profiler.git You may also download source tarballs of any snapshot from that URL. @@ -47,6 +53,13 @@ a compiler. If you wish to use it to run cProfile and not line-by-line profiling, you may copy it to a directory on your `PATH` manually and avoid trying to build any C extensions. +As of 2021-06-04 Linux (x86_64 and i686), OSX (10_9_x86_64), and Win32 (win32, +and amd64) binaries are available on pypi. + +Alternateively on windows you might consider using Christoph Gohlke's +unofficial line-profiler +`precompiled win32 wheels `_. + .. _git: http://git-scm.com/ .. _Cython: http://www.cython.org .. _build and install: http://docs.python.org/install/index.html @@ -235,13 +248,12 @@ command:: $ python -m pstats script_to_profile.py.prof -Such files may also be viewed with graphical tools like kcachegrind_ through the -converter program pyprof2calltree_ or RunSnakeRun_. +Such files may also be viewed with graphical tools like SnakeViz_ and converted +through pyprof2calltree_ to run on kcachegrind_ and compatible apps. .. _kcachegrind: http://kcachegrind.sourceforge.net/html/Home.html .. _pyprof2calltree: http://pypi.python.org/pypi/pyprof2calltree/ -.. _RunSnakeRun: http://www.vrplumber.com/programming/runsnakerun/ - +.. _SnakeViz: https://github.com/jiffyclub/snakeviz/ Frequently Asked Questions ========================== @@ -334,10 +346,14 @@ Frequently Asked Questions C sources. You will probably need version 0.10 or higher. There is a bug in some earlier versions in how it handles NULL PyObject* pointers. + As of version ``3.0.0`` manylinux wheels containing the binaries are + available on pypi. Work is still needed to publish osx and win32 wheels. + (PRs for this would be helpful!) + * What version of Python do I need? - Both `line_profiler` and `kernprof` have been tested with Python 2.7, and - 3.2-3.4. + Both `line_profiler` and `kernprof` have been tested with Python 3.5-3.9. + Older versions of `line_profiler` support older versions of Python. To Do @@ -357,56 +373,28 @@ Bugs and Such Bugs and pull requested can be submitted on GitHub_. -.. _GitHub: https://github.com/rkern/line_profiler +.. _GitHub: https://github.com/pyutils/line_profiler Changes ======= -2.1 -~~~ -* ENH: Add support for Python 3.5 coroutines -* ENH: Documentation updates -* ENH: CI for most recent Python versions (3.5, 3.6, 3.6-dev, 3.7-dev, nightly) -* ENH: Add timer unit argument for output time granularity spec - -2.0 -~~~ -* BUG: Added support for IPython 5.0+, removed support for IPython <=0.12 - -1.1 -~~~ -* BUG: Read source files as bytes. - -1.0 -~~~ -* ENH: `kernprof.py` is now installed as `kernprof`. -* ENH: Python 3 support. Thanks to the long-suffering Mikhail Korobov for being - patient. -* Dropped 2.6 as it was too annoying. -* ENH: The `stripzeros` and `add_module` options. Thanks to Erik Tollerud for - contributing it. -* ENH: Support for IPython cell blocks. Thanks to Michael Forbes for adding - this feature. -* ENH: Better warnings when building without Cython. Thanks to David Cournapeau - for spotting this. - -1.0b3 -~~~~~ - -* ENH: Profile generators. -* BUG: Update for compatibility with newer versions of Cython. Thanks to Ondrej - Certik for spotting the bug. -* BUG: Update IPython compatibility for 0.11+. Thanks to Yaroslav Halchenko and - others for providing the updated imports. - -1.0b2 -~~~~~ - -* BUG: fixed line timing overflow on Windows. -* DOC: improved the README. - -1.0b1 -~~~~~ - -* Initial release. +See `CHANGELOG`_. + +.. _CHANGELOG: CHANGELOG.rst + + +.. |CircleCI| image:: https://circleci.com/gh/pyutils/line_profiler.svg?style=svg + :target: https://circleci.com/gh/pyutils/line_profiler +.. |Travis| image:: https://img.shields.io/travis/pyutils/line_profiler/master.svg?label=Travis%20CI + :target: https://travis-ci.org/pyutils/line_profiler?branch=master +.. |Appveyor| image:: https://ci.appveyor.com/api/projects/status/github/pyutils/line_profiler?branch=master&svg=True + :target: https://ci.appveyor.com/project/pyutils/line_profiler/branch/master +.. |Codecov| image:: https://codecov.io/github/pyutils/line_profiler/badge.svg?branch=master&service=github + :target: https://codecov.io/github/pyutils/line_profiler?branch=master +.. |Pypi| image:: https://img.shields.io/pypi/v/line_profiler.svg + :target: https://pypi.python.org/pypi/line_profiler +.. |Downloads| image:: https://img.shields.io/pypi/dm/line_profiler.svg + :target: https://pypistats.org/packages/line_profiler +.. |ActionsTest| image:: https://github.com/pyutils/line_profiler/actions/workflows/tests.yml/badge.svg + :target: https://github.com/pyutils/line_profiler/actions/workflows/tests.yml diff --git a/clean.sh b/clean.sh new file mode 100755 index 0000000..87254ea --- /dev/null +++ b/clean.sh @@ -0,0 +1,25 @@ +#!/bin/bash +echo "start clean" + +rm -rf _skbuild +rm -rf _line_profiler.c +rm -rf *.so +rm -rf line_profiler/_line_profiler.c +rm -rf line_profiler/*.so +rm -rf build +rm -rf line_profiler.egg-info +rm -rf dist +rm -rf mb_work +rm -rf wheelhouse +rm -rf pip-wheel-metadata +rm -rf htmlcov + + +if [ -f "distutils.errors" ]; then + rm distutils.errors || echo "skip rm" +fi + +CLEAN_PYTHON='find . -regex ".*\(__pycache__\|\.py[co]\)" -delete || find . -iname *.pyc -delete || find . -iname *.pyo -delete' +bash -c "$CLEAN_PYTHON" + +echo "finish clean" diff --git a/dev/ci_public_gpg_key.pgp.enc b/dev/ci_public_gpg_key.pgp.enc new file mode 100644 index 0000000..f97b3b7 --- /dev/null +++ b/dev/ci_public_gpg_key.pgp.enc @@ -0,0 +1,31 @@ +U2FsdGVkX19UPUwZPHCMsKEaQZHQUOWtvvOrpZWsOqefx76gRn4uq8kP5PVpNZx2 +so8VYjFeg91ZfkQwVMDI9ILOonCSCbykgGe+PdbSK+IdMwmmYaxtZx/GTBU4wKZy +2X07635Yemh+Tmnwn8MvKoau6YMCCWVEG42usrOftxJjNLD+RG0ATDJc/RX/qUiM +r6UyWRHV85sakCnuMHa+7+DE/CDKAKmRubevdjnQiso20+ThpPqGGcanq/wa3NNC +89F0pEHoFoC0dQHT3JlBnigCeurgq5/VESb9BVl043qc5HQg1uXL2OCfJ8xYC8P8 +lIhiVm9rTgbxcR41nL7/7voVZA8pNvTCMr3dgUMtucDbZ3gIpn1dWc1dKpb2LP4m +MUh3G1uNML/RWYWA0zNELu+9s2ToTyK3pTEfL3UAR1a7drMNDybq4Kx2cZ6UqiXV +pasRcV4jtOGcHSZHVIdOIDGk4iP7/GnxV8AJl2oG1i+OICFfxSL+u79Izp/d0fRs +vTUvuYjKFPF4U8mL5cguuKvs8ApHDHCeZl1MTGfZrNYqa/Exkuw1Ic2CjjonSosx +lWOQM5z4M8/LNejKu8YwTQ1ffBck4enpoBZskOWhA/WDN51kLLOGpjG+KjHIV8aj +rblGUoNum7gcTiNCGY2jYin4lGLqhPwyZKIE1Nz7GJ8cGhvPGoMYfusJta7eHsj/ +m8JlbgAIcIy3mkPEezRg9RTHuoHjc4yp3q18GklS+FejDFPI744KXTPJW1SGem1L +tH6+QqDlD7eERYJ1WBWVtraCyvOUvgyhTsNZFDOxF7DeKobm6etdErMA7o0XlRQR +KgImy6c3koA8uXP50u8FbkFbqod7BAtExdtoDUkSozToUx4jsxldNhs0uEhExXbe +7Y+DDXZm+NyopxR4UnwNd+y4+qGWCOTFNBHPFmpgDaySqGfLmmR3VegyhTJRfQjx +m7NGwg/zLwP5L5Rf+xmbpFzotFkGOAlYfS86jIX3CjRl2u/6jw/7AkSLGtk7s03d +SUHHzqdSAJBLN3L9ZqfF4/yjGZaCV+Mflwd3ktkbEL64bRmRzzRLfJ/E15gg9SdO +C3IMUyD/tIrxvL7hUiKdKo1HwCNDGJgj9KTzDCVkBuYr5rMQ0VStydMbVHseQexZ +tREHd478XPJ404wwGGDDqz6kkv7/+pFIH9sFTg4JHTTI7sQlvgfjyp7TPc0moOh5 +5Wober6g5qAHDr8hHsK7jWF5FpebSpCiT3mfANjjZXmVPL0E2eAx1WQeCT3GEfC5 +7R4lOAozwLLbXHxaTzIE+MroQK9NXQA+6xxaxNjEgz00aEklFN4J4VXPguveuTou +IwD+kNhFxb9GNaOiL2xw03300U7SXVQpQmcZ7UoSj0k55IIdRv9bSAFQOCrDABkv +orZIr/QdeZdVje2mBeqGmrzsA4mdlInvf/AQJYx8abEmmuoL1Or2ClLR9811ejU9 +yeItLc/JUjCS5IqlXyYGDLXwgeghqH85U2fEPMLmTc+66U4VDB4+71holXKjPGsX +4+cYFJEVhpHazfSj0CZQOcRTrNcCwHkzSJKadOgEXOWglTshLdgH1VNczA23gq8q +RH5wonp7WyRmJJqzYSpPqxYPflACeJ6B2IAtUNc0yDTAV+1xPhbeUIkQRlD0LoSt +M2oW3jxLWyZdZf5MkqT7DjgkLA6ag4W2ADVdygdzY7T/64b3Qt3AkCMIH52sHZvm +jxjlageQsBvSynFeU1Mrb9Zo3KsGye2r+5AdgUSGrg6JZzozEqkdfKdEI2Txjx7q +YLCGGo/GopQVQLQMKvPoxlevpuajQXlkc+mWJOzJJ3GWfJ8xcxqqMFXXlVgKlfBv +Kw873xb6lTK7XWrSvClH7Zm5c/uBoFg8sU6fqO7ILnuS/0xS6vMMQULMsuhNIJPK +homicz1kkw/UcRONNvWhgW0DwmYpmUwGkXZ8u6D/ObqekVjMtRRxz+Y6U6IPhBxA diff --git a/dev/ci_secret_gpg_subkeys.pgp.enc b/dev/ci_secret_gpg_subkeys.pgp.enc new file mode 100644 index 0000000..1e89e00 --- /dev/null +++ b/dev/ci_secret_gpg_subkeys.pgp.enc @@ -0,0 +1,24 @@ +U2FsdGVkX1842tVQei/2sXwcbeTaud+N5VOXvGdXk1IE7j5dnERvy3MnUvps3smO +ORH5NRHmT6tKS5bfx6SwsuVDOFD7xn75T11BTARQxV73fVUHzuhLofk1R6EU+X7P +oNJPPLUymnDgwdrGxI7UB4ZH1yipTT05qsenPVFMISwOPAmZbAA5dgt/uEJChh/T +1v6LAgQMZ1M9YQjk6PjDoI9V6Ah/W/GNF8hn6pO3ir0+fIuoSJa0Xwflx7XLtU0I +xSHAcQ5C2hkuizz+lecIxN5jxcDPjCj4EYrmzY1Hg37HjQodxVqTWUhwo95CSrCT +3uT1YQofl0APBycnz0w4cM2DVfqtN2ITssz8Q0Mcm4VqF7wvmgRW9zSMI8U02LTg +Hxi3ITaXbwrVhvc1emCt0MJ2/DWQwmoFHY2nNHHmNO6odBjie15ewccEfcobaCXY +bZ3poBjmGtO/EDJuP0D3qEguIrcKX6zEvbhjWlrTqKpg6MfcVGeW0lU3WGcBJQnY +BVwKCbh82Kne1+orO8AM2u1ksghchYbQnbfXbUK09lHveRGkA6gKnmhvxlghRLnH +8F7vmjMU8ND5PkTt5dvJQbPrNWsX/+yOdGdNdFWwSQcDJsDAp9tnYvRmKLQJb5NF +vABLUk5fQ/HOqFSMg4RNxkLLPm/kQbjcIxlWNlpUr6BKgk7QWbXQ81U/WLzcpKpx +czU71/QvdTW2c2jkL6lasYK1Wg0KtqBClBDVTnFszcyYo+GzWMjVGxFZu0zN9o53 +6aEq4tPPDxmcYgpNFLAb/7FS+kGWObNRu0MZVvEN7ARbR7J+2tmYTNV1xr+w3Nil +6FV9G+XNRj2XsC6F+Bzm/qyU1U/91p125c4AvobLPA4lfrYVfkwYg14Icl38mamV +by4IiTG9CkQPkCQcRiXvOcWhVWMpXcVjtw48RIcR2df/oH13m47pAu9F05xhiyAO +ZvSjcl10/rkPf1AIkxPO6SD0TsLEn//u9D8PekNA8c/aMjgqqf3CTnNESRpq7GQf +AL1lTrP0YdH6mckR204PE1arEJfcG0+h7nd8AhpX991QTu0Yx/EW6IUb08IzImwN +2ThhZB+asAe/EVvsnCjbVMVnli+z5YSsAf6jSvV40FurXFEHt1BDilZJW2RVFyIC +rmOdvZoLVxxtWC2J/GWrbJjlpGfIFGwoCzI0DmHvYwOPKfVYYNz+gWFJyTcUW9pU +X/igLTlJm4WZtH6AeIF9WQ+32t4JOZeePO0vZcmzH7OzNWGqBKv25z//buQgYX4m +qEpWXBdQUyp45LnkoqXwjBewrYSEaqYu6CObPpWt/NHdNnb/PiXBCJAujOAvwnYX +NDVQWa+EACoKQY+QvHzKtVWV0QWlJ0GfwNQx0SYh9gnoyKp8S4CKlmx46rzg7v9j +z/Bg8yQPHUmHwZY8BG8ZcuT8GLOp9HxJT45PC1Q4UIq5VVMEpw4i0UvsSBgApysL +eOQlJ3071XR1kcNCUnPvkQ== diff --git a/dev/dev/ci_public_gpg_key.pgp.enc b/dev/dev/ci_public_gpg_key.pgp.enc new file mode 100644 index 0000000..24ef84e --- /dev/null +++ b/dev/dev/ci_public_gpg_key.pgp.enc @@ -0,0 +1,12 @@ +U2FsdGVkX19nHAfq4uFsY45nn2W10l0/eJaVmaku1mWrzvZ78YHgAn0Bd6uL7rsE +uvD9++WaCQd4aIphZjZ0cat4Eu45bAP729SOMrsrx8iSy1kwfkYe9tKEsV7Kp7f2 +SupCrVq9UNm/jQfPQDrNFUr+1xJm26k4IEvZ6XUe8q3VNt28MDCGw2jIK58FsRwQ +8KowxKoco/uP765C9dG/3j6nDu4LOZvPBtWcGnATG7BrNFUkaj8HTkm6SI0hb8oS +NyW/86POHUXPrVof4FZRuIWnCJL5vCzU/bC9jLsQ/+zFumkEPqXoL1n/ilUXVtPr +qhL81hP50Y8RIGeorUEw3HyZ0Ge9fGpALf3e0zzwpv25pYpvL2trLGhUEcjWxkXg +sgqf7r1kfHDFWgxAI+ZYFglZnsfB5aouK4Dq0GHjvAf644LUVGliQRsRUtEMkgOX +taspvT8cPzHK9ISZnulI12WATxELNTp2okKalKknp7ZQHXBHBg1NVQqWYmJTu4YF +UnCdrlulXhsXoe1KLoWmualrMV8LNinwoLTE++6QwoZB4t9EXlV0s8XSalSbj7pQ +/hlu1R3v9OjdmeexUiPF1kheo8ijDaSW2ZvXZRqIp7w9fYCCKAuXkaTy91/WkZGa +j2cqpLHq2tfBMJfwyZ33rvoIQXKfBNn4kIUWLwFbrR41r7LGuoJP14SNDcZcU0dS +NFDImrG8q3IdRWPVpPCzRm+7r9qJ0DgZbb8Tiht3fwJ9GdxLHrK6gDhwUAXe9vbU diff --git a/dev/dev/ci_secret_gpg_subkeys.pgp.enc b/dev/dev/ci_secret_gpg_subkeys.pgp.enc new file mode 100644 index 0000000..dca383a --- /dev/null +++ b/dev/dev/ci_secret_gpg_subkeys.pgp.enc @@ -0,0 +1,14 @@ +U2FsdGVkX18/KfhOtI/r26oQOxfW/opXr9Vdqj4KhQ+DtZK0hXgleGEJFCClK/g8 +H17SP7l11WUnzSePVFxd89rnb+ixe1KmeTrUBwaImH4CjNsNP4NQ4nqcCtQkdjMs +wjv05Eft8kUFxiAfK8N2TTVlLMbNDH6rjjhOGziJhNC3J9c0AqBsBA+1GfwNsw+K +X7jyhtL9i7hEoLFRCEEyyd4BRwiilLPiSKmdNm0arXCOJtUd5vLU0BoDJsdeM9Z4 +Q9aUkP40o9Va5XvXnCSmFbFye1Gn/SCO5MzSyxM1zq4Cr3JspcJb7qQHFsvns8tB +Ph/kT8OQsEA8bhj9DGw3XG+/ccHHjbhF7dq1bWH4uoKP7nEQA3ywtAaAv+ie+bxN +fnBoxMLHwFJH4LsebQKvDxt7g4Xfs2nwJrzRLr0M1JligE62ZIuungV5WlDFv3NS +46GVIdLVvpaR9kbikQZtH8R2H79UKi34lWRLGj+3FMYt02EB/iM9J0/ylvjWNBp/ +ygOiXbn/zUnzOCqpByA9s3Ms/ZCjY/lNkxg7yZOZLZTc6+1GTdrIhpC2U45dAwAq +qBknJ7zxloU0A+xVLXco320ARmbPZML3gOpvDmv7XamLTVcCxmCxARwP2DTtHloc +aMNTiNLbeFPI82YKB5ukZrjh1zrptqf6xtQgbO2B8j8+BJujIbXSDKPUK1QO9eHV ++s+Y6A1a4xPZO+SOzduk9EseDeYMjym7WcQ8D06q7pkxkBdgdgiSJX/9RMAnphcy +1wlvLLIjteaEx6L8b13QZK4y95gyP3CXdhSzlrWW9Ro/kdG5KXXEGV5f8akJHEB/ +csz7FrC4Y8oChYVbgGP+HWAy31wLHSEZYQLP2v4s/Xk= diff --git a/dev/dev/public_gpg_key b/dev/dev/public_gpg_key new file mode 100644 index 0000000..c947d95 --- /dev/null +++ b/dev/dev/public_gpg_key @@ -0,0 +1 @@ +2A290272C174D28EA9CA48E9D7224DAF0347B114 diff --git a/dev/gpg_owner_trust.enc b/dev/gpg_owner_trust.enc new file mode 100644 index 0000000..da038e6 --- /dev/null +++ b/dev/gpg_owner_trust.enc @@ -0,0 +1,10 @@ +U2FsdGVkX18uQe7kTKAV6xX6g9dAtemSZKysbpjPVj40eXzg5SczS+fxmSnkE3pL +XJGQHIa2VguiUfBGXgWGGYntDv8UN2gH+qiMa10x6cKALx0aEVLURDACJzyiEYjX +PYITU9OrlXysjLaxpdndYSzo1+zNABTzKqx4fY6sXnkeabl8Uh6Rz72hP7mcYmET +KNFrZqNIDM6JHldwqvhqrLjduoTr+w05FCaGl9+aJlJO+PAyRz5px9MNkJXLHyZT +/WSOEeYIaxPn9O4muHj/eMXWIlXdyTz+xWqQkwujXIHMm8tqbc7375ZoghbO+/f5 +hzan/+ge5CxowPeGRU3anB2EVH4vkWdcwe+3bBloaPEeJv6783a2PCA/m7kIYVI7 +UpyZKQcGGOBZZe4eIhW3yFFY/omGlzcJNvTsZ5AbziVNrr05g/g6XuBIwOuSckhp +Puwhzyru9IRSe2xxLQmM+bDfvYpky/I5ybYntpZPyTRoa6UaoEnThKj04CRdegWF +REHAxh7QkaSat9X2BXykPohT/XVTDHOmj5ZCvIvcAsc/tWb3d42RlgLF+80H0rsH +56PpkqOztD+6qSVUy3aq6w== diff --git a/dev/public_gpg_key b/dev/public_gpg_key new file mode 100644 index 0000000..c947d95 --- /dev/null +++ b/dev/public_gpg_key @@ -0,0 +1 @@ +2A290272C174D28EA9CA48E9D7224DAF0347B114 diff --git a/dev/secrets_configuration.sh b/dev/secrets_configuration.sh new file mode 100644 index 0000000..55a1ac5 --- /dev/null +++ b/dev/secrets_configuration.sh @@ -0,0 +1,6 @@ +export VARNAME_CI_SECRET="PYUTILS_CI_SECRET" +export GPG_IDENTIFIER="=PyUtils-CI " +export VARNAME_TWINE_PASSWORD="PYUTILS_TWINE_PASSWORD" +export VARNAME_TWINE_USERNAME="PYUTILS_TWINE_USERNAME" +export VARNAME_TEST_TWINE_PASSWORD="PYUTILS_TEST_TWINE_PASSWORD" +export VARNAME_TEST_TWINE_USERNAME="PYUTILS_TEST_TWINE_USERNAME" diff --git a/dev/setup_secrets.sh b/dev/setup_secrets.sh new file mode 100644 index 0000000..7ceac67 --- /dev/null +++ b/dev/setup_secrets.sh @@ -0,0 +1,224 @@ +__doc__=' +============================ +SETUP CI SECRET INSTRUCTIONS +============================ + +TODO: These instructions are currently pieced together from old disparate +instances, and are not yet fully organized. + +The original template file should be: +~/misc/templates/PYPKG/dev/setup_secrets.sh + +Development script for updating secrets when they rotate + + +The intent of this script is to help setup secrets for whichever of the +following CI platforms is used: + +../.github/workflows/tests.yml +../.gitlab-ci.yml +../.circleci/config.yml + + +========================= +GITHUB ACTION INSTRUCTIONS +========================= + +* `PERSONAL_GITHUB_PUSH_TOKEN` - + This is only needed if you want to automatically git-tag release branches. + + To make a API token go to: + https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/creating-a-personal-access-token + + +========================= +GITLAB ACTION INSTRUCTIONS +========================= + + ```bash + cat .setup_secrets.sh | \ + sed "s|utils||g" | \ + sed "s|PYPKG||g" | \ + sed "s|travis-ci-Erotemic||g" | \ + sed "s|CI_SECRET||g" | \ + sed "s|GITLAB_ORG_PUSH_TOKEN||g" | \ + sed "s|gitlab.org.com|gitlab.your-instance.com|g" | \ + tee /tmp/repl && colordiff .setup_secrets.sh /tmp/repl + ``` + + * Make sure you add Runners to your project + https://gitlab.org.com/utils/PYPKG/-/settings/ci_cd + in Runners-> Shared Runners + and Runners-> Available specific runners + + * Ensure that you are auto-cancel redundant pipelines. + Navigate to https://gitlab.kitware.com/utils/PYPKGS/-/settings/ci_cd and ensure "Auto-cancel redundant pipelines" is checked. + + More details are here https://docs.gitlab.com/ee/ci/pipelines/settings.html#auto-cancel-redundant-pipelines + + * TWINE_USERNAME - this is your pypi username + twine info is only needed if you want to automatically publish to pypi + + * TWINE_PASSWORD - this is your pypi password + + * CI_SECRET - We will use this as a secret key to encrypt/decrypt gpg secrets + This is only needed if you want to automatically sign published + wheels with a gpg key. + + * GITLAB_ORG_PUSH_TOKEN - + This is only needed if you want to automatically git-tag release branches. + + Create a new personal access token in User->Settings->Tokens, + You can name the token GITLAB_ORG_PUSH_TOKEN_VALUE + Give it api and write repository permissions + + SeeAlso: https://gitlab.org.com/profile/personal_access_tokens + + Take this variable and record its value somewhere safe. I put it in my secrets file as such: + + export GITLAB_ORG_PUSH_TOKEN_VALUE= + + I also create another variable with the prefix "git-push-token", which is necessary + + export GITLAB_ORG_PUSH_TOKEN=git-push-token:$GITLAB_ORG_PUSH_TOKEN_VALUE + + Then add this as a secret variable here: https://gitlab.org.com/groups/utils/-/settings/ci_cd + Note the value of GITLAB_ORG_PUSH_TOKEN will look something like: "{token-name}:{token-password}" + For instance it may look like this: "git-push-token:62zutpzqga6tvrhklkdjqm" + + References: + https://stackoverflow.com/questions/51465858/how-do-you-push-to-a-gitlab-repo-using-a-gitlab-ci-job + + # ADD RELEVANT VARIABLES TO GITLAB SECRET VARIABLES + # https://gitlab.kitware.com/computer-vision/kwcoco/-/settings/ci_cd + # Note that it is important to make sure that these variables are + # only decrpyted on protected branches by selecting the protected + # and masked option. Also make sure you have master and release + # branches protected. + # https://gitlab.kitware.com/computer-vision/kwcoco/-/settings/repository#js-protected-branches-settings + + +============================ +Relevant CI Secret Locations +============================ + +https://github.com/pyutils/line_profiler/settings/secrets/actions + +https://app.circleci.com/settings/project/github/pyutils/line_profiler/environment-variables?return-to=https%3A%2F%2Fapp.circleci.com%2Fpipelines%2Fgithub%2Fpyutils%2Fline_profiler +' + + +setup_package_environs(){ + __doc__=" + Setup environment variables specific for this project. + The remainder of this script should ideally be general to any repo. These + non-secret variables are written to disk and loaded by the script, such + that the specific repo only needs to modify that configuration file. + " + + echo ' + export VARNAME_CI_SECRET="PYUTILS_CI_SECRET" + export GPG_IDENTIFIER="=PyUtils-CI " + export VARNAME_TWINE_PASSWORD="PYUTILS_TWINE_PASSWORD" + export VARNAME_TWINE_USERNAME="PYUTILS_TWINE_USERNAME" + export VARNAME_TEST_TWINE_PASSWORD="PYUTILS_TEST_TWINE_PASSWORD" + export VARNAME_TEST_TWINE_USERNAME="PYUTILS_TEST_TWINE_USERNAME" + ' | python -c "import sys; from textwrap import dedent; print(dedent(sys.stdin.read()).strip(chr(10)))" > dev/secrets_configuration.sh + git add dev/secrets_configuration.sh + +} + + +upload_github_secrets(){ + load_secrets + unset GITHUB_TOKEN + gh auth login + source dev/secrets_configuration.sh + gh secret set $VARNAME_CI_SECRET -b"${!VARNAME_CI_SECRET}" + gh secret set $VARNAME_TWINE_USERNAME -b"${!VARNAME_TWINE_USERNAME}" + gh secret set $VARNAME_TWINE_PASSWORD -b"${!VARNAME_TWINE_PASSWORD}" + gh secret set $VARNAME_TEST_TWINE_PASSWORD -b"${!VARNAME_TEST_TWINE_PASSWORD}" + gh secret set $VARNAME_TEST_TWINE_USERNAME -b"${!VARNAME_TEST_TWINE_USERNAME}" +} + + +export_encrypted_code_signing_keys(){ + # You will need to rerun this whenever the signkeys expire and are renewed + + # Load or generate secrets + load_secrets + + source dev/secrets_configuration.sh + + CI_SECRET="${!VARNAME_CI_SECRET}" + echo "CI_SECRET=$CI_SECRET" + echo "GPG_IDENTIFIER=$GPG_IDENTIFIER" + + # ADD RELEVANT VARIABLES TO THE CI SECRET VARIABLES + + # HOW TO ENCRYPT YOUR SECRET GPG KEY + # You need to have a known public gpg key for this to make any sense + + MAIN_GPG_KEYID=$(gpg --list-keys --keyid-format LONG "$GPG_IDENTIFIER" | head -n 2 | tail -n 1 | awk '{print $1}') + GPG_SIGN_SUBKEY=$(gpg --list-keys --with-subkey-fingerprints "$GPG_IDENTIFIER" | grep "\[S\]" -A 1 | tail -n 1 | awk '{print $1}') + echo "MAIN_GPG_KEYID = $MAIN_GPG_KEYID" + echo "GPG_SIGN_SUBKEY = $GPG_SIGN_SUBKEY" + + # Only export the signing secret subkey + # Export plaintext gpg public keys, private sign key, and trust info + mkdir -p dev + gpg --armor --export-options export-backup --export-secret-subkeys "${GPG_SIGN_SUBKEY}!" > dev/ci_secret_gpg_subkeys.pgp + gpg --armor --export ${GPG_SIGN_SUBKEY} > dev/ci_public_gpg_key.pgp + gpg --export-ownertrust > dev/gpg_owner_trust + + # Encrypt gpg keys and trust with CI secret + GLKWS=$CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -e -a -in dev/ci_public_gpg_key.pgp > dev/ci_public_gpg_key.pgp.enc + GLKWS=$CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -e -a -in dev/ci_secret_gpg_subkeys.pgp > dev/ci_secret_gpg_subkeys.pgp.enc + GLKWS=$CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -e -a -in dev/gpg_owner_trust > dev/gpg_owner_trust.enc + echo $MAIN_GPG_KEYID > dev/public_gpg_key + + # Test decrpyt + GLKWS=$CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -d -a -in dev/ci_public_gpg_key.pgp.enc | gpg --list-packets --verbose + GLKWS=$CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -d -a -in dev/ci_secret_gpg_subkeys.pgp.enc | gpg --list-packets --verbose + GLKWS=$CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -d -a -in dev/gpg_owner_trust.enc | gpg --list-packets --verbose + cat dev/public_gpg_key + + unload_secrets + + # Look at what we did, clean up, and add it to git + ls dev/*.enc + rm dev/*.pgp + rm dev/gpg_owner_trust + git status + git add dev/*.enc + git add dev/gpg_owner_trust + git add dev/public_gpg_key +} + + +_test_gnu(){ + export GNUPGHOME=$(mktemp -d -t) + ls -al $GNUPGHOME + chmod 700 -R $GNUPGHOME + + source dev/secrets_configuration.sh + + gpg -k + + load_secrets + CI_SECRET="${!VARNAME_CI_SECRET}" + echo "CI_SECRET = $CI_SECRET" + + cat dev/public_gpg_key + GLKWS=$CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -d -a -in dev/ci_public_gpg_key.pgp.enc + GLKWS=$CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -d -a -in dev/gpg_owner_trust.enc + GLKWS=$CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -d -a -in dev/ci_secret_gpg_subkeys.pgp.enc + + GLKWS=$CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -d -a -in dev/ci_public_gpg_key.pgp.enc | gpg --import + GLKWS=$CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -d -a -in dev/gpg_owner_trust.enc | gpg --import-ownertrust + GLKWS=$CI_SECRET openssl enc -aes-256-cbc -pbkdf2 -md SHA512 -pass env:GLKWS -d -a -in dev/ci_secret_gpg_subkeys.pgp.enc | gpg --import + + gpg -k + # | gpg --import + # | gpg --list-packets --verbose +} diff --git a/dev/travis_yml_old b/dev/travis_yml_old new file mode 100644 index 0000000..cfc7452 --- /dev/null +++ b/dev/travis_yml_old @@ -0,0 +1,228 @@ + +env: + global: + - secure: "qb/0JHRxNNTmY0dmeiOMSjBN1fIzOuu8kEKu1UTijkZaBSQpL/fGLVactTNTSlEgXmygLv5DaR5dbaX68KdYruXw96TWOjXpUrfgSQ7GlExMgQJVmf4HTiGPjp94ek+YbgTw5stA1FmiIrt2/nHB/oeifo72zIRtpPZm4Az13odlEAZdXTlPs/KMb4GmUtmHtnotRWKO23OyfEbURgEFDLfmAdXpgtnpvDt87Iyw4QsCD/SFBN12sYPfDKdisaYN6qJ12jJrtjK60t+BnLFKUxm+97KNqoC3MuVskgiZzWBkOZR2r5h8Kft5+w2xjWAiTkf8J1JZk21IBDbeWRFOFGbrtE9hC1xfSnEYJYM2Y0LWR/8+Olw7KiOtD3bGjBmXV8lvir3qW75UWuhpiCnaXraE/ruWhoupJHBs7e6BUNUChCUW4/p5TiKw9qbyJ1gvMOHpaHxR4iEpGUAYdfu5gHjFGbzZDrC0R88FeI0lVDXCmcA5218XdPVkod9nYF+BlC2hJTDJkK2DLzq79SGAit0ZvI9DuowFaCVGzdGGGryDmCxo20L6iK1MTDo6XCUQh0L0Vk07pfqv8QtirSvR5bJlCYsywWfv1zTqBQEO0dT/D6VhL7jP1MV/JyWuLgcE53hvY8VXtiusYfQDJtLngJ1iQrjFxLR2fBbN7kZf1Cc=" + + - secure: "i8JHexqUL+t5vHh0tbq4ic+2q5swPN/hASX4liT0zplwXm93ku0ukWl14vstyPuJaxyCGAydHEKpMTMBf/kplpiIXZsHQgM0V3hsqRLbeeun5ZM6VtHSMXqdDKKSTKLObPujjfgVWAeHBDnrgx1Mnt1xm82aAZwyCKq/O/H2wqIVvR4lOchPUKIsXVlXwDdsYSeErDeHuY/rdVIB+BZM5KwWDggpTU1LWFFKhhsE+goD+OkXo5lzenrVGczIYzYWnpSDmtTCTRlBuz9dBDFXDORh1STIMEanJPhk/sHpsYH90cYLldTMsB3qAS3NH/cMS28If+qEe58uDD1P9aJnpcK8AAsAM03+qjUZdAm8Xi2wSFIPtt3r0GmKvzE75s8MIunj/5XDo0HSLrB276usnP0XrYbCf057FYCZnyn3XXPAQpXNz2g6vdCT1OqHVUJV1W/lnElJqZ5WjojeVD1HrM42aiRdsPw2VRVs5/INWD9KQ/ajKudGqZ9Tt0ZTaUcufDLCN+kg/2lycgmU5nA06JRFB6TKv+vAr1vTD7bGmn1fAdyZGEdGuFBjmPvfP6H14qoUzA9/p3+ZqMMx1z+ShiLqe/0j0nlFyWBs0qpw+Bk/FQBa1gGuPnAcMfsQdC34Kqb6T+bvvkp6ZtJYYwAgJHTDR0eJ7bf5y5A4Rkq8zXk=" + + - secure: "Ukgp7GN+CUDNDvi4cX6LEu++kbT35x9PLpVghW3GBtNjrAYzUzkdPNrYocizFiG/lfZRAoxiPgRvklPdN/IB2eJtn6e5mLJwzytG0YwGMSvDzrIOkXVBkkLUuOXN+q5XqPg6v1ceXSmZfvHpIB69hnO7gYCxQdAMY7AELmwhvzBcsl+kicR0bkumQI1bcnxorOrT0jNE03fh3MNFA3+4eLRS3agARYKnJzqgWAY+nBSXCtauGwXs+2CpbQFGSDObTNowJC1QbU70+SiGvm2RPAvlYGcPVAnYOKHPNhjUkgYVxBIkSDRIBGiU+bU4xmdS2rietPaOnyQLevQZH0fGr3pFQtFXlrI+pNQJkel1BNnm5/Z9zwFAonAGalZdq5JjrljohfFRtt3YKUcTfbcqkHIcUUT4xwb4ori8KrDexajnYijgp6vwJ6H2h+79qiiTSGezLyj0jX/fv/esASIfiRRAWZUk6ZD5VbOgeIj8mTGCutZob4nGCZ5J7+CtGJzExwUr95uXFEfNzcbWwTmLH4IoK/9+ToW6g7aVmqjcKmFoUqaEWtBQSQIdZQ/94OpPBpEYsnrH2GiChf9YkuyW1YrTFO7ZIbl+ofvCe4dYmUShZVMG2wXpPPm70nK8IjqV0NfTvPuJSpC8RW+HCC8nuDKL90qzHml4jmHvmcz4Ce4=" + +language: python + +cache: + apt: true + directories: + - $HOME/.cache/pip + - $HOME/download + - $HOME/.pip-cache + - $HOME/gpg_install_prefix + +python: + - "3.5" + - "3.6" + - "3.7" + - "3.8" + - "3.9" + - "nightly" + +jobs: + fast_finish: true + allow_failures: + - python: "nightly" + +before_install: + - pip install pip -U + - pip install -r requirements.txt + - docker pull quay.io/pypa/manylinux2010_x86_64:latest + +install: + - | + MB_PYTHON_TAG=$(python -c "import setup; print(setup.MB_PYTHON_TAG)") + VERSION=$(python -c "import setup; print(setup.VERSION)") + echo "VERSION = $VERSION" + echo "MB_PYTHON_TAG = $MB_PYTHON_TAG" + - MB_PYTHON_TAG=$MB_PYTHON_TAG ./run_manylinux_build.sh + - | + BDIST_WHEEL_PATH=$(ls dist/*-$VERSION-$MB_PYTHON_TAG*.whl) + echo "BDIST_WHEEL_PATH = $BDIST_WHEEL_PATH" + - pip install $BDIST_WHEEL_PATH + +script: + # Test the installed multibuild wheel + - travis_wait ./run_tests.py + +after_success: + - codecov + - gpg --version + - gpg2 --version + - export GPG_EXECUTABLE=gpg2 + - openssl version + - | + __heredoc__=''' + # Load or generate secrets + source $(secret_loader.sh) + echo $PYUTILS_TWINE_USERNAME + echo $PYUTILS_TWINE_PASSWORD + echo $PYUTILS_CI_GITHUB_SECRET + + + # In your repo directory run the command to ensure travis recognizes the repo + # It will say: Detected repository as /, is this correct? |yes| + # Answer yes before running the encrypt commands. + travis status + + # encrypt relevant travis variables (requires travis cli) + #sudo apt install ruby ruby-dev -y + #sudo gem install travis + SECURE_TWINE_USERNAME=$(travis encrypt --no-interactive PYUTILS_TWINE_USERNAME=$PYUTILS_TWINE_USERNAME) + SECURE_TWINE_PASSWORD=$(travis encrypt --no-interactive PYUTILS_TWINE_PASSWORD=$PYUTILS_TWINE_PASSWORD) + SECURE_CI_GITHUB_SECRET=$(travis encrypt --no-interactive PYUTILS_CI_GITHUB_SECRET=$PYUTILS_CI_GITHUB_SECRET) + echo " + Add the following lines to your .travis.yml + + env: + global: + - secure: $SECURE_TWINE_USERNAME + - secure: $SECURE_TWINE_PASSWORD + - secure: $SECURE_CI_GITHUB_SECRET + " + + # HOW TO ENCRYPT YOUR SECRET GPG KEY + IDENTIFIER=PyUtils + KEYID=$(gpg --list-keys --keyid-format LONG "$IDENTIFIER" | head -n 2 | tail -n 1 | awk '{print $1}' | tail -c 9) + echo "KEYID = $KEYID" + + # Export plaintext gpg public keys, private keys, and trust info + mkdir -p dev + gpg --armor --export-secret-keys $KEYID > dev/travis_secret_gpg_key.pgp + gpg --armor --export $KEYID > dev/travis_public_gpg_key.pgp + gpg --export-ownertrust > dev/gpg_owner_trust + + # Encrypt gpg keys and trust with travis secret + TSP=$PYUTILS_CI_GITHUB_SECRET openssl enc -aes-256-cbc -md MD5 -pass env:TSP -e -a -in dev/travis_public_gpg_key.pgp > dev/travis_public_gpg_key.pgp.enc + TSP=$PYUTILS_CI_GITHUB_SECRET openssl enc -aes-256-cbc -md MD5 -pass env:TSP -e -a -in dev/travis_secret_gpg_key.pgp > dev/travis_secret_gpg_key.pgp.enc + TSP=$PYUTILS_CI_GITHUB_SECRET openssl enc -aes-256-cbc -md MD5 -pass env:TSP -e -a -in dev/gpg_owner_trust > dev/gpg_owner_trust.enc + echo $KEYID > dev/public_gpg_key + + source $(secret_unloader.sh) + + # Look at what we did, clean up, and add it to git + ls dev/*.enc + rm dev/gpg_owner_trust dev/*.pgp + git status + git add dev/*.enc + git add dev/public_gpg_key + ''' # ' + - | + # Install a more recent version of GPG + # https://gnupg.org/download/ + export GPG_INSTALL_PREFIX=$HOME/gpg_install_prefix + export LD_LIBRARY_PATH=$GPG_INSTALL_PREFIX/lib:$LD_LIBRARY_PATH + export PATH=$GPG_INSTALL_PREFIX/bin:$PATH + export CPATH=$GPG_INSTALL_PREFIX/include:$CPATH + export GPG_EXECUTABLE=$GPG_INSTALL_PREFIX/bin/gpg + ls $GPG_INSTALL_PREFIX + ls $GPG_INSTALL_PREFIX/bin || echo "no bin" + if [[ ! -f "$GPG_INSTALL_PREFIX/bin/gpg" ]]; then + # try and have travis cache this + mkdir -p $GPG_INSTALL_PREFIX + echo $GPG_INSTALL_PREFIX + OLD=$(pwd) + cd $GPG_INSTALL_PREFIX + pip install ubelt + + ERROR_FPATH=$(python -c "import ubelt as ub; print(ub.grabdata( + 'https://gnupg.org/ftp/gcrypt/libgpg-error/libgpg-error-1.36.tar.bz2', + hash_prefix='6e5f853f77dc04f0091d94b224cab8e669042450f271b78d0ea0219', + dpath=ub.ensuredir('$HOME/.pip-cache'), verbose=0))") + GCRYPT_FPATH=$(python -c "import ubelt as ub; print(ub.grabdata( + 'https://gnupg.org/ftp/gcrypt/libgcrypt/libgcrypt-1.8.5.tar.bz2', + hash_prefix='b55e16e838d1b1208e7673366971ae7c0f9c1c79e042f41c03d1', + dpath=ub.ensuredir('$HOME/.pip-cache'), verbose=0))") + KSBA_CRYPT_FPATH=$(python -c "import ubelt as ub; print(ub.grabdata( + 'https://gnupg.org/ftp/gcrypt/libksba/libksba-1.3.5.tar.bz2', + hash_prefix='60179bfd109b7b4fd8d2b30a3216540f03f5a13620d9a5b63f1f95', + dpath=ub.ensuredir('$HOME/.pip-cache'), verbose=0))") + ASSUAN_FPATH=$(python -c "import ubelt as ub; print(ub.grabdata( + 'https://gnupg.org/ftp/gcrypt/libassuan/libassuan-2.5.3.tar.bz2', + hash_prefix='e7ccb651ea75b07b2e687d48d86d0ab83cba8e2af7f30da2aec', + dpath=ub.ensuredir('$HOME/.pip-cache'), verbose=0))") + NTBLTLS_FPATH=$(python -c "import ubelt as ub; print(ub.grabdata( + 'https://gnupg.org/ftp/gcrypt/ntbtls/ntbtls-0.1.2.tar.bz2', + hash_prefix='54468208359dc88155b14cba37773984d7d6f0f37c7a4ce13868d', + dpath=ub.ensuredir('$HOME/.pip-cache'), verbose=0))") + NPTH_FPATH=$(python -c "import ubelt as ub; print(ub.grabdata( + 'https://gnupg.org/ftp/gcrypt/npth/npth-1.6.tar.bz2', + hash_prefix='2ed1012e14a9d10665420b9a23628be7e206fd9348111ec751349b', + dpath=ub.ensuredir('$HOME/.pip-cache'), verbose=0))") + GPG_FPATH=$(python -c "import ubelt as ub; print(ub.grabdata( + 'https://gnupg.org/ftp/gcrypt/gnupg/gnupg-2.2.17.tar.bz2', + hash_prefix='a3cd094addac62b4b4ec1683005a2bec761ea2aacf6daf904316b', + dpath=ub.ensuredir('$HOME/.pip-cache'), verbose=0))") + + tar xjf $ERROR_FPATH + tar xjf $GCRYPT_FPATH + tar xjf $KSBA_CRYPT_FPATH + tar xjf $ASSUAN_FPATH + tar xjf $NTBLTLS_FPATH + tar xjf $NPTH_FPATH + tar xjf $GPG_FPATH + (cd libgpg-error-1.36 && ./configure --prefix=$GPG_INSTALL_PREFIX && make install) + (cd libgcrypt-1.8.5 && ./configure --prefix=$GPG_INSTALL_PREFIX && make install) + (cd libksba-1.3.5 && ./configure --prefix=$GPG_INSTALL_PREFIX && make install) + (cd libassuan-2.5.3 && ./configure --prefix=$GPG_INSTALL_PREFIX && make install) + (cd ntbtls-0.1.2 && ./configure --prefix=$GPG_INSTALL_PREFIX && make install) + (cd npth-1.6 && ./configure --prefix=$GPG_INSTALL_PREFIX && make install) + (cd gnupg-2.2.17 && ./configure --prefix=$GPG_INSTALL_PREFIX && make install) + echo "GPG_EXECUTABLE = '$GPG_EXECUTABLE'" + cd $OLD + fi + # Decrypt and import GPG Keys / trust + - $GPG_EXECUTABLE --version + - openssl version + - $GPG_EXECUTABLE --list-keys + - TSP=$PYUTILS_CI_GITHUB_SECRET openssl enc -aes-256-cbc -md MD5 -pass env:TSP -d -a -in dev/travis_public_gpg_key.pgp.enc | $GPG_EXECUTABLE --import + - TSP=$PYUTILS_CI_GITHUB_SECRET openssl enc -aes-256-cbc -md MD5 -pass env:TSP -d -a -in dev/gpg_owner_trust.enc | $GPG_EXECUTABLE --import-ownertrust + - TSP=$PYUTILS_CI_GITHUB_SECRET openssl enc -aes-256-cbc -md MD5 -pass env:TSP -d -a -in dev/travis_secret_gpg_key.pgp.enc | $GPG_EXECUTABLE --import + - $GPG_EXECUTABLE --list-keys + - MB_PYTHON_TAG=$(python -c "import setup; print(setup.MB_PYTHON_TAG)") + - VERSION=$(python -c "import setup; print(setup.VERSION)") + - | + pip install twine + if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then + pip install six pyopenssl ndg-httpsclient pyasn1 -U --user + pip install requests[security] twine --user + elfi + if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then + pip install six twine + pip install --upgrade pyOpenSSL + fi + # Package and publish to pypi (if on release) + - | + echo "TRAVIS_BRANCH = $TRAVIS_BRANCH" + + KEYID=$(cat dev/public_gpg_key) + echo "KEYID = '$KEYID'" + if [[ "$TRAVIS_BRANCH" == "release" ]]; then + # use set +x to log all intermediate commands + export CURRENT_BRANCH=$TRAVIS_BRANCH + # TODO: reliable and secure gpg keys + # Relies on a specific environmenmt being available + # git config --global user.signingkey D297D757 + # git config --local user.signingkey D297D757 + # git config --global gpg.program + # + TAG_AND_UPLOAD=yes + else + TAG_AND_UPLOAD=no + fi + MB_PYTHON_TAG=$MB_PYTHON_TAG \ + USE_GPG=True \ + GPG_KEYID=$KEYID \ + CURRENT_BRANCH=$TRAVIS_BRANCH \ + TWINE_PASSWORD=$PYUTILS_TWINE_PASSWORD \ + TWINE_USERNAME=$PYUTILS_TWINE_USERNAME \ + GPG_EXECUTABLE=$GPG_EXECUTABLE \ + DEPLOY_BRANCH=release \ + TAG_AND_UPLOAD=$TAG_AND_UPLOAD \ + ./publish.sh diff --git a/dev_requirements.txt b/dev_requirements.txt deleted file mode 100644 index 3756799..0000000 --- a/dev_requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -Cython -IPython>=0.13 \ No newline at end of file diff --git a/kernprof.py b/kernprof.py index 108d36e..b740fc9 100755 --- a/kernprof.py +++ b/kernprof.py @@ -4,9 +4,13 @@ """ import functools -import optparse import os import sys +from argparse import ArgumentError, ArgumentParser + +# NOTE: This version needs to be manually maintained with the line_profiler +# __version__ for now. +__version__ = '3.3.1' PY3 = sys.version_info[0] == 3 @@ -94,6 +98,8 @@ def wrapper(*args, **kwds): self.enable_by_count() try: item = next(g) + except StopIteration: + return finally: self.disable_by_count() input = (yield item) @@ -102,6 +108,8 @@ def wrapper(*args, **kwds): self.enable_by_count() try: item = g.send(input) + except StopIteration: + return finally: self.disable_by_count() input = (yield item) @@ -147,41 +155,45 @@ def find_script(script_name): def main(args=None): - if args is None: - args = sys.argv - usage = "%prog [-s setupfile] [-o output_file_path] scriptfile [arg] ..." - parser = optparse.OptionParser(usage=usage, version="%prog 1.0b2") - parser.allow_interspersed_args = False - parser.add_option('-l', '--line-by-line', action='store_true', - help="Use the line-by-line profiler from the line_profiler module " - "instead of Profile. Implies --builtin.") - parser.add_option('-b', '--builtin', action='store_true', - help="Put 'profile' in the builtins. Use 'profile.enable()' and " - "'profile.disable()' in your code to turn it on and off, or " - "'@profile' to decorate a single function, or 'with profile:' " - "to profile a single section of code.") - parser.add_option('-o', '--outfile', default=None, - help="Save stats to ") - parser.add_option('-s', '--setup', default=None, + def positive_float(value): + val = float(value) + if val <= 0: + raise ArgumentError + return val + + parser = ArgumentParser(description="Run and profile a python script.") + parser.add_argument('-V', '--version', action='version', version=__version__) + parser.add_argument('-l', '--line-by-line', action='store_true', + help="Use the line-by-line profiler instead of cProfile. Implies --builtin.") + parser.add_argument('-b', '--builtin', action='store_true', + help="Put 'profile' in the builtins. Use 'profile.enable()'/'.disable()', " + "'@profile' to decorate functions, or 'with profile:' to profile a " + "section of code.") + parser.add_argument('-o', '--outfile', + help="Save stats to (default: 'scriptname.lprof' with " + "--line-by-line, 'scriptname.prof' without)") + parser.add_argument('-s', '--setup', help="Code to execute before the code to profile") - parser.add_option('-v', '--view', action='store_true', - help="View the results of the profile in addition to saving it.") + parser.add_argument('-v', '--view', action='store_true', + help="View the results of the profile in addition to saving it") + parser.add_argument('-u', '--unit', default='1e-6', type=positive_float, + + help="Output unit (in seconds) in which the timing info is " + "displayed (default: 1e-6)") + parser.add_argument('-z', '--skip-zero', action='store_true', + help="Hide functions which have not been called") - if not sys.argv[1:]: - parser.print_usage() - sys.exit(2) + parser.add_argument('script', help="The python script file to run") + parser.add_argument('args', nargs='...', help="Optional script arguments") - options, args = parser.parse_args() + options = parser.parse_args(args) if not options.outfile: - if options.line_by_line: - extension = 'lprof' - else: - extension = 'prof' - options.outfile = '%s.%s' % (os.path.basename(args[0]), extension) + extension = 'lprof' if options.line_by_line else 'prof' + options.outfile = '%s.%s' % (os.path.basename(options.script), extension) - sys.argv[:] = args + sys.argv = [options.script] + options.args if options.setup is not None: # Run some setup code outside of the profiler. This is good for large # imports. @@ -207,7 +219,7 @@ def main(args=None): import __builtin__ as builtins builtins.__dict__['profile'] = prof - script_file = find_script(sys.argv[0]) + script_file = find_script(options.script) __file__ = script_file __name__ = '__main__' # Make sure the script's directory is on sys.path instead of just @@ -228,7 +240,12 @@ def main(args=None): prof.dump_stats(options.outfile) print('Wrote profile results to %s' % options.outfile) if options.view: - prof.print_stats() + if isinstance(prof, ContextualProfile): + prof.print_stats() + else: + prof.print_stats(output_unit=options.unit, + stripzeros=options.skip_zero) + if __name__ == '__main__': - sys.exit(main(sys.argv)) + main(sys.argv[1:]) diff --git a/line_profiler/CMakeLists.txt b/line_profiler/CMakeLists.txt new file mode 100644 index 0000000..a4192ad --- /dev/null +++ b/line_profiler/CMakeLists.txt @@ -0,0 +1,36 @@ +set(cython_source + "${CMAKE_CURRENT_SOURCE_DIR}/_line_profiler.pyx" + "${CMAKE_CURRENT_SOURCE_DIR}/python25.pxd" +) +set(module_name "_line_profiler") + +# Translate Cython into C/C++ +add_cython_target(${module_name} "${cython_source}" C OUTPUT_VAR sources) + +# Add any other non-cython dependencies to the sources +list(APPEND sources + "${CMAKE_CURRENT_SOURCE_DIR}/unset_trace.c" + "${CMAKE_CURRENT_SOURCE_DIR}/timers.c" +) +message(STATUS "[OURS] sources = ${sources}") + +# Create C++ library. Specify include dirs and link libs as normal +add_library(${module_name} MODULE ${sources}) +target_include_directories(${module_name} PUBLIC + ${PYTHON_INCLUDE_DIRS} + ${CMAKE_CURRENT_SOURCE_DIR} # for the pure c files defined here +) + +# Transform the C++ library into an importable python module +python_extension_module(${module_name}) + +# Install the C++ module to the correct relative location +# (this will be an inplace build if you use `pip install -e`) +#file(RELATIVE_PATH _install_dest "${CMAKE_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}") +#set(_install_dest ".") + +#message(STATUS "_install_dest = ${_install_dest}") +#install(TARGETS ${module_name} LIBRARY DESTINATION "${_install_dest}") +file(RELATIVE_PATH _install_dest "${CMAKE_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}") +message(STATUS "[OURS] _install_dest = ${_install_dest}") +install(TARGETS ${module_name} LIBRARY DESTINATION "${_install_dest}/") diff --git a/line_profiler/__init__.py b/line_profiler/__init__.py new file mode 100644 index 0000000..d60f68c --- /dev/null +++ b/line_profiler/__init__.py @@ -0,0 +1,18 @@ +""" +mkinit ~/code/line_profiler/line_profiler/__init__.py --relative +mkinit ~/code/line_profiler/line_profiler/__init__.py --relative -w +""" + +__submodules__ = [ + 'line_profiler', +] + +from .line_profiler import __version__ + +from .line_profiler import (LineProfiler, LineProfilerMagics, + load_ipython_extension, load_stats, main, + show_func, show_text,) + +__all__ = ['LineProfiler', 'LineProfilerMagics', 'line_profiler', + 'load_ipython_extension', 'load_stats', 'main', 'show_func', + 'show_text', '__version__'] diff --git a/line_profiler/__main__.py b/line_profiler/__main__.py new file mode 100644 index 0000000..c626c20 --- /dev/null +++ b/line_profiler/__main__.py @@ -0,0 +1,4 @@ +from .line_profiler import main + +if __name__ == '__main__': + main() diff --git a/_line_profiler.pyx b/line_profiler/_line_profiler.pyx similarity index 99% rename from _line_profiler.pyx rename to line_profiler/_line_profiler.pyx index 4798d32..72b8eaa 100644 --- a/_line_profiler.pyx +++ b/line_profiler/_line_profiler.pyx @@ -1,4 +1,4 @@ -from python25 cimport PyFrameObject, PyObject, PyStringObject +from .python25 cimport PyFrameObject, PyObject, PyStringObject cdef extern from "frameobject.h": diff --git a/line_profiler.py b/line_profiler/line_profiler.py similarity index 83% rename from line_profiler.py rename to line_profiler/line_profiler.py index a481dd2..5c1044d 100755 --- a/line_profiler.py +++ b/line_profiler/line_profiler.py @@ -1,6 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from __future__ import print_function +from __future__ import absolute_import, division, print_function + try: import cPickle as pickle except ImportError: @@ -13,16 +14,25 @@ import functools import inspect import linecache -import optparse +import tempfile import os import sys +from argparse import ArgumentError, ArgumentParser from IPython.core.magic import (Magics, magics_class, line_magic) from IPython.core.page import page from IPython.utils.ipstruct import Struct from IPython.core.error import UsageError -from _line_profiler import LineProfiler as CLineProfiler +try: + from ._line_profiler import LineProfiler as CLineProfiler +except ImportError as ex: + raise ImportError( + 'The line_profiler._line_profiler c-extension is not importable. ' + 'Has it been compiled? Underlying error is ex={!r}'.format(ex) + ) + +__version__ = '3.3.1' # Python 2/3 compatibility utils # =========================================================== @@ -57,6 +67,14 @@ def is_coroutine(f): # ============================================================ +def format_time(t): + if t >= 1000000: + return u"%.1f s " % (t / 1000000.0) + elif t >= 1000: + return u"%.1f ms" % (t / 1000.0) + else: + return u"%.1f µs" % t + CO_GENERATOR = 0x0020 def is_generator(f): """ Return True if a function is a generator. @@ -92,6 +110,8 @@ def wrapper(*args, **kwds): self.enable_by_count() try: item = next(g) + except StopIteration: + return finally: self.disable_by_count() input = (yield item) @@ -100,6 +120,8 @@ def wrapper(*args, **kwds): self.enable_by_count() try: item = g.send(input) + except StopIteration: + return finally: self.disable_by_count() input = (yield item) @@ -119,7 +141,7 @@ def wrapper(*args, **kwds): return wrapper if PY35: - import line_profiler_py35 + from . import line_profiler_py35 wrap_coroutine = line_profiler_py35.wrap_coroutine def dump_stats(self, filename): @@ -181,6 +203,16 @@ def add_module(self, mod): return nfuncsadded +def is_ipython_kernel_cell(filename): + """ Return True if a filename corresponds to a Jupyter Notebook cell + """ + return ( + filename.startswith(" #else @@ -48,11 +44,7 @@ hpTimer(void) { struct timeval tv; PY_LONG_LONG ret; -#ifdef GETTIMEOFDAY_NO_TZ - gettimeofday(&tv); -#else gettimeofday(&tv, (struct timezone *)NULL); -#endif ret = tv.tv_sec; ret = ret * 1000000 + tv.tv_usec; return ret; diff --git a/timers.h b/line_profiler/timers.h similarity index 100% rename from timers.h rename to line_profiler/timers.h diff --git a/unset_trace.c b/line_profiler/unset_trace.c similarity index 100% rename from unset_trace.c rename to line_profiler/unset_trace.c diff --git a/unset_trace.h b/line_profiler/unset_trace.h similarity index 100% rename from unset_trace.h rename to line_profiler/unset_trace.h diff --git a/publish.sh b/publish.sh new file mode 100755 index 0000000..bb19817 --- /dev/null +++ b/publish.sh @@ -0,0 +1,405 @@ +#!/bin/bash +__heredoc__=''' +Script to publish a new version of this library on PyPI. + +If your script has binary dependencies then we assume that you have built a +proper binary wheel with auditwheel and it exists in the wheelhouse directory. +Otherwise, for source tarballs and universal wheels this script runs the +setup.py script to create the wheels as well. + +Running this script with the default arguments will perform any builds and gpg +signing, but nothing will be uploaded to pypi unless the user explicitly sets +DO_UPLOAD=True or answers yes to the prompts. + +Args: + # These environment variables must / should be set + TWINE_USERNAME : username for pypi + TWINE_PASSWORD : password for pypi + DO_GPG : defaults to True + +Requirements: + twine >= 1.13.0 + gpg2 >= 2.2.4 + OpenSSL >= 1.1.1c + +Notes: + # NEW API TO UPLOAD TO PYPI + # https://docs.travis-ci.com/user/deployment/pypi/ + # https://packaging.python.org/tutorials/distributing-packages/ + # https://stackoverflow.com/questions/45188811/how-to-gpg-sign-a-file-that-is-built-by-travis-ci + +Usage: + cd + + # Set your variables or load your secrets + export TWINE_USERNAME= + export TWINE_PASSWORD= + TWINE_REPOSITORY_URL="https://test.pypi.org/legacy/" + + source $(secret_loader.sh) + + MB_PYTHON_TAG=cp38-cp38m + MB_PYTHON_TAG=cp37-cp37m + MB_PYTHON_TAG=cp36-cp36m + MB_PYTHON_TAG=cp35-cp35m + MB_PYTHON_TAG=cp27-cp27mu + + echo "MB_PYTHON_TAG = $MB_PYTHON_TAG" + MB_PYTHON_TAG=$MB_PYTHON_TAG ./run_multibuild.sh + DEPLOY_REMOTE=ibeis MB_PYTHON_TAG=$MB_PYTHON_TAG ./publish.sh yes + + MB_PYTHON_TAG=py3-none-any ./publish.sh +''' + +check_variable(){ + KEY=$1 + HIDE=$2 + VAL=${!KEY} + if [[ "$HIDE" == "" ]]; then + echo "[DEBUG] CHECK VARIABLE: $KEY=\"$VAL\"" + else + echo "[DEBUG] CHECK VARIABLE: $KEY=" + fi + if [[ "$VAL" == "" ]]; then + echo "[ERROR] UNSET VARIABLE: $KEY=\"$VAL\"" + exit 1; + fi +} + + +normalize_boolean(){ + ARG=$1 + ARG=$(echo "$ARG" | awk '{print tolower($0)}') + if [ "$ARG" = "true" ] || [ "$ARG" = "1" ] || [ "$ARG" = "yes" ] || [ "$ARG" = "on" ]; then + echo "True" + elif [ "$ARG" = "false" ] || [ "$ARG" = "0" ] || [ "$ARG" = "no" ] || [ "$ARG" = "off" ]; then + echo "False" + else + echo "$ARG" + fi +} + +# Options +DEPLOY_REMOTE=${DEPLOY_REMOTE:=origin} +NAME=${NAME:=$(python -c "import setup; print(setup.NAME)")} +VERSION=$(python -c "import setup; print(setup.VERSION)") +MB_PYTHON_TAG=${MB_PYTHON_TAG:=py3-none-any} + +# The default should change depending on the application +#DEFAULT_MODE_LIST=("sdist" "universal" "bdist") +#DEFAULT_MODE_LIST=("sdist" "native" "universal") +DEFAULT_MODE_LIST=("sdist" "bdist") + +check_variable DEPLOY_REMOTE + +ARG_1=$1 + +DO_UPLOAD=${DO_UPLOAD:=$ARG_1} +DO_TAG=${DO_TAG:=$ARG_1} +DO_GPG=${DO_GPG:="auto"} +DO_BUILD=${DO_BUILD:="auto"} + +DO_GPG=$(normalize_boolean "$DO_GPG") +DO_BUILD=$(normalize_boolean "$DO_BUILD") +DO_UPLOAD=$(normalize_boolean "$DO_UPLOAD") +DO_TAG=$(normalize_boolean "$DO_TAG") + +TWINE_USERNAME=${TWINE_USERNAME:=""} +TWINE_PASSWORD=${TWINE_PASSWORD:=""} + +if [[ "$(cat .git/HEAD)" != "ref: refs/heads/release" ]]; then + # If we are not on release, then default to the test pypi upload repo + TWINE_REPOSITORY_URL=${TWINE_REPOSITORY_URL:="https://test.pypi.org/legacy/"} +else + TWINE_REPOSITORY_URL=${TWINE_REPOSITORY_URL:="https://upload.pypi.org/legacy/"} +fi + +if [[ "$(which gpg2)" != "" ]]; then + GPG_EXECUTABLE=${GPG_EXECUTABLE:=gpg2} +else + GPG_EXECUTABLE=${GPG_EXECUTABLE:=gpg} +fi + +GPG_KEYID=${GPG_KEYID:=$(git config --local user.signingkey)} +GPG_KEYID=${GPG_KEYID:=$(git config --global user.signingkey)} + +WAS_INTERACTION="False" + + +echo " +=== PYPI BUILDING SCRIPT == +VERSION='$VERSION' +TWINE_USERNAME='$TWINE_USERNAME' +TWINE_REPOSITORY_URL = $TWINE_REPOSITORY_URL +GPG_KEYID = '$GPG_KEYID' +MB_PYTHON_TAG = '$MB_PYTHON_TAG' + +DO_UPLOAD=${DO_UPLOAD} +DO_TAG=${DO_TAG} +DO_GPG=${DO_GPG} +DO_BUILD=${DO_BUILD} +" + + +# Verify that we want to tag +if [[ "$DO_TAG" == "True" ]]; then + echo "About to tag VERSION='$VERSION'" +else + if [[ "$DO_TAG" == "False" ]]; then + echo "We are NOT about to tag VERSION='$VERSION'" + else + read -p "Do you want to git tag and push version='$VERSION'? (input 'yes' to confirm)" ANS + echo "ANS = $ANS" + WAS_INTERACTION="True" + DO_TAG="$ANS" + DO_TAG=$(normalize_boolean "$DO_TAG") + if [ "$DO_BUILD" == "auto" ]; then + DO_BUILD="" + DO_GPG="" + fi + fi +fi + + +# Verify that we want to build +if [ "$DO_BUILD" == "auto" ]; then + DO_BUILD="True" +fi +# Verify that we want to build +if [ "$DO_GPG" == "auto" ]; then + DO_GPG="True" +fi + +if [[ "$DO_BUILD" == "True" ]]; then + echo "About to build wheels" +else + if [[ "$DO_UPLOAD" == "False" ]]; then + echo "We are NOT about to build wheels" + else + read -p "Do you need to build wheels? (input 'yes' to confirm)" ANS + echo "ANS = $ANS" + WAS_INTERACTION="True" + DO_BUILD="$ANS" + DO_BUILD=$(normalize_boolean "$DO_BUILD") + fi +fi + + +# Verify that we want to publish +if [[ "$DO_UPLOAD" == "True" ]]; then + echo "About to directly publish VERSION='$VERSION'" +else + if [[ "$DO_UPLOAD" == "False" ]]; then + echo "We are NOT about to directly publish VERSION='$VERSION'" + else + read -p "Are you ready to directly publish version='$VERSION'? ('yes' will twine upload)" ANS + echo "ANS = $ANS" + WAS_INTERACTION="True" + DO_UPLOAD="$ANS" + DO_UPLOAD=$(normalize_boolean "$DO_UPLOAD") + fi +fi + + +if [[ "$WAS_INTERACTION" == "True" ]]; then + echo " + === PYPI BUILDING SCRIPT == + VERSION='$VERSION' + TWINE_USERNAME='$TWINE_USERNAME' + TWINE_REPOSITORY_URL = $TWINE_REPOSITORY_URL + GPG_KEYID = '$GPG_KEYID' + MB_PYTHON_TAG = '$MB_PYTHON_TAG' + + DO_UPLOAD=${DO_UPLOAD} + DO_TAG=${DO_TAG} + DO_GPG=${DO_GPG} + DO_BUILD=${DO_BUILD} + " + read -p "Look good? Ready? Enter any text to continue" ANS +fi + + + +MODE=${MODE:=all} + +if [[ "$MODE" == "all" ]]; then + MODE_LIST=("${DEFAULT_MODE_LIST[@]}") +else + MODE_LIST=("$MODE") +fi + +MODE_LIST_STR=$(printf '"%s" ' "${MODE_LIST[@]}") + + + +if [ "$DO_BUILD" == "True" ]; then + + echo " + === === + " + + echo "LIVE BUILDING" + # Build wheel and source distribution + + #WHEEL_PATHS=() + for _MODE in "${MODE_LIST[@]}" + do + echo "_MODE = $_MODE" + if [[ "$_MODE" == "sdist" ]]; then + python setup.py sdist || { echo 'failed to build sdist wheel' ; exit 1; } + WHEEL_PATH=$(ls dist/$NAME-$VERSION*.tar.gz) + #WHEEL_PATHS+=($WHEEL_PATH) + elif [[ "$_MODE" == "native" ]]; then + python setup.py bdist_wheel || { echo 'failed to build native wheel' ; exit 1; } + WHEEL_PATH=$(ls dist/$NAME-$VERSION*.whl) + #WHEEL_PATHS+=($WHEEL_PATH) + elif [[ "$_MODE" == "universal" ]]; then + python setup.py bdist_wheel --universal || { echo 'failed to build universal wheel' ; exit 1; } + UNIVERSAL_TAG="py3-none-any" + WHEEL_PATH=$(ls dist/$NAME-$VERSION-$UNIVERSAL_TAG*.whl) + #WHEEL_PATHS+=($WHEEL_PATH) + elif [[ "$_MODE" == "bdist" ]]; then + echo "Assume wheel has already been built" + WHEEL_PATH=$(ls wheelhouse/$NAME-$VERSION-$MB_PYTHON_TAG*.whl) + #WHEEL_PATHS+=($WHEEL_PATH) + else + echo "bad mode" + exit 1 + fi + echo "WHEEL_PATH = $WHEEL_PATH" + done + + echo " + === === + " + +else + echo "DO_BUILD=False, Skipping build" +fi + + +WHEEL_PATHS=() +for _MODE in "${MODE_LIST[@]}" +do + echo "_MODE = $_MODE" + if [[ "$_MODE" == "sdist" ]]; then + WHEEL_PATH=$(ls dist/$NAME-$VERSION*.tar.gz) + WHEEL_PATHS+=($WHEEL_PATH) + elif [[ "$_MODE" == "native" ]]; then + WHEEL_PATH=$(ls dist/$NAME-$VERSION*.whl) + WHEEL_PATHS+=($WHEEL_PATH) + elif [[ "$_MODE" == "universal" ]]; then + UNIVERSAL_TAG="py3-none-any" + WHEEL_PATH=$(ls dist/$NAME-$VERSION-$UNIVERSAL_TAG*.whl) + WHEEL_PATHS+=($WHEEL_PATH) + elif [[ "$_MODE" == "bdist" ]]; then + WHEEL_PATH=$(ls wheelhouse/$NAME-$VERSION-$MB_PYTHON_TAG*.whl) + WHEEL_PATHS+=($WHEEL_PATH) + else + echo "bad mode" + exit 1 + fi + echo "WHEEL_PATH = $WHEEL_PATH" +done + +WHEEL_PATHS_STR=$(printf '"%s" ' "${WHEEL_PATHS[@]}") + +echo " +MODE=$MODE +VERSION='$VERSION' +WHEEL_PATHS='$WHEEL_PATHS_STR' +" + + + +if [ "$DO_GPG" == "True" ]; then + + echo " + === === + " + + for WHEEL_PATH in "${WHEEL_PATHS[@]}" + do + echo "WHEEL_PATH = $WHEEL_PATH" + check_variable WHEEL_PATH + # https://stackoverflow.com/questions/45188811/how-to-gpg-sign-a-file-that-is-built-by-travis-ci + # secure gpg --export-secret-keys > all.gpg + + # REQUIRES GPG >= 2.2 + check_variable GPG_EXECUTABLE || { echo 'failed no gpg exe' ; exit 1; } + check_variable GPG_KEYID || { echo 'failed no gpg key' ; exit 1; } + + echo "Signing wheels" + GPG_SIGN_CMD="$GPG_EXECUTABLE --batch --yes --detach-sign --armor --local-user $GPG_KEYID" + echo "GPG_SIGN_CMD = $GPG_SIGN_CMD" + $GPG_SIGN_CMD --output $WHEEL_PATH.asc $WHEEL_PATH + + echo "Checking wheels" + twine check $WHEEL_PATH.asc $WHEEL_PATH || { echo 'could not check wheels' ; exit 1; } + + echo "Verifying wheels" + $GPG_EXECUTABLE --verify $WHEEL_PATH.asc $WHEEL_PATH || { echo 'could not verify wheels' ; exit 1; } + done + echo " + === === + " +else + echo "DO_GPG=False, Skipping GPG sign" +fi + + +if [[ "$DO_TAG" == "True" ]]; then + TAG_NAME="v${VERSION}" + # if we messed up we can delete the tag + # git push origin :refs/tags/$TAG_NAME + # and then tag with -f + # + git tag $TAG_NAME -m "tarball tag $VERSION" + git push --tags $DEPLOY_REMOTE + echo "Should also do a: git push $DEPLOY_REMOTE main:release" + echo "For github should draft a new release: https://github.com/PyUtils/line_profiler/releases/new" +else + echo "Not tagging" +fi + + +if [[ "$DO_UPLOAD" == "True" ]]; then + check_variable TWINE_USERNAME + check_variable TWINE_PASSWORD "hide" + + for WHEEL_PATH in "${WHEEL_PATHS[@]}" + do + if [ "$DO_GPG" == "True" ]; then + twine upload --username $TWINE_USERNAME --password=$TWINE_PASSWORD \ + --repository-url $TWINE_REPOSITORY_URL \ + --sign $WHEEL_PATH.asc $WHEEL_PATH --skip-existing --verbose || { echo 'failed to twine upload' ; exit 1; } + else + twine upload --username $TWINE_USERNAME --password=$TWINE_PASSWORD \ + --repository-url $TWINE_REPOSITORY_URL \ + $WHEEL_PATH --skip-existing --verbose || { echo 'failed to twine upload' ; exit 1; } + fi + done + echo """ + !!! FINISH: LIVE RUN !!! + """ +else + echo """ + DRY RUN ... Skiping upload + + DEPLOY_REMOTE = '$DEPLOY_REMOTE' + DO_UPLOAD = '$DO_UPLOAD' + WHEEL_PATH = '$WHEEL_PATH' + WHEEL_PATHS_STR = '$WHEEL_PATHS_STR' + MODE_LIST_STR = '$MODE_LIST_STR' + + VERSION='$VERSION' + NAME='$NAME' + TWINE_USERNAME='$TWINE_USERNAME' + GPG_KEYID = '$GPG_KEYID' + MB_PYTHON_TAG = '$MB_PYTHON_TAG' + + To do live run set DO_UPLOAD=1 and ensure deploy and current branch are the same + + !!! FINISH: DRY RUN !!! + """ +fi diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..7026acf --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,30 @@ +[build-system] +requires = ["setuptools>=41.0.1", "wheel", "Cython", "scikit-build>=0.9.0", "cmake", "ninja"] +# build-backend = "setuptools.build_meta" commented out to disable pep517 +### build-backend = "scikit-build" + + +[tool.coverage.run] +branch = true + +[tool.coverage.report] +exclude_lines =[ + "pragma: no cover", + ".* # pragma: no cover", + ".* # nocover", + "def __repr__", + "raise AssertionError", + "raise NotImplementedError", + "if 0:", + "if trace is not None", + "verbose = .*", + "^ *raise", + "^ *pass *$", + "if _debug:", + "if __name__ == .__main__.:", + ".*if six.PY2:" +] + +omit =[ + "*/setup.py" +] diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..17fa364 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +-r requirements/runtime.txt +-r requirements/build.txt +-r requirements/tests.txt diff --git a/requirements/build.txt b/requirements/build.txt new file mode 100644 index 0000000..1a23eea --- /dev/null +++ b/requirements/build.txt @@ -0,0 +1,4 @@ +Cython +scikit-build +cmake +ninja diff --git a/requirements/runtime.txt b/requirements/runtime.txt new file mode 100644 index 0000000..bec6588 --- /dev/null +++ b/requirements/runtime.txt @@ -0,0 +1,2 @@ +IPython >=0.13 ; python_version >= '3.7' +IPython >=0.13, <7.17.0 ; python_version <= '3.6' diff --git a/requirements/tests.txt b/requirements/tests.txt new file mode 100644 index 0000000..c939d58 --- /dev/null +++ b/requirements/tests.txt @@ -0,0 +1,4 @@ +pytest >= 4.6.11 +pytest-cov >= 2.10.1 +coverage[toml] >= 5.3 +ubelt >= 0.8.7 diff --git a/run_manylinux_build.sh b/run_manylinux_build.sh new file mode 100755 index 0000000..3ead042 --- /dev/null +++ b/run_manylinux_build.sh @@ -0,0 +1,121 @@ +#!/bin/bash +__heredoc__=""" + + +notes: + + Manylinux repo: https://github.com/pypa/manylinux + + Win + Osx repo: https://github.com/mavlink/MAVSDK-Python + + # TODO: use dind as the base image, + # Then run the multibuild in docker followed by a test in a different + # docker container + + # BETTER TODO: + # Use a build stage to build in the multilinux environment and then + # use a test stage with a different image to test and deploy the wheel + docker run --rm -it --entrypoint="" docker:dind sh + docker run --rm -it --entrypoint="" docker:latest sh + docker run --rm -v $PWD:/io -it --entrypoint="" docker:latest sh + + docker run --rm -v $PWD:/io -it python:2.7 bash + + cd /io + pip install -r requirements.txt + pip install pygments + pip install wheelhouse/pyflann_ibeis-0.5.0-cp27-cp27mu-manylinux1_x86_64.whl + + cd / + xdoctest pyflann_ibeis + pytest io/tests + + cd /io + python run_tests.py + + +MB_PYTHON_TAG=cp38-cp38 ./run_manylinux_build.sh +MB_PYTHON_TAG=cp37-cp37m ./run_manylinux_build.sh +MB_PYTHON_TAG=cp36-cp36m ./run_manylinux_build.sh +MB_PYTHON_TAG=cp35-cp35m ./run_manylinux_build.sh +MB_PYTHON_TAG=cp27-cp27m ./run_manylinux_build.sh + +# MB_PYTHON_TAG=cp27-cp27mu ./run_nmultibuild.sh + +docker pull quay.io/erotemic/manylinux-opencv:manylinux1_i686-opencv4.1.0-py3.6 +docker pull quay.io/pypa/manylinux2010_x86_64:latest + +""" + + +#DOCKER_IMAGE=${DOCKER_IMAGE:="quay.io/erotemic/manylinux-for:x86_64-opencv4.1.0-v2"} +DOCKER_IMAGE=${DOCKER_IMAGE:="quay.io/pypa/manylinux2010_x86_64:latest"} +# Valid multibuild python versions are: +# cp27-cp27m cp27-cp27mu cp34-cp34m cp35-cp35m cp36-cp36m cp37-cp37m, cp38-cp38m +MB_PYTHON_TAG=${MB_PYTHON_TAG:=$(python -c "import setup; print(setup.native_mb_python_tag())")} +NAME=${NAME:=$(python -c "import setup; print(setup.NAME)")} +VERSION=${VERSION:=$(python -c "import setup; print(setup.VERSION)")} +REPO_ROOT=${REPO_ROOT:=/io} +echo " +MB_PYTHON_TAG = $MB_PYTHON_TAG +VERSION = $VERSION +NAME = $NAME +_INSIDE_DOCKER = $_INSIDE_DOCKER +DOCKER_IMAGE = $DOCKER_IMAGE +" + +if [ "$_INSIDE_DOCKER" != "YES" ]; then + + set -e + docker run --rm \ + -v $PWD:/io \ + -e _INSIDE_DOCKER="YES" \ + -e NAME="$NAME" \ + -e VERSION="$VERSION" \ + -e MB_PYTHON_TAG="$MB_PYTHON_TAG" \ + -e WHEEL_NAME_HACK="$WHEEL_NAME_HACK" \ + $DOCKER_IMAGE bash -c 'cd /io && ./run_manylinux_build.sh' + + __interactive__=''' + docker run --rm \ + -v $PWD:/io \ + -e _INSIDE_DOCKER="YES" \ + -e NAME="$NAME" \ + -e VERSION="$VERSION" \ + -e MB_PYTHON_TAG="$MB_PYTHON_TAG" \ + -e WHEEL_NAME_HACK="$WHEEL_NAME_HACK" \ + -it $DOCKER_IMAGE bash + + set +e + set +x + ''' + + ls -al wheelhouse + BDIST_WHEEL_PATH=$(ls wheelhouse/$NAME-$VERSION-$MB_PYTHON_TAG*.whl) + echo "BDIST_WHEEL_PATH = $BDIST_WHEEL_PATH" +else + set -x + set -e + + VENV_DIR=/root/venv-$MB_PYTHON_TAG + + # Setup a virtual environment for the target python version + /opt/python/$MB_PYTHON_TAG/bin/python -m pip install pip + /opt/python/$MB_PYTHON_TAG/bin/python -m pip install setuptools pip virtualenv scikit-build cmake ninja ubelt wheel + /opt/python/$MB_PYTHON_TAG/bin/python -m virtualenv $VENV_DIR + + source $VENV_DIR/bin/activate + + cd $REPO_ROOT + pip install -r requirements/build.txt + python setup.py bdist_wheel + + chmod -R o+rw _skbuild + chmod -R o+rw dist + + /opt/python/cp37-cp37m/bin/python -m pip install auditwheel + /opt/python/cp37-cp37m/bin/python -m auditwheel show dist/$NAME-$VERSION-$MB_PYTHON_TAG*.whl + /opt/python/cp37-cp37m/bin/python -m auditwheel repair dist/$NAME-$VERSION-$MB_PYTHON_TAG*.whl + chmod -R o+rw wheelhouse + chmod -R o+rw $NAME.egg-info +fi diff --git a/run_tests.py b/run_tests.py new file mode 100755 index 0000000..84f5ef2 --- /dev/null +++ b/run_tests.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +from os.path import dirname, join, abspath +import sqlite3 +import sys +import os +import re + +def is_cibuildwheel(): + """Check if run with cibuildwheel.""" + return 'CIBUILDWHEEL' in os.environ + +def temp_rename_kernprof(repo_dir): + """ + Hacky workaround so kernprof.py doesn't get covered twice (installed and local). + This needed to combine the .coverage files, since file paths need to be unique. + + """ + original_path = repo_dir + '/kernprof.py' + tmp_path = original_path + '.tmp' + if os.path.isfile(original_path): + os.rename(original_path, tmp_path) + elif os.path.isfile(tmp_path): + os.rename(tmp_path, original_path) + +def replace_docker_path(path, runner_project_dir): + """Update path to a file installed in a temp venv to runner_project_dir.""" + pattern = re.compile(r"\/tmp\/.+?\/site-packages") + return pattern.sub(runner_project_dir, path) + +def update_coverag_file(coverage_path, runner_project_dir): + """ + Since the paths inside of docker vary from the runner paths, + the paths in the .coverage file need to be adjusted to combine them, + since 'coverage combine ' checks if the file paths exist. + """ + try: + sqliteConnection = sqlite3.connect(coverage_path) + cursor = sqliteConnection.cursor() + print('Connected to Coverage SQLite') + + read_file_query = 'SELECT id, path from file' + cursor.execute(read_file_query) + + old_records = cursor.fetchall() + new_records = [(replace_docker_path(path, runner_project_dir), _id) for _id, path in old_records] + print('Updated coverage file paths:\n', new_records) + + sql_update_query = 'Update file set path = ? where id = ?' + cursor.executemany(sql_update_query, new_records) + sqliteConnection.commit() + print('Coverage Updated successfully') + cursor.close() + + except sqlite3.Error as error: + print('Failed to coverage: ', error) + finally: + if sqliteConnection: + sqliteConnection.close() + print('The sqlite connection is closed') + +def copy_coverage_cibuildwheel_docker(runner_project_dir): + """ + When run with cibuildwheel under linux, the tests run in the folder /project + inside docker and the coverage files need to be copied to the output folder. + """ + coverage_path = '/project/tests/.coverage' + if os.path.isfile(coverage_path): + update_coverag_file(coverage_path, runner_project_dir) + env_hash = hash((sys.version, os.environ.get('AUDITWHEEL_PLAT', ''))) + os.makedirs('/output', exist_ok=True) + os.rename(coverage_path, '/output/.coverage.{}'.format(env_hash)) + + + +if __name__ == '__main__': + cwd = os.getcwd() + repo_dir = abspath(dirname(__file__)) + test_dir = join(repo_dir, 'tests') + print('cwd = {!r}'.format(cwd)) + + if is_cibuildwheel(): + # rename kernprof.py to kernprof.py.tmp + temp_rename_kernprof(repo_dir) + + import pytest + + # Prefer testing the installed version, but fallback to testing the + # development version. + try: + import ubelt as ub + except ImportError: + print('running this test script requires ubelt') + raise + # Statically check if ``line_profiler`` is installed outside of the repo. + # To do this, we make a copy of PYTHONPATH, remove the repodir, and use + # ubelt to check to see if ``line_profiler`` can be resolved to a path. + temp_path = list(map(abspath, sys.path)) + if repo_dir in temp_path: + temp_path.remove(repo_dir) + modpath = ub.modname_to_modpath('line_profiler', sys_path=temp_path) + if modpath is not None: + # If it does, then import it. This should cause the installed version + # to be used on further imports even if the repo_dir is in the path. + print('Using installed version of line_profiler') + module = ub.import_module_from_path(modpath, index=0) + print('Installed module = {!r}'.format(module)) + else: + print('No installed version of line_profiler found') + + try: + print('Changing dirs to test_dir={!r}'.format(test_dir)) + os.chdir(test_dir) + + package_name = 'line_profiler' + pytest_args = [ + '--cov-config', '../pyproject.toml', + '--cov-report', 'html', + '--cov-report', 'term', + '--cov-report', 'xml', + '--cov=' + package_name, + '--cov=' + 'kernprof', + ] + if is_cibuildwheel(): + pytest_args.append('--cov-append') + + pytest_args = pytest_args + sys.argv[1:] + sys.exit(pytest.main(pytest_args)) + finally: + os.chdir(cwd) + if is_cibuildwheel(): + # restore kernprof.py from kernprof.py.tmp + temp_rename_kernprof(repo_dir) + # for CIBW under linux + copy_coverage_cibuildwheel_docker('/home/runner/work/line_profiler/line_profiler') + print('Restoring cwd = {!r}'.format(cwd)) diff --git a/setup.py b/setup.py index edc096c..9570a17 100755 --- a/setup.py +++ b/setup.py @@ -1,28 +1,203 @@ -import os +#!/usr/bin/env python +# -*- coding: utf-8 -*- +from os.path import exists import sys +import setuptools # NOQA +from setuptools import find_packages -# Monkeypatch distutils. -import setuptools - -import distutils.errors -from distutils.core import setup -from distutils.extension import Extension -from distutils.log import warn - -try: - from Cython.Distutils import build_ext - cmdclass = dict(build_ext=build_ext) - line_profiler_source = '_line_profiler.pyx' -except ImportError: - cmdclass = {} - line_profiler_source = '_line_profiler.c' - if not os.path.exists(line_profiler_source): - raise distutils.errors.DistutilsError("""\ -You need Cython to build the line_profiler from a git checkout, or -alternatively use a release tarball from PyPI to build it without Cython.""") + +def parse_version(fpath): + """ + Statically parse the version number from a python file + """ + import ast + if not exists(fpath): + raise ValueError('fpath={!r} does not exist'.format(fpath)) + with open(fpath, 'r') as file_: + sourcecode = file_.read() + pt = ast.parse(sourcecode) + class Finished(Exception): + pass + class VersionVisitor(ast.NodeVisitor): + def visit_Assign(self, node): + for target in node.targets: + if getattr(target, 'id', None) == '__version__': + self.version = node.value.s + raise Finished + visitor = VersionVisitor() + try: + visitor.visit(pt) + except Finished: + pass + return visitor.version + + +def parse_description(): + """ + Parse the description in the README file + + CommandLine: + pandoc --from=markdown --to=rst --output=README.rst README.md + python -c "import setup; print(setup.parse_description())" + """ + from os.path import dirname, join, exists + readme_fpath = join(dirname(__file__), 'README.rst') + # This breaks on pip install, so check that it exists. + if exists(readme_fpath): + with open(readme_fpath, 'r') as f: + text = f.read() + return text + return '' + + +def parse_requirements(fname='requirements.txt', with_version=True): + """ + Parse the package dependencies listed in a requirements file but strips + specific versioning information. + + Args: + fname (str): path to requirements file + with_version (bool, default=True): if true include version specs + + Returns: + List[str]: list of requirements items + + References: + https://pip.pypa.io/en/stable/reference/pip_install/#requirement-specifiers + https://www.python.org/dev/peps/pep-0440/#version-specifiers + + CommandLine: + python -c "import setup; print(setup.parse_requirements())" + python -c "import setup; print(chr(10).join(setup.parse_requirements(with_version=True)))" + """ + from os.path import exists + import re + require_fpath = fname + + def parse_line(line): + """ + Parse information from a line in a requirements text file + + Ignore: + line = 'foobar >=1.0, <= 2.1' + """ + if line.startswith('-r '): + # Allow specifying requirements in other files + target = line.split(' ')[1] + for info in parse_require_file(target): + yield info + else: + info = {'line': line} + if line.startswith('-e '): + info['package'] = line.split('#egg=')[1] + else: + # Remove versioning from the package + cmp_ops = ['>=', '>', '<=', '<', '!=', '~=', '==', '==='] + pat = '(' + '|'.join(cmp_ops) + ')' + parts = re.split(pat, line, maxsplit=1) + parts = [p.strip() for p in parts] + + info['package'] = parts[0] + if len(parts) > 1: + op1, rest = parts[1:] + if ';' in rest: + # Handle platform specific dependencies + # http://setuptools.readthedocs.io/en/latest/setuptools.html#declaring-platform-specific-dependencies + version_rest, platform_deps = map(str.strip, rest.split(';')) + info['platform_deps'] = platform_deps + else: + version_rest = rest # NOQA + # Multiple version requirments may be specified + version = [] + version_text = op1 + version_rest + for clause in version_text.split(','): + cparts = [p.strip() for p in re.split(pat, clause)] + cparts = [p for p in cparts if p] + version.append(cparts) + info['version'] = version + yield info + + def parse_require_file(fpath): + with open(fpath, 'r') as f: + for line in f.readlines(): + line = line.strip() + if line and not line.startswith('#'): + for info in parse_line(line): + yield info + + def gen_packages_items(): + if exists(require_fpath): + for info in parse_require_file(require_fpath): + parts = [info['package']] + if 'version' in info: + # FIXME: add mode that lets you exclude minimum reqs + clauses = [] + for clause in info['version']: + op, arg = clause + if with_version: + clauses.append(op + arg) + version_part = ','.join(clauses) + parts.append(version_part) + if not sys.version.startswith('3.4'): + # apparently package_deps are broken in 3.4 + platform_deps = info.get('platform_deps') + if platform_deps is not None: + parts.append(';' + platform_deps) + item = ''.join(parts) + yield item + + packages = list(gen_packages_items()) + return packages + + +def native_mb_python_tag(plat_impl=None, version_info=None): + """ + Get the correct manylinux python version tag for this interpreter + + Example: + >>> print(native_mb_python_tag()) + >>> print(native_mb_python_tag('PyPy', (2, 7))) + >>> print(native_mb_python_tag('CPython', (3, 8))) + """ + if plat_impl is None: + import platform + plat_impl = platform.python_implementation() + + if version_info is None: + import sys + version_info = sys.version_info + + major, minor = version_info[0:2] + if minor > 9: + ver = '{}_{}'.format(major, minor) + else: + ver = '{}{}'.format(major, minor) + + if plat_impl == 'CPython': + # TODO: get if cp27m or cp27mu + impl = 'cp' + if ver == '27': + IS_27_BUILT_WITH_UNICODE = True # how to determine this? + if IS_27_BUILT_WITH_UNICODE: + abi = 'mu' + else: + abi = 'm' + else: + if sys.version_info[:2] >= (3, 8): + # bpo-36707: 3.8 dropped the m flag + abi = '' + else: + abi = 'm' + mb_tag = '{impl}{ver}-{impl}{ver}{abi}'.format(**locals()) + elif plat_impl == 'PyPy': + abi = '' + impl = 'pypy' + ver = '{}{}'.format(major, minor) + mb_tag = '{impl}-{ver}'.format(**locals()) else: - warn("Could not import Cython. " - "Using the available pre-generated C file.") + raise NotImplementedError(plat_impl) + return mb_tag + long_description = """\ line_profiler will profile the time individual lines of code take to execute. @@ -34,52 +209,60 @@ function-level profiling tools in the Python standard library. """ +VERSION = parse_version('line_profiler/line_profiler.py') +MB_PYTHON_TAG = native_mb_python_tag() +NAME = 'line_profiler' + -py_modules = ['line_profiler', 'kernprof'] -if sys.version_info > (3, 4): - py_modules += ['line_profiler_py35'] - -setup( - name = 'line_profiler', - version = '2.1.1', - author = 'Robert Kern', - author_email = 'robert.kern@enthought.com', - description = 'Line-by-line profiler.', - long_description = long_description, - url = 'https://github.com/rkern/line_profiler', - download_url = 'https://github.com/rkern/line_profiler/tarball/2.1', - ext_modules = [ - Extension('_line_profiler', - sources=[line_profiler_source, 'timers.c', 'unset_trace.c'], - depends=['python25.pxd'], - ), - ], - license = "BSD", - keywords = ['timing', 'timer', 'profiling', 'profiler', 'line_profiler'], - classifiers = [ - "Development Status :: 5 - Production/Stable", - "Intended Audience :: Developers", - "License :: OSI Approved :: BSD License", - "Operating System :: OS Independent", - "Programming Language :: C", - "Programming Language :: Python", - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.2', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: Implementation :: CPython', - "Topic :: Software Development", - ], - py_modules = py_modules, - entry_points = { - 'console_scripts': [ - 'kernprof=kernprof:main', +if __name__ == '__main__': + if '--universal' in sys.argv: + # Dont use scikit-build for universal wheels + # if 'develop' in sys.argv: + sys.argv.remove('--universal') + from setuptools import setup # NOQA + else: + from skbuild import setup + setupkw = dict( + name=NAME, + version=VERSION, + author='Robert Kern', + author_email='robert.kern@enthought.com', + description='Line-by-line profiler.', + long_description=long_description, + long_description_content_type='text/x-rst', + url='https://github.com/pyutils/line_profiler', + license='BSD', + license_files=['LICENSE.txt', 'LICENSE_Python.txt'], + keywords=['timing', 'timer', 'profiling', 'profiler', 'line_profiler'], + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: BSD License', + 'Operating System :: OS Independent', + 'Programming Language :: C', + 'Programming Language :: Python', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: Implementation :: CPython', + 'Topic :: Software Development', ], - }, - install_requires = [ - 'IPython>=0.13', - ], - cmdclass = cmdclass, -) + # py_modules=find_packages(), + packages=list(find_packages()), + py_modules=['kernprof', 'line_profiler'], + entry_points={ + 'console_scripts': [ + 'kernprof=kernprof:main', + ], + }, + install_requires=parse_requirements('requirements/runtime.txt'), + extras_require={ + 'all': parse_requirements('requirements.txt'), + 'tests': parse_requirements('requirements/tests.txt'), + 'build': parse_requirements('requirements/build.txt'), + }, + ) + setup(**setupkw) diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 0000000..f885911 --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,64 @@ +from os.path import join +from sys import executable + + +def test_cli(): + """ + Test command line interaction with kernprof and line_profiler. + + References: + https://github.com/pyutils/line_profiler/issues/9 + + CommandLine: + xdoctest -m ~/code/line_profiler/tests/test_cli.py test_cli + """ + + # Create a dummy source file + import ubelt as ub + code = ub.codeblock( + ''' + @profile + def my_inefficient_function(): + a = 0 + for i in range(10): + a += i + for j in range(10): + a += j + + if __name__ == '__main__': + my_inefficient_function() + ''') + import tempfile + tmp_dpath = tempfile.mkdtemp() + tmp_src_fpath = join(tmp_dpath, 'foo.py') + ub.writeto(tmp_src_fpath, code) + + # Run kernprof on it + info = ub.cmd('kernprof -l {}'.format(tmp_src_fpath), verbose=3, + cwd=tmp_dpath) + assert info['ret'] == 0 + + tmp_lprof_fpath = join(tmp_dpath, 'foo.py.lprof') + tmp_lprof_fpath + + info = ub.cmd('{} -m line_profiler {}'.format(executable,tmp_lprof_fpath), + cwd=tmp_dpath, verbose=3) + assert info['ret'] == 0 + # Check for some patterns that should be in the output + assert '% Time' in info['out'] + assert '7 100' in info['out'] + + +def test_version_agreement(): + """ + Ensure that line_profiler and kernprof have the same version info + """ + import ubelt as ub + info1 = ub.cmd('{} -m line_profiler --version'.format(executable)) + info2 = ub.cmd('{} -m kernprof --version'.format(executable)) + + # Strip local version suffixes + version1 = info1['out'].strip().split('+')[0] + version2 = info2['out'].strip().split('+')[0] + + assert version2 == version1, 'kernprof and line_profiler must be in sync' diff --git a/tests/test_kernprof.py b/tests/test_kernprof.py index 7702b54..c7cf664 100644 --- a/tests/test_kernprof.py +++ b/tests/test_kernprof.py @@ -73,7 +73,8 @@ def test_gen_decorator(self): self.assertEqual(profile.enable_count, 0) self.assertEqual(i.send(30), 50) self.assertEqual(profile.enable_count, 0) - with self.assertRaises(StopIteration): + + with self.assertRaises((StopIteration, RuntimeError)): next(i) self.assertEqual(profile.enable_count, 0) @@ -82,4 +83,8 @@ def test_gen_decorator(self): test_coroutine_decorator = _test_kernprof_py35.test_coroutine_decorator if __name__ == '__main__': + """ + CommandLine: + python ~/code/line_profiler/tests/test_kernprof.py + """ unittest.main()