diff --git a/Dockerfilei386 b/Dockerfilei386 new file mode 100644 index 00000000..b1107eff --- /dev/null +++ b/Dockerfilei386 @@ -0,0 +1,10 @@ +# this dockerfile is used to build the 32bit linux binary +FROM i386/ubuntu:18.04 +RUN apt-get update && apt-get -y install python3 python3-dev python3-pip +COPY requirements_dev.txt /requirements_dev.txt +RUN pip3 install -r /requirements_dev.txt +RUN mkdir /app +WORKDIR /app + +ENV LC_ALL=C.UTF-8 +ENV LANG=C.UTF-8 diff --git a/HISTORY.rst b/HISTORY.rst index fd5c5a06..b83f6de2 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -2,6 +2,11 @@ History ======= +1.8.6 (2019-03-10) +------------------ + +* Safety is now available as a binary release for macOS, Windows and Linux. + 1.8.5 (2019-02-04) ------------------ diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 00000000..c024cde2 --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,14 @@ +# Release + +Safety is distributed as a binary for Windows 32/64 bit, Linux 32/64 bit and macOS 64 bit. +The binary is built on appveyor (see `appveyor.py` and `appveyor.yml`) and distributed through GitHub. + +## Issuing a new release + +First, update the version string in `setup.py` and `safety/__init__.py` and push the changes to master. + +Make sure the release builds properly on appveyor prior to tagging it. + +To issue a new release, tag the release with `git tag 1.x.x` and push the tag with `git push origin --tags`. +Once the build is completed and all artifacts are collected, the binaries are uploaded as a GitHub release. + diff --git a/appveyor.py b/appveyor.py new file mode 100644 index 00000000..e371b790 --- /dev/null +++ b/appveyor.py @@ -0,0 +1,114 @@ +""" +This file is used to build and distribute the safety binary on appveyor, take a look at the corresponding appveyor.yml. +""" +import os +import sys +import subprocess + + +class environment: + + def __init__(self): + os_mapping = { + "Visual Studio 2019": "win", + "Ubuntu": "linux", + "macOS": "macos" + } + self.os = os_mapping[os.getenv("APPVEYOR_BUILD_WORKER_IMAGE")] + + @property + def python(self): + for arch, python in self.PYTHON_BINARIES[self.os].items(): + yield arch, python + + WIN = "win" + LINUX = "linux" + MACOS = "macos" + + PYTHON_BINARIES = { + WIN: { + 64: "C:\\Python38-x64\\python.exe", + 32: "C:\\Python38\\python.exe", + + }, + LINUX: { + # order is important. if the 32 bit release gets built first, + # you'll run into permission problems due to docker clobbering + # up the current working directory + 64: "python", + # 32 bit linux is built using docker + 32: f"docker run -t -v {os.getcwd()}:/app 32-bit-linux python3", + + }, + MACOS: { + # on macOS the binary is built using Python 3.7 (installed via Homebrew), because + # the shipped Python lacks libraries PyInstaller needs. + 64: "/usr/local/bin/python3", + } + } + + def run(self, command): + """ + Runs the given command via subprocess.check_output, exits with -1 if the command wasn't successfull. + """ + try: + print(f"RUNNING: {command}") + print("-" * 80) + print(subprocess.check_output(command, shell=True)) + except subprocess.CalledProcessError as e: + print(f"ERROR calling '{command}'") + print("-" * 20) + print(e.output) + sys.exit(-1) + + def install(self): + """ + Install required dependencies + """ + # special case: + # - build the 32 bit binary for linux on docker + # - create dist/ path to circumvent permission errors + if self.os == self.LINUX: + self.run("docker build -t 32-bit-linux -f Dockerfilei386 .") + + for arch, python in self.python: + self.run(f"{python} -m pip install -r requirements_dev.txt") + + def dist(self): + """ + Runs Pyinstaller producing a single file binary for every available arch for the current platform. + """ + for arch, python in self.python: + + # build the binary + build_path = os.path.join("dist", f"safety-{arch}") + self.run(f"{python} -m PyInstaller safety.spec --distpath {build_path}") + + # there seems to be no way to tell pyinstaller the name of the actual binary + # this leads to problems with appveyors artifact collector because every binary is named the same + # move them around so they can be picked up correctly + artifact_path = os.path.join(os.getcwd(), "dist", f"safety-{self.os}-{'i686' if arch == 32 else 'x86_64'}") + binary_path = os.path.join(os.getcwd(), build_path, "safety") + if self.os == self.WIN: + self.run(f"move {binary_path}.exe {artifact_path}.exe") + else: + self.run(f"cp {binary_path} {artifact_path}") + + def test(self): + """ + Runs tests for every available arch on the current platform. + """ + for arch, python in self.python: + self.run(f"{python} setup.py test") + + +if __name__ == "__main__": + + if len(sys.argv) <= 1 or sys.argv[1] not in ['install', 'test', 'dist']: + print("usage: appveyor.py [install|test|dist]") + sys.exit(-1) + + env = environment() + # runs the command in sys.argv[1] (install|test|dist) + getattr(env, sys.argv[1])() + sys.exit(0) diff --git a/appveyor.yml b/appveyor.yml index f96f3ca5..a2471e62 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,21 +1,58 @@ -environment: +image: + - Visual Studio 2019 + - Ubuntu + - macOS + +# note: on macOS the binary is built using Python 3.7 (installed via Homebrew), because +# the shipped Python lacks libraries PyInstaller needs. +stack: python 3.6 +# note: 32 bit linux binary is build using docker +for: +- matrix: - - PYTHON: "C:\\Python27" - - PYTHON: "C:\\Python35" - - PYTHON: "C:\\Python27-x64" - DISTUTILS_USE_SDK: "1" - - PYTHON: "C:\\Python34-x64" - DISTUTILS_USE_SDK: "1" - - PYTHON: "C:\\Python35-x64" + only: + - image: Ubuntu -install: - - "%PYTHON%\\python.exe -m pip install wheel" - - "%PYTHON%\\python.exe -m pip install setuptools --upgrade" - + services: + - docker + +environment: + PY_DIR: C:\Python36-x64 + +init: + - cmd: set PATH=%PY_DIR%;%PY_DIR%\Scripts;%PATH% build: off +artifacts: + - path: "dist\\safety-win-i686.exe" + name: "safety-win-i686.exe" + - path: "dist\\safety-win-x86_64.exe" + name: "safety-win-x86_64.exe" + - path: "dist\\safety-linux-i686" + name: "safety-linux-i686" + - path: "dist\\safety-linux-x86_64" + name: "safety-linux-x86_64" + - path: "dist\\safety-macos-x86_64" + name: "safety-macos-x86_64" + +install: + - "python --version" + - "python appveyor.py install" + test_script: - - "%PYTHON%\\python.exe setup.py test" - - "%PYTHON%\\python.exe -m pip freeze" + - "python appveyor.py test" + - "python appveyor.py dist" + + +deploy: + description: '' + provider: GitHub + auth_token: + secure: dDJgAsevLfBL9BKNuCKpbFhB5rlfbefw696Xe6Na8BhHabwoGYcg8FydNpppnKzX + draft: false + prerelease: false + on: + branch: master + APPVEYOR_REPO_TAG: true diff --git a/requirements_dev.txt b/requirements_dev.txt index 20e45df7..7d174de0 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,8 +1,5 @@ -pip==18.1 -wheel==0.32.3 -watchdog==0.9.0 -flake8==3.6.0 -PyYAML==4.2b4 -cryptography==2.4.2 -coverage==4.5.2 -Sphinx==1.8.3 \ No newline at end of file +Click==7.0 +requests==2.23.0 +packaging==20.3 +dparse==0.4.1 +pyinstaller==3.6 diff --git a/safety.spec b/safety.spec new file mode 100644 index 00000000..1a0692c8 --- /dev/null +++ b/safety.spec @@ -0,0 +1,40 @@ +# -*- mode: python ; coding: utf-8 -*- + +block_cipher = None + +a = Analysis( + ['safety/cli.py'], + pathex=['.'], + binaries=[], + datas=[], + hiddenimports=[ + 'click', + ], + hookspath=[], + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False +) +pyz = PYZ( + a.pure, a.zipped_data, + cipher=block_cipher +) +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, + [], + name='safety', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=True +) diff --git a/safety/__init__.py b/safety/__init__.py index 69563274..2cf994d8 100644 --- a/safety/__init__.py +++ b/safety/__init__.py @@ -2,4 +2,4 @@ __author__ = """pyup.io""" __email__ = 'support@pyup.io' -__version__ = '1.8.5' +__version__ = '1.8.6' diff --git a/setup.py b/setup.py index 1ef01762..edc64be6 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,7 @@ setup( name='safety', - version='1.8.5', + version='1.8.6', description="Safety checks your installed dependencies for known security vulnerabilities.", long_description=readme + '\n\n' + history, long_description_content_type="text/markdown",