mirror of
https://github.com/OpenHands/OpenHands.git
synced 2026-03-22 13:47:19 +08:00
Runtime build fixes for OpenHands as a python library (#3989)
This commit is contained in:
2
.github/workflows/pypi-release.yml
vendored
2
.github/workflows/pypi-release.yml
vendored
@@ -26,6 +26,6 @@ jobs:
|
|||||||
- name: Install Poetry Dependencies
|
- name: Install Poetry Dependencies
|
||||||
run: poetry install --no-interaction --no-root
|
run: poetry install --no-interaction --no-root
|
||||||
- name: Build poetry project
|
- name: Build poetry project
|
||||||
run: poetry build -v
|
run: ./build.sh
|
||||||
- name: publish
|
- name: publish
|
||||||
run: poetry publish -u __token__ -p ${{ secrets.PYPI_TOKEN }}
|
run: poetry publish -u __token__ -p ${{ secrets.PYPI_TOKEN }}
|
||||||
|
|||||||
5
build.sh
Executable file
5
build.sh
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
cp pyproject.toml poetry.lock openhands
|
||||||
|
poetry build -v
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
|
__package_name__ = 'openhands_ai'
|
||||||
|
|
||||||
|
|
||||||
def get_version():
|
def get_version():
|
||||||
try:
|
try:
|
||||||
from importlib.metadata import PackageNotFoundError, version
|
from importlib.metadata import PackageNotFoundError, version
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return version('openhands-ai')
|
return version(__package_name__)
|
||||||
except PackageNotFoundError:
|
except PackageNotFoundError:
|
||||||
pass
|
pass
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@@ -16,7 +18,7 @@ def get_version():
|
|||||||
from pkg_resources import DistributionNotFound, get_distribution
|
from pkg_resources import DistributionNotFound, get_distribution
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return get_distribution('openhands-ai').version
|
return get_distribution(__package_name__).version
|
||||||
except DistributionNotFound:
|
except DistributionNotFound:
|
||||||
pass
|
pass
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import importlib.metadata
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
import docker
|
import docker
|
||||||
@@ -10,6 +10,7 @@ from dirhash import dirhash
|
|||||||
from jinja2 import Environment, FileSystemLoader
|
from jinja2 import Environment, FileSystemLoader
|
||||||
|
|
||||||
import openhands
|
import openhands
|
||||||
|
from openhands import __package_name__
|
||||||
from openhands import __version__ as oh_version
|
from openhands import __version__ as oh_version
|
||||||
from openhands.core.logger import openhands_logger as logger
|
from openhands.core.logger import openhands_logger as logger
|
||||||
from openhands.runtime.builder import DockerRuntimeBuilder, RuntimeBuilder
|
from openhands.runtime.builder import DockerRuntimeBuilder, RuntimeBuilder
|
||||||
@@ -22,55 +23,50 @@ def get_runtime_image_repo():
|
|||||||
def _put_source_code_to_dir(temp_dir: str):
|
def _put_source_code_to_dir(temp_dir: str):
|
||||||
"""Builds the project source tarball directly in temp_dir and unpacks it.
|
"""Builds the project source tarball directly in temp_dir and unpacks it.
|
||||||
The OpenHands source code ends up in the temp_dir/code directory.
|
The OpenHands source code ends up in the temp_dir/code directory.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
- temp_dir (str): The directory to put the source code in
|
- temp_dir (str): The directory to put the source code in
|
||||||
"""
|
"""
|
||||||
if not os.path.isdir(temp_dir):
|
if not os.path.isdir(temp_dir):
|
||||||
raise RuntimeError(f'Temp directory {temp_dir} does not exist')
|
raise RuntimeError(f'Temp directory {temp_dir} does not exist')
|
||||||
|
|
||||||
project_root = os.path.dirname(os.path.dirname(os.path.abspath(openhands.__file__)))
|
dest_dir = os.path.join(temp_dir, 'code')
|
||||||
logger.info(f'Building source distribution using project root: {project_root}')
|
openhands_dir = None
|
||||||
|
|
||||||
# Fetch the correct version from pyproject.toml
|
try:
|
||||||
package_version = oh_version
|
# Try to get the source directory from the installed package
|
||||||
tarball_filename = f'openhands_ai-{package_version}.tar.gz'
|
distribution = importlib.metadata.distribution(__package_name__)
|
||||||
tarball_path = os.path.join(temp_dir, tarball_filename)
|
source_dir = os.path.dirname(distribution.locate_file(__package_name__))
|
||||||
|
openhands_dir = os.path.join(source_dir, 'openhands')
|
||||||
|
except importlib.metadata.PackageNotFoundError:
|
||||||
|
pass
|
||||||
|
|
||||||
# Run "python -m build -s" on project_root to create project tarball directly in temp_dir
|
if openhands_dir is not None and os.path.isdir(openhands_dir):
|
||||||
_cleaned_project_root = project_root.replace(
|
logger.info(f'Package {__package_name__} found')
|
||||||
' ', r'\ '
|
shutil.copytree(openhands_dir, os.path.join(dest_dir, 'openhands'))
|
||||||
) # escape spaces in the project root
|
# note: "pyproject.toml" and "poetry.lock" are included in the openhands
|
||||||
result = subprocess.run(
|
# package, so we need to move them out to the top-level directory
|
||||||
f'python -m build -s -o "{temp_dir}" {_cleaned_project_root}',
|
for filename in ['pyproject.toml', 'poetry.lock']:
|
||||||
shell=True,
|
shutil.move(os.path.join(dest_dir, 'openhands', filename), dest_dir)
|
||||||
stdout=subprocess.PIPE,
|
else:
|
||||||
stderr=subprocess.PIPE,
|
# If package is not found, build from source code
|
||||||
)
|
project_root = os.path.dirname(
|
||||||
logger.info(result.stdout.decode())
|
os.path.dirname(os.path.abspath(openhands.__file__))
|
||||||
err_logs = result.stderr.decode()
|
)
|
||||||
if err_logs:
|
logger.info(f'Building source distribution using project root: {project_root}')
|
||||||
logger.error(err_logs)
|
|
||||||
|
|
||||||
if result.returncode != 0:
|
# Copy the 'openhands' directory
|
||||||
logger.error(f'Image build failed:\n{result}')
|
openhands_dir = os.path.join(project_root, 'openhands')
|
||||||
raise RuntimeError(f'Image build failed:\n{result}')
|
if not os.path.isdir(openhands_dir):
|
||||||
|
raise RuntimeError(f"'openhands' directory not found in {project_root}")
|
||||||
|
shutil.copytree(openhands_dir, os.path.join(dest_dir, 'openhands'))
|
||||||
|
|
||||||
if not os.path.exists(tarball_path):
|
# Copy pyproject.toml and poetry.lock files
|
||||||
logger.error(f'Source distribution not found at {tarball_path}. (Do you need to run `make build`?)')
|
for file in ['pyproject.toml', 'poetry.lock']:
|
||||||
raise RuntimeError(f'Source distribution not found at {tarball_path}')
|
src_file = os.path.join(project_root, file)
|
||||||
logger.info(f'Source distribution created at {tarball_path}')
|
dest_file = os.path.join(dest_dir, file)
|
||||||
|
shutil.copy2(src_file, dest_file)
|
||||||
|
|
||||||
# Unzip the tarball
|
logger.info(f'Unpacked source code directory: {dest_dir}')
|
||||||
shutil.unpack_archive(tarball_path, temp_dir)
|
|
||||||
# Remove the tarball
|
|
||||||
os.remove(tarball_path)
|
|
||||||
# Rename the directory containing the code to 'code'
|
|
||||||
os.rename(
|
|
||||||
os.path.join(temp_dir, f'openhands_ai-{package_version}'),
|
|
||||||
os.path.join(temp_dir, 'code'),
|
|
||||||
)
|
|
||||||
logger.info(f'Unpacked source code directory: {os.path.join(temp_dir, "code")}')
|
|
||||||
|
|
||||||
|
|
||||||
def _generate_dockerfile(
|
def _generate_dockerfile(
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ authors = ["OpenHands"]
|
|||||||
license = "MIT"
|
license = "MIT"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
repository = "https://github.com/All-Hands-AI/OpenHands"
|
repository = "https://github.com/All-Hands-AI/OpenHands"
|
||||||
include = ["poetry.lock"]
|
|
||||||
packages = [
|
packages = [
|
||||||
{ include = "openhands/**/*" }
|
{ include = "openhands/**/*" }
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -54,14 +54,12 @@ def _check_source_code_in_dir(temp_dir):
|
|||||||
# check the source file is the same as the current code base
|
# check the source file is the same as the current code base
|
||||||
assert os.path.exists(os.path.join(code_dir, 'pyproject.toml'))
|
assert os.path.exists(os.path.join(code_dir, 'pyproject.toml'))
|
||||||
|
|
||||||
# The source code should only include the `openhands` folder, but not the other folders
|
# The source code should only include the `openhands` folder,
|
||||||
|
# and pyproject.toml & poetry.lock that are needed to build the runtime image
|
||||||
assert set(os.listdir(code_dir)) == {
|
assert set(os.listdir(code_dir)) == {
|
||||||
'openhands',
|
'openhands',
|
||||||
'pyproject.toml',
|
'pyproject.toml',
|
||||||
'poetry.lock',
|
'poetry.lock',
|
||||||
'LICENSE',
|
|
||||||
'README.md',
|
|
||||||
'PKG-INFO',
|
|
||||||
}
|
}
|
||||||
assert os.path.exists(os.path.join(code_dir, 'openhands'))
|
assert os.path.exists(os.path.join(code_dir, 'openhands'))
|
||||||
assert os.path.isdir(os.path.join(code_dir, 'openhands'))
|
assert os.path.isdir(os.path.join(code_dir, 'openhands'))
|
||||||
|
|||||||
Reference in New Issue
Block a user