From 54f1cce2a147a9efd132098a3b38bf11c70399f4 Mon Sep 17 00:00:00 2001 From: memsharded Date: Wed, 17 May 2023 09:53:44 +0200 Subject: [PATCH 1/4] tool-requires with different options and versions --- examples.rst | 1 + examples/graph.rst | 14 ++ .../graph/tool_requires/different_options.rst | 121 ++++++++++++++++++ .../tool_requires/different_versions.rst | 119 +++++++++++++++++ 4 files changed, 255 insertions(+) create mode 100644 examples/graph.rst create mode 100644 examples/graph/tool_requires/different_options.rst create mode 100644 examples/graph/tool_requires/different_versions.rst diff --git a/examples.rst b/examples.rst index b214ce20e8a5..0f2b6dcde3e3 100644 --- a/examples.rst +++ b/examples.rst @@ -12,3 +12,4 @@ Examples examples/tools examples/cross_build examples/config_files + examples/graph diff --git a/examples/graph.rst b/examples/graph.rst new file mode 100644 index 000000000000..4ee2f0c52095 --- /dev/null +++ b/examples/graph.rst @@ -0,0 +1,14 @@ +.. _examples_graph: + +Graph examples +============== + +This section contains example about different types of advanced graphs, using different types of ``requires`` and ``tool_requires``, +advanced usage of requirement traits, etc. + + +.. toctree:: + :maxdepth: 2 + + graph/tool_requires/different_versions + graph/tool_requires/different_options diff --git a/examples/graph/tool_requires/different_options.rst b/examples/graph/tool_requires/different_options.rst new file mode 100644 index 000000000000..de2285d2c24d --- /dev/null +++ b/examples/graph/tool_requires/different_options.rst @@ -0,0 +1,121 @@ +Depending on same version of a tool-require with different options +================================================================== + +.. note:: + + This is an **advanced** use case. It shouldn't be necessary in the vast majority of cases. + + +In the general case, trying to do something like this: + +.. code-block:: python + + def build_requirements(self): + self.tool_requires("gcc/1.0") + self.tool_requires("gcc/1.0") + +Will generate a "conflict", showing an error like ``Duplicated requirement``. + +However there are some exceptional situations that we could need depending on the same ``tool_requires`` version, +but using different binaries of that ``tool_requires``. This can be achieved by passing different ``options`` to those +``tool_requires``. Please, first, clone the sources to recreate this project, you can find them in the +`examples2.0 repository `_ on GitHub: + +.. code-block:: shell + + git clone https://github.com/conan-io/examples2.git + cd examples2/examples/graph/tool_requires/different_options + +There we have a ``gcc`` fake recipe with: + +.. code-block:: python + + class Pkg(ConanFile): + name = "gcc" + version = "1.0" + options = {"myoption": [1, 2]} + + def package(self): + # This fake compiler will print something different based on the option + echo = f"@echo off\necho MYGCC={self.options.myoption}!!" + save(self, os.path.join(self.package_folder, "bin", f"mygcc{self.options.myoption}.bat"), echo) + save(self, os.path.join(self.package_folder, "bin", f"mygcc{self.options.myoption}.sh"), echo) + os.chmod(os.path.join(self.package_folder, "bin", f"mygcc{self.options.myoption}.sh"), 0o777) + + +This is not an actual compiler, it fakes it with a shell or bat script that prints ``MYGCC=current-option`` when executed. +Note the binary itself is called ``mygcc1`` and ``mygcc2``, that is, it contains the option in the executable name itself. + +We can create 2 different binaries for ``gcc/1.0`` with: + + +.. code-block:: bash + + $ conan create gcc -o myoption=1 + $ conan create gcc -o myoption=2 + +Now, in the ``wine`` folder there is a ``conanfile.py`` like this: + +.. code-block:: python + + class Pkg(ConanFile): + name = "wine" + version = "1.0" + + def build_requirements(self): + self.tool_requires("gcc/1.0", run=False, options={"myoption": 1}) + self.tool_requires("gcc/1.0", run=False, options={"myoption": 2}) + + def generate(self): + gcc1 = self.dependencies.build.get("gcc", options={"myoption": 1}) + assert gcc1.options.myoption == "1" + gcc2 = self.dependencies.build.get("gcc", options={"myoption": 2}) + assert gcc2.options.myoption == "2" + + def build(self): + ext = "bat" if platform.system() == "Windows" else "sh" + self.run(f"mygcc1.{ext}") + self.run(f"mygcc2.{ext}") + + +The first important point is the ``build_requirements()`` method, that does a ``tool_requires()`` to both binaries, +but defining ``run=False`` and ``options={"myoption": value}`` traits. **This is very important**: we are telling Conan +that we actually don't need to run anything from those packages. As ``tool_requires`` are not visible, they don't define +headers or libraries and they define different ``options``, there is nothing that makes Conan identify those 2 ``tool_requires`` +as conflicting. So the dependency graph can be constructed without errors, and the ``wine/1.0`` package will contain +2 different tool-requires to both ``gcc/1.0`` with ``myoption=1`` and with ``myoption=2``. + +Of course, it is not true that we won't run anything from those ``tool_requires``, but now Conan is not aware of it, +and it is completely the responsibility of the user to manage it. + +.. warning:: + + Using ``run=False`` makes the ``tool_requires()`` completely invisible, that means that profile ``[tool_requires]`` + will not be able to override its version, but it would create an extra tool-require dependency with the version + injected from the profile. You might want to exclude specific packages with something like ``!wine/*: gcc/3.0``. + +The recipe has still access in the ``generate()`` method to each different ``tool_require`` version, just by providing +the options values for the dependency that we want ``self.dependencies.build.get("gcc", options={"myoption": 1})``. + +Finally, the most important part is that the usage of those tools is completely the responsibility of the user. The ``bin`` +folder of both ``tool_requires`` containing the executables will be in the path thanks to the ``VirtualBuildEnv`` generator +that by default updates the PATH env-var. In this case the executables are different like ``mygcc1.sh```and ``mygcc2.sh``, +so it is not an issue, and each one will be found inside its package. + +But if the executable file was exactly the same like ``gcc.exe``, then it would be necessary to obtain the full folder +(typically in the ``generate()`` method) with something like ``self.dependencies.build.get("gcc", options={"myoption": 1}).cpp_info.bindir`` and +use the full path to dissambiguate. + + +Let's see it working. If we execute: + + +.. code-block:: bash + + $ conan create wine + ... + wine/1.0: RUN: mygcc1.bat + MYGCC=1!! + + wine/1.0: RUN: mygcc2.bat + MYGCC=2!! diff --git a/examples/graph/tool_requires/different_versions.rst b/examples/graph/tool_requires/different_versions.rst new file mode 100644 index 000000000000..b4716e77e8d4 --- /dev/null +++ b/examples/graph/tool_requires/different_versions.rst @@ -0,0 +1,119 @@ +Depending on different versions of the same tool-require +======================================================== + +.. note:: + + This is an **advanced** use case. It shouldn't be necessary in the vast majority of cases. + + +In the general case, trying to do something like this: + +.. code-block:: python + + def build_requirements(self): + self.tool_requires("gcc/1.0") + self.tool_requires("gcc/2.0") + +Will generate a "conflict", showing an error like ``Duplicated requirement``. This is correct in most situations, +when it is obvious that it is not possible to use 2 versions of the same compiler to build the current package. + +However there are some exceptional situations when something like that is desired. Let's recreate the potential +scenario. Please, first, clone the sources to recreate this project, you can find them in the +`examples2.0 repository `_ on GitHub: + +.. code-block:: shell + + git clone https://github.com/conan-io/examples2.git + cd examples2/examples/graph/tool_requires/different_versions + +There we have a ``gcc`` fake recipe with: + +.. code-block:: python + + class Pkg(ConanFile): + name = "gcc" + + def package(self): + echo = f"@echo off\necho MYGCC={self.version}!!" + save(self, os.path.join(self.package_folder, "bin", f"mygcc{self.version}.bat"), echo) + save(self, os.path.join(self.package_folder, "bin", f"mygcc{self.version}.sh"), echo) + os.chmod(os.path.join(self.package_folder, "bin", f"mygcc{self.version}.sh"), 0o777) + + +This is not an actual compiler, it fakes it with a shell or bat script that prints ``MYGCC=current-version`` when executed. +Note the binary itself is called ``mygcc1.0`` and ``mygcc2.0``, that is, it contains the version in the executable name itself. + +We can create 2 different versions for ``gcc/1.0`` and ``gcc/2.0`` with: + + +.. code-block:: bash + + $ conan create gcc --version=1.0 + $ conan create gcc --version=2.0 + +Now, in the ``wine`` folder there is a ``conanfile.py`` like this: + +.. code-block:: python + + class Pkg(ConanFile): + name = "wine" + version = "1.0" + + def build_requirements(self): + # If we specify "run=False" they no longer conflict + self.tool_requires("gcc/1.0", run=False) + self.tool_requires("gcc/2.0", run=False) + + def generate(self): + # It is possible to individually reference each one + gcc1 = self.dependencies.build["gcc/1.0"] + assert gcc1.ref.version == "1.0" + gcc2 = self.dependencies.build["gcc/2.0"] + assert gcc2.ref.version == "2.0" + + def build(self): + ext = "bat" if platform.system() == "Windows" else "sh" + self.run(f"mygcc1.0.{ext}") + self.run(f"mygcc2.0.{ext}") + + +The first important point is the ``build_requirements()`` method, that does a ``tool_requires()`` to both versions, +but defining ``run=False``. **This is very important**: we are telling Conan that we actually don't need to run +anything from those packages. As ``tool_requires`` are not visible, they don't define headers or libraries, there is +nothing that makes Conan identify those 2 ``tool_requires`` as conflicting. So the dependency graph can be constructed +without errors, and the ``wine/1.0`` package will contain 2 different tool-requires to both ``gcc/1.0`` and ``gcc/2.0``. + +Of course, it is not true that we won't run anything from those ``tool_requires``, but now Conan is not aware of it, +and it is completely the responsibility of the user to manage it. + +.. warning:: + + Using ``run=False`` makes the ``tool_requires()`` completely invisible, that means that profile ``[tool_requires]`` + will not be able to override its version, but it would create an extra tool-require dependency with the version + injected from the profile. You might want to exclude specific packages with something like ``!wine/*: gcc/3.0``. + +The recipe has still access in the ``generate()`` method to each different ``tool_require`` version, just by providing +the full reference like ``self.dependencies.build["gcc/1.0"]``. + +Finally, the most important part is that the usage of those tools is completely the responsibility of the user. The ``bin`` +folder of both ``tool_requires`` containing the executables will be in the path thanks to the ``VirtualBuildEnv`` generator +that by default updates the PATH env-var. In this case the executables are different like ``mygcc1.0.sh```and ``mygcc2.0.sh``, +so it is not an issue, and each one will be found inside its package. + +But if the executable file was exactly the same like ``gcc.exe``, then it would be necessary to obtain the full folder +(typically in the ``generate()`` method) with something like ``self.dependencies.build["gcc/1.0"].cpp_info.bindir`` and +use the full path to dissambiguate. + + +Let's see it working. If we execute: + + +.. code-block:: bash + + $ conan create wine + ... + wine/1.0: RUN: mygcc1.0.bat + MYGCC=1.0!! + + wine/1.0: RUN: mygcc2.0.bat + MYGCC=2.0!! From cdea1e350d9727978df46bab2734f046ccfe8329 Mon Sep 17 00:00:00 2001 From: James Date: Wed, 17 May 2023 11:02:35 +0200 Subject: [PATCH 2/4] Update examples/graph.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rubén Rincón Blanco --- examples/graph.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/graph.rst b/examples/graph.rst index 4ee2f0c52095..036f7e03bc70 100644 --- a/examples/graph.rst +++ b/examples/graph.rst @@ -3,7 +3,7 @@ Graph examples ============== -This section contains example about different types of advanced graphs, using different types of ``requires`` and ``tool_requires``, +This section contains examples about different types of advanced graphs, using different types of ``requires`` and ``tool_requires``, advanced usage of requirement traits, etc. From 1ec48123179521b4d8e15c4b876c342baa5b63d2 Mon Sep 17 00:00:00 2001 From: James Date: Wed, 17 May 2023 11:02:43 +0200 Subject: [PATCH 3/4] Update examples/graph/tool_requires/different_options.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rubén Rincón Blanco --- examples/graph/tool_requires/different_options.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/graph/tool_requires/different_options.rst b/examples/graph/tool_requires/different_options.rst index de2285d2c24d..4c5460e905a6 100644 --- a/examples/graph/tool_requires/different_options.rst +++ b/examples/graph/tool_requires/different_options.rst @@ -16,7 +16,7 @@ In the general case, trying to do something like this: Will generate a "conflict", showing an error like ``Duplicated requirement``. -However there are some exceptional situations that we could need depending on the same ``tool_requires`` version, +However there are some exceptional situations that we could need to depend on the same ``tool_requires`` version, but using different binaries of that ``tool_requires``. This can be achieved by passing different ``options`` to those ``tool_requires``. Please, first, clone the sources to recreate this project, you can find them in the `examples2.0 repository `_ on GitHub: From 048aef814682e89c46d903965789a7f537098db4 Mon Sep 17 00:00:00 2001 From: James Date: Wed, 17 May 2023 11:02:50 +0200 Subject: [PATCH 4/4] Update examples/graph/tool_requires/different_options.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rubén Rincón Blanco --- examples/graph/tool_requires/different_options.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/graph/tool_requires/different_options.rst b/examples/graph/tool_requires/different_options.rst index 4c5460e905a6..754bfbec55d7 100644 --- a/examples/graph/tool_requires/different_options.rst +++ b/examples/graph/tool_requires/different_options.rst @@ -94,7 +94,7 @@ and it is completely the responsibility of the user to manage it. will not be able to override its version, but it would create an extra tool-require dependency with the version injected from the profile. You might want to exclude specific packages with something like ``!wine/*: gcc/3.0``. -The recipe has still access in the ``generate()`` method to each different ``tool_require`` version, just by providing +The recipe still has access in the ``generate()`` method to each different ``tool_require`` version, just by providing the options values for the dependency that we want ``self.dependencies.build.get("gcc", options={"myoption": 1})``. Finally, the most important part is that the usage of those tools is completely the responsibility of the user. The ``bin``