Skip to content

Commit

Permalink
Build stand alone executable
Browse files Browse the repository at this point in the history
Use PyInstaller to build ykman as
a single executable on macOS and Windows.
  • Loading branch information
dagheyman committed Sep 5, 2018
1 parent 96028da commit d66c04d
Show file tree
Hide file tree
Showing 3 changed files with 211 additions and 43 deletions.
85 changes: 60 additions & 25 deletions .travis.yml
@@ -1,39 +1,74 @@
language: python
dist: trusty
sudo: required

services:
- docker

python:
- "2.7"
- "3.4"
- "3.5"
- "3.6"

cache:
directories:
- $HOME/.cache/pip
matrix:
include:
- os: linux
sudo: required
python: 3.6
services: docker
- os: osx
sudo: required
language: generic
env: PY_VERSION="3.6.6"
osx_image: xcode7.3

before_install:
- docker build -t xenial -f docker/xenial/Dockerfile .
- sudo apt-add-repository -y ppa:yubico/stable
- sudo apt-get update
- sudo apt-get install -qq libykpers-1-1 python-pyscard swig libpcsclite-dev
- pip install --disable-pip-version-check --upgrade pip
- |
if [[ "$TRAVIS_OS_NAME" == "osx" ]]
then
brew update
brew install ykpers libyubikey libusb swig
brew upgrade pyenv
eval "$(pyenv init -)"
env PYTHON_CONFIGURE_OPTS="--enable-framework CC=clang" pyenv install $PY_VERSION
pyenv global $PY_VERSION
fi
- |
if [[ "$TRAVIS_OS_NAME" == "linux" ]]
then
docker build -t xenial -f docker/xenial/Dockerfile .
sudo apt-add-repository -y ppa:yubico/stable
sudo apt-get update
sudo apt-get install -qq libykpers-1-1 python-pyscard swig libpcsclite-dev
pip install --disable-pip-version-check --upgrade pip
fi
install:
- pip install pre-commit
- pip install -e .
- |
if [[ "$TRAVIS_OS_NAME" == "linux" ]]
then
pip install pre-commit
pre-commit run --all-files
pip install -e .
python setup.py test
fi
script:
- pre-commit run --all-files
- python setup.py test
- |
if [[ "$TRAVIS_OS_NAME" == "osx" ]]
then
find /usr/local/Cellar/json-c/ -name '*.dylib' -exec sudo cp '{}' . ';'
find /usr/local/Cellar/ykpers/ -name '*.dylib' -exec sudo cp '{}' . ';'
find /usr/local/Cellar/libyubikey/ -name '*.dylib' -exec sudo cp '{}' . ';'
pip3 install .
pip3 install pyinstaller
pyinstaller --console --onefile --clean ykman.spec
fi
after_success:
- mkdir deploy
- id=$(docker create xenial)
- docker cp $id:/yubikey-manager-debian-packages.tar.gz deploy/yubikey-manager-$TRAVIS_BRANCH-deb.tar.gz
- |
if [[ "$TRAVIS_OS_NAME" == "osx" ]]
then
cd dist && zip yubikey-manager-$TRAVIS_BRANCH-mac.zip ykman && cd ..
cp dist/yubikey-manager-$TRAVIS_BRANCH-mac.zip deploy/
fi
- |
if [[ "$TRAVIS_OS_NAME" == "linux" ]]
then
id=$(docker create xenial)
docker cp $id:/yubikey-manager-debian-packages.tar.gz deploy/yubikey-manager-$TRAVIS_BRANCH-deb.tar.gz
fi
deploy:
provider: s3
Expand Down
56 changes: 38 additions & 18 deletions appveyor.yml
Expand Up @@ -5,39 +5,59 @@
# Syntax for this file:
# https://www.appveyor.com/docs/appveyor-yml

image: Visual Studio 2015

environment:
LIBU2F_HOST_VERSION: "1.1.4"
YKPERS_VERSION: "1.19.0"
LIBUSB_VERSION: "1.0.21"
YKPERS_BASEURL: https://developers.yubico.com/yubikey-personalization/Releases

matrix:
- PYTHON: "C:\\Python27"
- PYTHON: "C:\\Python35"
- PYTHON: "C:\\Python36"

matrix:
fast_finish: true
- platform: x86
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
PYTHON: C:\Python36
WIN_ARCH: win32
LIBUSB_DLL_FOLDER: MS32

- platform: x64
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
PYTHON: C:\Python36-x64
WIN_ARCH: win64
LIBUSB_DLL_FOLDER: MS64

init:
- set PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%
- set PATH=%PYTHON%;%PYTHON%\Scripts;%PATH%
- echo %PATH%

install:
- choco install swig -y
- choco upgrade swig

- ps: wget "https://developers.yubico.com/yubikey-personalization/Releases/ykpers-$env:YKPERS_VERSION-win32.zip" -OutFile "ykpers-$env:YKPERS_VERSION-win32.zip"
- 7z x ykpers-%YKPERS_VERSION%-win32.zip -o".\ykpers"
- ps: wget "$env:YKPERS_BASEURL/ykpers-$env:YKPERS_VERSION-$env:WIN_ARCH.zip" -OutFile "ykpers-$env:YKPERS_VERSION-$env:WIN_ARCH.zip"
- 7z x ykpers-%YKPERS_VERSION%-%WIN_ARCH%.zip -o".\ykpers"
- set PATH=%PATH%;%APPVEYOR_BUILD_FOLDER%\ykpers\bin
- ps: Copy-Item ".\ykpers\bin\*.dll" "." -Force -verbose

