diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..c5e940093f94dbe0663d08295f41a315b78b62c6 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,134 @@ +# +# SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its affiliates +# +# SPDX-License-Identifier: Apache-2.0 +# + +# Auto-cancel outdated runs +workflow: + auto_cancel: + on_new_commit: interruptible + +# Global runner & version variables +variables: + BASE_IMAGE_LINUX_x86_64: "registry.gitlab.arm.com/tosa/tosa-converter-for-tflite/tcft-base-linux_x86_64" # default Docker image for Linux jobs + XDG_CACHE_HOME: "$CI_PROJECT_DIR/.cache" + PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" + PRE_COMMIT_HOME: "$XDG_CACHE_HOME/pre-commit" + +.runner-tags: &runner-tags + - amd64 + - tosa-static + +# Matrix definition for build-and-test jobs +.ci-build-and-test-matrix: &ci-build-and-test-matrix + - OS: "linux_x86_64" + IMAGE: $BASE_IMAGE_LINUX_x86_64 + +# Shared cache configuration across builds and tests +cache: &global_cache + key: "${CI_COMMIT_REF_SLUG}" + policy: pull-push + paths: + - .cache/pip + - .cache/bazel + - .cache/pre-commit + +# Pipeline stages +stages: + - lint + - build + - test + +# Standard timeout and gating rules +.standard-rules: + timeout: 20m + # we run CI on MRs, default-branch commits, and protected tags + rules: + - if: $CI_PIPELINE_SOURCE == 'merge_request_event' + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH + - if: $CI_COMMIT_TAG && $CI_COMMIT_REF_PROTECTED == "true" + +# Base runner settings +.base-job: &base-job + interruptible: true + retry: + max: 2 + when: + - job_execution_timeout + - stuck_or_timeout_failure + - runner_system_failure + + +# Lint Step +lint: + extends: + - .standard-rules + - .base-job + stage: lint + timeout: 5m + image: $BASE_IMAGE_LINUX_x86_64 + tags: *runner-tags + before_script: + - mkdir -p "$PRE_COMMIT_HOME" + script: + - 'echo "Running Lint Stage: verifying code formatting and hygiene"' + - 'echo "User: $(whoami), UID: $(id -u)"' + - pre-commit run --config .pre-commit-config.yaml --all-files + +# Build stage: produce Python wheel +build: + extends: + - .standard-rules + - .base-job + stage: build + needs: + - lint + timeout: 20m + parallel: + matrix: *ci-build-and-test-matrix + image: $IMAGE + tags: *runner-tags + script: + - 'echo "Running Build Stage: starting build for OS = $OS"' + - bazel version + - echo "Building Python wheel" + - python -m build --wheel + cache: + - <<: *global_cache + - key: "$CI_PIPELINE_ID" + paths: + - dist/*.whl + +# Test stage: run CLI tests +test-cli: + extends: + - .standard-rules + - .base-job + stage: test + needs: + - job: build + artifacts: false + timeout: 15m + parallel: + matrix: *ci-build-and-test-matrix + image: $IMAGE + tags: *runner-tags + cache: + <<: *global_cache + key: "$CI_PIPELINE_ID" + paths: + - dist/*.whl + policy: pull + script: + - 'echo "Running Test-CLI Stage: installing wheel and running tests on OS = $OS"' + - python -m pip install dist/*.whl + - echo "Executing pytest" + - python -m pytest -v tests/test_cli.py --log-cli-level=DEBUG --junit-xml=junit-cli-${OS}.xml + artifacts: + when: always + expire_in: 7 days + reports: + junit: junit-cli-${OS}.xml + paths: + - junit-cli-${OS}.xml diff --git a/docker/Dockerfile.linux b/docker/Dockerfile.linux new file mode 100644 index 0000000000000000000000000000000000000000..f05aba49963dd01ff6428215f4531c12b943dc0a --- /dev/null +++ b/docker/Dockerfile.linux @@ -0,0 +1,68 @@ +# syntax=docker/dockerfile:1.4 +# +# SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its affiliates +# +# SPDX-License-Identifier: Apache-2.0 +# + +# Build arguments and base +ARG UBUNTU_VERSION=24.04 +FROM ubuntu:${UBUNTU_VERSION} + +# overrideable at build time +ARG CI_USER=tcftciuser +ARG TARGETARCH=amd64 +ARG BAZELISK_VERSION=1.26.0 + + +# Environment & user setup +ENV DEBIAN_FRONTEND=noninteractive \ + XDG_CACHE_HOME=/cache \ + PIP_CACHE_DIR=/cache/pip \ + HOME=/home/${CI_USER} + +# create non-root user and cache/workspace dirs +# we dont want CI runner to have root priviledges +RUN useradd --create-home --shell /bin/bash ${CI_USER} \ + && mkdir -p "${XDG_CACHE_HOME}" "${PIP_CACHE_DIR}" /workspace \ + && chown -R "${CI_USER}:${CI_USER}" "${XDG_CACHE_HOME}" "${PIP_CACHE_DIR}" /workspace + +# Install dependencies +RUN --mount=type=cache,target=/var/cache/apt \ + --mount=type=cache,target=/var/lib/apt \ + apt-get update && \ + apt-get install -y --no-install-recommends \ + ca-certificates \ + curl \ + clang \ + clang-format \ + git \ + build-essential \ + python3 \ + python3-pip \ + python3-venv && \ + rm -rf /var/lib/apt/lists/* + +# Add Bazelisk +RUN curl -fsSL \ + "https://github.com/bazelbuild/bazelisk/releases/download/v${BAZELISK_VERSION}/bazelisk-linux-${TARGETARCH}" \ + -o /usr/local/bin/bazel \ + && chmod +x /usr/local/bin/bazel + +# Setup Python virtualenv and install packages +COPY --chown=${CI_USER}:${CI_USER} requirements.txt /tmp/requirements.txt +RUN python3 -m venv /opt/venv && \ + /opt/venv/bin/pip install --upgrade pip build && \ + /opt/venv/bin/pip install -r /tmp/requirements.txt && \ + chown -R "${CI_USER}:${CI_USER}" /opt/venv + +ENV PATH="/opt/venv/bin:${PATH}" + +# Final user and perform healthcheck +USER ${CI_USER} +WORKDIR /workspace + +HEALTHCHECK --interval=30s --timeout=5s \ + CMD bash -lc "command -v bazel && command -v python3" || exit 1 + +CMD ["bash"] diff --git a/docker/build_linux.sh b/docker/build_linux.sh new file mode 100755 index 0000000000000000000000000000000000000000..6352f445cbb758ff315a821d6fa4be0a256c99f6 --- /dev/null +++ b/docker/build_linux.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +# +# SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its affiliates +# +# SPDX-License-Identifier: Apache-2.0 +# + +# Image Configuration +IMAGE_NAME="tcft-base-linux_x86_64" +IMAGE_TAG="latest" +DOCKERFILE="Dockerfile.linux" + +export DOCKER_BUILDKIT=1 + +# Build Image +docker build \ + -f "docker/${DOCKERFILE}" \ + -t "${IMAGE_NAME}:${IMAGE_TAG}" \ + . + +echo "Image created with tag: ${IMAGE_NAME}:${IMAGE_TAG}" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..56d9d1945f7c635daf73e0619b404822a78639d2 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,9 @@ +# +# SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its affiliates +# +# SPDX-License-Identifier: Apache-2.0 +# +hjson==3.1.0 +pytest==8.4.1 +pre-commit==4.2.0 +tensorflow==2.19.0 diff --git a/tests/test_cli.py b/tests/test_cli.py index 1352ef7f48583ef9f3636aa598a4a7eeda3133ba..5c85c2191562391601dcf77f1615493dd352f3fb 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -29,6 +29,7 @@ Troubleshoot: - Ensure `tosa-converter-for-tflite` is on your PATH. """ import logging +import os import random import re import shutil @@ -318,6 +319,14 @@ def test_help_flag_writes_usage(cli_path: str, out_dir: Path) -> None: assert "help" in help_txt.read_text().lower() +# Skip tests that depend on filesystem permission errors when running as root, +# since root privileges override normal file permission checks. +skip_if_root = pytest.mark.skipif( + os.geteuid() == 0, reason="Skipping tests requiring permission errors under root" +) + + +@skip_if_root @pytest.mark.cli def test_output_to_unwritable_directory( cli_path: str, mobilenet_tflite: Path, out_dir: Path @@ -362,6 +371,7 @@ def test_output_to_non_directory( assert not target.exists() +@skip_if_root @pytest.mark.cli def test_readonly_existing_file( cli_path: str, mobilenet_tflite: Path, out_dir: Path