diff --git a/.craft.yml b/.craft.yml index 484d7a9..01423ff 100644 --- a/.craft.yml +++ b/.craft.yml @@ -7,3 +7,7 @@ targets: - name: pypi - name: sentry-pypi internalPypiRepo: getsentry/pypi + +requireNames: + - /^devservices-darwin$/ + - /^devservices-linux$/ diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c90680e..15a2927 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,22 +4,107 @@ on: branches: - main - release/** + pull_request: jobs: dist: name: Create Distribution - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 timeout-minutes: 10 steps: - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5.1.1 with: python-version: 3.12 + + - name: Install dev requirements + run: pip install -r requirements-dev.txt + - name: "Prepare Artifacts" + run: python -m build + + - name: Cache dist + uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 + with: + path: dist + key: devservices-dist-${{ github.sha }} + + binary: + name: Build on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-22.04, macos-14] + include: + - os: ubuntu-22.04 + asset_name: devservices-linux + - os: macos-14 + asset_name: devservices-darwin + + steps: + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5.1.1 + with: + python-version: 3.12 + + - name: Install pyoxidizer + run: pip install pyoxidizer==0.24.0 + + - name: Install dev requirements + run: pip install -r requirements-dev.txt + + - name: Generate metadata + run: python -m build --sdist --no-isolation + + - name: Build binary + run: pyoxidizer build --release + + - name: Locate binary + id: locate_binary + shell: bash run: | - pip install build - python -m build - - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + BINARY_PATH=$(find build -name devservices -type f) + mkdir -p binaries + cp $BINARY_PATH binaries/${{ matrix.asset_name }} + + - name: Cache binary + uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 + with: + path: binaries/${{ matrix.asset_name }} + key: ${{ matrix.asset_name }}-${{ github.sha }} + + + upload-artifacts: + name: Upload build artifacts + runs-on: ubuntu-22.04 + needs: [dist, binary] + if: github.event_name != 'pull_request' + steps: + - name: Restore dist cache + uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 + with: + path: dist + key: devservices-dist-${{ github.sha }} + + - name: Restore Linux binary cache + uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 + with: + path: binaries/devservices-linux + key: devservices-linux-${{ github.sha }} + + - name: Restore macOS binary cache + uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 # v4.0.1 + with: + path: binaries/devservices-darwin + key: devservices-darwin-${{ github.sha }} + + - name: Upload combined artifacts + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: ${{ github.sha }} - path: dist/* + path: | + dist/* + binaries/* + diff --git a/devservices/main.py b/devservices/main.py index 6e9187b..4831209 100644 --- a/devservices/main.py +++ b/devservices/main.py @@ -30,6 +30,7 @@ def cleanup() -> None: def main() -> None: parser = argparse.ArgumentParser( + prog="devservices", description="CLI tool for managing service dependencies.", usage="devservices [-h] [--version] COMMAND ...", ) diff --git a/pyoxidizer.bzl b/pyoxidizer.bzl new file mode 100644 index 0000000..81718fd --- /dev/null +++ b/pyoxidizer.bzl @@ -0,0 +1,107 @@ +# This file defines how PyOxidizer application building and packaging is +# performed. See PyOxidizer's documentation at +# https://gregoryszorc.com/docs/pyoxidizer/stable/pyoxidizer.html for details +# of this configuration file format. + +# Configuration files consist of functions which define build "targets." +# This function creates a Python executable and installs it in a destination +# directory. +def make_exe(): + # Obtain the default PythonDistribution for our build target. We link + # this distribution into our produced executable and extract the Python + # standard library from it. + dist = default_python_distribution() + + # This function creates a `PythonPackagingPolicy` instance, which + # influences how executables are built and how resources are added to + # the executable. You can customize the default behavior by assigning + # to attributes and calling functions. + policy = dist.make_python_packaging_policy() + + # Resources are loaded from "in-memory" or "filesystem-relative" paths. + # The locations to attempt to add resources to are defined by the + # `resources_location` and `resources_location_fallback` attributes. + # The former is the first/primary location to try and the latter is + # an optional fallback. + + # Use in-memory location for adding resources by default. + policy.resources_location = "in-memory" + + # Attempt to add resources relative to the built binary when + # `resources_location` fails. + policy.resources_location_fallback = "filesystem-relative:prefix" + + # This variable defines the configuration of the embedded Python + # interpreter. By default, the interpreter will run a Python REPL + # using settings that are appropriate for an "isolated" run-time + # environment. + # + # The configuration of the embedded Python interpreter can be modified + # by setting attributes on the instance. Some of these are + # documented below. + python_config = dist.make_python_interpreter_config() + + # Set initial value for `sys.path`. If the string `$ORIGIN` exists in + # a value, it will be expanded to the directory of the built executable. + python_config.module_search_paths = ["$ORIGIN/devservices"] + + # Run a Python module as __main__ when the interpreter starts. + python_config.run_module = "devservices.main" + + # Produce a PythonExecutable from a Python distribution, embedded + # resources, and other options. The returned object represents the + # standalone executable that will be built. + exe = dist.to_python_executable( + name="devservices", + + # If no argument passed, the default `PythonPackagingPolicy` for the + # distribution is used. + packaging_policy=policy, + + # If no argument passed, the default `PythonInterpreterConfig` is used. + config=python_config, + ) + + # Invoke `pip install` using a requirements file and add the collected resources + # to our binary. + exe.add_python_resources(exe.pip_install(["-r", "requirements.txt"])) + + # Read Python files from a local directory and add them to our embedded + # context, taking just the resources belonging to the `foo` and `bar` + # Python packages. + exe.add_python_resources(exe.read_package_root( + path=".", + packages=["devservices"], + )) + + # Add the generated metadata + exe.add_python_resources(exe.read_package_root( + path=".", + packages=["devservices.egg-info"], # or the appropriate metadata directory name + )) + + # Return our `PythonExecutable` instance so it can be built and + # referenced by other consumers of this target. + return exe + +def make_embedded_resources(exe): + return exe.to_embedded_resources() + +def make_install(exe): + # Create an object that represents our installed application file layout. + files = FileManifest() + + # Add the generated executable to our install layout in the root directory. + files.add_python_resource(".", exe) + + return files + + +# Tell PyOxidizer about the build targets defined above. +register_target("exe", make_exe) +register_target("resources", make_embedded_resources, depends=["exe"], default_build_script=True) +register_target("install", make_install, depends=["exe"], default=True) + +# Resolve whatever targets the invoker of this configuration file is requesting +# be resolved. +resolve_targets() diff --git a/pyproject.toml b/pyproject.toml index ee8ee2a..580fcfa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools>=45", "wheel"] +requires = ["setuptools>=70", "wheel"] build-backend = "setuptools.build_meta" [project] diff --git a/requirements-dev.txt b/requirements-dev.txt index bf0afe8..b8d3ebf 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -3,4 +3,7 @@ black==24.4.2 mypy==1.11.2 pre-commit==3.6.0 pytest==8.1.1 -types-PyYAML==6.0.11 \ No newline at end of file +types-PyYAML==6.0.11 +setuptools==70.0.0 +build==0.8.0 +wheel==0.42.0 \ No newline at end of file