diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..b2f4dddd --- /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@v4 + - uses: actions/setup-python@v5 + 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@v4 + 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@v4 + 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@v4 + with: + name: dist + path: dist/ + - uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/README.md b/README.md index 5d78cd73..2b267436 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,22 @@ interface can be invoked as follows (adjust paths for actual location): This will show an [overview](doc/cli-output.txt) of the commands available. +## 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 analyzer itself (`chx86_analyze`, `parseFile`), which is built from the +[CodeHawk](https://github.com/static-analysis-engineering/codehawk) repository; +copy `chb/util/ConfigLocal.template` to `chb/util/ConfigLocal.py` and point it at +that build, as described in that file. + 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. 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..0ac5bd71 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,53 @@ +[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 = "info@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" } + +# Only the chb package ships. Without the include filter, package discovery also +# picks up doc/ and tests/ and publishes them as importable top-level names. +[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() resolves each of these by path at runtime, so they have to ship. +[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=[], -)