Skip to content

Commit

Permalink
chore: Bundle Ruby standalones into dist artifact.
Browse files Browse the repository at this point in the history
Install all ruby standalone packages when running sdist.

Devloppers can also now provide a --bin-path that contains the binaries required
for their OS, so that can they use this package in offline environments.
  • Loading branch information
taj-p committed Aug 20, 2021
1 parent da49cd7 commit 18409bf
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 19 deletions.
2 changes: 1 addition & 1 deletion MANIFEST.in
@@ -1,6 +1,6 @@
include LICENSE
include *.txt
include *.md
include pact/bin/*
prune pact/test
prune pact/bin
prune e2e
11 changes: 10 additions & 1 deletion README.md
Expand Up @@ -437,7 +437,7 @@ To setup a development environment:
1. If you want to run tests for all Python versions, install 2.7, 3.3, 3.4, 3.5, and 3.6 from source or using a tool like [pyenv]
2. Its recommended to create a Python [virtualenv] for the project

The setup the environment, run tests, and package the application, run:
To setup the environment, run tests, and package the application, run:
`make release`

If you are just interested in packaging pact-python so you can install it using pip:
Expand All @@ -449,6 +449,15 @@ From there you can use pip to install it:

`pip install ./dist/pact-python-N.N.N.tar.gz`

## Offline Installation of Standalone Packages

Although all Ruby standalone applications are predownloaded into the wheel artifact, it may be useful, for development, purposes to install custom Ruby binaries. In which case, use the `bin-path` flag.
```
pip install pact-python --bin-path=/absolute/path/to/folder/containing/pact/binaries/for/your/os
```

Pact binaries can be found at [Pact Ruby Releases](https://github.com/pact-foundation/pact-ruby-standalone/releases).

## Testing

This project has unit and end to end tests, which can both be run from make:
Expand Down
121 changes: 104 additions & 17 deletions setup.py
Expand Up @@ -2,6 +2,7 @@

import os
import platform
import shutil
import sys
import tarfile

Expand All @@ -10,18 +11,39 @@
from setuptools import setup
from setuptools.command.develop import develop
from setuptools.command.install import install
from distutils.command.sdist import sdist as sdist_orig


IS_64 = sys.maxsize > 2 ** 32
PACT_STANDALONE_VERSION = '1.88.51'

PACT_STANDALONE_SUFFIXES = ['osx.tar.gz',
'linux-x86_64.tar.gz',
'linux-x86.tar.gz',
'win32.zip']

here = os.path.abspath(os.path.dirname(__file__))

about = {}
with open(os.path.join(here, "pact", "__version__.py")) as f:
exec(f.read(), about)

class sdist(sdist_orig):
"""
Subclass sdist so that we can download all standalone ruby applications
into ./pact/bin so our users receive all the binaries on pip install.
"""
def run(self):
package_bin_path = os.path.join(os.path.dirname(__file__), 'pact', 'bin')

if os.path.exists(package_bin_path):
shutil.rmtree(package_bin_path, ignore_errors=True)
os.mkdir(package_bin_path)

for suffix in PACT_STANDALONE_SUFFIXES:
filename = ('pact-{version}-{suffix}').format(version=PACT_STANDALONE_VERSION, suffix=suffix)
download_ruby_app_binary(package_bin_path, filename, suffix)
super().run()


class PactPythonDevelopCommand(develop):
"""
Expand All @@ -35,11 +57,11 @@ class PactPythonDevelopCommand(develop):
def run(self):
"""Install ruby command."""
develop.run(self)
bin_path = os.path.join(os.path.dirname(__file__), 'pact', 'bin')
if not os.path.exists(bin_path):
os.mkdir(bin_path)
package_bin_path = os.path.join(os.path.dirname(__file__), 'pact', 'bin')
if not os.path.exists(package_bin_path):
os.mkdir(package_bin_path)

install_ruby_app(bin_path)
install_ruby_app(package_bin_path, download_bin_path=None)


class PactPythonInstallCommand(install):
Expand All @@ -48,25 +70,66 @@ class PactPythonInstallCommand(install):
Installs the Python package and unpacks the platform appropriate version
of the Ruby mock service and provider verifier.
User Options:
--bin-path An absolute folder path containing predownloaded pact binaries
that should be used instead of fetching from the internet.
"""

user_options = install.user_options + [('bin-path=', None, None)]

def initialize_options(self):
"""Load our preconfigured options"""
install.initialize_options(self)
self.bin_path = None

def finalize_options(self):
"""Load provided CLI arguments into our options"""
install.finalize_options(self)

def run(self):
"""Install python binary."""
install.run(self)
bin_path = os.path.join(self.install_lib, 'pact', 'bin')
os.mkdir(bin_path)
install_ruby_app(bin_path)
package_bin_path = os.path.join(self.install_lib, 'pact', 'bin')
if not os.path.exists(package_bin_path):
os.mkdir(package_bin_path)
install_ruby_app(package_bin_path, self.bin_path)


def install_ruby_app(bin_path):
def install_ruby_app(package_bin_path, download_bin_path):
"""
Download a Ruby application and install it for use.
Installs the ruby standalone application for this OS.
:param bin_path: The path where binaries should be installed.
:param package_bin_path: The path where we want our pact binaries unarchived.
:param download_bin_path: An optional path containing pre-downloaded pact binaries.
"""

binary = ruby_app_binary()
if download_bin_path is None:
download_bin_path = package_bin_path

path = os.path.join(download_bin_path, binary['filename'])

if os.path.isfile(path) is True:
extract_ruby_app_binary(download_bin_path, package_bin_path, binary['filename'])
else:
if download_bin_path is not None:
if os.path.isfile(path) is not True:
raise RuntimeError('Could not find {} binary.'.format(path))
extract_ruby_app_binary(download_bin_path, package_bin_path, binary['filename'])
else:
download_ruby_app_binary(package_bin_path, binary['filename'], binary['suffix'])
extract_ruby_app_binary(package_bin_path, package_bin_path, binary['filename'])

def ruby_app_binary():
"""
Determines the ruby app binary required for this OS.
:return A dictionary of type {'filename': string, 'version': string, 'suffix': string }
"""
target_platform = platform.platform().lower()
uri = ('https://github.com/pact-foundation/pact-ruby-standalone/releases'
'/download/v{version}/pact-{version}-{suffix}')

binary = ('pact-{version}-{suffix}')

if 'darwin' in target_platform or 'macos' in target_platform:
suffix = 'osx.tar.gz'
Expand All @@ -82,12 +145,26 @@ def install_ruby_app(bin_path):
platform.platform())
raise Exception(msg)

binary = binary.format(version=PACT_STANDALONE_VERSION, suffix=suffix)
return {'filename': binary, 'version': PACT_STANDALONE_VERSION, 'suffix': suffix}

def download_ruby_app_binary(path_to_download_to, filename, suffix):
"""
Downloads `binary` into `path_to_download_to`.
:param path_to_download_to: The path where binaries should be downloaded.
:param filename: The filename that should be installed.
:param suffix: The suffix of the standalone app to install.
"""
uri = ('https://github.com/pact-foundation/pact-ruby-standalone/releases'
'/download/v{version}/pact-{version}-{suffix}')

if sys.version_info.major == 2:
from urllib import urlopen
else:
from urllib.request import urlopen

path = os.path.join(bin_path, suffix)
path = os.path.join(path_to_download_to, filename)
resp = urlopen(uri.format(version=PACT_STANDALONE_VERSION, suffix=suffix))
with open(path, 'wb') as f:
if resp.code == 200:
Expand All @@ -97,12 +174,21 @@ def install_ruby_app(bin_path):
'Received HTTP {} when downloading {}'.format(
resp.code, resp.url))

def extract_ruby_app_binary(source, destination, binary):
"""
Extracts the ruby app binary from `source` into `destination`.
:param source: The location of the binary to unarchive.
:param destination: The location to unarchive to.
:param binary: The binary that needs to be unarchived.
"""
path = os.path.join(source, binary)
if 'windows' in platform.platform().lower():
with ZipFile(path) as f:
f.extractall(bin_path)
f.extractall(destination)
else:
with tarfile.open(path) as f:
f.extractall(bin_path)
f.extractall(destination)


def read(filename):
Expand All @@ -126,7 +212,8 @@ def read(filename):
setup(
cmdclass={
'develop': PactPythonDevelopCommand,
'install': PactPythonInstallCommand},
'install': PactPythonInstallCommand,
'sdist': sdist},
name='pact-python',
version=about['__version__'],
description=(
Expand Down

0 comments on commit 18409bf

Please sign in to comment.