Linting conan packages using the conan test command

check

When building conan packages for iOS I want to know if packages have been built as specified. Because that has sometimes not been the case.

For example, a package for iOS arm64 should contain binaries that are for this architecture and have bitcode enabled. Some packages need special options so that happens, and I want to know about issues as soon as possible. For sure before consuming some package as dependency and/or let users find out about problems.

Using conan test to check a package

Luckily, Conan has a convenient way to test a package. There is the conan test command.

In short, the most important arguments for this command are:

  • a test folder that contains a conanfile.py that consumes a package

  • a reference to a package

  • some other options where profiles are most important

Check the conan test help page to get a detailed description of all options.

Usually tests are part of a recipe and check that a recipe can be consumed. But no one says it can not be used to also verify other things.

Implementing a iOS package check

For linting iOS binaries I added a test package that checks all libraries in a package if they have the expected arch and contain bitcode.

Some custom validation methods

One way to find out if a binary was built with bitcode support is this:

def check_bitcode(file, arch):
    cmd = ["otool", "-arch" , arch, "-l" , file]
    output = subprocess.check_output(cmd, text=True)
    bitcode_flag="__bitcode"
    for line in output.splitlines():
        if bitcode_flag in line:
            return True
    return False

This is an example how to get the supported architectures of a binary on MacOS.

def get_file_archs(file):
    cmd = ["lipo", "-archs", file]
    result = run(cmd, stdout=PIPE, stderr=PIPE, text=True)
    if result.returncode != 0:
        raise ValueError(f"lipo -archs returns errno {result.returncode}, {result.stderr}")
    return result.stdout.split()

I won’t go much into details about what those two scripts do since such MacOS internals are not part of this post.
The important part is that you do whatever you want to verify files in a package.

The conanfile to test a package

The conanfile.py in the test folder looks like that:

import os, sys
from conans import ConanFile
from pathlib import Path
from ioschecks import get_file_archs, check_bitcode

class TestPackage(ConanFile):

    settings = "os", "arch"

    def build(self):
        pass # supress warning message

    def test(self):
        if self.settings.os != "iOS":
            return

        name = self.display_name.split()[0].split("/")[0]
        lib_paths = self.deps_cpp_info[name].lib_paths
        libs = self.deps_cpp_info[name].libs
        static_libs = list(map(lambda lib: f"lib{lib}.a", libs))

        pkg_libs = []
        for lib in static_libs:
            for libdir in lib_paths:
                libfile = f"{libdir}/{lib}"
                if Path(libfile).is_file():
                    pkg_libs.append(libfile)
                    continue

        assert len(pkg_libs) == len(libs), "not all libs found in package"

        for lib in pkg_libs:
            if self.settings.arch == "armv8":
                check_arch = "arm64"
            elif self.settings.arch == "x86_64":
                check_arch = "x86_64"

            assert check_arch in get_file_archs(lib),
                f"Expected arch {check_arch} not found in {lib}"
            assert check_bitcode(lib, check_arch),
                f"Bitcode flag not found for {lib}"

This is a very specific test, not a generic universal one. It expects the tested package to have static libraries and checks for the architecture in the profile and that all libraries have been built with bitcode support.

If you want other tests, just implement them as extra checks. I recommend keeping the test files small and run multiple tests if multiple checks are required.

Running the test

Testing a local conan package looks like that:

conan test bitcodecheck/ djinni-support-lib/1.1.0@ --profile:build default --profile:host ios-arm64

This tests the local ios arm64 (armv8) build for the djinni support lib. The conantest.py is in the bitcodecheck folder, and of course, the profiles that where used to build that package are also provided.

Add more verification into your conan dependencies build chain

Such tests, as shown above, can easily be added to a CI and can help to catch problems in conan packages as early as possible.

The conan test command is awesome!