From d4e9c9ce0a438fd327f1ba7dda9495eb505363ae Mon Sep 17 00:00:00 2001 From: memsharded Date: Wed, 10 Sep 2025 14:46:19 +0200 Subject: [PATCH 01/20] clarifying usage of --build-require --- .../other_packages/tool_requires/ci_test_example.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial/creating_packages/other_packages/tool_requires/ci_test_example.py b/tutorial/creating_packages/other_packages/tool_requires/ci_test_example.py index 399de4f6..d68e2b3b 100644 --- a/tutorial/creating_packages/other_packages/tool_requires/ci_test_example.py +++ b/tutorial/creating_packages/other_packages/tool_requires/ci_test_example.py @@ -6,7 +6,7 @@ run('conan create .') with chdir("consumer"): - cmd_out = run('conan create .') + cmd_out = run('conan create . --build-require') assert re.search("Security Scanner: The path '.*' is secure!", cmd_out) assert "MY_VAR=23" in cmd_out From 8b6813bd29d34f166e61752f60377558d3052866 Mon Sep 17 00:00:00 2001 From: Uilian Ries Date: Fri, 7 Nov 2025 10:01:26 +0100 Subject: [PATCH 02/20] Add Sanitizers examples (#192) * Add asan example Signed-off-by: Uilian Ries * Add profiles and code example Signed-off-by: Uilian Ries * Improve README Signed-off-by: Uilian Ries * Add settings_user section in the README Signed-off-by: Uilian Ries * Fix ubsan profile Signed-off-by: Uilian Ries * Add flags to the linker Signed-off-by: Uilian Ries * Sanitizer setting is optional Signed-off-by: Uilian Ries * Add CI scripts Signed-off-by: Uilian Ries * Chmod +x Signed-off-by: Uilian Ries * Add more flags Signed-off-by: Uilian Ries * Moved sanitizers example to security Signed-off-by: Uilian Ries * Simplify example build steps Signed-off-by: Uilian Ries * Fix sanitizers build steps Signed-off-by: Uilian Ries * Rename profile files Signed-off-by: Uilian Ries * Adjust batch according Windows steps Signed-off-by: Uilian Ries * Keep same structure as for docs Signed-off-by: Uilian Ries --------- Signed-off-by: Uilian Ries Signed-off-by: Uilian Ries Co-authored-by: Carlos Zoido --- README.md | 2 ++ examples/security/README.md | 3 ++ examples/security/sanitizers/README.md | 5 ++++ .../security/sanitizers/ci_test_example.bat | 22 +++++++++++++++ .../security/sanitizers/ci_test_example.sh | 21 ++++++++++++++ .../index_out_of_bounds/CMakeLists.txt | 9 ++++++ .../index_out_of_bounds/conanfile.py | 28 +++++++++++++++++++ .../sanitizers/index_out_of_bounds/main.cpp | 15 ++++++++++ .../security/sanitizers/profiles/clang_asan | 14 ++++++++++ .../sanitizers/profiles/clang_asan_ubsan | 11 ++++++++ .../security/sanitizers/settings_user.yml | 9 ++++++ .../signed_integer_overflow/CMakeLists.txt | 9 ++++++ .../signed_integer_overflow/conanfile.py | 28 +++++++++++++++++++ .../signed_integer_overflow/main.cpp | 16 +++++++++++ 14 files changed, 192 insertions(+) create mode 100644 examples/security/README.md create mode 100644 examples/security/sanitizers/README.md create mode 100644 examples/security/sanitizers/ci_test_example.bat create mode 100755 examples/security/sanitizers/ci_test_example.sh create mode 100644 examples/security/sanitizers/index_out_of_bounds/CMakeLists.txt create mode 100644 examples/security/sanitizers/index_out_of_bounds/conanfile.py create mode 100644 examples/security/sanitizers/index_out_of_bounds/main.cpp create mode 100644 examples/security/sanitizers/profiles/clang_asan create mode 100644 examples/security/sanitizers/profiles/clang_asan_ubsan create mode 100644 examples/security/sanitizers/settings_user.yml create mode 100644 examples/security/sanitizers/signed_integer_overflow/CMakeLists.txt create mode 100644 examples/security/sanitizers/signed_integer_overflow/conanfile.py create mode 100644 examples/security/sanitizers/signed_integer_overflow/main.cpp diff --git a/README.md b/README.md index 7e4d2af6..0c3d8eb4 100644 --- a/README.md +++ b/README.md @@ -29,3 +29,5 @@ Sources for the [examples section](https://docs.conan.io/2/examples.html) of the ### [Libraries examples](examples/libraries) ### [Graph examples](examples/graph) + +### [Security examples](examples/security) \ No newline at end of file diff --git a/examples/security/README.md b/examples/security/README.md new file mode 100644 index 00000000..5f8b8d92 --- /dev/null +++ b/examples/security/README.md @@ -0,0 +1,3 @@ +# Conan security examples + +### [Using Compiler Sanitizers with Conan](sanitizers) diff --git a/examples/security/sanitizers/README.md b/examples/security/sanitizers/README.md new file mode 100644 index 00000000..6248505e --- /dev/null +++ b/examples/security/sanitizers/README.md @@ -0,0 +1,5 @@ +# Using Compiler Sanitizers with Conan + +This example follows the documented page https://docs.conan.io/2/examples/security/sanitizers.html about using compiler sanitizers with Conan. + +For more information, please refer to the [C, C++ Compiler Sanitizers¶](https://docs.conan.io/2/security/sanitizers.html) documentation page. \ No newline at end of file diff --git a/examples/security/sanitizers/ci_test_example.bat b/examples/security/sanitizers/ci_test_example.bat new file mode 100644 index 00000000..ea069e4b --- /dev/null +++ b/examples/security/sanitizers/ci_test_example.bat @@ -0,0 +1,22 @@ +@echo off +setlocal enabledelayedexpansion + +echo Setup settings user +for /f "usebackq delims=" %%H in (conan config home) do set "CONAN_HOME=%%H" +copy /Y settings_user.yml "%CONAN_HOME%" + +echo Conan Examples 2: Compiler Sanitizers - Index Out of Bounds + +CD index_out_of_bounds/ +CALL conan build . -pr ../profiles/msvc_asan -c tools.compilation:verbosity=verbose +CALL build/Debug/index_out_of_bounds 2>nul || echo Process completed with errors (expected for sanitizer demo) +CD .. + +echo Conan Examples 2: Compiler Sanitizers - Signed Integer Overflow + +CD signed_integer_overflow/ +CALL conan build . -pr ../profiles/msvc_asan -c tools.compilation:verbosity=verbose +CALL build/Debug/signed_integer_overflow 2>nul || echo Process completed with errors (expected for sanitizer demo) +CD .. + +exit /b 0 diff --git a/examples/security/sanitizers/ci_test_example.sh b/examples/security/sanitizers/ci_test_example.sh new file mode 100755 index 00000000..30d911f8 --- /dev/null +++ b/examples/security/sanitizers/ci_test_example.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +set -e +set -x + +echo "Setup settings user" +cp -f settings_user.yml $(conan config home) + +echo "Conan Examples 2: Compiler Sanitizers - Index Out of Bounds" + +pushd index_out_of_bounds/ +conan build . -pr ../profiles/clang_asan -c tools.compilation:verbosity=verbose +build/Debug/index_out_of_bounds || true +popd + +echo "Conan Examples 2: Compiler Sanitizers - Signed Integer Overflow" + +pushd signed_integer_overflow/ +conan build . -pr ../profiles/clang_asan_ubsan -c tools.compilation:verbosity=verbose +build/Debug/signed_integer_overflow || true +popd diff --git a/examples/security/sanitizers/index_out_of_bounds/CMakeLists.txt b/examples/security/sanitizers/index_out_of_bounds/CMakeLists.txt new file mode 100644 index 00000000..1498db2a --- /dev/null +++ b/examples/security/sanitizers/index_out_of_bounds/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.15) +project(index_out_of_bounds LANGUAGES CXX) + +add_executable(index_out_of_bounds main.cpp) +target_compile_features(index_out_of_bounds PUBLIC cxx_std_11) + +include(GNUInstallDirs) +install(TARGETS index_out_of_bounds + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/examples/security/sanitizers/index_out_of_bounds/conanfile.py b/examples/security/sanitizers/index_out_of_bounds/conanfile.py new file mode 100644 index 00000000..85cd00a3 --- /dev/null +++ b/examples/security/sanitizers/index_out_of_bounds/conanfile.py @@ -0,0 +1,28 @@ +from conan import ConanFile +from conan.tools.cmake import CMake, cmake_layout, CMakeToolchain + +required_conan_version = ">=2.1.0" + +class IndexOutOfBoundsConan(ConanFile): + name = "index_out_of_bounds" + version = "0.1.0" + settings = "os", "arch", "compiler", "build_type" + exports_sources = "CMakeLists.txt", "main.cpp" + package_type = "application" + languages = ["C++"] + + def layout(self): + cmake_layout(self) + + def generate(self): + tc = CMakeToolchain(self) + tc.generate() + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + + def package(self): + cmake = CMake(self) + cmake.install() \ No newline at end of file diff --git a/examples/security/sanitizers/index_out_of_bounds/main.cpp b/examples/security/sanitizers/index_out_of_bounds/main.cpp new file mode 100644 index 00000000..8067fe62 --- /dev/null +++ b/examples/security/sanitizers/index_out_of_bounds/main.cpp @@ -0,0 +1,15 @@ +#include +#include + +int main() { + #ifdef __SANITIZE_ADDRESS__ + std::cout << "Address sanitizer enabled\n"; + #else + std::cout << "Address sanitizer not enabled\n"; + #endif + + int foo[100]; + foo[100] = 42; // Out-of-bounds write + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/examples/security/sanitizers/profiles/clang_asan b/examples/security/sanitizers/profiles/clang_asan new file mode 100644 index 00000000..7ce2d243 --- /dev/null +++ b/examples/security/sanitizers/profiles/clang_asan @@ -0,0 +1,14 @@ +include(default) + +[settings] +build_type=Debug +compiler.sanitizer=Address + +[conf] +tools.build:cflags=['-fsanitize=address'] +tools.build:cxxflags=['-fsanitize=address'] +tools.build:exelinkflags=['-fsanitize=address'] +tools.build:sharedlinkflags+=["-fsanitize=address"] + +[runenv] +ASAN_OPTIONS="halt_on_error=1:detect_leaks=1" \ No newline at end of file diff --git a/examples/security/sanitizers/profiles/clang_asan_ubsan b/examples/security/sanitizers/profiles/clang_asan_ubsan new file mode 100644 index 00000000..0d54f55c --- /dev/null +++ b/examples/security/sanitizers/profiles/clang_asan_ubsan @@ -0,0 +1,11 @@ +include(default) + +[settings] +build_type=Debug +compiler.sanitizer=AddressUndefinedBehavior + +[conf] +tools.build:cflags=['-fsanitize=address,undefined'] +tools.build:cxxflags=['-fsanitize=address,undefined'] +tools.build:exelinkflags=['-fsanitize=address,undefined'] +tools.build:sharedlinkflags+=["-fsanitize=address"] \ No newline at end of file diff --git a/examples/security/sanitizers/settings_user.yml b/examples/security/sanitizers/settings_user.yml new file mode 100644 index 00000000..c42cea1d --- /dev/null +++ b/examples/security/sanitizers/settings_user.yml @@ -0,0 +1,9 @@ +compiler: + gcc: + sanitizer: [null, Address, Leak, Thread, UndefinedBehavior, HardwareAssistanceAddress, KernelAddress, AddressUndefinedBehavior, ThreadUndefinedBehavior] + clang: + sanitizer: [null, Address, Leak, Thread, Memory, UndefinedBehavior, HardwareAssistanceAddress, KernelAddress, AddressUndefinedBehavior, ThreadUndefinedBehavior] + apple-clang: + sanitizer: [null, Address, Leak, Thread, Memory, UndefinedBehavior, HardwareAssistanceAddress, KernelAddress, AddressUndefinedBehavior, ThreadUndefinedBehavior] + msvc: + sanitizer: [null, Address, KernelAddress] diff --git a/examples/security/sanitizers/signed_integer_overflow/CMakeLists.txt b/examples/security/sanitizers/signed_integer_overflow/CMakeLists.txt new file mode 100644 index 00000000..73687d17 --- /dev/null +++ b/examples/security/sanitizers/signed_integer_overflow/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.15) +project(signed_integer_overflow LANGUAGES CXX) + +add_executable(signed_integer_overflow main.cpp) +target_compile_features(signed_integer_overflow PUBLIC cxx_std_11) + +include(GNUInstallDirs) +install(TARGETS signed_integer_overflow + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/examples/security/sanitizers/signed_integer_overflow/conanfile.py b/examples/security/sanitizers/signed_integer_overflow/conanfile.py new file mode 100644 index 00000000..c7c85802 --- /dev/null +++ b/examples/security/sanitizers/signed_integer_overflow/conanfile.py @@ -0,0 +1,28 @@ +from conan import ConanFile +from conan.tools.cmake import CMake, cmake_layout, CMakeToolchain + +required_conan_version = ">=2.1.0" + +class SignedIntegerOverflowConan(ConanFile): + name = "signed_integer_overflow" + version = "0.1.0" + settings = "os", "arch", "compiler", "build_type" + exports_sources = "CMakeLists.txt", "main.cpp" + package_type = "application" + languages = ["C++"] + + def layout(self): + cmake_layout(self) + + def generate(self): + tc = CMakeToolchain(self) + tc.generate() + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + + def package(self): + cmake = CMake(self) + cmake.install() \ No newline at end of file diff --git a/examples/security/sanitizers/signed_integer_overflow/main.cpp b/examples/security/sanitizers/signed_integer_overflow/main.cpp new file mode 100644 index 00000000..52a5eac5 --- /dev/null +++ b/examples/security/sanitizers/signed_integer_overflow/main.cpp @@ -0,0 +1,16 @@ +#include +#include +#include + +int main(int argc, char* argv[]) { + #ifdef __SANITIZE_ADDRESS__ + std::cout << "Address sanitizer enabled\n"; + #else + std::cout << "Address sanitizer not enabled\n"; + #endif + + int foo = 0x7fffffff; + foo += argc; // Signed integer overflow + + return EXIT_SUCCESS; +} \ No newline at end of file From 63e3384b03ae3701578498e63dc4fd8e6c0628ce Mon Sep 17 00:00:00 2001 From: Carlos Zoido Date: Wed, 12 Nov 2025 12:01:38 +0100 Subject: [PATCH 03/20] [CI] Use GitHub Actions for CI (#200) * use GA * add PYTHONPATH * fix permissions * wip * wip * zlib does not build for clang 17 in macOS * install automake autoconf * wip * revert * group * wip * wip * clean * clean * wip * wip * wip * wip * wip * wip --- .ci/Jenkinsfile | 204 ------------------ .github/workflows/ci.yml | 114 ++++++++++ .../editable_components/ci_test_example.py | 4 + .../android/ndk_basic/ci_test_example.py | 2 +- .../development_deploy/conanfile.txt | 2 +- test/examples_tools.py | 21 +- .../ci_test_example.py | 2 + .../versioning/ci_test_example.py | 2 +- .../versioning/conanfile.py | 2 +- .../editable_packages/ci_test_example.py | 14 +- 10 files changed, 142 insertions(+), 225 deletions(-) delete mode 100644 .ci/Jenkinsfile create mode 100644 .github/workflows/ci.yml diff --git a/.ci/Jenkinsfile b/.ci/Jenkinsfile deleted file mode 100644 index fc2b76ed..00000000 --- a/.ci/Jenkinsfile +++ /dev/null @@ -1,204 +0,0 @@ -void cancelPrevious() { - stage("Cancelling previous") { - def buildNumber = env.BUILD_NUMBER as int - if (buildNumber > 1) milestone(buildNumber - 1) - milestone(buildNumber) - } -} - -def isPR = env.CHANGE_ID ? true : false - -def envInside(Map ctxt, Closure closure) { - String randDir = UUID.randomUUID().toString().substring(0, 4) - String venvDirectory = ctxt.runningUnix ? "${pwd()}/conanenv" : "${pwd()}\\conanenv" - String binPath = ctxt.runningUnix ? "$venvDirectory/bin/" : "$venvDirectory\\Scripts\\" - String conanHome = ctxt.runningUnix ? "${pwd()}/${randDir}/.conan" : "${pwd()}\\${randDir}\\.conan" - String cmakePath = ctxt.runningUnix ? "/Users/jenkins/cmake/cmake-3.23.1/bin" : "C:\\cmake\\cmake-3.23.1-win64-x64\\bin" - String pythonPath = ctxt.runningUnix ? "${env.WORKSPACE}/${ctxt.subDir}" : "${env.WORKSPACE}\\${ctxt.subDir}" - withEnv(["PATH+EXTRA=${binPath}", "PATH+CMAKE=${cmakePath}", "CONAN_HOME=${conanHome}", "PYTHONPATH=${pythonPath}"]) { - // This doesn't work if running docker.inside (https://issues.jenkins.io/browse/JENKINS-49076) - closure() - } -} - -def envCreate(Map ctxt) { - String venvDirectory = ctxt.runningUnix ? "${pwd()}/conanenv" : "${pwd()}\\conanenv" - String binPath = ctxt.runningUnix ? "$venvDirectory/bin/" : "$venvDirectory\\Scripts\\" - ctxt.shFunction("${ctxt.python_host} --version") - if (ctxt.isInsideDocker) { - // Alternate implementation because of https://issues.jenkins.io/browse/JENKINS-49076 - // Always install latest pre-release - ctxt.shFunction("pip install --upgrade pip") - ctxt.shFunction("pip uninstall -y conan") - ctxt.shFunction("pip install ${ctxt.branch.branchInstall} --upgrade") - ctxt.shFunction("pip install meson") - } - else { - ctxt.shFunction("${ctxt.python_host} -m venv $venvDirectory") - ctxt.shFunction("${binPath}python -m pip install -U pip") - // Always install latest pre-release - ctxt.shFunction("${binPath}python -m pip install ${ctxt.branch.branchInstall} --upgrade") - } -} - -String pipConanBranchInstall(String branch, String fork='conan-io') { - return "-e git+https://github.com/${fork}/conan.git@${branch}#egg=conan" -} - -// Run on latest development branch and latest released version -List getBranchesInstalls() { - def branches = [['name': 'latest Conan 2.0 release', 'branchInstall': 'conan']] - branches.add(0, ['name': 'develop Conan 2.0', 'branchInstall': pipConanBranchInstall('develop2')]) - return branches -} - -def runExample(Map ctxt, String example) { - for (extension in ctxt.extensions) { - if (example.contains(extension)) { - example = example.replace("\\","/") - split_path = example.split('/') - String script = split_path[split_path.length-1] - String path = example - script - script = ctxt.runningUnix ? "./${script}" : "${script}" - String command = script.contains(".py") ? "python ${script}" : "${script}" - dir("${path}") { - try { - echo "Running example: ${path}" - ctxt.shFunction("${command}") - } catch (Exception e) { - throw new Exception("Error running example ${example}: ${e.message}") - } - } - } - } -} - -def runExamples(Map ctxt) { - try { - ctxt.subDir = UUID.randomUUID().toString().substring(0, 4) - dir(ctxt.subDir) { - checkout scm - envCreate(ctxt) - envInside(ctxt) { - ctxt.shFunction("cmake --version") - ctxt.shFunction('conan profile detect --force') - if (ctxt.runningUnix) { - profile_path = sh(script: 'conan profile path default', returnStdout: true) - sh(script: "sed -i -e 's/gnu98/11/g' ${profile_path}") - } - echo "Running examples with CONAN_HOME: ${env.CONAN_HOME}" - for (example in ctxt.examples) { - runExample(ctxt, example) - } - } - } - } - finally { - cleanWs(cleanWhenAborted: true, cleanWhenFailure: true, cleanWhenNotBuilt: true, - cleanWhenSuccess: true, cleanWhenUnstable: true, disableDeferredWipeout: true, deleteDirs: true, - notFailBuild: true) - } -} - -def isMain() { - String mainBranch = 'main' - return env.BRANCH_NAME == mainBranch -} - -node('LinuxFunctional') { - - try { - cancelPrevious() - - List examples = [] - - // FIXME - // These examples won't run in the PR's at least until we have binaries for - // all libraries examples in CC to not slow down the ci on PR's - List skipExamplesInPR = ['tensorflow'] - List skipExamplesAlways = [] - - stage('Find examples') { - checkout scm - def _examples = sh(script: 'find . -name ci_test_example.*', returnStdout: true).readLines() - for (example in _examples) { - def shouldSkipinPR = skipExamplesInPR.any { skipExample -> example.contains(skipExample) } - def shouldSkipAlways = skipExamplesAlways.any { skipExample -> example.contains(skipExample) } - if ((example.contains(".sh") || example.contains(".bat") || example.contains(".py")) && (!shouldSkipinPR || !isPR) && (!shouldSkipAlways)) { - examples.add(example) - } - } - examples = examples.sort() - echo "Examples to run:\n - ${ examples.collect({ "$it" }).join('\n - ')}" - } - - if (!examples.isEmpty()) { - stage('Run examples') { - def conanBranchesInstalls = getBranchesInstalls() - parallel( - linux: { - conanBranchesInstalls.each { branch -> - stage("${branch.name}: Run Linux examples") { - def sourcedir = "/home/conan/" - def pyenvdir = "${sourcedir}.pyenv" - def pythonVersion = "3.9.2" - def command = "export PYENV_ROOT=${pyenvdir} && " \ - + "export PATH=\"${pyenvdir}/versions/\${pythonVersion}/bin:${pyenvdir}/bin:\$PATH\" && " \ - + "pyenv global ${pythonVersion} && " \ - + "sudo /home/conan/select_cmake.sh 3.23.5 && sudo /home/conan/select_gcc.sh 11" - sh(script: command) - runExamples([python_host: 'python3', - shFunction: { data -> sh(data) }, - isInsideDocker: true, - runningUnix: true, - examples: examples, - branch: branch, - extensions: ['.py', '.sh']]) - } - } - }, - macos: { - node('M2Macos') { - conanBranchesInstalls.each { branch -> - stage("${branch.name}: Run Macos examples") { - withEnv(["PYENV_ROOT=/Users/jenkins/.pyenv", "PATH+EXTRA=/Users/jenkins/.pyenv/shims:/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin"]) { - runExamples([python_host: '/Users/jenkins/.pyenv/versions/3.9.10/bin/python', - shFunction: { data -> sh(data) }, - isInsideDocker: false, - runningUnix: true, - examples: examples, - branch: branch, - extensions: ['.py', '.sh']]) - } - } - } - } - }, - windows: { - node('Windows') { - conanBranchesInstalls.each { branch -> - stage("${branch.name}: Run Windows examples") { - runExamples([python_host: 'C:\\Python39\\python.exe', - shFunction: { data -> bat(data) }, - isInsideDocker: false, - runningUnix: false, - examples: examples, - branch: branch, - extensions: ['.py', '.bat']]) - } - } - } - } - ) - } - } - } - catch(e){ - if (env.BRANCH_NAME == "main") { - def subject = "FAILED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]'" - def summary = "${subject} (${env.BUILD_URL}), Conan Branch: ${env.BRANCH_NAME}" - slackSend (color: '#FF0000', message: summary) - } - throw e - } -} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..bcd8d2de --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,114 @@ +name: CI Examples + +permissions: + contents: read + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + run-examples: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + conan-version: [release, develop] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install CMake + uses: jwlawson/actions-setup-cmake@v1.14 + with: + cmake-version: "3.23" + + - name: Install autotools (macOS) + if: runner.os == 'macOS' + run: brew install automake autoconf + + - name: Install Conan + shell: bash + run: | + python -m pip install --upgrade pip + if [[ "${{ matrix.conan-version }}" == "develop" ]]; then + pip install -e git+https://github.com/conan-io/conan.git@develop2#egg=conan --upgrade + else + pip install conan --upgrade + fi + pip install meson + conan --version + conan profile detect --force + + - name: Find and run examples + shell: bash + run: | + export PYTHONPATH="${{ github.workspace }}${PYTHONPATH:+:$PYTHONPATH}" + + # Find all ci_test_example files + if [[ "${{ runner.os }}" == "Windows" ]]; then + EXAMPLES=$(find . -name "ci_test_example.*" -type f \( -name "*.py" -o -name "*.bat" \) | sort) + else + EXAMPLES=$(find . -name "ci_test_example.*" -type f \( -name "*.py" -o -name "*.sh" \) | sort) + fi + + # Filter out examples that require specific versions or tools not available in CI + # FIXME: Bazel examples require specific Bazel versions (6.3.2, 7.1.2) that are not installed in GitHub Actions runners + EXAMPLES=$(echo "$EXAMPLES" | grep -v "bazeltoolchain" || true) + # FIXME: Cross-building examples require cross-compilation toolchains (e.g., arm-linux-gnueabihf-gcc-9) that are not installed in GitHub Actions runners + EXAMPLES=$(echo "$EXAMPLES" | grep -v "cross_building" || true) + + # FIXME: Filter out tensorflow examples in PRs + IS_PR="${{ github.event_name == 'pull_request' }}" + if [[ "$IS_PR" == "true" ]]; then + EXAMPLES=$(echo "$EXAMPLES" | grep -v tensorflow || true) + fi + + if [[ -z "$EXAMPLES" ]]; then + echo "No examples found to run" + exit 0 + fi + + echo "Examples to run:" + echo "$EXAMPLES" + + # Run each example + set -e + while IFS= read -r example; do + if [[ -z "$example" ]]; then + continue + fi + + example_normalized=$(echo "$example" | sed 's|\\|/|g') + example_dir=$(dirname "$example_normalized") + example_file=$(basename "$example_normalized") + + # Start group for this example + echo "::group::Running example: $example_dir" + + cd "${{ github.workspace }}/$example_dir" || exit 1 + + if [[ "$example_file" == *.py ]]; then + python "$example_file" + elif [[ "$example_file" == *.sh ]]; then + bash "$example_file" + elif [[ "$example_file" == *.bat ]]; then + if [[ "${{ runner.os }}" == "Windows" ]]; then + cmd //c "$example_file" + else + echo "Skipping .bat file on non-Windows platform" + fi + fi + + cd "${{ github.workspace }}" || exit 1 + + # End group for this example + echo "::endgroup::" + done <<< "$EXAMPLES" diff --git a/examples/conanfile/layout/editable_components/ci_test_example.py b/examples/conanfile/layout/editable_components/ci_test_example.py index 8b0883a4..ad77f83f 100644 --- a/examples/conanfile/layout/editable_components/ci_test_example.py +++ b/examples/conanfile/layout/editable_components/ci_test_example.py @@ -23,6 +23,10 @@ # Do a modification to one component, to verify it is used correctly replace(os.path.join("greetings", "src", "bye.cpp"), "bye:", "adios:") run("conan build greetings") +# Clean app build to ensure it uses the updated library +app_build_path = os.path.join("app", "build") +if os.path.exists(app_build_path): + shutil.rmtree(app_build_path) cmd_out = run("conan build app") assert cmd_out.count("hello: Release!") == 2 assert cmd_out.count("adios: Release!") == 1 diff --git a/examples/cross_build/android/ndk_basic/ci_test_example.py b/examples/cross_build/android/ndk_basic/ci_test_example.py index 6e2825b0..bca5e08b 100644 --- a/examples/cross_build/android/ndk_basic/ci_test_example.py +++ b/examples/cross_build/android/ndk_basic/ci_test_example.py @@ -23,7 +23,7 @@ tools.android:ndk_path={} """ -ndk_path = {"Darwin": "/opt/homebrew/share/android-ndk", "Linux": "/opt/android-ndk-r23c"}.get(platform.system()) +ndk_path = os.environ.get("ANDROID_NDK") or os.environ.get("ANDROID_NDK_HOME") if ndk_path: profile = profile.format(ndk_path) diff --git a/examples/extensions/deployers/development_deploy/conanfile.txt b/examples/extensions/deployers/development_deploy/conanfile.txt index 92b9102d..7c5ead5f 100644 --- a/examples/extensions/deployers/development_deploy/conanfile.txt +++ b/examples/extensions/deployers/development_deploy/conanfile.txt @@ -1,5 +1,5 @@ [requires] -zlib/1.2.13 +zlib/1.3.1 [tool_requires] cmake/3.25.3 diff --git a/test/examples_tools.py b/test/examples_tools.py index 16028d27..9ebd1c42 100644 --- a/test/examples_tools.py +++ b/test/examples_tools.py @@ -1,6 +1,7 @@ import os import subprocess import shutil +import sys from contextlib import contextmanager import time @@ -49,7 +50,10 @@ def run(cmd, error=False): output = '' for line in iter(process.stdout.readline, ''): - print(line, end='', flush=True) + # Write directly to stdout to preserve GitHub Actions workflow commands + # This ensures ::group:: and ::endgroup:: are detected by GitHub Actions + sys.stdout.write(line) + sys.stdout.flush() output += line ret = process.wait() @@ -64,18 +68,3 @@ def run(cmd, error=False): raise Exception(f"Cmd succeeded (failure expected): {cmd}\n{output}") return output - - -def replace(file_path, text, replace): - with open(file_path, "r") as f: - content = f.read() - content2 = content.replace(text, replace) - assert content != content2 - with open(file_path, "w") as f: - f.write(content2) - - -def load(file_path): - with open(file_path, "r") as f: - content = f.read() - return content diff --git a/tutorial/consuming_packages/different_configurations/ci_test_example.py b/tutorial/consuming_packages/different_configurations/ci_test_example.py index d051b957..0a27b8ab 100644 --- a/tutorial/consuming_packages/different_configurations/ci_test_example.py +++ b/tutorial/consuming_packages/different_configurations/ci_test_example.py @@ -56,6 +56,7 @@ cmd_out = run("./compressor") assert "Release configuration!" in cmd_out + run("rm -rf build") run("conan install . --output-folder=build --build=missing -s build_type=Debug") with chdir("build"): command = [] @@ -68,6 +69,7 @@ assert "Debug configuration!" in cmd_out # Build for Release with shared libraries + run("rm -rf build") run("conan install . --output-folder=build --build=missing -s build_type=Release --options=zlib/1.3.1:shared=True") with chdir("build"): command = [] diff --git a/tutorial/consuming_packages/versioning/ci_test_example.py b/tutorial/consuming_packages/versioning/ci_test_example.py index e11c67ba..706d465f 100644 --- a/tutorial/consuming_packages/versioning/ci_test_example.py +++ b/tutorial/consuming_packages/versioning/ci_test_example.py @@ -3,4 +3,4 @@ cmd_out = run('conan remove "zlib/*" -c') cmd_out = run("conan install . --build=missing") -assert "zlib/[~1.2]: zlib/1.2." in cmd_out +assert "zlib/[~1.3]: zlib/1.3." in cmd_out diff --git a/tutorial/consuming_packages/versioning/conanfile.py b/tutorial/consuming_packages/versioning/conanfile.py index 52466198..d42bc5ad 100644 --- a/tutorial/consuming_packages/versioning/conanfile.py +++ b/tutorial/consuming_packages/versioning/conanfile.py @@ -10,4 +10,4 @@ def layout(self): cmake_layout(self) def requirements(self): - self.requires("zlib/[~1.2]") + self.requires("zlib/[~1.3]") diff --git a/tutorial/developing_packages/editable_packages/ci_test_example.py b/tutorial/developing_packages/editable_packages/ci_test_example.py index 87d66306..c829488e 100644 --- a/tutorial/developing_packages/editable_packages/ci_test_example.py +++ b/tutorial/developing_packages/editable_packages/ci_test_example.py @@ -1,5 +1,6 @@ import platform import os +import shutil from conan import conan_version @@ -55,7 +56,16 @@ run(f"cmake --build --preset {prefix_preset_name}release") with chdir("hello"): - if platform.system() == "Windows": + # Clean hello build to ensure it uses the updated say library + hello_build_path = "build" + if os.path.exists(hello_build_path): + shutil.rmtree(hello_build_path) + + # Reconfigure CMake after cleaning the build directory + if platform.system() == "Windows": + run("conan install . -s build_type=Release") + run("conan install . -s build_type=Debug") + run(f"cmake --preset {prefix_preset_name}default") run(f"cmake --build --preset {prefix_preset_name}release") run(f"cmake --build --preset {prefix_preset_name}debug") cmd_out = run("build\Release\hello.exe") @@ -63,6 +73,8 @@ cmd_out = run("build\Debug\hello.exe") assert "say/1.0: Bye World Debug!" in cmd_out else: + run("conan install . -s build_type=Release") + run(f"cmake --preset {prefix_preset_name}release") run(f"cmake --build --preset {prefix_preset_name}release") cmd_out = run("./build/Release/hello") assert "say/1.0: Bye World Release!" in cmd_out From 6a51bc83ac722cf29503447ba45aaee94e54ae01 Mon Sep 17 00:00:00 2001 From: Uilian Ries Date: Thu, 13 Nov 2025 08:44:03 +0100 Subject: [PATCH 04/20] Install Bazel 7 on CI Workflow (#202) * Install Bazel 7 Signed-off-by: Uilian Ries * Fix bazelisk version Signed-off-by: Uilian Ries * Fix bazel env Signed-off-by: Uilian Ries * Only Build Ros When changing ROS files Signed-off-by: Uilian Ries * Split folders Signed-off-by: Uilian Ries * Use separate build for bazel Signed-off-by: Uilian Ries * Fix path Signed-off-by: Uilian Ries * Fix path Signed-off-by: Uilian Ries * Only install Bazel for examples Signed-off-by: Uilian Ries * Revert separate build execution Signed-off-by: Uilian Ries * Set Bazel version based on .bazelversion Signed-off-by: Uilian Ries * Include back Bazel examples to the regular list Signed-off-by: Uilian Ries --------- Signed-off-by: Uilian Ries Signed-off-by: Uilian Ries --- .github/workflows/ci.yml | 41 ++++++++++--------- .github/workflows/ros-tests.yml | 4 ++ .../6_x/string_formatter/.bazelversion | 1 + .../7_x/string_formatter/.bazelversion | 1 + 4 files changed, 28 insertions(+), 19 deletions(-) create mode 100644 examples/tools/google/bazeltoolchain/6_x/string_formatter/.bazelversion create mode 100644 examples/tools/google/bazeltoolchain/7_x/string_formatter/.bazelversion diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bcd8d2de..3a1f3e27 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,21 +19,26 @@ jobs: runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - + - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.11" - + - name: Install CMake uses: jwlawson/actions-setup-cmake@v1.14 with: cmake-version: "3.23" - + + - name: Install Bazel + uses: bazel-contrib/setup-bazel@0.15.0 + with: + bazelisk-version: "1.27.0" + - name: Install autotools (macOS) if: runner.os == 'macOS' run: brew install automake autoconf - + - name: Install Conan shell: bash run: | @@ -46,55 +51,53 @@ jobs: pip install meson conan --version conan profile detect --force - + - name: Find and run examples shell: bash run: | export PYTHONPATH="${{ github.workspace }}${PYTHONPATH:+:$PYTHONPATH}" - + # Find all ci_test_example files if [[ "${{ runner.os }}" == "Windows" ]]; then EXAMPLES=$(find . -name "ci_test_example.*" -type f \( -name "*.py" -o -name "*.bat" \) | sort) else EXAMPLES=$(find . -name "ci_test_example.*" -type f \( -name "*.py" -o -name "*.sh" \) | sort) fi - + # Filter out examples that require specific versions or tools not available in CI - # FIXME: Bazel examples require specific Bazel versions (6.3.2, 7.1.2) that are not installed in GitHub Actions runners - EXAMPLES=$(echo "$EXAMPLES" | grep -v "bazeltoolchain" || true) # FIXME: Cross-building examples require cross-compilation toolchains (e.g., arm-linux-gnueabihf-gcc-9) that are not installed in GitHub Actions runners EXAMPLES=$(echo "$EXAMPLES" | grep -v "cross_building" || true) - + # FIXME: Filter out tensorflow examples in PRs IS_PR="${{ github.event_name == 'pull_request' }}" if [[ "$IS_PR" == "true" ]]; then EXAMPLES=$(echo "$EXAMPLES" | grep -v tensorflow || true) fi - + if [[ -z "$EXAMPLES" ]]; then echo "No examples found to run" exit 0 fi - + echo "Examples to run:" echo "$EXAMPLES" - + # Run each example set -e while IFS= read -r example; do if [[ -z "$example" ]]; then continue fi - + example_normalized=$(echo "$example" | sed 's|\\|/|g') example_dir=$(dirname "$example_normalized") example_file=$(basename "$example_normalized") - + # Start group for this example echo "::group::Running example: $example_dir" - + cd "${{ github.workspace }}/$example_dir" || exit 1 - + if [[ "$example_file" == *.py ]]; then python "$example_file" elif [[ "$example_file" == *.sh ]]; then @@ -106,9 +109,9 @@ jobs: echo "Skipping .bat file on non-Windows platform" fi fi - + cd "${{ github.workspace }}" || exit 1 - + # End group for this example echo "::endgroup::" done <<< "$EXAMPLES" diff --git a/.github/workflows/ros-tests.yml b/.github/workflows/ros-tests.yml index 39837030..8232fb86 100644 --- a/.github/workflows/ros-tests.yml +++ b/.github/workflows/ros-tests.yml @@ -4,9 +4,13 @@ on: push: branches: - main + paths: + - examples/tools/ros/** pull_request: branches: - main + paths: + - examples/tools/ros/** jobs: test_conan_release: diff --git a/examples/tools/google/bazeltoolchain/6_x/string_formatter/.bazelversion b/examples/tools/google/bazeltoolchain/6_x/string_formatter/.bazelversion new file mode 100644 index 00000000..f9da12e1 --- /dev/null +++ b/examples/tools/google/bazeltoolchain/6_x/string_formatter/.bazelversion @@ -0,0 +1 @@ +6.3.2 \ No newline at end of file diff --git a/examples/tools/google/bazeltoolchain/7_x/string_formatter/.bazelversion b/examples/tools/google/bazeltoolchain/7_x/string_formatter/.bazelversion new file mode 100644 index 00000000..0e7b60da --- /dev/null +++ b/examples/tools/google/bazeltoolchain/7_x/string_formatter/.bazelversion @@ -0,0 +1 @@ +7.1.2 \ No newline at end of file From 18e598c49c7a74fe221e117b1899b878a92a11cb Mon Sep 17 00:00:00 2001 From: Uilian Ries Date: Tue, 18 Nov 2025 14:57:47 +0100 Subject: [PATCH 05/20] [infrastructure] Build only what you changed but run nightly build for main branch (#204) * Use nightly build instead of every push Signed-off-by: Uilian Ries * Allow running manual build for main branch Signed-off-by: Uilian Ries * Build only affected files Signed-off-by: Uilian Ries * Fix nested folder filter * Run ROS example on nightly Signed-off-by: Uilian Ries * Move CI workflow to python script Signed-off-by: Uilian Ries * Build main branch after merging Signed-off-by: Uilian Ries * Keep previous group messages Signed-off-by: Uilian Ries * Follow Github groups format Signed-off-by: Uilian Ries --------- Signed-off-by: Uilian Ries --- .github/run_ci_tests.py | 219 ++++++++++++++++++++++++++++++++ .github/workflows/ci.yml | 73 ++--------- .github/workflows/ros-tests.yml | 8 +- 3 files changed, 233 insertions(+), 67 deletions(-) create mode 100644 .github/run_ci_tests.py diff --git a/.github/run_ci_tests.py b/.github/run_ci_tests.py new file mode 100644 index 00000000..3e2b0c97 --- /dev/null +++ b/.github/run_ci_tests.py @@ -0,0 +1,219 @@ +""" +Script to run CI test examples based on changed files in a PR. + +This script identifies which example files to run based on the files +changed in a pull request. It supports filtering examples by affected +directories and excludes certain known-broken examples. + +In case running in a non-PR context, all examples are executed. +""" + +import os +import subprocess +import platform +from pathlib import Path + + +def run_command(cmd) -> str: + """ + Run a shell command and return output. + + In case of error, it raises a CalledProcessError exception. + + :param cmd: str - command to run + :returns: str - command output + """ + result = subprocess.run(cmd, shell=True, capture_output=True, text=True, check=True) + return result.stdout.strip() + +def find_examples(runner_os) -> list: + """ + Find all ci_test_example[.py|.bat|.sh] files based on OS. + + :param runner_os: str - the OS of the runner + :returns: list of example file paths + """ + examples = [] + patterns = ["*.py"] + patterns += ["*.bat"] if runner_os == "Windows" else ["*.sh"] + + for pattern in patterns: + for path in Path(".").rglob(f"ci_test_example{pattern[1:]}"): + examples.append(str(path)) + + return sorted(examples) + +def find_affected_directories(base_ref) -> list: + """ + Find directories affected by PR changes that contain example files. + + :param base_ref: str - the base branch to compare against + :returns: list of affected directory paths + """ + # Get changed files + cmd = f"git diff --name-only --diff-filter=ACMRT origin/{base_ref}...HEAD" + changed_files = run_command(cmd).split('\n') + + print("Changed files:") + for file in changed_files: + if file: + print(file) + + affected_dirs = set() + + for file_path in changed_files: + if not file_path: + continue + + # Get directory of changed file + dir_path = Path(file_path).parent + + # Walk up from deepest to root and find first dir with example files + current = Path(".") / dir_path + selected = None + + while str(current) != "." and not selected: + # Check if this directory contains any ci_test_example files + has_example = any(current.glob("**/ci_test_example.*")) + if has_example: + selected = str(current) + break + + # Move one level up + if current.parent == current: + break + current = current.parent + + if selected: + affected_dirs.add(selected) + + affected_dirs = sorted(affected_dirs) + print("\nAffected directories (deepest with examples):") + for d in affected_dirs: + print(d) + + return affected_dirs + +def filter_examples_by_dirs(examples, affected_dirs) -> list: + """ + Filter examples to only those in affected directories. + + :param examples: list of example file paths + :param affected_dirs: list of affected directory paths + :returns: filtered list of example file paths + """ + filtered = [] + + for example in examples: + example_path = Path(example) + for affected_dir in affected_dirs: + affected_path = Path(affected_dir) + try: + # Check if example is under affected directory + example_path.relative_to(affected_path) + filtered.append(example) + break + except ValueError: + continue + + return sorted(set(filtered)) + +def filter_exclusions(examples, is_pr) -> list: + """ + FIXME: Filter out broken examples after GitHub Actions migration + + :param examples: list of example file paths + :param is_pr: bool - whether this is a pull request + :returns: filtered list of example file paths + """ + filtered = [] + + for example in examples: + # FIXME: Cross-building examples require cross-compilation toolchains + # (e.g., arm-linux-gnueabihf-gcc-9) that are not installed in GitHub Actions runners + if "cross_building" in example: + continue + + # FIXME: Filter out tensorflow examples in PRs + if is_pr and "tensorflow" in example: + continue + + filtered.append(example) + + return filtered + +def run_example(example, workspace, runner_os) -> None: + """ + Run a single example file. + + :param example: str - path to the example file + :param workspace: Path - the GitHub workspace directory + :param runner_os: str - the OS of the runner + :returns: None + """ + example_path = Path(example) + example_dir = example_path.parent + example_file = example_path.name + + # Use GitHub Actions grouping for better log readability + print(f"##[group]Running example: {example_dir}") + + # Change to example directory + os.chdir(workspace / example_dir) + + try: + if example_file.endswith(".py"): + subprocess.run(["python", example_file], check=True) + elif example_file.endswith(".sh"): + subprocess.run(["bash", example_file], check=True) + elif example_file.endswith(".bat"): + if runner_os == "Windows": + subprocess.run(["cmd", "/c", example_file], check=True) + else: + print("Skipping .bat file on non-Windows platform") + finally: + os.chdir(workspace) + print(f"##[endgroup]") + +def main(): + """ + The main logic captures environment variables, finds affected directories, + filters examples, and runs them accordingly. + + In case an example fails, it raises an exception and stops further execution. + """ + # INFO: Using env vars so we can test locally too + event_name = os.environ.get("GITHUB_EVENT_NAME", "") + base_ref = os.environ.get("GITHUB_BASE_REF", "main") + workspace = Path(os.environ.get("GITHUB_WORKSPACE", ".")) + runner_os = os.environ.get("RUNNER_OS", platform.system()) + + is_pr = event_name == "pull_request" + + affected_dirs = [] + if is_pr: + print("Pull request detected - finding affected directories\n") + affected_dirs = find_affected_directories(base_ref) + + examples = find_examples(runner_os) + + # INFO: Filter examples only when is a PR. + if is_pr and affected_dirs: + examples = filter_examples_by_dirs(examples, affected_dirs) + print("\nFiltered to affected directories only:") + for example in examples: + print(example) + + # FIXME: Filter out some non-working examples in GitHub Actions + examples = filter_exclusions(examples, is_pr) + + print("\nExamples to run:") + for example in examples: + print(example) + + for example in examples: + if example: + run_example(example, workspace, runner_os) + +if __name__ == "__main__": + main() diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3a1f3e27..5ccd57e4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,10 +4,13 @@ permissions: contents: read on: - push: - branches: [ main ] + schedule: + - cron: '0 0 * * *' + workflow_dispatch: {} pull_request: branches: [ main ] + push: + branches: [ main ] jobs: run-examples: @@ -19,6 +22,8 @@ jobs: runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Set up Python uses: actions/setup-python@v5 @@ -54,64 +59,6 @@ jobs: - name: Find and run examples shell: bash - run: | - export PYTHONPATH="${{ github.workspace }}${PYTHONPATH:+:$PYTHONPATH}" - - # Find all ci_test_example files - if [[ "${{ runner.os }}" == "Windows" ]]; then - EXAMPLES=$(find . -name "ci_test_example.*" -type f \( -name "*.py" -o -name "*.bat" \) | sort) - else - EXAMPLES=$(find . -name "ci_test_example.*" -type f \( -name "*.py" -o -name "*.sh" \) | sort) - fi - - # Filter out examples that require specific versions or tools not available in CI - # FIXME: Cross-building examples require cross-compilation toolchains (e.g., arm-linux-gnueabihf-gcc-9) that are not installed in GitHub Actions runners - EXAMPLES=$(echo "$EXAMPLES" | grep -v "cross_building" || true) - - # FIXME: Filter out tensorflow examples in PRs - IS_PR="${{ github.event_name == 'pull_request' }}" - if [[ "$IS_PR" == "true" ]]; then - EXAMPLES=$(echo "$EXAMPLES" | grep -v tensorflow || true) - fi - - if [[ -z "$EXAMPLES" ]]; then - echo "No examples found to run" - exit 0 - fi - - echo "Examples to run:" - echo "$EXAMPLES" - - # Run each example - set -e - while IFS= read -r example; do - if [[ -z "$example" ]]; then - continue - fi - - example_normalized=$(echo "$example" | sed 's|\\|/|g') - example_dir=$(dirname "$example_normalized") - example_file=$(basename "$example_normalized") - - # Start group for this example - echo "::group::Running example: $example_dir" - - cd "${{ github.workspace }}/$example_dir" || exit 1 - - if [[ "$example_file" == *.py ]]; then - python "$example_file" - elif [[ "$example_file" == *.sh ]]; then - bash "$example_file" - elif [[ "$example_file" == *.bat ]]; then - if [[ "${{ runner.os }}" == "Windows" ]]; then - cmd //c "$example_file" - else - echo "Skipping .bat file on non-Windows platform" - fi - fi - - cd "${{ github.workspace }}" || exit 1 - - # End group for this example - echo "::endgroup::" - done <<< "$EXAMPLES" + env: + PYTHONPATH: ${{ github.workspace }} + run: python -u "${{ github.workspace }}/.github/run_ci_tests.py" diff --git a/.github/workflows/ros-tests.yml b/.github/workflows/ros-tests.yml index 8232fb86..6bdafb00 100644 --- a/.github/workflows/ros-tests.yml +++ b/.github/workflows/ros-tests.yml @@ -2,10 +2,10 @@ name: ROS and Conan Integration Test on: push: - branches: - - main - paths: - - examples/tools/ros/** + branches: [ main ] + schedule: + - cron: '0 0 * * *' + workflow_dispatch: {} pull_request: branches: - main From 32d3379c4bcc5fa40a78d6e19631488ecbf6bc90 Mon Sep 17 00:00:00 2001 From: Uilian Ries Date: Wed, 19 Nov 2025 15:22:38 +0100 Subject: [PATCH 06/20] [infrastructure] Run cross-building examples on CI (#206) * Trigger Cross-building on CI Signed-off-by: Uilian Ries * Update Android profile according to the CI Signed-off-by: Uilian Ries * Install arm-linux-gnueabihf-gcc-12 on Linux Signed-off-by: Uilian Ries * Clean up CI trigger Signed-off-by: Uilian Ries * Use C++17 by default Signed-off-by: Uilian Ries --------- Signed-off-by: Uilian Ries --- .github/run_ci_tests.py | 5 ----- .github/workflows/ci.yml | 6 ++++++ examples/cross_build/android/ndk_basic/ci_test_example.py | 6 +++--- .../consuming_packages/cross_building/profiles/raspberry | 8 ++++---- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/.github/run_ci_tests.py b/.github/run_ci_tests.py index 3e2b0c97..45d45096 100644 --- a/.github/run_ci_tests.py +++ b/.github/run_ci_tests.py @@ -129,11 +129,6 @@ def filter_exclusions(examples, is_pr) -> list: filtered = [] for example in examples: - # FIXME: Cross-building examples require cross-compilation toolchains - # (e.g., arm-linux-gnueabihf-gcc-9) that are not installed in GitHub Actions runners - if "cross_building" in example: - continue - # FIXME: Filter out tensorflow examples in PRs if is_pr and "tensorflow" in example: continue diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5ccd57e4..bc7db299 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,6 +25,12 @@ jobs: with: fetch-depth: 0 + - name: Install cross-compilation toolchain (Linux) + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y gcc-12-arm-linux-gnueabihf g++-12-arm-linux-gnueabihf + - name: Set up Python uses: actions/setup-python@v5 with: diff --git a/examples/cross_build/android/ndk_basic/ci_test_example.py b/examples/cross_build/android/ndk_basic/ci_test_example.py index bca5e08b..958f0ea9 100644 --- a/examples/cross_build/android/ndk_basic/ci_test_example.py +++ b/examples/cross_build/android/ndk_basic/ci_test_example.py @@ -11,12 +11,12 @@ [settings] os=Android -os.api_level=21 +os.api_level=27 arch=armv8 compiler=clang -compiler.version=12 +compiler.version=18 compiler.libcxx=c++_static -compiler.cppstd=14 +compiler.cppstd=17 build_type=Debug [conf] diff --git a/tutorial/consuming_packages/cross_building/profiles/raspberry b/tutorial/consuming_packages/cross_building/profiles/raspberry index 09349279..c3433dc9 100644 --- a/tutorial/consuming_packages/cross_building/profiles/raspberry +++ b/tutorial/consuming_packages/cross_building/profiles/raspberry @@ -2,11 +2,11 @@ os=Linux arch=armv7hf compiler=gcc -compiler.cppstd=gnu14 +compiler.cppstd=gnu17 compiler.libcxx=libstdc++11 -compiler.version=9 +compiler.version=12 build_type=Release [buildenv] -CC=arm-linux-gnueabihf-gcc-9 -CXX=arm-linux-gnueabihf-g++-9 +CC=arm-linux-gnueabihf-gcc-12 +CXX=arm-linux-gnueabihf-g++-12 LD=arm-linux-gnueabihf-ld From cc0338436beef9e7e7c04b55918b5f10943c611c Mon Sep 17 00:00:00 2001 From: Uilian Ries Date: Wed, 26 Nov 2025 10:57:43 +0100 Subject: [PATCH 07/20] [infrastructure] Fix Tensorflow CI build (#208) * Update Tensorflow example lockfile Signed-off-by: Uilian Ries * Fix boolean value name Signed-off-by: Uilian Ries * Generate lockfile on Windows profile Signed-off-by: Uilian Ries * Remove tensorflow from filter Signed-off-by: Uilian Ries * Merge lockfiles Signed-off-by: Uilian Ries * Fill the gaps in lockfile Signed-off-by: Uilian Ries * Do apply build rules on GHA Signed-off-by: Uilian Ries * Only update what is needed in the lockfile Signed-off-by: Uilian Ries * Update ffmpeg (and its dependencies) in the lockfile Signed-off-by: Uilian Ries * Update revisions due cmake 4.x Signed-off-by: Uilian Ries * Build without lock (get latest) Signed-off-by: Uilian Ries * Use OpenCV 4.12 Signed-off-by: Uilian Ries * Add lockfile based on Windows Signed-off-by: Uilian Ries * Merge unit lockfile Signed-off-by: Uilian Ries * Merge windows and unix lockfiles Signed-off-by: Uilian Ries --------- Signed-off-by: Uilian Ries Signed-off-by: Uilian Ries --- .github/run_ci_tests.py | 21 --- .../pose-estimation/ci_test_example.py | 10 +- .../pose-estimation/conan.lock | 161 ++++++++++-------- .../pose-estimation/conanfile.py | 2 +- 4 files changed, 89 insertions(+), 105 deletions(-) diff --git a/.github/run_ci_tests.py b/.github/run_ci_tests.py index 45d45096..0ac9b633 100644 --- a/.github/run_ci_tests.py +++ b/.github/run_ci_tests.py @@ -118,24 +118,6 @@ def filter_examples_by_dirs(examples, affected_dirs) -> list: return sorted(set(filtered)) -def filter_exclusions(examples, is_pr) -> list: - """ - FIXME: Filter out broken examples after GitHub Actions migration - - :param examples: list of example file paths - :param is_pr: bool - whether this is a pull request - :returns: filtered list of example file paths - """ - filtered = [] - - for example in examples: - # FIXME: Filter out tensorflow examples in PRs - if is_pr and "tensorflow" in example: - continue - - filtered.append(example) - - return filtered def run_example(example, workspace, runner_os) -> None: """ @@ -199,9 +181,6 @@ def main(): for example in examples: print(example) - # FIXME: Filter out some non-working examples in GitHub Actions - examples = filter_exclusions(examples, is_pr) - print("\nExamples to run:") for example in examples: print(example) diff --git a/examples/libraries/tensorflow-lite/pose-estimation/ci_test_example.py b/examples/libraries/tensorflow-lite/pose-estimation/ci_test_example.py index 34d1102b..8859df78 100644 --- a/examples/libraries/tensorflow-lite/pose-estimation/ci_test_example.py +++ b/examples/libraries/tensorflow-lite/pose-estimation/ci_test_example.py @@ -4,15 +4,7 @@ print("Pose estimation example with Tensorflow Lite and OpenCV") install_cmd = "conan install . -c tools.system.package_manager:mode=install " \ - "-c tools.system.package_manager:sudo=True -s compiler.cppstd=17 --build=missing " - -if platform.system() == "Windows": - install_cmd += "-c tools.cmake.cmaketoolchain:system_version=10.0" # to force CMake pick a newer SDK -elif platform.system() == "Linux": - # affected by this: https://github.com/conan-io/conan-center-index/issues/18951 - install_cmd += "--build=libx26* --build=openjpeg*" - # install_cmd += "--build=libx26* -c \"libx26*:tools.build:cxxflags=+['-fno-finite-math-only']\" " \ - # "--build=openjpeg* -c \"openjpeg*:tools.build:cxxflags=+['-fno-finite-math-only']\"" + "-c tools.system.package_manager:sudo=true -s compiler.cppstd=17 --build=missing " run(install_cmd) diff --git a/examples/libraries/tensorflow-lite/pose-estimation/conan.lock b/examples/libraries/tensorflow-lite/pose-estimation/conan.lock index d34f8a36..8aa3a0f1 100644 --- a/examples/libraries/tensorflow-lite/pose-estimation/conan.lock +++ b/examples/libraries/tensorflow-lite/pose-estimation/conan.lock @@ -1,92 +1,105 @@ { "version": "0.5", "requires": [ - "zstd/1.5.5#b87dc3b185caa4b122979ac4ae8ef7e8%1699046809.017", - "zlib/1.3.1#f52e03ae3d251dec704634230cd806a2%1708593606.497", - "xz_utils/5.4.5#a26a2d42ab9c3dabc151bbe1bf898e53%1700748506.394", - "xorg/system#f626cbdd0ba57d9c53bce2d8b9362fad%1709213216.576", - "xnnpack/cci.20231026#20bfc7ccd2bd2dcfcf3ea76446fb5b6a%1702571834.403", - "vorbis/1.3.7#37e58f52e59a6232199b34ef402714a6%1699046045.52", + "zlib/1.3.1#b8bc2603263cf7eccbd6e17e66b0ed76%1733936244.862", + "xz_utils/5.8.1#536e4c68ef30b7308b28a1c50e663c31%1762252965.869", + "xorg/system#98f82cb669e4ebc6b4d9d8a4f3f1faf4%1725460968.361", + "xnnpack/cci.20231026#c1deb0daf1cf7b0eeffa003da7944c30%1734022961.731", + "xkeyboard-config/system#74aea985b91f987b93b220b44963d7ed%1755758035.041", + "xkbcommon/1.6.0#06776a6aa42a7fd93244c4d1fd517be9%1741781632.663", + "wayland/1.22.0#52f5e560014b3a69ae11173b45031ae1%1757604147.545", + "vorbis/1.3.7#a019e93187ade6ee8a51762f88092604%1743676138.386", "vdpau/system#bdd7d010c4e3d8762a8f6f08a3cf6760%1681427145.051", - "vaapi/system#9fa40059fb979af4f9b301b84fb2dfa1%1681427134.881", - "tensorflow-lite/2.12.0#2f91fe419ebc59dba4af1adcac4963a4%1710763572.919", - "ruy/cci.20220628#81a2ef59e9f38e838fbcb8bf8246133c%1702562524.182", - "quirc/1.2#92179dd521786aea0729f2c859cbbcb9%1684855866.655", - "pulseaudio/14.2#bedb313afed20242c7caa27ae3e10af8%1705079591.3", - "pthreadpool/cci.20231129#c721f98463f06cc5bf835df5d6fd9843%1702580771.579", + "vaapi/system#a922c176e277714eaa07374e9e051df9%1752835567.918", + "tensorflow-lite/2.12.0#b23c6c9b994692844cf9b1e3987e2b86%1764056082.042", + "ruy/cci.20231129#f23bfbab527048bfffba001f7a81236d%1702571818.886", + "quirc/1.2#5398c0f120ebdeeeb3b2535a32774d25%1741858844.528", + "pulseaudio/14.2#55571c21cc121960309b7345cd04397d%1763482610.95", + "pthreadpool/cci.20231129#7bea77db5af9a570f7510a92cada3065%1741701495.916", "psimd/cci.20200517#83200a06ebb1ff39c5adff0d712c05fa%1700638971.11", - "protobuf/3.21.12#ff413e7c6d81724e872661875af83e68%1700600971.129", - "opus/1.4#54631f551fc450783fb2df8cd63f80a2%1692264047.36", - "openssl/3.2.1#edbeabd3bfc383d2cca3858aa2a78a0d%1709295668.562", - "openjpeg/2.5.0#2886244f2492121aa7c6c493a814db06%1709098264.352", - "openh264/2.3.1#8b94a94f5472b67355953c7748ad4381%1687851599.659", - "openexr/3.2.3#cc76d7ca85ee7dfe03114eaa2a8e8c1b%1710016380.492", - "opencv/4.5.5#9ef77602c86fe380fe53421d452053ee%1710161064.541", - "ogg/1.3.5#062626875f5c8c59f069f76f148098ef%1676030023.878", - "mpg123/1.31.2#ce831c936b2284e2066ab3dc58a2628e%1697088458.795", + "protobuf/3.21.12#44ee56c0a6eea0c19aeeaca680370b88%1759134885.45", + "opus/1.5.2#18fcc73dcd16edae063173f3ceb3ed56%1757941039.736", + "openssl/3.6.0#89e8af1d4a21afcac0557079d23d8890%1759746682.365", + "openjph/0.25.3#1210c79a119928427759d31018e608e0%1763323382.089", + "openjpeg/2.5.4#372fbc2b4348d45ab0c0a62a8475dc2f%1760446899.685", + "openh264/2.6.0#4534089daeda770cfb605220c6f5256b%1754125009.16", + "openexr/3.4.4#ef9eb9cb16c7e97a5e633cbcd0c13d63%1763590953.739", + "opencv/4.12.0#4725ed1b5620a70148949a7c6e8c53ba%1761555759.606", + "ogg/1.3.5#e8186fe05ebe9f8e706fa4bde2164d12%1743515256.147", + "mpg123/1.31.2#2dfb30511ada5a43fd9f4e328281a1a8%1751271778.102", "m4/1.4.19#b38ced39a01e31fef5435bc634461fd2%1700758725.451", - "libx265/3.4#60fd8f9d5a93fd2338a46a0664ddc768%1697088391.25", - "libx264/cci.20220602#310132b8f365d0d74028128d3677f132%1699409106.287", - "libwebp/1.3.2#52f69c4a31c5cf033fdd9230d77a8e38%1694806992.059", - "libvpx/1.13.1#31c5cc0bc9fff2b5baf5a1fac60bd9c9%1705664199.024", - "libtool/2.4.7#08316dad5c72c541ed21e039e4cf217b%1702300906.107", - "libtiff/4.6.0#51d0e7e15d032aeec1b64e65c44ecd9f%1710002558.618", - "libsndfile/1.2.2#b3662f832e29507e8d9840a43737cc14%1701864257.026", - "libpng/1.6.43#c219d8f01983bac10c404fc613605eef%1708791038.007", + "libxml2/2.15.0#8d852c332225c0aa07a723fba8bcd0f5%1759824466.773", + "libx265/3.4#d92862056e216025fc154565baab7cb9%1753866095.417", + "libx264/cci.20240224#a037063ce0a3e5a2f75b9c1b56038aed%1739359620.16", + "libwebp/1.6.0#eb5f8e35fc95980e32b5544a33a270b4%1754386366.205", + "libvpx/1.15.2#8bc6d81cb5df9ae5e7b039583e26aefd%1753866111.376", + "libtool/2.4.7#14e7739cc128bc1623d2ed318008e47e%1755679003.847", + "libtiff/4.7.1#0ab2d15639faa1c301ccfb271fc0b55f%1759735981.08", + "libsvtav1/2.1.0#3afefba35b1b1218a700c4699f0c84b2%1739359600.393", + "libsndfile/1.2.2#dfd79c3cd79b766ea10b1ad4dc175b07%1757941029.246", + "libpng/1.6.51#4ef0d6173fddf0ac625647c71ffb4a84%1763764892.554", "libmp3lame/3.100#44b12d19316eb2b223d98d3e75dae438%1674992501.853", - "libjpeg/9e#68269859e4325ddc3f995d1fd3fc9187%1674953155.168", - "libiconv/1.17#73fefc1b696e069df90fd1d18aa63edd%1707122814.387", - "libfdk_aac/2.0.3#a4041d75d31de57214adeb9c6f9cae26%1703759492.632", - "libdeflate/1.19#3ea74a4549efc14d4b1202dc4bfbf602%1694914376.803", + "libjpeg/9f#8edfe2699565c80c825d0256002504ff%1723665907.087", + "libiconv/1.17#1e65319e945f2d31941a9d28cc13c058%1751451666.321", + "libffi/3.4.8#a045c00fb26779635e3bed40e80c5254%1753360042.396", + "libfdk_aac/2.0.3#0115f6598be7303e042684e3a846b12d%1720774838.081", + "libdeflate/1.25#49fcd3fe6c130c2ec5a01cabb0481ded%1761981110.678", "libcap/2.69#7ef2d60864c2e58e89db957be936dc49%1693427138.461", - "libaom-av1/3.6.1#a569b342c6f8129cf327709e18691072%1689885838.656", - "libalsa/1.2.10#8b568d62190ef588cd5dcdd81171c289%1694958059.674", - "jbig/20160605#2d29fa02aacd76902e0d2cbbc24631ef%1676066289.194", - "jasper/4.2.0#bbda6a34e0a3d3e9caff91c15f632902%1707492379.368", + "libaom-av1/3.6.1#a2b22c70d6fce43887881431808ab8a6%1722584909.89", + "libalsa/1.2.10#e64d5e1ced869a2f676145bab4f4a181%1724146566.895", "intel-neon2sse/cci.20210225#56e8b51d756e9ae2a612e3489039e07b%1701333748.561", - "imath/3.1.9#2e7f5802b247baae47235b4c8d5642c9%1708982814.433", - "gtk/system#3671596ecc796ec65f881b2cad66ce26%1681427046.339", + "imath/3.2.1#47a001e9196b8d377c839c4725a99772%1756476362.494", "gemmlowp/cci.20210928#28483fa833aa47549961bb9c5ab84bfa%1676224257.989", "fxdiv/cci.20200417#0b3afe4c9d1b8d05f5f017984c8cb15a%1678543821.122", - "freetype/2.13.2#dfa3d504cae4a08d5c72113bd6f28498%1709733500.506", + "freetype/2.13.2#18656f7a6d52256a930f1cbd79f1509d%1756828316.696", "fp16/cci.20210320#34dbac7f6fa3dee68830028b53de6c84%1700638970.182", - "flatbuffers/23.3.3#9ae28c67104fca222622513355755d3e%1683895439.098", - "flac/1.4.2#6a17595ab773d2de32e18b5d3b24afff%1703803162.098", - "fft/cci.20061228#76a056c8ad3656ad557071c230f0f50c%1676213461.547", - "ffmpeg/4.4.4#0e4bd98a7a3303ae108adb9940f9ec3f%1706722475.079", - "farmhash/cci.20190513#af6593f545dd2b496e6bd019f1deb66f%1676156947.749", - "eigen/3.4.0#2e192482a8acff96fe34766adca2b24c%1680436083.8", - "dav1d/1.2.1#94259c8baa2b1e7ab9976f1c20822be8%1695167740.826", - "cpuinfo/cci.20231129#306f0c2325f566b37835fdea3af1cc2d%1702566017.149", - "bzip2/1.0.8#457c272f7da34cb9c67456dd217d36c4%1703591832.799", - "brotli/1.1.0#d56d7bb9ca722942aba17369cb5c0519%1696161049.808", - "automake/1.16.5#058bda3e21c36c9aa8425daf3c1faf50%1688481772.751", - "autoconf/2.71#00a1e46d8ba5baaf7f10d64c1a6a0342%1703977033.025", - "ade/0.1.2d#f225d0a218a7c9fbb81746806c7de53d%1697752528.048", - "abseil/20230125.3#207b0d386ea564ac506dfe34e4cd6b55%1710262073.193" + "flatbuffers/23.3.3#c8315b9616eb2a4086937ad42d60d897%1743154513.58", + "flac/1.4.2#a903f1a261e796e4a1061fdfd48927a3%1761738290.975", + "fft/cci.20061228#30ae3da3bd671aa271a4816b8e92dc33%1741856930.734", + "ffmpeg/7.1.2#c083ae3319ed57a517d73cc8482d51d2%1762926555.399", + "farmhash/cci.20190513#60980ffb85c63bbba6514c209f2ee1b8%1741856928.45", + "expat/2.7.3#f529802a90f0758a01f498a18f8c657b%1759399780.465", + "eigen/3.4.0#e7256a460e38f651ab0aa5246f94cf6a%1759398149.964", + "dav1d/1.5.2#02adf43f86f51bfcfa38950037cb1047%1762589938.824", + "cpuinfo/cci.20231129#15e94782b128bee8bfd047f6102a4d21%1716817358.974", + "bzip2/1.0.8#c470882369c2d95c5c77e970c0c7e321%1762886692.465", + "brotli/1.1.0#406ce8f1c997f4ef7852fa01ff85ef9f%1743158659.041", + "automake/1.16.5#b91b7c384c3deaa9d535be02da14d04f%1755524470.56", + "autoconf/2.71#51077f068e61700d65bb05541ea1e4b0%1731054366.86", + "ade/0.1.2d#82546b5d78a6a8393f705a8f4f826dff%1742833246.863", + "abseil/20250127.0#4242e8b46c00cdfbeb976f3d02990ba6%1761741491.734" ], "build_requires": [ - "zlib/1.3.1#f52e03ae3d251dec704634230cd806a2%1708593606.497", - "yasm/1.3.0#fb800a15413dca19bfaef9e4b5d50694%1676208399.011", - "strawberryperl/5.32.1.1#8f83d05a60363a422f9033e52d106b47%1666134191.176", - "strawberryperl/5.30.0.1#d125df083747d815c66e9ee621f3909f%1666134190.827", - "protobuf/3.21.12#ff413e7c6d81724e872661875af83e68%1700600971.129", - "pkgconf/2.1.0#27f44583701117b571307cf5b5fe5605%1701537936.436", - "pkgconf/2.0.3#f996677e96e61e6552d85e83756c328b%1696606182.229", - "ninja/1.11.1#77587f8c8318662ac8e5a7867eb4be21%1684431244.21", - "nasm/2.15.05#058c93b2214a49ca1cfe9f8f26205568%1703550024.076", - "msys2/cci.latest#5a31efa2bde593541fd5ac3bcc50c01c%1699871190.424", - "meson/1.2.2#04bdfb85d665c82b08a3510aee3ffd19%1702568761.764", - "meson/1.2.1#f641f02771e4660c772354736da0b9c6%1702568762.471", + "zlib/1.3.1#b8bc2603263cf7eccbd6e17e66b0ed76%1733936244.862", + "yasm/1.3.0#c0bc84844465e1cd7850c820ad5b09b6%1752493247.057", + "wayland-protocols/1.33#b349fc274b8ef9ba525dda076385f3fd%1757598413.204", + "wayland/1.22.0#52f5e560014b3a69ae11173b45031ae1%1757604147.545", + "strawberryperl/5.32.1.1#8d114504d172cfea8ea1662d09b6333e%1751971032.423", + "protobuf/3.21.12#44ee56c0a6eea0c19aeeaca680370b88%1759134885.45", + "pkgconf/2.5.1#93c2051284cba1279494a43a4fcfeae2%1757684701.089", + "pkgconf/2.1.0#21f96520faf7660b99f872e956d2ac13%1755505630.82", + "pkgconf/2.0.3#c7ba7fd1d7d22534ee070ae60ed79604%1755505633.107", + "ninja/1.13.1#294f8721dbcde145674f7ba44994700e%1753400352.374", + "nasm/2.16.01#31e26f2ee3c4346ecd347911bd126904%1745483323.489", + "nasm/2.15.05#f7d6a6f3b14f583e001d9ae84e8cd152%1745483325.985", + "msys2/cci.latest#5b73b10144f73cc5bfe0572ed9be39e1%1751977009.857", + "meson/1.9.1#abbc783cd297bedce14581b4aec060b8%1758626166.349", + "meson/1.3.2#26ce8a76a36cc275cdfee1d757bc6561%1726730118.251", "m4/1.4.19#b38ced39a01e31fef5435bc634461fd2%1700758725.451", - "libtool/2.4.7#08316dad5c72c541ed21e039e4cf217b%1702300906.107", - "libiconv/1.17#73fefc1b696e069df90fd1d18aa63edd%1707122814.387", - "gnu-config/cci.20210814#dc430d754f465e8c74463019672fb97b%1701248168.479", - "gettext/0.21#6a3dee40c7c7ddcd671205b5770c0d34%1710785562.76", - "cmake/3.28.1#92f79424d7b65b12a84a2180866c3a78%1703679314.116", - "automake/1.16.5#058bda3e21c36c9aa8425daf3c1faf50%1688481772.751", - "autoconf/2.71#00a1e46d8ba5baaf7f10d64c1a6a0342%1703977033.025" + "libxml2/2.15.0#8d852c332225c0aa07a723fba8bcd0f5%1759824466.773", + "libtool/2.4.7#14e7739cc128bc1623d2ed318008e47e%1755679003.847", + "libiconv/1.17#1e65319e945f2d31941a9d28cc13c058%1751451666.321", + "libffi/3.4.8#a045c00fb26779635e3bed40e80c5254%1753360042.396", + "gnu-config/cci.20210814#466e9d4d7779e1c142443f7ea44b4284%1762363589.329", + "gettext/0.22.5#4705a1582f4a611eadb15d0417427993%1755528989.046", + "flex/2.6.4#efa781fc5088b47c895bd4eef6911f2e%1761560242.855", + "expat/2.7.3#f529802a90f0758a01f498a18f8c657b%1759399780.465", + "cmake/4.1.3#d862653d72f0d0560c0bd2f356cba3e7%1763665421.934", + "cmake/3.31.10#313d16a1aa16bbdb2ca0792467214b76%1763665505.054", + "bison/3.8.2#c3490cbe0078b6fd3eb4cf5ed64144dc%1755688988.644", + "automake/1.16.5#b91b7c384c3deaa9d535be02da14d04f%1755524470.56", + "autoconf/2.71#51077f068e61700d65bb05541ea1e4b0%1731054366.86" ], "python_requires": [], "config_requires": [] -} \ No newline at end of file +} diff --git a/examples/libraries/tensorflow-lite/pose-estimation/conanfile.py b/examples/libraries/tensorflow-lite/pose-estimation/conanfile.py index 79c09db9..56dbc430 100644 --- a/examples/libraries/tensorflow-lite/pose-estimation/conanfile.py +++ b/examples/libraries/tensorflow-lite/pose-estimation/conanfile.py @@ -8,7 +8,7 @@ class PoseEstimationRecipe(ConanFile): def requirements(self): self.requires("tensorflow-lite/2.12.0") - self.requires("opencv/4.5.5") + self.requires("opencv/4.12.0") def layout(self): cmake_layout(self) From b405271950fe049b155277995923dec3a47bbceb Mon Sep 17 00:00:00 2001 From: Uilian Ries Date: Wed, 26 Nov 2025 13:03:24 +0100 Subject: [PATCH 08/20] [fix] Update URL for ARM toolchain download (#209) * Update URl for ARM toolchain Signed-off-by: Uilian Ries * Use Known agent for ARM toolchain download Signed-off-by: Uilian Ries * Revert "Use Known agent for ARM toolchain download" This reverts commit 446b5a3f100d82d443fb7f408acc67516a06aed9. --------- Signed-off-by: Uilian Ries --- examples/cross_build/toolchain_packages/toolchain/conanfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/cross_build/toolchain_packages/toolchain/conanfile.py b/examples/cross_build/toolchain_packages/toolchain/conanfile.py index f154840d..ad6c0ccb 100644 --- a/examples/cross_build/toolchain_packages/toolchain/conanfile.py +++ b/examples/cross_build/toolchain_packages/toolchain/conanfile.py @@ -52,7 +52,7 @@ def source(self): def build(self): toolchain, sha = self._get_toolchain(self.settings_target.arch) - get(self, f"https://developer.arm.com/-/media/Files/downloads/gnu/13.2.rel1/binrel/arm-gnu-toolchain-13.2.rel1-x86_64-{toolchain}.tar.xz", + get(self, f"https://armkeil.blob.core.windows.net/developer/Files/downloads/gnu/13.2.rel1/binrel/arm-gnu-toolchain-13.2.rel1-x86_64-{toolchain}.tar.xz", sha256=sha, strip_root=True) def package(self): From be5bf9de55e16cf38c1104dbb208e22b49bb60be Mon Sep 17 00:00:00 2001 From: Carlos Zoido Date: Tue, 9 Dec 2025 08:18:01 +0100 Subject: [PATCH 09/20] fix --- .../conanfile/layout/editable_components/ci_test_example.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/examples/conanfile/layout/editable_components/ci_test_example.py b/examples/conanfile/layout/editable_components/ci_test_example.py index ad77f83f..794b48f0 100644 --- a/examples/conanfile/layout/editable_components/ci_test_example.py +++ b/examples/conanfile/layout/editable_components/ci_test_example.py @@ -23,6 +23,12 @@ # Do a modification to one component, to verify it is used correctly replace(os.path.join("greetings", "src", "bye.cpp"), "bye:", "adios:") run("conan build greetings") + +# Force a clean build for the editable package to avoid timestamp issues +greetings_build_path = os.path.join("greetings", "build") +if os.path.exists(greetings_build_path): + shutil.rmtree(greetings_build_path) + # Clean app build to ensure it uses the updated library app_build_path = os.path.join("app", "build") if os.path.exists(app_build_path): From 85e2d7763b34048d18a8b894ed6e1407e0f4becb Mon Sep 17 00:00:00 2001 From: Carlos Zoido Date: Tue, 9 Dec 2025 08:33:26 +0100 Subject: [PATCH 10/20] fix --- .../conanfile/layout/editable_components/ci_test_example.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/conanfile/layout/editable_components/ci_test_example.py b/examples/conanfile/layout/editable_components/ci_test_example.py index 794b48f0..3bbb8f6b 100644 --- a/examples/conanfile/layout/editable_components/ci_test_example.py +++ b/examples/conanfile/layout/editable_components/ci_test_example.py @@ -22,13 +22,14 @@ # Do a modification to one component, to verify it is used correctly replace(os.path.join("greetings", "src", "bye.cpp"), "bye:", "adios:") -run("conan build greetings") # Force a clean build for the editable package to avoid timestamp issues greetings_build_path = os.path.join("greetings", "build") if os.path.exists(greetings_build_path): shutil.rmtree(greetings_build_path) +run("conan build greetings") + # Clean app build to ensure it uses the updated library app_build_path = os.path.join("app", "build") if os.path.exists(app_build_path): From da1d1f31efa0bc615cdd68d359830f1d9a652792 Mon Sep 17 00:00:00 2001 From: Uilian Ries Date: Wed, 10 Dec 2025 11:12:42 +0100 Subject: [PATCH 11/20] [example] GameDev Raylib: Running on Android (#210) * Add Android raylib example Signed-off-by: Uilian Ries * Add conanfile.txt Signed-off-by: Uilian Ries * Remove extra projects Signed-off-by: Uilian Ries * Find gradlew Signed-off-by: Uilian Ries * Use gradle Signed-off-by: Uilian Ries * Fix android profile path Signed-off-by: Uilian Ries * Fix android profile path 2 Signed-off-by: Uilian Ries * Create build folder Signed-off-by: Uilian Ries * Update Raylib version Signed-off-by: Uilian Ries * toolchain Signed-off-by: Uilian Ries * toolchain Signed-off-by: Uilian Ries * Validate generated .apk Signed-off-by: Uilian Ries * Add link to blog post Signed-off-by: Uilian Ries * Stop gradle daemon after building Signed-off-by: Uilian Ries * No daemon Signed-off-by: Uilian Ries * Use Jinja to populate ANDROID_NDK Signed-off-by: Uilian Ries * Add log ANDROID_NDK environment Signed-off-by: Uilian Ries --------- Signed-off-by: Uilian Ries --- examples/cross_build/README.md | 7 + .../android/raylib/app/build.gradle | 56 +++++++ .../android/raylib/app/build/android | 12 ++ .../raylib/app/src/main/AndroidManifest.xml | 19 +++ .../raylib/app/src/main/cpp/CMakeLists.txt | 24 +++ .../main/cpp/conan_android_toolchain.cmake | 15 ++ .../raylib/app/src/main/cpp/conanfile.txt | 9 ++ .../raylib/app/src/main/cpp/native-lib.cpp | 152 ++++++++++++++++++ .../cross_build/android/raylib/build.gradle | 9 ++ .../android/raylib/ci_test_example.py | 15 ++ .../android/raylib/settings.gradle | 18 +++ 11 files changed, 336 insertions(+) create mode 100644 examples/cross_build/android/raylib/app/build.gradle create mode 100644 examples/cross_build/android/raylib/app/build/android create mode 100644 examples/cross_build/android/raylib/app/src/main/AndroidManifest.xml create mode 100644 examples/cross_build/android/raylib/app/src/main/cpp/CMakeLists.txt create mode 100644 examples/cross_build/android/raylib/app/src/main/cpp/conan_android_toolchain.cmake create mode 100644 examples/cross_build/android/raylib/app/src/main/cpp/conanfile.txt create mode 100644 examples/cross_build/android/raylib/app/src/main/cpp/native-lib.cpp create mode 100644 examples/cross_build/android/raylib/build.gradle create mode 100644 examples/cross_build/android/raylib/ci_test_example.py create mode 100644 examples/cross_build/android/raylib/settings.gradle diff --git a/examples/cross_build/README.md b/examples/cross_build/README.md index d0dd7f55..7b226345 100644 --- a/examples/cross_build/README.md +++ b/examples/cross_build/README.md @@ -8,3 +8,10 @@ ### [Use Android NDK to cross-build](android/ndk_basic) - Learn how to cross-build packages for Android. [Docs](https://docs.conan.io/2/examples/cross_build/android.html) + + +### [GameDev Raylib: Running on Android](android/raylib) + +- Learn how to port your Raylib C++ game to Android using Android Studio, the NDK, and Conan for dependency management. + Configure the Android NDK path by adding `ANDROID_NDK` environment variable pointing to your NDK installation. + [Blog](https://blog.conan.io/cpp/gamedev/android/conan/raylib/2025/11/24/GameDev-Raylib-Android.html) diff --git a/examples/cross_build/android/raylib/app/build.gradle b/examples/cross_build/android/raylib/app/build.gradle new file mode 100644 index 00000000..618ad0ec --- /dev/null +++ b/examples/cross_build/android/raylib/app/build.gradle @@ -0,0 +1,56 @@ +plugins { + id 'com.android.application' +} + +task conanInstall { + def conanExecutable = "conan" // define the path to your conan installation + def buildDir = new File("app/build") + buildDir.mkdirs() + ["Debug", "Release"].each { String build_type -> + ["armv8"].each { String arch -> + def cmd = conanExecutable + " install " + + "../src/main/cpp --profile android -s build_type="+ build_type +" -s arch=" + arch + + " --build missing -c tools.cmake.cmake_layout:build_folder_vars=['settings.arch']" + print(">> ${cmd} \n") + + def sout = new StringBuilder(), serr = new StringBuilder() + def proc = cmd.execute(null, buildDir) + proc.consumeProcessOutput(sout, serr) + proc.waitFor() + println "$sout $serr" + if (proc.exitValue() != 0) { + throw new Exception("out> $sout err> $serr" + "\nCommand: ${cmd}") + } + } + } +} + +android { + namespace 'com.example.raylibexample' + compileSdk 34 + + defaultConfig { + applicationId "com.example.raylibexample" + minSdk 27 + targetSdk 34 + versionCode 1 + versionName "1.0" + + ndk { + abiFilters 'arm64-v8a' + } + + externalNativeBuild { + cmake { + cppFlags '-v' + arguments("-DCMAKE_TOOLCHAIN_FILE=conan_android_toolchain.cmake", "-DANDROID_STL=c++_shared") + } + } + } + + buildTypes { + release { + minifyEnabled false + } + } +} \ No newline at end of file diff --git a/examples/cross_build/android/raylib/app/build/android b/examples/cross_build/android/raylib/app/build/android new file mode 100644 index 00000000..51b42dc8 --- /dev/null +++ b/examples/cross_build/android/raylib/app/build/android @@ -0,0 +1,12 @@ +[settings] +os=Android +os.api_level=27 +arch=armv8 +compiler=clang +compiler.version=18 +compiler.libcxx=c++_shared +compiler.cppstd=17 +build_type=Release + +[conf] +tools.android:ndk_path={{ os.getenv("ANDROID_NDK") }} \ No newline at end of file diff --git a/examples/cross_build/android/raylib/app/src/main/AndroidManifest.xml b/examples/cross_build/android/raylib/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..8f58e318 --- /dev/null +++ b/examples/cross_build/android/raylib/app/src/main/AndroidManifest.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/cross_build/android/raylib/app/src/main/cpp/CMakeLists.txt b/examples/cross_build/android/raylib/app/src/main/cpp/CMakeLists.txt new file mode 100644 index 00000000..515b501a --- /dev/null +++ b/examples/cross_build/android/raylib/app/src/main/cpp/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.22.1) +project("raylibexample" LANGUAGES C CXX) + +set(NATIVE_APP_GLUE_DIR ${ANDROID_NDK}/sources/android/native_app_glue) + +find_package(raylib CONFIG REQUIRED) + +add_library(${CMAKE_PROJECT_NAME} SHARED) + +target_sources(${CMAKE_PROJECT_NAME} PRIVATE + ${NATIVE_APP_GLUE_DIR}/android_native_app_glue.c + native-lib.cpp) + +target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE + ${NATIVE_APP_GLUE_DIR}) + +target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE + android + log + EGL + GLESv2 + OpenSLES + m + raylib) \ No newline at end of file diff --git a/examples/cross_build/android/raylib/app/src/main/cpp/conan_android_toolchain.cmake b/examples/cross_build/android/raylib/app/src/main/cpp/conan_android_toolchain.cmake new file mode 100644 index 00000000..8ea03e74 --- /dev/null +++ b/examples/cross_build/android/raylib/app/src/main/cpp/conan_android_toolchain.cmake @@ -0,0 +1,15 @@ +if ( NOT ANDROID_ABI OR NOT CMAKE_BUILD_TYPE ) + return() +endif() + +if(${ANDROID_ABI} STREQUAL "x86_64") + include("${CMAKE_CURRENT_LIST_DIR}/build/x86_64/${CMAKE_BUILD_TYPE}/generators/conan_toolchain.cmake") +elseif(${ANDROID_ABI} STREQUAL "x86") + include("${CMAKE_CURRENT_LIST_DIR}/build/x86/${CMAKE_BUILD_TYPE}/generators/conan_toolchain.cmake") +elseif(${ANDROID_ABI} STREQUAL "arm64-v8a") + include("${CMAKE_CURRENT_LIST_DIR}/build/armv8/${CMAKE_BUILD_TYPE}/generators/conan_toolchain.cmake") +elseif(${ANDROID_ABI} STREQUAL "armeabi-v7a") + include("${CMAKE_CURRENT_LIST_DIR}/build/armv7/${CMAKE_BUILD_TYPE}/generators/conan_toolchain.cmake") +else() + message(FATAL_ERROR "Not supported configuration: ${ANDROID_ABI}") +endif() \ No newline at end of file diff --git a/examples/cross_build/android/raylib/app/src/main/cpp/conanfile.txt b/examples/cross_build/android/raylib/app/src/main/cpp/conanfile.txt new file mode 100644 index 00000000..b556b149 --- /dev/null +++ b/examples/cross_build/android/raylib/app/src/main/cpp/conanfile.txt @@ -0,0 +1,9 @@ +[requires] +raylib/5.5 + +[generators] +CMakeToolchain +CMakeDeps + +[layout] +cmake_layout diff --git a/examples/cross_build/android/raylib/app/src/main/cpp/native-lib.cpp b/examples/cross_build/android/raylib/app/src/main/cpp/native-lib.cpp new file mode 100644 index 00000000..76f53fa5 --- /dev/null +++ b/examples/cross_build/android/raylib/app/src/main/cpp/native-lib.cpp @@ -0,0 +1,152 @@ +#include +#include +#include +#include "raylib.h" +#include + +#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "RaylibApp", __VA_ARGS__)) + +extern "C" { + +int main() { + LOGI("Starting Raylib"); + // --- Initialization --- + const int screenW = 800; + const int screenH = 450; + InitWindow(screenW, screenH, "Jump to Survive!"); + SetExitKey(0); // Disable back button from closing app automatically + + // --- Player Setup --- + Rectangle player = { 100, screenH - 80, 40, 60 }; + float vy = 0; + const float gravity = 1000.0f; + const float jumpImpulse = -450.0f; + + // --- Ground Definition --- + const int groundY = screenH - 20; + + // --- Obstacle Management --- + std::vector obstacles; + float spawnTimer = 0.0f; + float spawnInterval = 1.2f; + const float obstacleSpeed = 300.0f; + + const float minSpawnInterval = 0.8f; + const float maxSpawnInterval = 1.6f; + + const int minObsWidth = 40; + const int maxObsWidth = 120; + + // --- Game State Variables --- + int score = 0; + bool gameOver = false; + float gameOverTimer = 0.0f; + + // --- Back button double press logic --- + float backPressTime = 0.0f; + bool backPressedOnce = false; + const float doublePressInterval = 0.5f; // 0.5 seconds + + SetTargetFPS(60); + + while (!WindowShouldClose()) { + float dt = GetFrameTime(); + + // --- Back button exit logic --- + if (backPressedOnce) { + backPressTime += dt; + if (backPressTime > doublePressInterval) { + backPressedOnce = false; + } + } + + if (IsKeyPressed(KEY_BACK)) { + if (backPressedOnce) { + break; // Exit game + } else { + backPressedOnce = true; + backPressTime = 0.0f; + } + } + + + if (!gameOver) { + // Jump logic + if (GetTouchPointCount() > 0 && player.y + player.height >= groundY) { + vy = jumpImpulse; + } + vy += gravity * dt; + player.y += vy * dt; + if (player.y + player.height > groundY) { + player.y = groundY - player.height; + vy = 0; + } + + // Spawn obstacles with random width & interval + spawnTimer += dt; + if (spawnTimer >= spawnInterval) { + spawnTimer = 0.0f; + // recalc next interval + spawnInterval = GetRandomValue(int(minSpawnInterval*100), int(maxSpawnInterval*100)) / 100.0f; + // random width + int w = GetRandomValue(minObsWidth, maxObsWidth); + obstacles.push_back({ float(screenW), float(groundY - 40), float(w), 40.0f }); + } + + // Move & collide obstacles + for (int i = 0; i < (int)obstacles.size(); i++) { + obstacles[i].x -= obstacleSpeed * dt; + if (CheckCollisionRecs(player, obstacles[i])) { + gameOver = true; + gameOverTimer = 0.0f; // Reset timer on game over + } + } + // Remove off-screen & score + if (!obstacles.empty() && obstacles.front().x + obstacles.front().width < 0) { + obstacles.erase(obstacles.begin()); + score++; + } + } + else { + gameOverTimer += dt; + // Want to wait 2 seconds before accepting the restart + if (GetTouchPointCount() > 0 && gameOverTimer > 1.0f) { + // reset everything + player.y = screenH - 80; + vy = 0; + obstacles.clear(); + spawnTimer = 0.0f; + spawnInterval = 1.2f; + score = 0; + gameOver = false; + } + } + + // --- Drawing --- + BeginDrawing(); + ClearBackground(RAYWHITE); + + DrawRectangle(0, groundY, screenW, 20, DARKGRAY); + DrawRectangleRec(player, BLUE); + for (auto &obs : obstacles) DrawRectangleRec(obs, RED); + + DrawText(TextFormat("Score: %d", score), 10, 10, 20, BLACK); + + if (gameOver) { + DrawText("GAME OVER! Tap to restart", 200, screenH/2 - 20, 20, MAROON); + } + + if (backPressedOnce) { + const char *msg = "Press back again to exit"; + int textWidth = MeasureText(msg, 20); + DrawText(msg, (screenW - textWidth) / 2, screenH - 420, 20, BLACK); + } + + EndDrawing(); + } + + CloseWindow(); + return 0; +} + +} // extern "C" diff --git a/examples/cross_build/android/raylib/build.gradle b/examples/cross_build/android/raylib/build.gradle new file mode 100644 index 00000000..36941ff5 --- /dev/null +++ b/examples/cross_build/android/raylib/build.gradle @@ -0,0 +1,9 @@ +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:8.2.0' + } +} diff --git a/examples/cross_build/android/raylib/ci_test_example.py b/examples/cross_build/android/raylib/ci_test_example.py new file mode 100644 index 00000000..668e255d --- /dev/null +++ b/examples/cross_build/android/raylib/ci_test_example.py @@ -0,0 +1,15 @@ +import os + +from test.examples_tools import run + +# ############# Example ################ +print("- Use the Android NDK to cross-build a package -") + + +ndk_path = os.environ.get("ANDROID_NDK") +if ndk_path: + print(f"Using Android NDK at: {ndk_path}") + run("gradle --no-daemon assembleDebug") + assert os.path.exists(os.path.join("app", "build", "outputs", "apk", "debug", "app-debug.apk")) +else: + print("WARNING: Skipping Android example, ANDROID_NDK environment variable not set") diff --git a/examples/cross_build/android/raylib/settings.gradle b/examples/cross_build/android/raylib/settings.gradle new file mode 100644 index 00000000..28ab1b0e --- /dev/null +++ b/examples/cross_build/android/raylib/settings.gradle @@ -0,0 +1,18 @@ +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} + +rootProject.name = 'RaylibExample' +include ':app' \ No newline at end of file From 88025b398ad999bdbf763d35bec99ff1e07efc82 Mon Sep 17 00:00:00 2001 From: Carlos Zoido Date: Mon, 19 Jan 2026 18:39:27 +0100 Subject: [PATCH 12/20] Fix CI to only run examples in directly affected directories (#214) * fix find examples * check * remove tests --- .github/run_ci_tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/run_ci_tests.py b/.github/run_ci_tests.py index 0ac9b633..26e3a5b7 100644 --- a/.github/run_ci_tests.py +++ b/.github/run_ci_tests.py @@ -73,8 +73,8 @@ def find_affected_directories(base_ref) -> list: selected = None while str(current) != "." and not selected: - # Check if this directory contains any ci_test_example files - has_example = any(current.glob("**/ci_test_example.*")) + # Check if this directory directly contains any ci_test_example files + has_example = any(current.glob("ci_test_example.*")) if has_example: selected = str(current) break From 5afd23a1b1f1193ab185d3fd59face59fc5c2e8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abril=20Rinc=C3=B3n=20Blanco?= <5364255+AbrilRBS@users.noreply.github.com> Date: Thu, 22 Jan 2026 13:09:29 +0100 Subject: [PATCH 13/20] Libtorch regression example (#213) * Sketch libtorch test * Readme for libtorch example * cppstd 17 * Testing * It's gnu... * It's gnu..., but not in windows! * DOn't build for windows * Use update --------- Co-authored-by: Carlos Zoido --- examples/libraries/README.md | 2 ++ .../libraries/libtorch/regression/README.md | 22 ++++++++++++++++ .../libtorch/regression/ci_test_example.py | 26 +++++++++++++++++++ .../libtorch/regression/conanfile.txt | 9 +++++++ 4 files changed, 59 insertions(+) create mode 100644 examples/libraries/libtorch/regression/README.md create mode 100644 examples/libraries/libtorch/regression/ci_test_example.py create mode 100644 examples/libraries/libtorch/regression/conanfile.txt diff --git a/examples/libraries/README.md b/examples/libraries/README.md index a0ed8526..6d158600 100644 --- a/examples/libraries/README.md +++ b/examples/libraries/README.md @@ -9,3 +9,5 @@ ### [Using libcurl to download an image and stb to read it](libcurl/download_image) ### [Using libcurl to download an image and stb to read it (color version with fmt)](libcurl/ascii_art_color) + +### [Using libtorch](libtorch/regression) diff --git a/examples/libraries/libtorch/regression/README.md b/examples/libraries/libtorch/regression/README.md new file mode 100644 index 00000000..f161adc8 --- /dev/null +++ b/examples/libraries/libtorch/regression/README.md @@ -0,0 +1,22 @@ +## Libtorch example with Conan + +This example shows how to use Libtorch with Conan package manager to build a simple regression model. + +The regression source code is not hosted in this repository, +we use [the official PyTorch examples repository](https://github.com/pytorch/examples/tree/main/cpp/regression), +so make sure to clone it first and then navigate to the `cpp/regression` folder: + +```bash +$ git clone https://github.com/pytorch/examples/tree/main/cpp/regression +$ cd cpp/regression +``` + +Then, copy the `conanfile.txt` from this directory to the `cpp/regression` folder, +and finally run Conan and CMake as usual: + +```bash +$ conan install --build=missing +$ cmake --preset conan-release # conan-default for Windows +$ cmake --build --preset conan-release +$ ./build/Release/regression +``` diff --git a/examples/libraries/libtorch/regression/ci_test_example.py b/examples/libraries/libtorch/regression/ci_test_example.py new file mode 100644 index 00000000..f4b1bea0 --- /dev/null +++ b/examples/libraries/libtorch/regression/ci_test_example.py @@ -0,0 +1,26 @@ +import platform +import shutil + +from test.examples_tools import run, chdir + +print("Libtorch regression example with Conan") + +run("git clone --depth 1 https://github.com/pytorch/examples.git") +shutil.copy("conanfile.txt", "examples/cpp/regression/conanfile.txt") + +with chdir("examples/cpp/regression"): + cppstd = "17" if platform.system() == "Windows" else "gnu17" + run(f"conan install -b=missing -s compiler.cppstd={cppstd} --update") + + if platform.system() == "Windows": + run("cmake --preset conan-default") + # Don't build on Windows. CI's msvc can't build this + else: + run("cmake --preset conan-release") + run("cmake --build --preset conan-release") + + # Only execute this in macos in the CI, there are some issues with the generated binary and the + # GitHub images + if platform.system() == "Darwin": + run("./build/Release/regression") + diff --git a/examples/libraries/libtorch/regression/conanfile.txt b/examples/libraries/libtorch/regression/conanfile.txt new file mode 100644 index 00000000..e887cb31 --- /dev/null +++ b/examples/libraries/libtorch/regression/conanfile.txt @@ -0,0 +1,9 @@ +[requires] +libtorch/[*] + +[generators] +CMakeToolchain +CMakeDeps + +[layout] +cmake_layout From 97dac2a3d318e7476cb4e56afd5665a913c14c19 Mon Sep 17 00:00:00 2001 From: Carlos Zoido Date: Thu, 22 Jan 2026 15:35:57 +0100 Subject: [PATCH 14/20] reference license (#215) --- .../toolchain_packages/toolchain/conanfile.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/examples/cross_build/toolchain_packages/toolchain/conanfile.py b/examples/cross_build/toolchain_packages/toolchain/conanfile.py index ad6c0ccb..679ec69c 100644 --- a/examples/cross_build/toolchain_packages/toolchain/conanfile.py +++ b/examples/cross_build/toolchain_packages/toolchain/conanfile.py @@ -1,6 +1,6 @@ import os from conan import ConanFile -from conan.tools.files import get, copy, download +from conan.tools.files import get, copy, save from conan.errors import ConanInvalidConfiguration from conan.tools.scm import Version @@ -48,7 +48,12 @@ def validate(self): "Only 13.X versions are supported for the compiler.") def source(self): - download(self, "https://developer.arm.com/GetEula?Id=37988a7c-c40e-4b78-9fd1-62c20b507aa8", "LICENSE", verify=False) + # The ARM toolchain is distributed under GPL-3.0-only license + # Reference: https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads + save(self, "LICENSE", "ARM GNU Toolchain\n" + "License: GNU General Public License v3.0 (GPL-3.0-only)\n" + "https://www.gnu.org/licenses/gpl-3.0.html\n\n" + "EULA: https://developer.arm.com/GetEula?Id=37988a7c-c40e-4b78-9fd1-62c20b507aa8\n") def build(self): toolchain, sha = self._get_toolchain(self.settings_target.arch) @@ -60,7 +65,7 @@ def package(self): dirs_to_copy = [toolchain, "bin", "include", "lib", "libexec"] for dir_name in dirs_to_copy: copy(self, pattern=f"{dir_name}/*", src=self.build_folder, dst=self.package_folder, keep_path=True) - copy(self, "LICENSE", src=self.build_folder, dst=os.path.join(self.package_folder, "licenses"), keep_path=False) + copy(self, "LICENSE", src=self.source_folder, dst=os.path.join(self.package_folder, "licenses"), keep_path=False) def package_id(self): self.info.settings_target = self.settings_target From e05450e31b4da74d027009ba4b08452f9703b3d1 Mon Sep 17 00:00:00 2001 From: Igor Date: Wed, 28 Jan 2026 16:32:46 +0300 Subject: [PATCH 15/20] remove imported but unused CMakeDeps --- examples/tools/cmake/pkg_config_files/conanfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/tools/cmake/pkg_config_files/conanfile.py b/examples/tools/cmake/pkg_config_files/conanfile.py index cb3b02d5..94d6e28c 100644 --- a/examples/tools/cmake/pkg_config_files/conanfile.py +++ b/examples/tools/cmake/pkg_config_files/conanfile.py @@ -1,5 +1,5 @@ from conan import ConanFile -from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout, CMakeDeps +from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout class pkgRecipe(ConanFile): From 7207c0a94640ce228e88ad3d0c58a4ff2eceba13 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 26 Feb 2026 09:18:18 +0100 Subject: [PATCH 16/20] Add package signing plugin example using OpenSSL (#207) * Add package signing plugin example * Update examples/extensions/plugins/sign/readme.md Co-authored-by: Carlos Zoido * update * minor update * update plugin example * update * fix mkdirs * fix folders * try * fix path * fix output asserts * fix leftovers * strip * fix * Apply suggestion from @danimtb * Apply suggestion from @danimtb * Apply suggestion from @danimtb * Apply suggestion from @danimtb * Apply suggestion from @danimtb * Apply suggestion from @danimtb * Apply suggestion from @danimtb * Apply suggestion from @danimtb * Apply suggestion from @danimtb * conan version check in test * Apply suggestion from @danimtb * Apply suggestion from @danimtb * fix test * Update examples/extensions/plugins/openssl_sign/readme.md Co-authored-by: Carlos Zoido * Update examples/extensions/plugins/openssl_sign/sign.py Co-authored-by: Carlos Zoido * Update examples/extensions/plugins/openssl_sign/sign.py Co-authored-by: Carlos Zoido * Update examples/extensions/plugins/openssl_sign/sign.py Co-authored-by: Carlos Zoido * Update examples/extensions/plugins/openssl_sign/sign.py Co-authored-by: Carlos Zoido * review * use warning * fixes * Rename readme.md to README.md * add note to code and readme * Update examples/extensions/plugins/openssl_sign/README.md Co-authored-by: Carlos Zoido * Apply suggestion from @czoido Co-authored-by: Carlos Zoido --------- Co-authored-by: Carlos Zoido --- examples/extensions/README.md | 8 +- .../extensions/plugins/openssl_sign/README.md | 17 +++ .../plugins/openssl_sign/ci_test_example.py | 28 +++++ .../extensions/plugins/openssl_sign/sign.py | 117 ++++++++++++++++++ 4 files changed, 168 insertions(+), 2 deletions(-) create mode 100644 examples/extensions/plugins/openssl_sign/README.md create mode 100644 examples/extensions/plugins/openssl_sign/ci_test_example.py create mode 100644 examples/extensions/plugins/openssl_sign/sign.py diff --git a/examples/extensions/README.md b/examples/extensions/README.md index 42c7fe29..42e55859 100644 --- a/examples/extensions/README.md +++ b/examples/extensions/README.md @@ -1,9 +1,13 @@ # Conan extensions examples -### [Use custom commands in your Conan CLI](extensions/commands/) +### [Use custom commands in your Conan CLI](commands) - Learn how to create custom commands in Conan. [Docs](https://docs.conan.io/2/reference/commands/custom_commands.html) -### [Use custom deployers](extensions/deployers/) +### [Use custom deployers](deployers) - Learn how to create a custom deployer in Conan. [Docs](https://docs.conan.io/2/reference/extensions/deployers.html) + +### [Package signing plugin example with OpenSSL](plugins/openssl_sign) + +- Learn how to create a package signing plugin in Conan. [Docs](https://docs.conan.io/2/reference/extensions/package_signing.html) diff --git a/examples/extensions/plugins/openssl_sign/README.md b/examples/extensions/plugins/openssl_sign/README.md new file mode 100644 index 00000000..dc7b0ecb --- /dev/null +++ b/examples/extensions/plugins/openssl_sign/README.md @@ -0,0 +1,17 @@ + +## Package signing plugin example with OpenSSL + +> **_SECURITY NOTE:_** This example stores a private key next to the plugin for simplicity. **Do not do this in production**. +> Instead, load the signing key from environment variables or a secret manager, or delegate signing to a remote signing service. +> **Always keep the private key out of the Conan cache and out of source control**. + + +Steps to test the example: + +- Copy the ``sign.py`` file to your Conan home at ```CONAN_HOME/extensions/plugins/sign/sign.py```. +- Generate your signing keys (see comment at the top of the ``sign.py`` file) and place them inside a folder with the name of your provider (``my-organization`` in the example) next to the ``sign.py`` file (``CONAN_HOME/extensions/plugins/sign/my-organization/``). +- Generate a new project to test the sign and verify commands: ``conan new cmake_lib -d name=hello -d version=1.0`` +- Create the package: ``conan create`` +- Sign the package: ``conan cache sign hello/1.0`` +- Verify the package signature: ```conan cache verify hello/1.0``` +- You can also use the ``conan install`` command, and the packages should be verified automatically when they are downloaded from a remote. diff --git a/examples/extensions/plugins/openssl_sign/ci_test_example.py b/examples/extensions/plugins/openssl_sign/ci_test_example.py new file mode 100644 index 00000000..f9f308ad --- /dev/null +++ b/examples/extensions/plugins/openssl_sign/ci_test_example.py @@ -0,0 +1,28 @@ +import os +import shutil + +from conan import conan_version +from test.examples_tools import run + +if conan_version >= "2.26.0-dev": + current_dir = os.path.abspath(os.path.dirname(__file__)) + provider_folder = os.path.join(current_dir, "my-organization") + + os.makedirs(provider_folder) + run(f"openssl genpkey -algorithm RSA -out {provider_folder}/private_key.pem -pkeyopt rsa_keygen_bits:2048") + run(f"openssl pkey -in {provider_folder}/private_key.pem -pubout -out {provider_folder}/public_key.pem") + + run(f"conan config install {current_dir} -t dir --target-folder extensions/plugins/sign") + + run("conan new cmake_lib -d name=hello -d version=1.0") + run("conan create") + + output = run("conan cache sign hello/1.0") + assert "Package signed for reference hello/1.0" in output + assert "[Package sign] Summary: OK=2, FAILED=0" in output + output = run("conan cache verify hello/1.0") + assert "Package verified for reference hello/1.0" in output + assert "[Package sign] Summary: OK=2, FAILED=0" in output + + conan_home = run("conan config home").strip() + shutil.rmtree(os.path.join(conan_home, "extensions", "plugins", "sign")) diff --git a/examples/extensions/plugins/openssl_sign/sign.py b/examples/extensions/plugins/openssl_sign/sign.py new file mode 100644 index 00000000..0f090483 --- /dev/null +++ b/examples/extensions/plugins/openssl_sign/sign.py @@ -0,0 +1,117 @@ +""" +Plugin to sign/verify Conan packages with OpenSSL. + +You will need to have ``openssl`` installed at the system level and available in your ``PATH``. + +To use this plugin, first generate a compatible keypair: + + $ openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048 + +And extract the public key: + + $ openssl pkey -in private_key.pem -pubout -out public_key.pem + +The private_key.pem and public_key.pem files should be placed inside a folder named with the the provider's name +('my-organization' for this example). The 'my-organization' folder should be next to this plugins' file sign.py +(inside the CONAN_HOME/extensions/plugins/sign folder). + +SECURITY NOTE: + This example stores a private key next to the plugin for simplicity. **Do not do this in production**. + Instead, load the signing key from environment variables or a secret manager, or delegate signing to a remote signing service. + **Always keep the private key out of the Conan cache and out of source control**. +""" + +import os +import json +import subprocess + +from conan.api.output import ConanOutput +from conan.errors import ConanException + + +def _run_command(command): + ConanOutput().info(f"Running command: {' '.join(command)}") + result = subprocess.run( + command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, # returns strings instead of bytes + check=False # we'll manually handle error checking + ) + + if result.returncode != 0: + raise subprocess.CalledProcessError( + result.returncode, result.args, output=result.stdout, stderr=result.stderr + ) + + +def sign(ref, artifacts_folder, signature_folder, **kwargs): + provider = "my-organization" # This maps to the folder containing the signing keys (for simplicity) + manifest_filepath = os.path.join(signature_folder, "pkgsign-manifest.json") + signature_filename = "pkgsign-manifest.json.sig" + signature_filepath = os.path.join(signature_folder, signature_filename) + if os.path.isfile(signature_filepath): + ConanOutput().warning(f"Package {ref.repr_notime()} was already signed") + + privkey_filepath = os.path.join(os.path.dirname(__file__), provider, "private_key.pem") + # openssl dgst -sha256 -sign private_key.pem -out document.sig document.txt + openssl_sign_cmd = [ + "openssl", + "dgst", + "-sha256", + "-sign", privkey_filepath, + "-out", signature_filepath, + manifest_filepath + ] + try: + _run_command(openssl_sign_cmd) + ConanOutput().success(f"Package signed for reference {ref}") + except Exception as exc: + raise ConanException(f"Error signing artifact: {exc}") + return [{"method": "openssl-dgst", + "provider": provider, + "sign_artifacts": { + "manifest": "pkgsign-manifest.json", + "signature": signature_filename}}] + + +def verify(ref, artifacts_folder, signature_folder, files, **kwargs): + signatures_path = os.path.join(signature_folder, "pkgsign-signatures.json") + try: + with open(signatures_path, "r", encoding="utf-8") as f: + signatures = json.loads(f.read()).get("signatures") + except Exception: + ConanOutput().warning("Could not verify unsigned package") + return + + for signature in signatures: + signature_filename = signature.get("sign_artifacts").get("signature") + signature_filepath = os.path.join(signature_folder, signature_filename) + if not os.path.isfile(signature_filepath): + raise ConanException(f"Signature file does not exist at {signature_filepath}") + + # The provider is useful to choose the correct public key to verify packages with + provider = signature.get("provider") + pubkey_filepath = os.path.join(os.path.dirname(__file__), provider, "public_key.pem") + if not os.path.isfile(pubkey_filepath): + raise ConanException(f"Public key not found for provider '{provider}'") + + manifest_filepath = os.path.join(signature_folder, "pkgsign-manifest.json") + signature_method = signature.get("method") + if signature_method == "openssl-dgst": + # openssl dgst -sha256 -verify public_key.pem -signature document.sig document.txt + openssl_verify_cmd = [ + "openssl", + "dgst", + "-sha256", + "-verify", pubkey_filepath, + "-signature", signature_filepath, + manifest_filepath, + ] + try: + _run_command(openssl_verify_cmd) + ConanOutput().success(f"Package verified for reference {ref}") + except Exception as exc: + raise ConanException(f"Error verifying signature {signature_filepath}: {exc}") + else: + raise ConanException(f"Sign method {signature_method} not supported. Cannot verify package") From f1efc433f3252bb4795cf046879f73615cc473c9 Mon Sep 17 00:00:00 2001 From: Carlos Zoido Date: Tue, 3 Mar 2026 07:11:57 +0100 Subject: [PATCH 17/20] Update conan install command for compiler settings --- examples/libraries/libtorch/regression/ci_test_example.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/libraries/libtorch/regression/ci_test_example.py b/examples/libraries/libtorch/regression/ci_test_example.py index f4b1bea0..96ca501f 100644 --- a/examples/libraries/libtorch/regression/ci_test_example.py +++ b/examples/libraries/libtorch/regression/ci_test_example.py @@ -10,7 +10,7 @@ with chdir("examples/cpp/regression"): cppstd = "17" if platform.system() == "Windows" else "gnu17" - run(f"conan install -b=missing -s compiler.cppstd={cppstd} --update") + run(f"conan install -b=missing -s:a compiler.cppstd={cppstd} --update") if platform.system() == "Windows": run("cmake --preset conan-default") From 8ded4f8cf9f8303aff13dab68ec3ea298b21c0c1 Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 3 Mar 2026 15:54:56 +0100 Subject: [PATCH 18/20] Add lockfile to libtorch example (#219) * Add lockfile to libtorch example * fix lock * wip --- .../libraries/libtorch/regression/README.md | 4 +- .../libtorch/regression/ci_test_example.py | 1 + .../libraries/libtorch/regression/conan.lock | 55 +++++++++++++++++++ 3 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 examples/libraries/libtorch/regression/conan.lock diff --git a/examples/libraries/libtorch/regression/README.md b/examples/libraries/libtorch/regression/README.md index f161adc8..eb66b721 100644 --- a/examples/libraries/libtorch/regression/README.md +++ b/examples/libraries/libtorch/regression/README.md @@ -7,11 +7,11 @@ we use [the official PyTorch examples repository](https://github.com/pytorch/exa so make sure to clone it first and then navigate to the `cpp/regression` folder: ```bash -$ git clone https://github.com/pytorch/examples/tree/main/cpp/regression +$ git clone https://github.com/pytorch/examples $ cd cpp/regression ``` -Then, copy the `conanfile.txt` from this directory to the `cpp/regression` folder, +Then, copy the `conanfile.txt` and the `conan.lock` from this directory to the `cpp/regression` folder, and finally run Conan and CMake as usual: ```bash diff --git a/examples/libraries/libtorch/regression/ci_test_example.py b/examples/libraries/libtorch/regression/ci_test_example.py index 96ca501f..00879d20 100644 --- a/examples/libraries/libtorch/regression/ci_test_example.py +++ b/examples/libraries/libtorch/regression/ci_test_example.py @@ -7,6 +7,7 @@ run("git clone --depth 1 https://github.com/pytorch/examples.git") shutil.copy("conanfile.txt", "examples/cpp/regression/conanfile.txt") +shutil.copy("conan.lock", "examples/cpp/regression/conan.lock") with chdir("examples/cpp/regression"): cppstd = "17" if platform.system() == "Windows" else "gnu17" diff --git a/examples/libraries/libtorch/regression/conan.lock b/examples/libraries/libtorch/regression/conan.lock new file mode 100644 index 00000000..1d50fed8 --- /dev/null +++ b/examples/libraries/libtorch/regression/conan.lock @@ -0,0 +1,55 @@ +{ + "version": "0.5", + "requires": [ + "zlib/1.3.1#cac0f6daea041b0ccf42934163defb20%1765284699.337", + "xnnpack/cci.20241203#ad9ea0f52b0891197b5139ad9746c79b%1765878220.067", + "sleef/3.9.0#ec959dbbec0f86e44daa4a8e385cf79e%1766052574.907", + "pthreadpool/cci.20231129#7bea77db5af9a570f7510a92cada3065%1741701495.916", + "psimd/cci.20200517#83200a06ebb1ff39c5adff0d712c05fa%1700638971.11", + "protobuf/6.32.1#b54f00da2e0f61d821330b5b638b0f80%1768401317.762", + "pocketfft/0.0.0.cci.20240801#68062a9fc92c10cd3a612ad24f57636c%1722884640.938", + "opentelemetry-cpp/1.24.0#d95a14847a31279733bae9076e30b1ad%1770977807.314", + "openssl/3.6.1#2e0be4a996c7ca91feb31b7fe65117b9%1769599203.736", + "openblas/0.3.30#aca4131c143d4c109923372e052c643c%1755683868.451", + "onnx/1.20.0#d4e18543dc432018f80c66dd72c5a719%1766424397.845", + "nnpack/0.0.0.cci.20230202#f370696d5d78b36b551cfd9c6ee0b803%1765467379.4", + "nlohmann_json/3.11.3#45828be26eb619a2e04ca517bb7b828d%1701220705.259", + "mimalloc/2.2.4#2548f5dbd79a3ded0cbaf00b297a2ecc%1758014162.287", + "libuv/1.51.0#ea781dcacf58c65462ab0d3d75e9b002%1764745176.352", + "libtorch/2.9.1#15078e553a9982321dc5c9ecebb27291%1770990078.865", + "libnuma/2.0.19#2bb8a66d041db062de63e7f8a6d06fcd%1731341382.748", + "libcurl/8.18.0#364bc3755cb9ef84ed9a7ae9c7efc1c1%1770984390.024", + "libbacktrace/cci.20210118#a7691bfccd8caaf66309df196790a5a1%1722218217.276", + "kleidiai/1.18.0#80a7e62eba979fe0f40ff210e5fd7440%1765816565.09", + "ittapi/3.25.5#0a69ebab18bd8cfa17094fa7cc88203a%1768396972.499", + "fxdiv/cci.20200417#0b3afe4c9d1b8d05f5f017984c8cb15a%1678543821.122", + "fp16/cci.20210320#34dbac7f6fa3dee68830028b53de6c84%1700638970.182", + "fmt/10.2.1#658771bb858b77f380be2ebb22c338e9%1735899167.615", + "flatbuffers/24.12.23#80629ff8f39788daff0a904c0133ca7b%1743154510.004", + "eigen/3.4.1#596b5f9599915bc0de4e7dd7fca20380%1761650594.516", + "cpuinfo/cci.20251210#d736780a00f0de0e0638b2b0cd88845d%1765382268.331", + "cpp-httplib/0.30.1#353559f1eab8a82ac5a6d2c33cc3d278%1770647395.187", + "concurrentqueue/1.0.4#1e48e1c712bcfd892087c9c622a51502%1687274728.048", + "abseil/20250814.0#c7e85f3abefdd1e05d0a8118f3320417%1761741487.631" + ], + "build_requires": [ + "zlib/1.3.1#cac0f6daea041b0ccf42934163defb20%1765284699.337", + "strawberryperl/5.32.1.1#8d114504d172cfea8ea1662d09b6333e%1751971032.423", + "protobuf/6.32.1#b54f00da2e0f61d821330b5b638b0f80%1768401317.762", + "pkgconf/2.5.1#93c2051284cba1279494a43a4fcfeae2%1757684701.089", + "opentelemetry-proto/1.7.0#ed6d5bd761bef0afb0ba09676420b9ea%1749461220.268", + "ninja/1.13.2#c8c5dc2a52ed6e4e42a66d75b4717ceb%1764096931.974", + "nasm/2.16.01#31e26f2ee3c4346ecd347911bd126904%1745483323.489", + "meson/1.10.1#426d7d29e3e5f7285cb8398d84546734%1772032339.966", + "m4/1.4.19#1f9bd25d2bd53f49ad509caa467f57b9%1770982506.688", + "libtool/2.4.7#14e7739cc128bc1623d2ed318008e47e%1755679003.847", + "gnu-config/cci.20210814#466e9d4d7779e1c142443f7ea44b4284%1762363589.329", + "cmake/4.2.3#c5c10d4c90874997553970589940ab9c%1769792867.095", + "cmake/3.31.11#f325c933f618a1fcebc1e1c0babfd1ba%1769622857.944", + "automake/1.16.5#b91b7c384c3deaa9d535be02da14d04f%1755524470.56", + "autoconf/2.71#51077f068e61700d65bb05541ea1e4b0%1731054366.86", + "abseil/20250814.0#c7e85f3abefdd1e05d0a8118f3320417%1761741487.631" + ], + "python_requires": [], + "config_requires": [] +} From a9ae9df5a32ee976a3046d23e130c2608474102a Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 6 Mar 2026 12:27:17 +0100 Subject: [PATCH 19/20] [CI] Avoid setup-python action populating the environment (#222) * [CI] Avoid setup-python action populating the environment * add python from action to path * wip * wip --- .github/workflows/ci.yml | 5 ++ .../libtorch/regression/ci_test_example.py | 1 - .../libraries/libtorch/regression/conan.lock | 55 ------------------- 3 files changed, 5 insertions(+), 56 deletions(-) delete mode 100644 examples/libraries/libtorch/regression/conan.lock diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bc7db299..4c8cb8da 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,9 +32,14 @@ jobs: sudo apt-get install -y gcc-12-arm-linux-gnueabihf g++-12-arm-linux-gnueabihf - name: Set up Python + id: setup_python uses: actions/setup-python@v5 with: python-version: "3.11" + update-environment: false + + - name: Prepend Python to System PATH + run: echo "$(dirname ${{ steps.setup_python.outputs.python-path }})" >> $GITHUB_PATH - name: Install CMake uses: jwlawson/actions-setup-cmake@v1.14 diff --git a/examples/libraries/libtorch/regression/ci_test_example.py b/examples/libraries/libtorch/regression/ci_test_example.py index 00879d20..96ca501f 100644 --- a/examples/libraries/libtorch/regression/ci_test_example.py +++ b/examples/libraries/libtorch/regression/ci_test_example.py @@ -7,7 +7,6 @@ run("git clone --depth 1 https://github.com/pytorch/examples.git") shutil.copy("conanfile.txt", "examples/cpp/regression/conanfile.txt") -shutil.copy("conan.lock", "examples/cpp/regression/conan.lock") with chdir("examples/cpp/regression"): cppstd = "17" if platform.system() == "Windows" else "gnu17" diff --git a/examples/libraries/libtorch/regression/conan.lock b/examples/libraries/libtorch/regression/conan.lock deleted file mode 100644 index 1d50fed8..00000000 --- a/examples/libraries/libtorch/regression/conan.lock +++ /dev/null @@ -1,55 +0,0 @@ -{ - "version": "0.5", - "requires": [ - "zlib/1.3.1#cac0f6daea041b0ccf42934163defb20%1765284699.337", - "xnnpack/cci.20241203#ad9ea0f52b0891197b5139ad9746c79b%1765878220.067", - "sleef/3.9.0#ec959dbbec0f86e44daa4a8e385cf79e%1766052574.907", - "pthreadpool/cci.20231129#7bea77db5af9a570f7510a92cada3065%1741701495.916", - "psimd/cci.20200517#83200a06ebb1ff39c5adff0d712c05fa%1700638971.11", - "protobuf/6.32.1#b54f00da2e0f61d821330b5b638b0f80%1768401317.762", - "pocketfft/0.0.0.cci.20240801#68062a9fc92c10cd3a612ad24f57636c%1722884640.938", - "opentelemetry-cpp/1.24.0#d95a14847a31279733bae9076e30b1ad%1770977807.314", - "openssl/3.6.1#2e0be4a996c7ca91feb31b7fe65117b9%1769599203.736", - "openblas/0.3.30#aca4131c143d4c109923372e052c643c%1755683868.451", - "onnx/1.20.0#d4e18543dc432018f80c66dd72c5a719%1766424397.845", - "nnpack/0.0.0.cci.20230202#f370696d5d78b36b551cfd9c6ee0b803%1765467379.4", - "nlohmann_json/3.11.3#45828be26eb619a2e04ca517bb7b828d%1701220705.259", - "mimalloc/2.2.4#2548f5dbd79a3ded0cbaf00b297a2ecc%1758014162.287", - "libuv/1.51.0#ea781dcacf58c65462ab0d3d75e9b002%1764745176.352", - "libtorch/2.9.1#15078e553a9982321dc5c9ecebb27291%1770990078.865", - "libnuma/2.0.19#2bb8a66d041db062de63e7f8a6d06fcd%1731341382.748", - "libcurl/8.18.0#364bc3755cb9ef84ed9a7ae9c7efc1c1%1770984390.024", - "libbacktrace/cci.20210118#a7691bfccd8caaf66309df196790a5a1%1722218217.276", - "kleidiai/1.18.0#80a7e62eba979fe0f40ff210e5fd7440%1765816565.09", - "ittapi/3.25.5#0a69ebab18bd8cfa17094fa7cc88203a%1768396972.499", - "fxdiv/cci.20200417#0b3afe4c9d1b8d05f5f017984c8cb15a%1678543821.122", - "fp16/cci.20210320#34dbac7f6fa3dee68830028b53de6c84%1700638970.182", - "fmt/10.2.1#658771bb858b77f380be2ebb22c338e9%1735899167.615", - "flatbuffers/24.12.23#80629ff8f39788daff0a904c0133ca7b%1743154510.004", - "eigen/3.4.1#596b5f9599915bc0de4e7dd7fca20380%1761650594.516", - "cpuinfo/cci.20251210#d736780a00f0de0e0638b2b0cd88845d%1765382268.331", - "cpp-httplib/0.30.1#353559f1eab8a82ac5a6d2c33cc3d278%1770647395.187", - "concurrentqueue/1.0.4#1e48e1c712bcfd892087c9c622a51502%1687274728.048", - "abseil/20250814.0#c7e85f3abefdd1e05d0a8118f3320417%1761741487.631" - ], - "build_requires": [ - "zlib/1.3.1#cac0f6daea041b0ccf42934163defb20%1765284699.337", - "strawberryperl/5.32.1.1#8d114504d172cfea8ea1662d09b6333e%1751971032.423", - "protobuf/6.32.1#b54f00da2e0f61d821330b5b638b0f80%1768401317.762", - "pkgconf/2.5.1#93c2051284cba1279494a43a4fcfeae2%1757684701.089", - "opentelemetry-proto/1.7.0#ed6d5bd761bef0afb0ba09676420b9ea%1749461220.268", - "ninja/1.13.2#c8c5dc2a52ed6e4e42a66d75b4717ceb%1764096931.974", - "nasm/2.16.01#31e26f2ee3c4346ecd347911bd126904%1745483323.489", - "meson/1.10.1#426d7d29e3e5f7285cb8398d84546734%1772032339.966", - "m4/1.4.19#1f9bd25d2bd53f49ad509caa467f57b9%1770982506.688", - "libtool/2.4.7#14e7739cc128bc1623d2ed318008e47e%1755679003.847", - "gnu-config/cci.20210814#466e9d4d7779e1c142443f7ea44b4284%1762363589.329", - "cmake/4.2.3#c5c10d4c90874997553970589940ab9c%1769792867.095", - "cmake/3.31.11#f325c933f618a1fcebc1e1c0babfd1ba%1769622857.944", - "automake/1.16.5#b91b7c384c3deaa9d535be02da14d04f%1755524470.56", - "autoconf/2.71#51077f068e61700d65bb05541ea1e4b0%1731054366.86", - "abseil/20250814.0#c7e85f3abefdd1e05d0a8118f3320417%1761741487.631" - ], - "python_requires": [], - "config_requires": [] -} From a5dba02d8098c34c975913d1f1278bcbfd1c2880 Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 31 Mar 2026 15:21:46 +0200 Subject: [PATCH 20/20] Add package signing plugin example using Sigstore's cosign (#223) * add sigstore cosign example * fix * fix env * fix env 2 * fix env 2 * readmes * fix * fix * Apply suggestions from code review Co-authored-by: Carlos Zoido * Update examples/extensions/plugins/sigstore_sign/ci_test_example.py * Apply suggestions from code review Co-authored-by: Daniel --------- Co-authored-by: Carlos Zoido --- .github/workflows/ci.yml | 4 + README.md | 2 +- examples/extensions/README.md | 12 +- .../plugins/sigstore_sign/README.md | 40 +++++ .../plugins/sigstore_sign/ci_test_example.py | 31 ++++ .../extensions/plugins/sigstore_sign/sign.py | 149 ++++++++++++++++++ .../plugins/sigstore_sign/signing-config.json | 1 + 7 files changed, 236 insertions(+), 3 deletions(-) create mode 100644 examples/extensions/plugins/sigstore_sign/README.md create mode 100644 examples/extensions/plugins/sigstore_sign/ci_test_example.py create mode 100644 examples/extensions/plugins/sigstore_sign/sign.py create mode 100644 examples/extensions/plugins/sigstore_sign/signing-config.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4c8cb8da..267df92c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,6 +55,9 @@ jobs: if: runner.os == 'macOS' run: brew install automake autoconf + - name: Install Cosign + uses: sigstore/cosign-installer@v4.1.1 + - name: Install Conan shell: bash run: | @@ -72,4 +75,5 @@ jobs: shell: bash env: PYTHONPATH: ${{ github.workspace }} + COSIGN_PASSWORD: "" run: python -u "${{ github.workspace }}/.github/run_ci_tests.py" diff --git a/README.md b/README.md index 0c3d8eb4..4dd8739e 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Sources for the [examples section](https://docs.conan.io/2/examples.html) of the ### [Conanfile methods](examples/conanfile) -### [Conan extensions](examples/consuming_packages) +### [Conan extensions](examples/extensions) ### [Conan recipe tools](examples/tools) diff --git a/examples/extensions/README.md b/examples/extensions/README.md index 42e55859..07cfff5e 100644 --- a/examples/extensions/README.md +++ b/examples/extensions/README.md @@ -8,6 +8,14 @@ - Learn how to create a custom deployer in Conan. [Docs](https://docs.conan.io/2/reference/extensions/deployers.html) -### [Package signing plugin example with OpenSSL](plugins/openssl_sign) +### Package signing plugin examples -- Learn how to create a package signing plugin in Conan. [Docs](https://docs.conan.io/2/reference/extensions/package_signing.html) +- Learn how to implement Conan's package signing plugin. [Docs](https://docs.conan.io/2/reference/extensions/package_signing.html) + +#### [OpenSSL](plugins/openssl_sign) + +- Sign and verify with `openssl dgst` and PEM keys. + +#### [Sigstore (cosign)](plugins/sigstore_sign) + +- Sign and verify packages with [Sigstore](https://www.sigstore.dev/) using [cosign](https://github.com/sigstore/cosign). diff --git a/examples/extensions/plugins/sigstore_sign/README.md b/examples/extensions/plugins/sigstore_sign/README.md new file mode 100644 index 00000000..8d187b06 --- /dev/null +++ b/examples/extensions/plugins/sigstore_sign/README.md @@ -0,0 +1,40 @@ + +## Package signing plugin example with Sigstore (cosign) + +> **This is not a production-ready plugin.** It is a **minimal example** only, to show how you might wire Conan’s package signing hook to cosign. For real deployments, harden key handling, configuration, error handling, and policy before relying on it. + +> **_SECURITY NOTE:_** This example stores a private key next to the plugin for simplicity. **Do not do this in production**. +> Instead, load the signing key from environment variables or a secret manager, or delegate signing to a remote signing service. +> **Always keep the private key out of the Conan cache and out of source control**. + +This example implements Conan’s signing hook with [cosign](https://github.com/sigstore/cosign) in **offline** mode (no public Rekor upload), using the smallest amount of code that still demonstrates sign and verify. + + +### Requirements + +- **`cosign`** on your `PATH` (required >= 3.0.0 version). See [releases](https://github.com/sigstore/cosign/releases). +- **`COSIGN_PASSWORD`:** Required environment variable for non-interactive signing (CI, scripts). Set it to the password for your cosign private key. If the key has no password, set it to an empty value (for example `export COSIGN_PASSWORD=` in bash). Cosign reads this variable instead of prompting. + +### Signing method name + +The plugin records **`method`: `sigstore`** in signature metadata (`pkgsign-signatures.json`). That string is the convention for this cosign-based backend: keep it stable so verifiers and other tooling can recognize and handle this format alongside other methods (`openssl-dgst`, `gpg`, etc.). + +### Try it + +1. Copy `sign.py`, `signing-config.json`, and your provider key directory into `CONAN_HOME/extensions/plugins/sign/` (same layout as this folder: `sign.py` and `signing-config.json` at the root of `sign/`, keys under `my-organization/`). +2. Generate keys and move them into `my-organization/` as `signing.key` / `signing.pub`: + + ```bash + cosign generate-key-pair --output-key-prefix signing + ``` + +3. Create and sign a package: + + ```bash + conan new cmake_lib -d name=hello -d version=1.0 + conan create + conan cache sign hello/1.0 + conan cache verify hello/1.0 + ``` + +Packages downloaded from a remote are verified automatically when verification is enabled (e.g. `conan install`), as with any signing plugin. diff --git a/examples/extensions/plugins/sigstore_sign/ci_test_example.py b/examples/extensions/plugins/sigstore_sign/ci_test_example.py new file mode 100644 index 00000000..eb715492 --- /dev/null +++ b/examples/extensions/plugins/sigstore_sign/ci_test_example.py @@ -0,0 +1,31 @@ +import os +import shutil +import subprocess + +from conan import conan_version +from test.examples_tools import run + +if conan_version >= "2.26.0": + current_dir = os.path.abspath(os.path.dirname(__file__)) + provider_folder = os.path.join(current_dir, "my-organization") + os.makedirs(provider_folder, exist_ok=True) + os.chdir(provider_folder) + + run("cosign generate-key-pair --output-key-prefix signing") + + os.chdir("..") + + run(f"conan config install {current_dir} -t dir --target-folder extensions/plugins/sign") + + run("conan new cmake_lib -d name=hello -d version=1.0") + run("conan create") + + output = run("conan cache sign hello/1.0") + assert "Package signed for reference hello/1.0" in output + assert "[Package sign] Summary: OK=2, FAILED=0" in output + output = run("conan cache verify hello/1.0") + assert "Package verified for reference hello/1.0" in output + assert "[Package sign] Summary: OK=2, FAILED=0" in output + + conan_home = run("conan config home").strip() + shutil.rmtree(os.path.join(conan_home, "extensions", "plugins", "sign")) diff --git a/examples/extensions/plugins/sigstore_sign/sign.py b/examples/extensions/plugins/sigstore_sign/sign.py new file mode 100644 index 00000000..7225edb6 --- /dev/null +++ b/examples/extensions/plugins/sigstore_sign/sign.py @@ -0,0 +1,149 @@ +""" +Example-only plugin to sign and verify Conan packages with Sigstore (cosign). + +**Not production-ready** — illustrative code to show how the signing hook can call cosign. Adapt and harden before any real use. + +Requires **cosign 3.0.0 or newer** on ``PATH`` (https://github.com/sigstore/cosign/releases). + +Generate a key pair (you will be prompted for a password unless you rely on ``COSIGN_PASSWORD``): + + $ cosign generate-key-pair --output-key-prefix signing + +Place ``signing.key`` and ``signing.pub`` in a folder named after your provider (``my-organization`` in this +example), next to this file under ``CONAN_HOME/extensions/plugins/sign/``. + +For non-interactive signing (CI, automation), set ``COSIGN_PASSWORD`` to the key password (empty string if the +key has no password). + +This plugin explicitly disables the usage of Rekor public log by disabling it in the signing and verification processes + +SECURITY NOTE: + This example keeps keys beside the plugin for clarity only. **Do not do this in production** — use a + secret store, or similar, and never commit private keys. +""" + +import json +import os +import subprocess + +from conan.api.output import ConanOutput +from conan.errors import ConanException + +# Stored in pkgsign-signatures.json as ``method``; use a stable, distinctive name across your org/ecosystem. +SIGNING_METHOD = "sigstore" +BUNDLE_FILENAME = "artifact.sigstore.json" + + +def _signing_config_path(): + return os.path.join(os.path.dirname(__file__), "signing-config.json") + + +def _run_command(command): + ConanOutput().info(f"Running command: {' '.join(command)}") + result = subprocess.run( + command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + check=False, + ) + if result.returncode != 0: + raise subprocess.CalledProcessError( + result.returncode, result.args, output=result.stdout, stderr=result.stderr + ) + + +def sign(ref, artifacts_folder, signature_folder, **kwargs): + provider = "my-organization" + manifest_filepath = os.path.join(signature_folder, "pkgsign-manifest.json") + bundle_filepath = os.path.join(signature_folder, BUNDLE_FILENAME) + + if os.path.isfile(bundle_filepath): + ConanOutput().warning(f"Package {ref.repr_notime()} was already signed (bundle exists)") + + if "COSIGN_PASSWORD" not in os.environ: + raise ConanException( + "COSIGN_PASSWORD is not set. Set it to your cosign key password (use an empty value if the key " + "has no password) so signing can run non-interactively." + ) + + privkey_filepath = os.path.join(os.path.dirname(__file__), provider, "signing.key") + if not os.path.isfile(privkey_filepath): + raise ConanException(f"Private key not found at {privkey_filepath}") + + cosign_sign_cmd = [ + "cosign", + "sign-blob", + "--key", + privkey_filepath, + "--bundle", + bundle_filepath, + "-y", + f"--signing-config={_signing_config_path()}", + manifest_filepath, + ] + try: + _run_command(cosign_sign_cmd) + ConanOutput().success(f"Package signed for reference {ref}") + except Exception as exc: + raise ConanException(f"Error signing artifact: {exc}") from exc + + return [ + { + "method": SIGNING_METHOD, + "provider": provider, + "sign_artifacts": { + "manifest": "pkgsign-manifest.json", + "bundle": BUNDLE_FILENAME, + }, + } + ] + + +def verify(ref, artifacts_folder, signature_folder, files, **kwargs): + signatures_path = os.path.join(signature_folder, "pkgsign-signatures.json") + try: + with open(signatures_path, "r", encoding="utf-8") as f: + signatures = json.loads(f.read()).get("signatures") + except Exception: + ConanOutput().warning("Could not verify unsigned package") + return + + for signature in signatures: + artifacts = signature.get("sign_artifacts") or {} + manifest_name = artifacts.get("manifest") + bundle_name = artifacts.get("bundle") + if not manifest_name or not bundle_name: + raise ConanException("Signature entry missing manifest or bundle path") + + manifest_filepath = os.path.join(signature_folder, manifest_name) + bundle_filepath = os.path.join(signature_folder, bundle_name) + if not os.path.isfile(bundle_filepath): + raise ConanException(f"Signature bundle not found at {bundle_filepath}") + + provider = signature.get("provider") + pubkey_filepath = os.path.join(os.path.dirname(__file__), provider, "signing.pub") + if not os.path.isfile(pubkey_filepath): + raise ConanException(f"Public key not found for provider '{provider}'") + + signature_method = signature.get("method") + if signature_method == SIGNING_METHOD: + cosign_verify_cmd = [ + "cosign", + "verify-blob", + "--key", + pubkey_filepath, + "--bundle", + bundle_filepath, + "--private-infrastructure=true", + manifest_filepath, + ] + try: + _run_command(cosign_verify_cmd) + ConanOutput().success(f"Package verified for reference {ref}") + except Exception as exc: + raise ConanException(f"Error verifying signature {bundle_filepath}: {exc}") from exc + else: + raise ConanException( + f"Sign method {signature_method!r} not supported. Cannot verify package" + ) diff --git a/examples/extensions/plugins/sigstore_sign/signing-config.json b/examples/extensions/plugins/sigstore_sign/signing-config.json new file mode 100644 index 00000000..8f2374e0 --- /dev/null +++ b/examples/extensions/plugins/sigstore_sign/signing-config.json @@ -0,0 +1 @@ +{"mediaType":"application/vnd.dev.sigstore.signingconfig.v0.2+json","rekorTlogConfig":{},"tsaConfig":{}}