Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

conan create remote WIP #15856

Merged
merged 39 commits into from
May 6, 2024
Merged

conan create remote WIP #15856

merged 39 commits into from
May 6, 2024

Conversation

davidsanfal
Copy link
Contributor

@davidsanfal davidsanfal commented Mar 12, 2024

Changelog: Feature: Added transparent support for running Conan within a Docker container.
Docs: conan-io/docs#3699

  • Refer to the issue that supports this Pull Request.
  • If the issue has missing info, explain the purpose/use case/pain/need that covers this Pull Request.
  • I've read the Contributing guide.
  • I've followed the PEP8 style guides for Python code.
  • I've opened another PR in the Conan docs repo to the develop branch, documenting this one.

@davidsanfal davidsanfal mentioned this pull request Mar 12, 2024
5 tasks
@davidsanfal davidsanfal force-pushed the feature/docker_wrapper branch 2 times, most recently from 69b9720 to 9a2a05a Compare March 13, 2024 08:24
@@ -4,3 +4,4 @@ parameterized>=0.6.3
mock>=1.3.0, <1.4.0
WebTest>=2.0.18, <2.1.0
bottle
docker
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the docker pypi package is pure python, but it's worth checking whether all the dependencies are also pure python and to estimate the likelihood of dependency conflicts in the future.

otherwise I wonder if we may want to consider making it an optional dependency?

pip install conan[docker]

Copy link
Contributor

@jcar87 jcar87 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a few comments :D but this is amazing

@@ -0,0 +1,7 @@
FROM ubuntu
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if it's forst tests I would do FROM ubuntu:jammy or a specific LTS

RUN apt update && apt upgrade -y
RUN apt install -y build-essential
RUN apt install -y python3-pip cmake git build-essential
RUN pip install docker
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

each RUN line is cached independently, this Dockerfile is only guaranteed to build properly from a clean cache (or a --no-cache flag when building)

Remember that apt install does the dependency resolution against the cached outcome of apt update ) - we want to avoid running an apt install against an outdated cache (it will fail, since the apt remotes delete versions quite quickly)

Recommended way is something like:

RUN apt-get update \ 
    && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
        build-essential \
        cmake \
        ninja-build \
        python3 \
        python3-venv
#    && rm -rf /var/lib/apt/lists/*

The && guarantees that apt-get update and apt-get install are part of the same RUN command, and the last line ensures that the apt cache is removed as part of the same command: it is outdated almost by definition.

RUN apt install -y python3-pip cmake git build-essential
RUN pip install docker
COPY . /root/conan-io
RUN cd /root/conan-io && pip install -e .
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC newer versions of Python in ubuntu will complain when pip is invoked at the system level (for the better) - I'd install it inside a virtual environment

type=docker
dockerfile={dockerfile_path("Dockerfile_test")}
docker_build_path={conan_base_path()}
image=conan-runner-default-test
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My intuition here... but having both dockerfile and image strike me as contradictory? I would expect to have one or the other:

My expectation:

  • if we have an image, assume it exists and launch a container from that image
  • if we are given a dockerfile - build the dockerfile and launch a container from the resulting image, I would not expect to pass an image - I would expect this to be internal to conan to decide what the image is called

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you only define the dockerfile, it will generate the image with the default name. If you have several profiles with dockerfiles, you can have a mechanism that doesn't overwrite the images and not need to rebuild them when changing profiles.
In addition, it assumes that the image exists if you only define the name of the image.

docker_build_path={conan_base_path()}
image=conan-runner-default-test
cache=copy
remove=True
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the meaning of remove?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove the container at the end of the conan command execution

