conan-io / conan

Conan - The open-source C and C++ package manager
https://conan.io
MIT License
8.31k stars 986 forks source link

[question] During deploy(), self.package_folder seems to be None, but why? #17175

Closed Niketin closed 1 month ago

Niketin commented 1 month ago

What is your question?

Hi! I am using Conan version 2.8.0 from PyPI. I have created a package, and I want to deploy it to a directory of my choice. I noticed, that during deploy with command conan install driver -pr:a conan/profiles/driver --deployer-package=* --deployer-folder . I get this exception

======== Finalizing install (deploy, generators) ========
conanfile.py (asd_driver/1.1.1-5-g622361f-dirty): Executing deploy()
ERROR: conanfile.py (asd_driver/1.1.1-5-g622361f-dirty): Error in deploy() method, line 136
        copy(self, "*", src=self.package_folder, dst=self.deploy_folder)
        ConanException: copy() received 'src=None' argument

and that copy statement is the only statement in the method. Am I doing something wrong? Why is the self.package_folder defined None here? I am trying to follow the example from docs for deployment. Building and packaging works just fine.

I can see that the self.package_folder is defined during conan create but not during conan install. Is this a bug or a user error?

Thanks!

Cleaned version of the Conanfile.py that I use.

from conan import ConanFile
from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout, CMakeDeps
from conan.errors import ConanInvalidConfiguration
from conan.tools.scm import Git, Version
from conan.tools.files import copy
from pathlib import Path

class DcpfDriverConan(ConanFile):
    name = "asd_driver"
    settings = "os", "compiler", "build_type", "arch"
    description = "description"
    options = {"shared": [True, False], "fPIC": [True, False]}
    default_options = {"shared": False, "fPIC": True}
    no_copy_source = True

    def set_version(self):
        git = Git(self, self.recipe_folder)
        self.version = git.run('describe --dirty --match "driver/v*"').removeprefix("driver/v")

    def validate(self):
        if self.settings.os != "Windows":
            raise ConanInvalidConfiguration(f"OS '{self.settings.os}' not supported, only Windows is supported")

    def config_options(self):
        if self.settings.os == "Windows":
            del self.options.fPIC

    def configure(self):
        if self.options.shared:
            # If os=Windows, fPIC will have been removed in config_options()
            # use rm_safe to avoid double delete errors
            self.options.rm_safe("fPIC")

    def requirements(self):
        pass

    def layout(self):
        cmake_layout(self)

    def export_sources(self):
        # source files are copiped here from self.recipe_folder to self.export_sources_folder. I left the real ones out from this snippet. Following are just examples.
        # copy(self, "CMakeLists.txt", self.recipe_folder, self.export_sources_folder)
        # copy(self, "conanfile.py", self.recipe_folder, self.export_sources_folder)
        # copy(self, "include/*.h", self.recipe_folder, self.export_sources_folder)
        # copy(self, "src/*.h", self.recipe_folder, self.export_sources_folder)
        pass

    def version_major_minor_patch(self):
        v = Version(str(self.version))
        return f"{v.major}.{v.minor}.{v.patch}"

    def generate(self):
        cmakedeps = CMakeDeps(self)
        cmakedeps.generate()

        tc = CMakeToolchain(self)
        tc.cache_variables["PROJECT_VERSION"] = self.version_major_minor_patch()
        tc.variables["LIB_VERSION"] = str(self.version)
        tc.generate()

    def build(self):
        cmake = CMake(self)
        cmake.configure()
        cmake.build()

    def package(self):
        cmake = CMake(self)
        cmake.install()

    def package_info(self):
        self.cpp_info.system_libs = ["ntdll.lib"]
        self.cpp_info.includedirs = ["include"]
        self.cpp_info.libdirs = []
        self.cpp_info.rundirs = []

    def deploy(self):
        copy(self, "*", src=self.package_folder, dst=self.deploy_folder)

Have you read the CONTRIBUTING guide?

memsharded commented 1 month ago

Hi @Niketin

Thanks for your question.

Yes, there are some cases in which a self.package_folder can be None. This happens when the binary for a given package is marked as Skip. You should be able to see that in the output of the Conan command, when the different package actions (Cache, Download, Update, Build and Skip) are defined. If you share the command output it should be visible there.

The Skip situation happens when the binary is not necessary, for example if you tool_requires an application that depends requires on a static library, retrieving the application binary is enough, it is not necessary to download the static library binary, and it will be skipped to save the download transfer time.

There are mechanisms to force the download of all binaries (via conf), but the recommended approach for deployers is to check for .package_folder is not None before doing any action on them (we might need to add some clarifications or explicit examples in the docs).

Please let me know if this clarifies it.

Niketin commented 1 month ago

This clarifies it @memsharded.

My intention is to:

  1. Build and create a package.
  2. Deploy the built package (and possible dependencies) to an arbitrary directory in the same machine for later consumption by CI/CD.

Step 1 is easy with conan create. I tried to complete step 2 with conan install <recipe> [<option> ...] but I misunderstood one thing. It only deploys the dependencies in the recipe, not the package itself. Am I correct? How can I achieve step 2?

Niketin commented 1 month ago

I want to avoid copying artifacts manually from the cache, as suggested in the highlighted bullet point in the picture below. image

Niketin commented 1 month ago

I got it now working. I used the conan install with --require to my desired package and no recipes. From the different deployers built in, the direct_deploy is most fit to my case.

Example:

conan install --require "asd_driver/<exact_version>" -pr:a <path_to_profile> --deployer="direct_deploy" --deployer-folder <arbitrary_directory>

I used some powershell to automatically get the name and version from the current recipe.

$json = conan inspect driver -f json | ConvertFrom-Json
$ref = "$($json.name)/$($json.version)"

and $ref now contains the string <recipe_name>/<version> e.g. asd_driver/1.1.1-5-g622361f-dirty.

Thank you @memsharded for the initial reply.

memsharded commented 1 month ago

Step 1 is easy with conan create. I tried to complete step 2 with conan install [

It works by using the created package, not the local recipe in user folder (which is not a package). So conan install --requires=mypkg/version instead of conan install <path-to-recipe>.

I got it now working. I used the conan install with --require to my desired package and no recipes. From the different deployers built in, the direct_deploy is most fit to my case.

Yes, exactly, you got it. Only packages in the cache can be deployed, local recipes are not packages, so they cant (they might not even have built binaries locally).

I used some powershell to automatically get the name and version from the current recipe.

Probably can avoid using the extra conan inspect, use conan create --format=json > graph.json, and then read the json["graph"]["nodes"]["1"]["ref"] value (this can easily be done with jq for example

memsharded commented 1 month ago

If everything is clear now, maybe we can close this ticket as resolved?

Niketin commented 1 month ago

Yes, definitely. Thank you so much!

memsharded commented 1 month ago

Great, thanks very much to you for the feedback!