- ps: Invoke-WebRequest "http://prdownloads.sourceforge.net/libusb/libusb-$env:LIBUSB_VERSION.7z" -OutFile "libusb-$env:LIBUSB_VERSION.7z" -UserAgent [Microsoft.PowerShell.Commands.PSUserAgent]::Chrome
- 7z x libusb-%LIBUSB_VERSION%.7z -o".\libusb"
- set PATH=%PATH%;%APPVEYOR_BUILD_FOLDER%\libusb\MS32\dll
- ps: Copy-Item ".\libusb\$env:LIBUSB_DLL_FOLDER\dll\*.dll" "." -Force -verbose

build_script:
- pip install -e .
- ykman -v

test_script:
- python setup.py test
- pip install .
- pip install pyinstaller
- pyinstaller --console --onefile --clean ykman.spec

after_build:
- set ARTIFACT_NAME=%APPVEYOR_REPO_BRANCH%-%WIN_ARCH%
- if defined APPVEYOR_REPO_TAG_NAME set ARTIFACT_NAME=%APPVEYOR_REPO_TAG_NAME%-%WIN_ARCH%
- 7z a yubikey-manager-%ARTIFACT_NAME%.zip .\dist\*

artifacts:
- path: yubikey-manager-$(ARTIFACT_NAME).zip

deploy:
- provider: S3
access_key_id: $(AWS_KEY_ID)
secret_access_key: $(AWS_SECRET_KEY)
bucket: $(AWS_BUCKET)
folder: "yubikey-manager"
region: eu-west-1
set_public: true
113 changes: 113 additions & 0 deletions ykman.spec
@@ -0,0 +1,113 @@
# -*- mode: python -*-

# This is a spec file used by PyInstaller to build a single executable for ykman.
# See: https://pyinstaller.readthedocs.io/en/stable/spec-files.html

# This recipe allows PyInstaller to understand the entrypoint.
# See: https://github.com/pyinstaller/pyinstaller/wiki/Recipe-Setuptools-Entry-Point
def Entrypoint(dist, group, name, **kwargs):
import pkg_resources

# get toplevel packages of distribution from metadata
def get_toplevel(dist):
distribution = pkg_resources.get_distribution(dist)
if distribution.has_metadata('top_level.txt'):
return list(distribution.get_metadata('top_level.txt').split())
else:
return []

kwargs.setdefault('hiddenimports', [])
packages = []
for distribution in kwargs['hiddenimports']:
packages += get_toplevel(distribution)

kwargs.setdefault('pathex', [])
# get the entry point
ep = pkg_resources.get_entry_info(dist, group, name)
# insert path of the egg at the verify front of the search path
kwargs['pathex'] = [ep.dist.location] + kwargs['pathex']
# script name must not be a valid module name to avoid name clashes on import
script_path = os.path.join(workpath, name + '-script.py')
print("creating script for entry point", dist, group, name)
with open(script_path, 'w') as fh:
print("import", ep.module_name, file=fh)
print("%s.%s()" % (ep.module_name, '.'.join(ep.attrs)), file=fh)
for package in packages:
print("import", package, file=fh)

return Analysis(
[script_path] + kwargs.get('scripts', []),
**kwargs
)


block_cipher = None

# Extra .dlls and .dylibs are added to the executable.
macos_dylibs = [
('libjson-c.4.dylib', '.' ),
('libjson-c.dylib', '.'),
('libykpers-1.1.dylib', '.'),
('libykpers-1.dylib', '.'),
('libyubikey.0.dylib', '.'),
('libyubikey.dylib', '.'),
]

win_dlls = [
('libjson-c-2.dll', '.' ),
('libusb-1.0.dll', '.' ),
('libykpers-1-1.dll', '.' ),
('libyubikey-0.dll', '.' ),
('libjson-0.dll', '.' ),
]

# Extra data files to be added to executable.
data_files = [('ykman/VERSION', 'ykman/')]

import sys
import platform

universal_crt = ['']
binary_files = None

# On Windows we bundle the Universal CRT, see:
# https://github.com/pyinstaller/pyinstaller/blob/develop/doc/usage.rst#windows

# We also do a workaround for pyscard, see:
# https://stackoverflow.com/questions/49718551/pyinstaller-fails-with-pyscard-on-windows
if sys.platform == 'win32':
binary_files = win_dlls
pyscard_patch = None
if platform.architecture()[0] == '32bit':
pyscard_patch = 'C:\\Python36\\lib\\site-packages\\smartcard\\scard\\_scard.cp36-win32.pyd'
universal_crt = ['C:\\Program Files (x86)\\Windows Kits\\10\\Redist\\ucrt\\DLLs\\x86']
if platform.architecture()[0] == '64bit':
pyscard_patch = 'C:\\Python36-x64\\lib\\site-packages\\smartcard\\scard\\_scard.cp36-win_amd64.pyd'
universal_crt = ['C:\\Program Files (x86)\\Windows Kits\\10\\Redist\\ucrt\\DLLs\\x64']
data_files.append((pyscard_patch , '.\\smartcard\\scard\\'))

if sys.platform == 'darwin':
binary_files = macos_dylibs

a = Entrypoint(
'yubikey-manager',
'console_scripts',
'ykman',
datas=data_files,
pathex=universal_crt,
binaries=binary_files)

pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)

exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
name='ykman',
debug=False,
strip=False,
upx=True,
runtime_tmpdir=None,
console=True)

0 comments on commit d66c04d

Please sign in to comment.