[runner]
type=docker
dockerfile={dockerfile_path("Dockerfile_test")}
docker_build_path={conan_base_path()}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest to rename this to docker_build_context or similar - and document:

  • what the default behaviour is when this is not provided
  • what it is relative to, if a relative path is provided (if it's even possible)
  • (assuming that it will work as expected if a full path is provided, but that tends to be tricky when this is expressed in distributable config files)

return os.path.dirname(os.path.dirname(conans.__file__))


def dockerfile_path(name=None):
Copy link
Contributor

@czoido czoido Mar 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One minor comment regarding the test style. I think it would be better to declare the file as a string here (or import the string from a common place once many tests in different places use it) and create the file using client.save(), then you can access the path with client.current_folder...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a static path inside the conan test folder.
For example: conans/test/integration/command/dockerfiles/Dockerfile

cwd = os.getcwd()
if profiles:
for profile in profiles:
profile_path = ProfileLoader.get_profile_path(os.path.join(ConfigAPI(self.conan_api).home(), 'profiles'), profile, cwd)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could be wrong here - if we are using the profile name, rather than the profile already "composed" by the create command - this may result in an incomplete thing being copied if the profile has a include() referring to other files - we are only going to copy the referenced file, rather than anything else it might need.

if we propagate the resolved profile from the conan create command - we can just do a profile.dumps() and save it in the container - that should resolve includes(), but I suspect it will also render jinja templates and the likes

a reason this is important is that we may have a profile like this:

include(windows-msvc193)
[runner]
type=ssh
host=my-remote-host

and then the file windows-msvc193 is a different profile file that doesn't have any [runner] related things. I expect devs to operate like this to avoid repetition

@memsharded may be able to provide more context here : D

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is correct, I initially discussed with @davidsanfal that we might want to transfer the files, because of the same reason, there would be files that would evaluate differently in different machines due to jinja rendering.

But it is totally true that the include() would be a limitation and fail unexpectedly. We need to think about it.

@memsharded memsharded self-assigned this Mar 20, 2024
@memsharded memsharded added this to the 2.3.0 milestone Mar 20, 2024
@davidsanfal davidsanfal force-pushed the feature/docker_wrapper branch 4 times, most recently from 2c50c14 to e31be33 Compare March 22, 2024 08:42
@davidsanfal davidsanfal force-pushed the feature/docker_wrapper branch 2 times, most recently from db89115 to 84ef97d Compare April 19, 2024 08:45
@davidsanfal davidsanfal marked this pull request as ready for review April 23, 2024 13:39
@AbrilRBS
Copy link
Member

Just noting down here that we had excellent used feeback on the addition of this feature on the Using cpp::std conference after we showed it in our talk! 🥳

except:
raise ConanException("Docker Client failed to initialize."
"\n - Check if docker is installed and running"
"\n - Run 'pip install docker>=5.0.0, <=5.0.3'")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better pip isntall ... [runners]?

Comment on lines 71 to 74
'docker': DockerRunner,
'ssh': SSHRunner,
'wsl': WSLRunner,
}[profile_host.runner.get('type')](conan_api, 'create', profile_host, profile_build, args, raw_args).run()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit too much, I wouldn't mind something in 3 separate lines, and also something that raise a controlled ConanException if the runner.type is not defined or incorrect.

conan/internal/runner/docker.py Outdated Show resolved Hide resolved
Comment on lines +189 to +195
import os
from conan import ConanFile
from conan.tools.cmake import CMake, CMakeToolchain

class Library(ConanFile):
name = "hello"
version = "1.0"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reason not to use a standard conan new cmake_lib template? That would be better.
The Ninja generator can be defined in the profile if necessary.

Comment on lines +1 to +9
FROM ubuntu:22.04
RUN apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
build-essential \
cmake \
ninja-build \
python3 \
python3-pip \
python3-venv \
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the 3 dockerfiles are very similar, wouldn't it make sense to define a common base one? that would make tests that use these dockerfiles faster to build locally, isn't it? test speed is important to keep as fast as possible.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could be done via multi stage feature: https://docs.docker.com/build/building/multi-stage/

@@ -24,6 +24,7 @@ def __init__(self):
self.conf = ConfDefinition()
self.buildenv = ProfileEnvironment()
self.runenv = ProfileEnvironment()
self.runner = {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems the Profile might need to add serialize() and dumps() for full management of the runners information

Comment on lines +129 to +130
self.args.profile_host[0]: self.host_profile.dumps(),
self.args.profile_build[0]: self.build_profile.dumps()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Profile dumps() doesn't include the runners information now, but it might.

@AbrilRBS AbrilRBS self-requested a review May 5, 2024 22:20
Copy link
Member

@AbrilRBS AbrilRBS left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great, I have some comments about errors I found while testing this locally

conan/cli/commands/create.py Outdated Show resolved Hide resolved
conan/cli/commands/create.py Outdated Show resolved Hide resolved
conan/internal/runner/docker.py Outdated Show resolved Hide resolved
conan/cli/commands/create.py Outdated Show resolved Hide resolved
conan/internal/runner/docker.py Outdated Show resolved Hide resolved
Copy link
Member

@AbrilRBS AbrilRBS left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🥳

@czoido czoido assigned czoido and unassigned memsharded May 6, 2024
self.image = host_profile.runner.get('image') or self.configfile.image
if not (self.dockerfile or self.image):
raise ConanException("'dockerfile' or docker image name is needed")
self.image = self.image or 'conan-runner-default'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe not for this PR, but suggesting here that we make the default image name configurable with something like core.runners.docker.default_image_name

Comment on lines +108 to +110
self.dockerfile = host_profile.runner.get('dockerfile') or self.configfile.build.dockerfile
self.docker_build_context = host_profile.runner.get('build_context') or self.configfile.build.build_context
self.image = host_profile.runner.get('image') or self.configfile.image
Copy link
Contributor

@czoido czoido May 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find the preference order in selecting from the profile or the config file a bit unnatural, because you can set in the profile contradictory things, like for example using a configfile that points to a dockerfile and be pointing in the profile to another one, shouldn't the configfile and the profile settings that overlap be incompatible in some way?

@czoido czoido merged commit bd60587 into develop2 May 6, 2024
2 checks passed
@czoido czoido deleted the feature/docker_wrapper branch May 6, 2024 14:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants