conan icon indicating copy to clipboard operation
conan copied to clipboard

[question] how to deploy without dependencies

Open pwqbot opened this issue 1 year ago • 3 comments

What is your question?

Currently, I use conan install --requires="my_package" --deployer="my_deployer" to deploy my binary. However, this command pulls all dependencies also, but I only need my package binary. Is there a way to avoid pulling other dependencies?

Have you read the CONTRIBUTING guide?

  • [X] I've read the CONTRIBUTING guide

pwqbot avatar May 17 '24 11:05 pwqbot

Hi @pwqbot

Thanks for your question.

You have the direct_deploy that only deploys the direct reference, not the dependencies.

But I guess you don't mean that, but the fact that Conan is resolving the dependency graph. It is indeed necessary to compute the dependency graph, because the binary that is pulled when you do conan install --requires="mypkg/version" also depends on the dependencies and transitive dependencies. For example if mypkg is an application, and the dependencies are static libraries, it might be necessary to re-build mypkg from source to get an update binary. Only computing the dependency graph allows to compute updated package_ids and making sure the thing that is being deployed is what is expected.

Conan has the "skip" binaries mechanism, so if things are updated and all is looking good, Conan will mark most of the binaries as "skip" and avoid pulling the dependencies if they are not necessary (like all static libraries will be skipped), making the process efficient.

If all you want is access to the artifacts for a given pkg/version:package_id, then you can do conan download + conan cache path for that specific package-id, and copy the files from the package. If you want to do something more advanced (without modifying the cache, that is not allowed), then you might want to wrap that in a custom command instead of trying a deployer, and your custom command can download the specific package-id and do what you want. But that is a different approach than the deployers that are designed to do operations on a dependency graph, not on one package.

memsharded avatar May 17 '24 11:05 memsharded

Hi @pwqbot

Thanks for your question.

You have the direct_deploy that only deploys the direct reference, not the dependencies.

But I guess you don't mean that, but the fact that Conan is resolving the dependency graph. It is indeed necessary to compute the dependency graph, because the binary that is pulled when you do conan install --requires="mypkg/version" also depends on the dependencies and transitive dependencies. For example if mypkg is an application, and the dependencies are static libraries, it might be necessary to re-build mypkg from source to get an update binary. Only computing the dependency graph allows to compute updated package_ids and making sure the thing that is being deployed is what is expected.

Conan has the "skip" binaries mechanism, so if things are updated and all is looking good, Conan will mark most of the binaries as "skip" and avoid pulling the dependencies if they are not necessary (like all static libraries will be skipped), making the process efficient.

If all you want is access to the artifacts for a given pkg/version:package_id, then you can do conan download + conan cache path for that specific package-id, and copy the files from the package. If you want to do something more advanced (without modifying the cache, that is not allowed), then you might want to wrap that in a custom command instead of trying a deployer, and your custom command can download the specific package-id and do what you want. But that is a different approach than the deployers that are designed to do operations on a dependency graph, not on one package.

Thanks! I think download + cache path may work. How to query for package id with params like package/version -s build_type=Debug -pr x86_linux_gcc_13_Debug -pr:b x86_linux_gcc_13_Debug?

pwqbot avatar May 20 '24 10:05 pwqbot

Thanks! I think download + cache path may work. How to query for package id with params like package/version -s build_type=Debug -pr x86_linux_gcc_13_Debug -pr:b x86_linux_gcc_13_Debug?

With conan graph info ... passing those arguments. Note that this will compute the dependency graph, as commented above the package_id is also a function of the dependencies versions and revisions, so it is necessary to compute the graph, even if the binaries for the dependencies are not downloaded.

Alternatively, the conan list command has the --filter-profile and --filter-settings that can help listing the packages that matches some profiles and settings, but not this is just a filter of the existing binaries that matches those conditions, not an update package_id computation.

memsharded avatar May 20 '24 10:05 memsharded

Any further feedback here @pwqbot? I think we can close the ticket as solved, but please let us know if there is any further question.

memsharded avatar Jul 29 '24 15:07 memsharded

Any further feedback here @pwqbot? I think we can close the ticket as solved, but please let us know if there is any further question.

sure, we have resolved this issue. Here is the extension:

import json

from conan.api.conan_api import (
    ConanAPI,
    ListAPI,
    RemotesAPI,
    DownloadAPI,
    CacheAPI,
)
import shutil, os
from conan.api.model import ListPattern
from conans.model.package_ref import PkgReference
from conan.api.output import ConanOutput
from conan.cli.command import conan_command


@conan_command(group="Custom commands")
def download_bin(conan_api: ConanAPI, parser, *args):
    """
    Download a package from a remote, with calculating dependencies
    """
    parser.add_argument("package", help="Package to download", default="")
    parser.add_argument(
        "-p", "--profile", dest="profile", help="Profile to use", required=True
    )
    parser.add_argument(
        "-r", "--remote", dest="remote", help="Remote to use", required=True
    )
    parser.add_argument(
        "-of",
        "--output-folder",
        dest="output_folder",
        help="Output folder",
        required=True,
    )
    args = parser.parse_args(*args)

    package = args.package

    list_api = ListAPI(conan_api)
    ref_pattern = ListPattern(f"{package}:*")
    remote_api = RemotesAPI(conan_api)
    download_api = DownloadAPI(conan_api)
    cache_api = CacheAPI(conan_api)

    # split by "/"
    package = package.split(":")[0]
    name, version = package.split("/")
    output_folder = args.output_folder

    profiles = conan_api.profiles.get_profile([args.profile])

    for remote in remote_api.list(args.remote):
        pkg_list = list_api.select(ref_pattern, profile=profiles, remote=remote)
        ConanOutput().info(
            f"Downloading package: {pkg_list.serialize()} from {remote}\n"
        )
        download_api.download_full(pkg_list, remote, None)
        for _, package in pkg_list.serialize().items():
            for revision, package in package["revisions"].items():
                for id, _ in package["packages"].items():
                    package_ref = PkgReference.loads(
                        f"{name}/{version}#{revision}:{id}"
                    )
                    path = cache_api.package_path(package_ref)
                    ConanOutput().info(f"Downloaded {name}/{version} to {path}\n")
                    bin_dir = os.path.join(path, "bin")
                    if not os.path.exists(output_folder):
                        os.makedirs(output_folder)
                    for file in os.listdir(bin_dir):
                        ConanOutput().info(f"Copying {file} to {output_folder}")
                        shutil.copy(
                            os.path.join(bin_dir, file),
                            os.path.join(output_folder, file),
                        )

    return ""

pwqbot avatar Jul 30 '24 09:07 pwqbot

Great, thanks for the feedback!

Some comments:

  • Please don't instantiate yourself sub-apis like list_api = ListAPI(conan_api). This will not be a supported use case, and using the conan_api.subapi.method will be the way. Please check https://docs.conan.io/2/examples/extensions/commands/clean/custom_command_clean_revisions.html#code-tour for an example.
  • You might leverage the conan_api.command, something like:
     @conan_command(group="custom commands")
             def mycommand(conan_api, parser, *args, **kwargs):
                 \""" mycommand help \"""
                 result = conan_api.command.run({argument})
    
    To run other commands. In this case, it seems that you could further simplify your code by calling conan list ... --filter-profile=myprofile via conan_api.command.run() to do the job

We need to further document the ConanAPI, sorry we couldn't sufficiently prioritize that yet, but it is in our plans.

I think your custom command is exactly the goal of the custom commands framework, to be able to implement this kind of automation that is a bit too specific to be built-in. It was a great job.

I think this question can be closed as solved, thanks again for the feedback, don't hesitate to create new tickets for any further question.

memsharded avatar Jul 30 '24 09:07 memsharded