Skip to content

Instantly share code, notes, and snippets.

@azriel91
Last active May 15, 2017 16:03
Show Gist options
  • Save azriel91/29e5c1e09367aa9eacd1 to your computer and use it in GitHub Desktop.
Save azriel91/29e5c1e09367aa9eacd1 to your computer and use it in GitHub Desktop.
Conan Quickstart document, WIP. Meant to be a tutorial.

Conan Quickstart

Background

In software development, build and dependency management is a complex topic to solve, especially when a dependency is not simply requiring a certain version of a library and its transitive dependencies, but also stating the following:

  • options: flags that affect what features are enabled or disabled
  • compiler: application and version
  • build mode: static or shared objects
  • build type: debug or release builds
  • operating system: platform specific behaviour and dependencies
  • architecture: 32-bit or 64-bit
  • tests and dependencies

Many tools already exist that attempt to alleviate or even completely remove some of these complexities. For example, Java removes the complexity of static/shared, debug/release, and platform-dependent topics from the development process by masking it behind the Java Virtual Machine. Similarly, Ruby uses an interpreter, avoiding the need to compile ruby source for each operating system.

However, in the C/C++ world, these complexities still exist, and many existing tools solve a particular part of the development, but the maturity of tooling support is not as far as many other languages have gone, and a lot of manual steps still has to be done for a project to be built with additional dependencies.

Conan Workflow

The development build-publish-consume workflow that conan supports is as follows:

  • install: download the dependencies required for a project (defaults to the current directory)

    If the conan server does not contain pre-built packages for the dependency, conan will instead attempt to build the project from source.

  • build: build a project (defaults to the current directory)

    This must be done after the install command, as there are com

  • export: locally publish the descriptor of a project, tracked with a reference string (defaults to the current directory)

    The descriptor is conanfile.txt or conanfile.py, depending on which is used.

    All other exports declared in the project descriptor are also exported.

    An additional file, conanmanifest.txt is also published, containing the md5 sums of all exported files.

  • package: package up a pre-built project suitable for consumption

    This step is optional if consumers are able to build from source.

    Every package must be unique to the options, compiler, build mode, build type, operating system, and architecture, except when the same pre-built package is ABI compatible (binary compatible), or does not contain binaries at all.

    !! TODO: How do you get the package id without creating another project to depend on it? !!

  • upload: publish the descriptor (and optionally pre-built packages) to a conan server

Out-of-source Build Workflow

The Conan Workflow section is still necessary to be read before this section.

Conan also supports storing the project descriptor in a separate repository to the original library. This split repository structure is especially suitable in the following scenarios:

  • projects that you may not be able to or is high effort to commit the conanfile.txt or conanfile.py project descriptor to source
  • projects that allow binaries to be freely distributed and used, but retain privacy of source code

In this workflow, a conanfile.txt or conanfile.py is still written, and committed to a separate repository.

!! TODO: Even if you define the source function in conanfile.py, you can't any conan command to retrieve the source. Perhaps it should be retrieved to the local data storage as part of conan install. And perhaps conan build should always build it inside the local data store. !!

!! TODO: Work out a way to do versioning of references:!!

When a library developed outside of the conan packaging process is tagged stable in conan, and an issue is discovered with the conan scripts, people find different ways to solve it. If the tool is opinionated about how "the same release is uploaded, but with a fix to the conan script" is solved, then people tend to follow that method rather than invent their own solution. When someone wants to pick up the fix, they can then follow that best practise and bump the tag from stable-1 to stable-2 (just an example, but it's one way to solve it).

For private-source projects, the package step cannot be omitted, and packages must be uplodaed as part of the upload step for consumers to download.

Conanfile (project descriptor)

from conans import *
import os.path

class MyProjectConan(ConanFile):
    name = 'MyProject'
    version = '0.1.0'
    settings = ['os', 'arch', 'compiler', 'build_type']
    generators = ['cmake']
    url = 'https://github.com/user/myproject.git'
    options = { 'BUILD_SHARED_LIBS': ['ON', 'OFF'] }
    default_options = 'BUILD_SHARED_LIBS=OFF'

    project_dir = '.' if os.path.exists('src') else self.name.lower()
    build_dir = 'build'

    def source(self):
        """
        Run during `conan install --build(|=(missing|all|pattern))`, when this project is downloaded as a dependency
        and there is no uploaded pre-built package, for a given set of values for options, compiler, build mode, build type, operating system, and architecture.
        """
        self.run("git clone {url} --depth 1".format(url=self.url))

    def requirements(self):
        """ Run during `conan install` to determine this project's dependencies """
        self.requires('OtherLibrary/1.2.3@user/stable')
        self.requires('googletest/1.7.0@azriel91/stable-1')

    def config(self):
        """ Run during `conan install` after downloading requirements """

        # Example of passing through options to a dependency
        # TODO: check the order:
        # A, B, C are projects. We are building C, which depends on B, and B depends on A.
        # If C passes through an option to B, and B passes it through to A, does C pass it all the way up to A?
        # It should, so it should call this function bottom up. If it doesn't it's a bug.
        otherlibrary_options = self.options['OtherLibrary']
        setattr(otherlibrary_options, 'BUILD_SHARED_LIBS', getattr(self.options, 'BUILD_SHARED_LIBS'))

    def imports(self):
        """
        TODO: figure out this part on mac/linux/windows, conan docs already have something
        http://docs.conan.io/en/latest/manage_deps/conanfile_txt.html#imports
        """
        pass

    def build(self):
        """
        Run during `conan build` when building locally, and during `conan install --build(|=(missing|all|pattern))`
        For the in-source conanfile, the project sources are next to conanfile.py, whereas for the out-of-source conanfile and during dependency retrieval that use the `source` function, it would be in a sub directory.

        Usually this means you need to tell the build tool what the working directory is.

        TODO: what about exporting all source files in `exports`, instead of using the `source` function?
        """
        option_defines = ' '.join("-D%s=%s" % (option, val) for (option, val) in self.options.iteritems())

        self.run("cmake {project_dir} -B{build_dir} {defines}".format(project_dir=self.project_dir,
                                                                      build_dir=self.build_dir,
                                                                      defines=option_defines))
        self.run("cmake --build {build_dir}".format(build_dir=self.build_dir))

    def test(self):
        """
        Run during `conan test`. I haven't actually tried using this.

        This looks for a conanfile.txt or conanfile.py inside a `test` sub directory. Feels weird to me. See:
        http://docs.conan.io/en/latest/packaging/testing.html
        """
        pass

    def package(self):
        """
        Run during `conan package <reference> <sha/--all>`

        TODO: check if what is mentioned earlier in this document is correct
        TODO: try on mac/windows and figure out what other files need to be copied
        """
        self.copy('*.h', dst='include', src="{project_dir}/include".format(project_dir=self.project_dir))

        # Built artifacts
        lib_dir = "{build_dir}/lib".format(build_dir=self.build_dir)
        self.copy('*.so', dst='lib', src=lib_dir)
        self.copy('*.a', dst='lib', src=lib_dir)
        self.copy('*.lib', dst='lib', src=lib_dir)

    def package_info(self):
        """
        Run during `conan package <reference> <sha/--all>`

        TODO: there's good info on existing docs, just need to tidy it up:
        http://docs.conan.io/en/latest/packaging/getting_started.html
        """
        self.cpp_info.libs = ['MyProject']

!! TODO: INCOMPLETE !!

This explains what things are, not how to use it. i.e. it's missing a hands on tutorial section. Existing docs have tutorials for parts of the workflow, but I'd like to see a complete workflow.

Also missing, concept of test dependencies. There may be a feature implemented for this: conan-io/conan#80

@memsharded
Copy link

Thanks for your efforts documenting this. I have added some comments in: https://gist.github.com/memsharded/3b7affcb754279ee75f4, I hope they clarify something.

I agree that the docs could benefit for a full perspective of the workflow for package creators. Now, the docs start for the basic pure "consumer" perspective, and this information could be too much. Maybe after the "Creating packages" section? Something called "full process overview"? Any suggestion welcomed, thanks

@objarni
Copy link

objarni commented Apr 4, 2017

Bump. Conan is a valiant effort, however it lacks a step-by-step tutorial. The Poco Timer example was as close I could google; however, it lacked steps on how to generate the conanfile.py from the conanfile.txt (if that is even necessary?).

What I'd like: a hello world example that uses google test, so that

  1. one can see how to build a hello_world program with no dependencies
  2. one can see how to build a hello_world program with a single unit test, written in Google Test (one dependency).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment