diff --git a/.github/workflows/build-wheel.yml b/.github/workflows/build-wheel.yml index 9b7b35c8..b1f56048 100644 --- a/.github/workflows/build-wheel.yml +++ b/.github/workflows/build-wheel.yml @@ -73,6 +73,9 @@ jobs: rm -f /io/dist/*-linux_*.whl " + # Install twine to verify wheels + pip install twine + # Verify the wheel was built echo "Contents of dist directory:" ls -la dist/ @@ -81,6 +84,10 @@ jobs: echo "Wheel filenames:" find dist -name "*.whl" -exec basename {} \; + # Verify wheel structure + echo "Verifying wheels using twine check" + twine check dist/* + - name: Build Windows wheel (x64) if: runner.os == 'Windows' shell: pwsh @@ -95,6 +102,7 @@ jobs: pip install -r requirements.txt pip install -r requirements-dev.txt pip install wheel + pip install twine # Download native artifacts Write-Host "Starting artifact download process..." @@ -110,8 +118,11 @@ jobs: # Build wheel python setup.py bdist_wheel --plat-name win_amd64 - - name: Build macOS wheel (Apple Silicon) - if: runner.os == 'macOS' && runner.arch == 'arm64' + # Verify wheel structure + twine check dist/* + + - name: Build macOS wheel + if: runner.os == 'macOS' run: | # Create necessary directories mkdir -p artifacts @@ -122,19 +133,48 @@ jobs: pip install -r requirements.txt pip install -r requirements-dev.txt pip install wheel + pip install twine # Download native artifacts python scripts/download_artifacts.py $C2PA_VERSION - # Build wheel - python setup.py bdist_wheel --plat-name macosx_11_0_arm64 - - # Rename wheel to ensure unique filename - cd dist - for wheel in *.whl; do - mv "$wheel" "${wheel/macosx_11_0_arm64/macosx_11_0_arm64}" - done - cd .. + # Detect actual CPU architecture at runtime + echo "Detecting CPU architecture..." + if [ "${{ inputs.architecture }}" = "universal2" ]; then + PLATFORM_NAME="macosx_10_9_universal2" + else + # Detect the actual CPU architecture of the runner + ACTUAL_ARCH=$(uname -m) + echo "Detected CPU architecture: $ACTUAL_ARCH" + + if [ "$ACTUAL_ARCH" = "arm64" ] || [ "$ACTUAL_ARCH" = "aarch64" ]; then + PLATFORM_NAME="macosx_11_0_arm64" + EXPECTED_ARTIFACT_DIR="aarch64-apple-darwin" + elif [ "$ACTUAL_ARCH" = "x86_64" ]; then + PLATFORM_NAME="macosx_10_9_x86_64" + EXPECTED_ARTIFACT_DIR="x86_64-apple-darwin" + else + echo "Unknown CPU architecture: $ACTUAL_ARCH" + exit 1 + fi + + # Verify we have the correct artifacts for this architecture + if [ ! -d "artifacts/$EXPECTED_ARTIFACT_DIR" ]; then + echo "Error: $EXPECTED_ARTIFACT_DIR artifacts not found for $ACTUAL_ARCH" + echo "Available artifacts:" + ls -la artifacts/ + exit 1 + fi + fi + + echo "Building wheel with platform name: $PLATFORM_NAME" + + # Build wheel with specific platform + python setup.py bdist_wheel --plat-name $PLATFORM_NAME + + # Verify wheel structure + echo "Verifying wheels using twine check" + twine check dist/* - name: Log wheel filename if: runner.os == 'Linux' || runner.os == 'macOS' diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6978367a..437cbc54 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -332,13 +332,21 @@ jobs: with: python-version: "3.10" artifact-name: wheels-macos-${{ matrix.target }} - runs-on: macos-latest + architecture: ${{ matrix.target }} + runs-on: ${{ matrix.runs-on }} c2pa-version: ${{ needs.read-version.outputs.c2pa-native-version }} secrets: github-token: ${{ secrets.GITHUB_TOKEN }} strategy: matrix: - target: [aarch64] + include: + - target: universal2 + runs-on: macos-latest + - target: x86_64 + runs-on: macos-13 + - target: aarch64 + runs-on: macos-latest + if: | github.event_name != 'pull_request' || github.event.pull_request.author_association == 'COLLABORATOR' || @@ -349,10 +357,15 @@ jobs: test-built-macos-wheel: name: Test macOS built wheel needs: build-macos-wheel - runs-on: macos-latest + runs-on: ${{ matrix.runs-on }} strategy: matrix: - target: [aarch64] + include: + - target: aarch64 + runs-on: macos-latest + - target: x86_64 + runs-on: macos-13 + if: | github.event_name != 'pull_request' || github.event.pull_request.author_association == 'COLLABORATOR' || diff --git a/.github/workflows/upload-test.yml b/.github/workflows/upload-test.yml deleted file mode 100644 index 8bacc094..00000000 --- a/.github/workflows/upload-test.yml +++ /dev/null @@ -1,98 +0,0 @@ -name: Upload Test for Pypi. - -on: - workflow_dispatch: - -jobs: - linux: - runs-on: ubuntu-latest - - strategy: - matrix: - target: [aarch64] - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: "3.10" - cache: "pip" - - run: pip install -r requirements.txt - - name: Setup QEMU - uses: docker/setup-qemu-action@v1 - if: ${{ matrix.target == 'aarch64' }} - - name: Build wheels - uses: PyO3/maturin-action@v1 - with: - target: ${{ matrix.target }} - maturin-version: "1.2.0" - args: --release --out dist --find-interpreter - sccache: "true" - manylinux: ${{ matrix.target == 'aarch64' && 'manylinux_2_28' || 'auto' }} - before-script-linux: | - pip install uniffi-bindgen==0.24.1 - - # ISSUE: https://github.com/sfackler/rust-openssl/issues/2036#issuecomment-1724324145 - # If we're running on rhel centos, install needed packages. - if command -v yum &> /dev/null; then - yum update -y && yum install -y perl-core openssl openssl-devel pkgconfig libatomic - - # If we're running on i686 we need to symlink libatomic - # in order to build openssl with -latomic flag. - if [[ ! -d "/usr/lib64" ]]; then - ln -s /usr/lib/libatomic.so.1 /usr/lib/libatomic.so - fi - else - # If we're running on debian-based system. - apt update -y && apt-get install -y libssl-dev openssl pkg-config - fi - - name: Upload wheels - uses: actions/upload-artifact@v4 - with: - name: wheels-${{ matrix.target }} - path: dist - - sdist: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - name: Build sdist - uses: PyO3/maturin-action@v1 - with: - command: sdist - args: --out dist - - name: Upload sdist - uses: actions/upload-artifact@v4 - with: - name: wheels - path: dist - - pypi-publish: - name: upload release to PyPI - runs-on: ubuntu-latest - needs: [linux, sdist] - - # Specifying a GitHub environment is optional, but strongly encouraged - environment: testpublish - permissions: - # IMPORTANT: this permission is mandatory for Trusted Publishing - id-token: write - steps: - # retrieve your distributions here - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Download aarch64 wheels artifact - uses: actions/download-artifact@v4 - with: - name: wheels-aarch64 - - - name: Download sdist artifact - uses: actions/download-artifact@v4 - with: - name: sdist - - - name: Publish package distributions to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - repository-url: https://test.pypi.org/legacy/ \ No newline at end of file diff --git a/scripts/download_artifacts.py b/scripts/download_artifacts.py index 3da65246..d04558a5 100644 --- a/scripts/download_artifacts.py +++ b/scripts/download_artifacts.py @@ -30,21 +30,33 @@ def detect_os(): 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" + if sys.platform == "darwin": + # On macOS, we need to check if we're running under Rosetta + # platform.processor() gives us the actual CPU, not the emulated one + if platform.processor() == 'arm': + return "aarch64" + else: + return "x86_64" else: - raise ValueError(f"Unsupported CPU architecture: {machine}") + # For other platforms, use platform.machine() + 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) + - universal-apple-darwin (for Mac, when CPU arch is None) + - aarch64-apple-darwin (for Mac ARM64) + - x86_64-apple-darwin (for Mac Intel) - x86_64-pc-windows-msvc (for Windows 64-bit) - x86_64-unknown-linux-gnu (for Linux x86_64) - aarch64-unknown-linux-gnu (for Linux ARM64) @@ -53,7 +65,15 @@ def get_platform_identifier(): machine = platform.machine().lower() if system == "darwin": - return "universal-apple-darwin" + # Identify the CPU architecture for macOS + current_arch = detect_arch() + if current_arch == "aarch64": + return "aarch64-apple-darwin" + elif current_arch == "x86_64": + return "x86_64-apple-darwin" + else: + # Fallback to universal if architecture detection fails + return "universal-apple-darwin" elif system == "windows": return "x86_64-pc-windows-msvc" elif system == "linux": @@ -92,7 +112,7 @@ def download_and_extract_libs(url, platform_name): 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}") + print(f" Processing lib file from downloaded zip: {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) diff --git a/setup.py b/setup.py index 03fccaa2..41f5b6d5 100644 --- a/setup.py +++ b/setup.py @@ -35,6 +35,29 @@ def get_version(): PACKAGE_LIBS_DIR = Path('src/c2pa/libs') # Where libraries will be copied for the wheel +def detect_arch(): + """Detect the CPU architecture and return the corresponding identifier.""" + + if sys.platform == "darwin": + # On macOS, we need to check if we're running under Rosetta + # platform.processor() gives us the actual CPU, not the emulated one + if platform.processor() == 'arm': + return "aarch64" + else: + return "x86_64" + else: + # For other platforms, use platform.machine() + 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() -> str: """Get a platform identifier (arch-os) for the current system, matching downloaded identifiers used by the Github publisher. @@ -44,7 +67,9 @@ def get_platform_identifier() -> str: cpu_arch: Optional CPU architecture for macOS. If not provided, returns universal build. Returns one of: - - universal-apple-darwin (for macOS) + - universal-apple-darwin (for Mac, when CPU arch is None) + - aarch64-apple-darwin (for Mac ARM64) + - x86_64-apple-darwin (for Mac Intel) - x86_64-pc-windows-msvc (for Windows 64-bit) - x86_64-unknown-linux-gnu (for Linux 64-bit) - aarch64-unknown-linux-gnu (for Linux ARM64) @@ -52,7 +77,15 @@ def get_platform_identifier() -> str: system = platform.system().lower() if system == "darwin": - return "universal-apple-darwin" + # Identify the CPU architecture for macOS + current_arch = detect_arch() + if current_arch == "aarch64": + return "aarch64-apple-darwin" + elif current_arch == "x86_64": + return "x86_64-apple-darwin" + else: + # Fallback to universal if architecture detection fails + return "universal-apple-darwin" elif system == "windows": return "x86_64-pc-windows-msvc" elif system == "linux": @@ -144,6 +177,68 @@ def find_available_platforms(): # For wheel building (both bdist_wheel and build) if 'bdist_wheel' in sys.argv or 'build' in sys.argv: + # Check if we're building a specific platform wheel (e.g., with --plat-name) + # If so, we need to determine which platform to build for + target_platform = None + + # Look for --plat-name argument to determine target platform + for i, arg in enumerate(sys.argv): + if arg == '--plat-name' and i + 1 < len(sys.argv): + plat_name = sys.argv[i + 1] + if 'arm64' in plat_name or 'aarch64' in plat_name: + target_platform = 'aarch64-apple-darwin' + elif 'x86_64' in plat_name: + target_platform = 'x86_64-apple-darwin' + elif 'universal2' in plat_name: + target_platform = 'universal-apple-darwin' + elif 'linux' in plat_name: + if 'aarch64' in plat_name: + target_platform = 'aarch64-unknown-linux-gnu' + else: + target_platform = 'x86_64-unknown-linux-gnu' + elif 'win' in plat_name: + target_platform = 'x86_64-pc-windows-msvc' + break + + if target_platform: + # Building for a specific platform + print(f"Building wheel for specific platform: {target_platform}") + + # Verify the target platform artifacts exist + platform_dir = ARTIFACTS_DIR / target_platform + if not platform_dir.exists(): + print(f"Error: Target platform artifacts not found: {platform_dir}") + print("Available artifacts:") + for p in ARTIFACTS_DIR.iterdir(): + if p.is_dir(): + print(f" - {p.name}") + sys.exit(1) + + # Copy libraries for this platform + copy_platform_libraries(target_platform, clean_first=True) + + # Build the wheel + setup( + name=PACKAGE_NAME, + version=VERSION, + package_dir={"": "src"}, + packages=find_namespace_packages(where="src"), + include_package_data=True, + package_data={ + "c2pa": ["libs/*"], # Include all files in libs directory + }, + classifiers=[ + "Programming Language :: Python :: 3", + get_platform_classifier(get_current_platform()), + ], + python_requires=">=3.10", + long_description=open("README.md").read(), + long_description_content_type="text/markdown", + license="MIT OR Apache-2.0", + ) + sys.exit(0) + + # Fallback to building for all available platforms (original behavior) available_platforms = find_available_platforms() if not available_platforms: print("No platform-specific libraries found. Building wheel without platform-specific libraries.") diff --git a/src/c2pa/lib.py b/src/c2pa/lib.py index af324b93..92d8eebc 100644 --- a/src/c2pa/lib.py +++ b/src/c2pa/lib.py @@ -33,7 +33,9 @@ def get_platform_identifier() -> str: matching the downloaded identifiers used by the Github publisher. Returns one of: - - universal-apple-darwin (for Mac, ARM or Intel) + - universal-apple-darwin (for Mac, when CPU arch is None) + - aarch64-apple-darwin (for Mac ARM64) + - x86_64-apple-darwin (for Mac Intel) - x86_64-pc-windows-msvc (for Windows 64-bit) - x86_64-unknown-linux-gnu (for Linux 64-bit) - aarch64-unknown-linux-gnu (for Linux ARM) @@ -41,7 +43,15 @@ def get_platform_identifier() -> str: system = platform.system().lower() if system == "darwin": - return "universal-apple-darwin" + # Identify the CPU architecture for macOS + current_arch = _get_architecture() + if current_arch == CPUArchitecture.ARM64.value: + return "aarch64-apple-darwin" + elif current_arch == CPUArchitecture.X86_64.value: + return "x86_64-apple-darwin" + else: + # Fallback to universal if architecture detection fails + return "universal-apple-darwin" elif system == "windows": return "x86_64-pc-windows-msvc" elif system == "linux":