diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..a177afa2 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,68 @@ +name: release + +# Builds the distribution on every pull request so packaging problems surface in +# review, and publishes to PyPI when a version tag is pushed. +on: + pull_request: + workflow_dispatch: + inputs: + publish_to_testpypi: + description: "Also publish the built distribution to TestPyPI" + type: boolean + default: false + push: + tags: ["v*"] + +jobs: + build: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-python@v6 + with: + python-version: "3.11" + - name: Install build tooling + run: python -m pip install --upgrade build twine + - name: Build sdist and wheel + run: python -m build + - name: Check metadata + run: twine check dist/* + - name: Show what is in the wheel + run: python -m zipfile -l dist/*.whl + - uses: actions/upload-artifact@v7 + with: + name: dist + path: dist/ + + # Dry run, on demand: same artifact, TestPyPI instead of PyPI. TestPyPI keeps + # every version it has seen, so a repeat run needs a bumped chbversion. + publish-testpypi: + if: github.event_name == 'workflow_dispatch' && inputs.publish_to_testpypi + needs: build + runs-on: ubuntu-22.04 + environment: testpypi + permissions: + id-token: write + steps: + - uses: actions/download-artifact@v7 + with: + name: dist + path: dist/ + - uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ + + publish: + # Tags only. Trusted publishing, so no API token is stored in the repository. + if: startsWith(github.ref, 'refs/tags/v') + needs: build + runs-on: ubuntu-22.04 + environment: pypi + permissions: + id-token: write + steps: + - uses: actions/download-artifact@v7 + with: + name: dist + path: dist/ + - uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/README.md b/README.md index 5d78cd73..393ae058 100644 --- a/README.md +++ b/README.md @@ -14,15 +14,28 @@ interface can be invoked as follows (adjust paths for actual location): This will show an [overview](doc/cli-output.txt) of the commands available. -At present the analyzer supports x86 (32-bits), both ELF and PE32, mips32, -and arm32 (both ARM and Thumb-2) binaries (ELF only); arm32 is stil under active +## Installing the python API + +The python API is published on PyPI, so a script that imports `chb` needs no +`PYTHONPATH`: + +``` +> pip install codehawk-binary +> python -c "import chb; print(chb.__file__)" +``` + +The distribution contains the python API only. Running an analysis additionally +needs the CodeHawk Binary Analyzer (see Requirements section). + +At present the analyzer supports x86 (32-bits), both ELF and PE32, mips32, and +arm32 (both ARM and Thumb-2) binaries (ELF only); arm32 is stil under active development and thus somewhat experimental. ### Requirements Ensure you have `zip` installed. -The command-line interface requires python3.5 or higher. +The command-line interface requires python 3.9 or higher. Build instructions for the CodeHawk Binary Analyzer are available [here](https://github.com/static-analysis-engineering/codehawk/tree/master/CodeHawk). diff --git a/chb/py.typed b/chb/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..f42b4ea1 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,56 @@ +[build-system] +requires = ["setuptools>=77"] +build-backend = "setuptools.build_meta" + +[project] +name = "codehawk-binary" +description = "Python API for the CodeHawk Binary Analyzer" +readme = "README.md" +license = "MIT" +license-files = ["LICENSE"] +requires-python = ">=3.9" +authors = [{ name = "Aarno Labs LLC", email = "codehawk@aarno-labs.com" }] +keywords = ["binary analysis", "reverse engineering", "static analysis", "abstract interpretation"] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "Intended Audience :: Science/Research", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + "Topic :: Security", + "Topic :: Software Development :: Disassemblers", + "Typing :: Typed", +] +# The package imports nothing outside the standard library. +dependencies = [] +dynamic = ["version"] + +[project.urls] +Homepage = "https://github.com/static-analysis-engineering/CodeHawk-Binary" +Source = "https://github.com/static-analysis-engineering/CodeHawk-Binary" +Issues = "https://github.com/static-analysis-engineering/CodeHawk-Binary/issues" + +[tool.setuptools.dynamic] +version = { attr = "chb.app.CHVersion.chbversion" } + +# Include only the chb package in the distribution. Without this filter, package +# discovery also picks up doc/ and tests/ and installs them as importable +# top-level names in every environment that installs this package. +[tool.setuptools.packages.find] +include = ["chb*"] + +# ConfigLocal.py is gitignored local configuration that exists in most working +# copies. It is a .py file inside the package, so discovery would otherwise bake +# a developer's absolute analyzer paths into the distribution. +[tool.setuptools.exclude-package-data] +"chb.util" = ["ConfigLocal.py"] + +# Config() opens each of these by path inside the installed package, so they must +# be included in the wheel. A wheel holding only .py files imports fine and then +# fails at runtime with FileNotFoundError under site-packages. +[tool.setuptools.package-data] +"chb" = ["py.typed"] +"chb.summaries" = ["bchsummaries.jar", "bch_header.c"] +"chb.util" = ["localetable.json", "ConfigLocal.template"] +"chb.arm.opcodes" = ["opcodes_covered.json"] +"chb.pwr.opcodes" = ["opcodes_covered.json"] diff --git a/setup.py b/setup.py deleted file mode 100644 index 0067fcee..00000000 --- a/setup.py +++ /dev/null @@ -1,10 +0,0 @@ -from setuptools import setup, find_namespace_packages - -setup( - name="chb", - packages=find_namespace_packages(), - include_package_data=True, - zip_safe=False, - entry_points={}, - install_requires=[], -)