Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ __pycache__/
*$py.class

/artifacts
scripts/artifacts


# C extensions
Expand Down Expand Up @@ -108,3 +109,4 @@ target/
*.dylib
*.dll
*.so
src/c2pa/libs/
2 changes: 1 addition & 1 deletion MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
include src/c2pa/libs/*.dylib
include src/c2pa/libs/*.dll
include src/c2pa/libs/*.so
include src/c2pa/libs/*.so
29 changes: 22 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,34 @@
# Start from clean env: Delete `.venv`, then `python3 -m venv .venv`
# Pre-requisite: Python virtual environment is active (source .venv/bin/activate)

release:
cargo build --release
clean:
rm -rf artifacts/ build/ dist/

clean-c2pa-env:
python3 -m pip uninstall -y c2pa
python3 -m pip cache purge

build-python:
rm -rf c2pa/c2pa
python3 -m pip uninstall -y maturin
python3 -m pip uninstall -y uniffi
python3 -m pip install -r requirements.txt
maturin develop
python3 -m pip install -r requirements-dev.txt
pip install -e .

test:
python3 ./tests/test_unit_tests.py
python3 ./tests/test_api.py

test-local-wheel-build:
# Clean any existing builds
rm -rf build/ dist/
# Download artifacts and place them where they should go
python scripts/download_artifacts.py c2pa-v0.49.5
# Install Python
python3 -m pip install -r requirements.txt
python3 -m pip install -r requirements-dev.txt
python setup.py bdist_wheel
# Install local build in venv
pip install $$(ls dist/*.whl)
# Verify installation in local venv
python -c "import c2pa; print('C2PA package installed at:', c2pa.__file__)"

publish: release
python3 -m pip install twine
Expand Down
9 changes: 9 additions & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Build dependencies
wheel==0.41.2 # For building wheels
setuptools==68.0.0 # For building packages

# Testing dependencies
pytest==7.4.0

# for downloading the library artifacts
requests>=2.0.0
8 changes: 4 additions & 4 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ setuptools==68.0.0 # For building packages
toml ==0.10.2 # For reading pyproject.toml files

# Testing dependencies
pytest==7.4.0
# only used in the training example
cryptography>=41.0.0
pytest==7.4.0
# only used in the training example
cryptography>=41.0.0
# for downloading the library artifacts
requests>=2.0.0
requests>=2.0.0
112 changes: 94 additions & 18 deletions scripts/download_artifacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,72 @@
from pathlib import Path
import zipfile
import io
import shutil
import platform
import subprocess

# Constants
REPO_OWNER = "contentauth"
REPO_NAME = "c2pa-rs"
GITHUB_API_BASE = "https://api.github.com"
ARTIFACTS_DIR = Path("artifacts")
SCRIPTS_ARTIFACTS_DIR = Path("scripts/artifacts")
Copy link
Collaborator Author

@tmathern tmathern May 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we can run this from the root of the repo. I used this instead of build.py because build.py seems broken, whereas that one works. Let me know if build.py was meant to stay or not - but then it likely needs debugging.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did work on build.py to get it functional, but I can merge that in.

ROOT_ARTIFACTS_DIR = Path("artifacts")

def detect_os():
"""Detect the operating system and return the corresponding platform identifier."""
system = platform.system().lower()
if system == "darwin":
return "apple-darwin"
elif system == "linux":
return "unknown-linux-gnu"
elif system == "windows":
return "pc-windows-msvc"
else:
raise ValueError(f"Unsupported operating system: {system}")

def detect_arch():
"""Detect the CPU architecture and return the corresponding identifier."""
machine = platform.machine().lower()

# Handle common architecture names
if machine in ["x86_64", "amd64"]:
return "x86_64"
elif machine in ["arm64", "aarch64"]:
return "aarch64"
else:
raise ValueError(f"Unsupported CPU architecture: {machine}")

def get_platform_identifier():
"""Get the full platform identifier (arch-os) for the current system,
matching the identifiers used by the Github publisher.
Returns one of:
- universal-apple-darwin (for Mac)
- x86_64-pc-windows-msvc (for Windows 64-bit)
- x86_64-unknown-linux-gnu (for Linux 64-bit)
"""
system = platform.system().lower()

if system == "darwin":
return "universal-apple-darwin"
elif system == "windows":
return "x86_64-pc-windows-msvc"
elif system == "linux":
return "x86_64-unknown-linux-gnu"
else:
raise ValueError(f"Unsupported operating system: {system}")

def get_release_by_tag(tag):
"""Get release information for a specific tag from GitHub."""
url = f"{GITHUB_API_BASE}/repos/{REPO_OWNER}/{REPO_NAME}/releases/tags/{tag}"
print(f"Fetching release information from {url}...")
response = requests.get(url)
response.raise_for_status()
return response.json()

def download_and_extract_libs(url, platform_name):
"""Download a zip artifact and extract only the libs folder."""
print(f"Downloading artifact for {platform_name}...")
platform_dir = ARTIFACTS_DIR / platform_name
platform_dir = SCRIPTS_ARTIFACTS_DIR / platform_name
platform_dir.mkdir(parents=True, exist_ok=True)

response = requests.get(url)
Expand All @@ -31,12 +79,28 @@ def download_and_extract_libs(url, platform_name):
with zipfile.ZipFile(io.BytesIO(response.content)) as zip_ref:
# Extract only files inside the libs/ directory
for member in zip_ref.namelist():
print(f" Processing zip member: {member}")
if member.startswith("lib/") and not member.endswith("/"):
print(f" Processing lib file from downloadedzip: {member}")
target_path = platform_dir / os.path.relpath(member, "lib")
print(f" Moving file to target path: {target_path}")
target_path.parent.mkdir(parents=True, exist_ok=True)
with zip_ref.open(member) as source, open(target_path, "wb") as target:
target.write(source.read())
print(f"Successfully downloaded and extracted libraries for {platform_name}")

print(f"Done downloading and extracting libraries for {platform_name}")

def copy_artifacts_to_root():
"""Copy the artifacts folder from scripts/artifacts to the root of the repository."""
if not SCRIPTS_ARTIFACTS_DIR.exists():
print("No artifacts found in scripts/artifacts")
return

print("Copying artifacts from scripts/artifacts to root...")
if ROOT_ARTIFACTS_DIR.exists():
shutil.rmtree(ROOT_ARTIFACTS_DIR)
shutil.copytree(SCRIPTS_ARTIFACTS_DIR, ROOT_ARTIFACTS_DIR)
print("Done copying artifacts")

def main():
if len(sys.argv) < 2:
Expand All @@ -46,31 +110,43 @@ def main():

release_tag = sys.argv[1]
try:
ARTIFACTS_DIR.mkdir(exist_ok=True)
SCRIPTS_ARTIFACTS_DIR.mkdir(exist_ok=True)
print(f"Fetching release information for tag {release_tag}...")
release = get_release_by_tag(release_tag)
print(f"Found release: {release['tag_name']}")
print(f"Found release: {release['tag_name']} \n")

for asset in release['assets']:
if not asset['name'].endswith('.zip'):
continue
# Get the platform identifier for the current system
env_platform = os.environ.get("C2PA_LIBS_PLATFORM")
if env_platform:
print(f"Using platform from environment variable C2PA_LIBS_PLATFORM: {env_platform}")
platform_id = env_platform or get_platform_identifier()
platform_source = "environment variable" if env_platform else "auto-detection"
print(f"Target platform: {platform_id} (set through{platform_source})")

# Example asset name: c2pa-v0.49.5-aarch64-apple-darwin.zip
# Platform name: aarch64-apple-darwin
parts = asset['name'].split('-')
if len(parts) < 4:
continue # Unexpected naming, skip
platform_name = '-'.join(parts[3:]).replace('.zip', '')
# Construct the expected asset name
expected_asset_name = f"{release_tag}-{platform_id}.zip"
print(f"Looking for asset: {expected_asset_name}")

download_and_extract_libs(asset['browser_download_url'], platform_name)
# Find the matching asset in the release
matching_asset = None
for asset in release['assets']:
if asset['name'] == expected_asset_name:
matching_asset = asset
break

print("\nAll artifacts have been downloaded and extracted successfully!")
if matching_asset:
print(f"Found matching asset: {matching_asset['name']}")
download_and_extract_libs(matching_asset['browser_download_url'], platform_id)
print("\nArtifacts have been downloaded and extracted successfully!")
copy_artifacts_to_root()
else:
print(f"\nNo matching asset found: {expected_asset_name}")

except requests.exceptions.RequestException as e:
print(f"Error downloading artifacts: {e}", file=sys.stderr)
print(f"Error: {e}")
sys.exit(1)
except Exception as e:
print(f"Unexpected error: {e}", file=sys.stderr)
print(f"Error: {e}")
sys.exit(1)

if __name__ == "__main__":
Expand Down
20 changes: 10 additions & 10 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,29 +36,29 @@ def get_current_platform():

def copy_platform_libraries(platform_name, clean_first=False):
"""Copy libraries for a specific platform to the package libs directory.

Args:
platform_name: The platform to copy libraries for
clean_first: If True, remove existing files in PACKAGE_LIBS_DIR first
"""
platform_dir = ARTIFACTS_DIR / platform_name

# Ensure the platform directory exists and contains files
if not platform_dir.exists():
raise ValueError(f"Platform directory not found: {platform_dir}")

# Get list of all files in the platform directory
platform_files = list(platform_dir.glob('*'))
if not platform_files:
raise ValueError(f"No files found in platform directory: {platform_dir}")

# Clean and recreate the package libs directory if requested
if clean_first and PACKAGE_LIBS_DIR.exists():
shutil.rmtree(PACKAGE_LIBS_DIR)

# Ensure the package libs directory exists
PACKAGE_LIBS_DIR.mkdir(parents=True, exist_ok=True)

# Copy files from platform-specific directory to the package libs directory
for file in platform_files:
if file.is_file():
Expand All @@ -85,10 +85,10 @@ def find_available_platforms():
platform_dir = ARTIFACTS_DIR / platform_name
if platform_dir.exists() and any(platform_dir.iterdir()):
available_platforms.append(platform_name)

if not available_platforms:
raise ValueError("No platform-specific libraries found in artifacts directory")

return available_platforms

# For development installation
Expand All @@ -100,13 +100,13 @@ def find_available_platforms():
if 'bdist_wheel' in sys.argv:
available_platforms = find_available_platforms()
print(f"Found libraries for platforms: {', '.join(available_platforms)}")

for platform_name in available_platforms:
print(f"\nBuilding wheel for {platform_name}...")
try:
# Copy libraries for this platform (cleaning first)
copy_platform_libraries(platform_name, clean_first=True)

# Build the wheel
setup(
name="c2pa",
Expand Down
22 changes: 11 additions & 11 deletions src/c2pa/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,48 +23,48 @@ def get_latest_release() -> dict:
def download_artifact(url: str, platform_name: str) -> None:
"""Download and extract an artifact to the appropriate platform directory."""
print(f"Downloading artifact for {platform_name}...")

# Create platform directory
platform_dir = ARTIFACTS_DIR / platform_name
platform_dir.mkdir(parents=True, exist_ok=True)

# Download the zip file
response = requests.get(url)
response.raise_for_status()

# Extract the zip file
with zipfile.ZipFile(io.BytesIO(response.content)) as zip_ref:
# Extract all files to the platform directory
zip_ref.extractall(platform_dir)

print(f"Successfully downloaded and extracted artifacts for {platform_name}")

def download_artifacts() -> None:
"""Main function to download artifacts. Can be called as a script or from hatch."""
try:
# Create artifacts directory if it doesn't exist
ARTIFACTS_DIR.mkdir(exist_ok=True)

# Get latest release
print("Fetching latest release information...")
release = get_latest_release()
print(f"Found release: {release['tag_name']}")

# Download each asset
for asset in release['assets']:
# Skip non-zip files
if not asset['name'].endswith('.zip'):
continue

# Determine platform from asset name
# Example: c2pa-rs-v1.0.0-macosx-arm64.zip
platform_name = asset['name'].split('-')[-1].replace('.zip', '')

# Download and extract the artifact
download_artifact(asset['browser_download_url'], platform_name)

print("\nAll artifacts have been downloaded successfully!")

except requests.exceptions.RequestException as e:
print(f"Error downloading artifacts: {e}", file=sys.stderr)
sys.exit(1)
Expand Down Expand Up @@ -96,4 +96,4 @@ def initialize_build() -> None:

if __name__ == "__main__":
inject_version()
download_artifacts()
download_artifacts()
Loading
Loading