diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..084f41e1b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,26 @@ +FROM python + +USER root +WORKDIR / +RUN git clone https://github.com/yaml/yamlscript +WORKDIR /yamlscript +RUN make build + +RUN pip install fmtr.tools[debug] setuptools + +COPY . . +WORKDIR /yamlscript/python + + +RUN make dist +WORKDIR /tmp + +RUN pip uninstall yamlscript -y +RUN pip install /yamlscript/python/dist/yamlscript-*.whl +RUN bash -c "ys-py-show-info > bdist_wheel.info.txt" + +RUN pip install /yamlscript/python/dist/yamlscript-0.1.96.tar.gz +RUN bash -c "export LD_LIBRARY_PATH=/yamlscript/libyamlscript/lib && ys-py-show-info > sdist.info.txt" + + +CMD sleep infinity \ No newline at end of file diff --git a/python/Makefile b/python/Makefile index e5c74f5dd..e415972ff 100644 --- a/python/Makefile +++ b/python/Makefile @@ -40,18 +40,20 @@ pkg-test: venv tar xzf dist/yamlscript-*.tar.gz cat yamlscript-*/PKG-INFO + + dist: venv MANIFEST.in .long_description.md - ( \ - $(VENV) && \ - $(PYTHON) setup.py sdist \ - ) + $(VENV) && \ + $(PYTHON) setup.py sdist && \ + cp $(LIBYS_SO_FQNP) ./lib/yamlscript/ && \ + $(PYTHON) setup.py bdist_wheel release: publish publish: dist ( \ $(VENV) && \ - twine upload --verbose --repository yamlscript dist/yamlscript-*.tar.gz \ + twine upload --verbose --repository yamlscript dist/yamlscript-*.tar.gz dist/yamlscript-*.whl \ ) clean:: @@ -72,7 +74,8 @@ $(PYTHON_VENV): pip install \ pytest \ pyyaml \ - twine + twine \ + setuptools MANIFEST.in: echo 'include ReadMe.md' > $@ diff --git a/python/lib/yamlscript/__init__.py b/python/lib/yamlscript/__init__.py index 7cd624be6..fb1b8f39e 100644 --- a/python/lib/yamlscript/__init__.py +++ b/python/lib/yamlscript/__init__.py @@ -16,21 +16,25 @@ # This value is automatically updated by 'make bump'. # The version number is used to find the correct shared library file. # We currently only support binding to an exact version of libyamlscript. +NAME = 'yamlscript' yamlscript_version = '0.1.96' -import os, sys import ctypes import json +import os +import sys +from pathlib import Path # Require Python 3.6 or greater: assert sys.version_info >= (3, 6), \ "Python 3.6 or greater required for 'yamlscript'." -# Find the libyamlscript shared library file path: -def find_libyamlscript_path(): + +def get_libyamlscript_name(): # We currently only support platforms that GraalVM supports. # And Windows is not yet implemented... # Confirm platform and determine file extension: + if sys.platform == 'linux': so = 'so' elif sys.platform == 'darwin': @@ -44,6 +48,17 @@ def find_libyamlscript_path(): libyamlscript_name = \ "libyamlscript.%s.%s" % (so, yamlscript_version) + return libyamlscript_name + +# Find the libyamlscript shared library file path: +def find_libyamlscript_path(): + libyamlscript_name = get_libyamlscript_name() + + # First check for shared library in bindings directory, in case of binary wheel distribution. + path = (Path(__file__).parent / libyamlscript_name).absolute() + if path.exists(): + return str(path) + # Use LD_LIBRARY_PATH to find libyamlscript shared library, or default to # '/usr/local/lib' (where it is installed by default): ld_library_path = os.environ.get('LD_LIBRARY_PATH') @@ -69,7 +84,8 @@ def find_libyamlscript_path(): return libyamlscript_path # Load libyamlscript shared library: -libyamlscript = ctypes.CDLL(find_libyamlscript_path()) +libyamlscript_path = find_libyamlscript_path() +libyamlscript = ctypes.CDLL(libyamlscript_path) # Create binding to 'load_ys_to_json' function: load_ys_to_json = libyamlscript.load_ys_to_json @@ -138,3 +154,21 @@ def __del__(self): rc = libyamlscript.graal_tear_down_isolate(self.isolatethread) if rc != 0: raise Exception("Failed to tear down isolate") + + +def show_info(): + """ + + Show YAMLScript package info for debugging purposes + + """ + from textwrap import dedent + info = f""" + {yamlscript_version=} + {sys.platform=} + {libyamlscript_path=} + {YAMLScript().load("inc: 41")=} + {YAMLScript().load("!YS-v0\ninc: 41")=} + """ + info = dedent(info).strip() + print(info) diff --git a/python/setup.py b/python/setup.py index 59963b8e1..f5fcc55f4 100644 --- a/python/setup.py +++ b/python/setup.py @@ -1,16 +1,62 @@ +import sys +from pathlib import Path + +from setuptools import setup, Extension +from setuptools.command.build_ext import build_ext + version = '0.1.96' -from setuptools import setup -import pathlib +NAME = 'yamlscript' +PACKAGE_DIR = 'lib' +EXTENSIONS = dict(linux='so', darwin='dylib') +so = EXTENSIONS.get(sys.platform) -root = pathlib.Path(__file__).parent.resolve() +if not so: + raise RuntimeError(f"Unsupported platform: {sys.platform}. Should be one of {','.join(EXTENSIONS.keys())}.") +root = Path(__file__).parent.resolve() +filename = f"lib{NAME}.{so}.{version}" +path_lib = root / PACKAGE_DIR / NAME / filename long_description = \ (root / '.long_description.md') \ .read_text(encoding='utf-8') +class LibYAMLScriptExtensionBuilder(build_ext): + """ + + The shared library is pre-built, but we need to provide setuptools + with a dummy extension builder, so that it knows that the wheels + aren't just pure-Python and tags them with the correct + platform/architecture-specific naming and metadata. + + """ + + def build_extensions(self): + """ + + Build nothing. + + """ + pass + + +if path_lib.exists(): + # If the shared library exists, only then add the relevant extension builder. + # Otherwise keep the package generic. + extension_config = dict( + ext_modules=[ + Extension(name=NAME, sources=[]) + ], + cmdclass=dict( + build_ext=LibYAMLScriptExtensionBuilder, + ), + package_data={NAME: [filename]}, + ) +else: + extension_config = dict() + setup( - name = 'yamlscript', + name=NAME, version = version, description = 'Program in YAML — Code is Data', license = 'MIT', @@ -19,10 +65,10 @@ author = 'Ingy döt Net', author_email = 'ingy@ingy.net', - packages = ['yamlscript'], - package_dir = {'': 'lib'}, + packages=[NAME], + package_dir={'': PACKAGE_DIR}, - python_requires = '>=3.6, <4', + python_requires='>=3.6, <4', install_requires = [ 'pyyaml', ], @@ -45,4 +91,12 @@ long_description = long_description, long_description_content_type = 'text/markdown', + + entry_points=dict( + console_scripts=[ + f'ys-py-show-info = {NAME}:show_info', + ], + ), + + **extension_config, )