diff --git a/Dockerfile b/Dockerfile index 9a78f76a52ba8..13b6d4d4e4f66 100644 --- a/Dockerfile +++ b/Dockerfile @@ -136,7 +136,7 @@ ARG PYTHON_VERSION ARG PYENV_GIT_TAG ENV PYENV_ROOT=/opt/python -ENV PATH=$PATH:$PYENV_ROOT/shims:$PYENV_ROOT/bin:$PYENV_ROOT/conan2/bin +ENV PATH=$PATH:$PYENV_ROOT/shims:$PYENV_ROOT/bin RUN curl -kSs https://pyenv.run | bash \ && pyenv install -v $PYTHON_VERSION \ && pyenv global $PYTHON_VERSION @@ -170,18 +170,26 @@ RUN pip install --no-cache-dir -U \ wheel \ && pip install --no-cache-dir -U \ Mercurial \ - conan=="$CONAN_VERSION" \ pipenv=="$PYTHON_PIPENV_VERSION" \ poetry=="$PYTHON_POETRY_VERSION" \ poetry-plugin-export=="$PYTHON_POETRY_PLUGIN_EXPORT_VERSION" \ python-inspector=="$PYTHON_INSPECTOR_VERSION" \ setuptools=="$PYTHON_SETUPTOOLS_VERSION" -RUN mkdir /tmp/conan2 && cd /tmp/conan2 \ - && wget https://github.com/conan-io/conan/releases/download/$CONAN2_VERSION/conan-$CONAN2_VERSION-linux-x86_64.tgz \ - && tar -xvf conan-$CONAN2_VERSION-linux-x86_64.tgz\ - # Rename the Conan 2 executable to "conan2" to be able to call both Conan version from the package manager. - && mkdir $PYENV_ROOT/conan2 && mv /tmp/conan2/bin $PYENV_ROOT/conan2/ \ - && mv $PYENV_ROOT/conan2/bin/conan $PYENV_ROOT/conan2/bin/conan2 + +# Create conan environments +COPY scripts/setup_conan.sh ${PYENV_ROOT}/bin/conan +RUN eval "$(pyenv init - bash)" \ + && eval "$(pyenv virtualenv-init -)" \ + && pyenv virtualenv conan \ + && pyenv activate conan \ + && pip install conan==${CONAN_VERSION} \ + && pyenv deactivate \ + && pyenv virtualenv conan2 \ + && pyenv activate conan2 \ + && pip install conan==${CONAN2_VERSION} \ + && pyenv deactivate \ + && sudo chmod +x ${PYENV_ROOT}/bin/conan + FROM scratch AS python COPY --from=pythonbuild /opt/python /opt/python @@ -484,7 +492,7 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ # Python ENV PYENV_ROOT=/opt/python -ENV PATH=$PATH:$PYENV_ROOT/shims:$PYENV_ROOT/bin:$PYENV_ROOT/conan2/bin +ENV PATH=$PATH:$PYENV_ROOT/shims:$PYENV_ROOT/bin:$PYENV_ROOT/plugins/pyenv-virtualenv/shims COPY --from=python --chown=$USER:$USER $PYENV_ROOT $PYENV_ROOT # NodeJS diff --git a/plugins/package-managers/conan/src/funTest/kotlin/ConanFunTest.kt b/plugins/package-managers/conan/src/funTest/kotlin/ConanFunTest.kt index 773a9e947a7de..4912b4d9c3a62 100644 --- a/plugins/package-managers/conan/src/funTest/kotlin/ConanFunTest.kt +++ b/plugins/package-managers/conan/src/funTest/kotlin/ConanFunTest.kt @@ -30,8 +30,12 @@ import org.ossreviewtoolkit.utils.test.matchExpectedResult import org.ossreviewtoolkit.utils.test.patchActualResult /** - * This test class performs tests with both Conan 1 and Conan 2. For it to be successful, it needs both a "conan" - * command for Conan 1 and a "conan2" command for Conan 2 in the PATH environment variable (as in ORT Docker image). + * This test class performs tests with both Conan 1 and Conan 2. To be able to run it locally, it needs to have access + * to some Conan installations as it is done in the ORT Docker image: + * - prerequisites: bash, pyenv, pyenv virtualenv. + * - a directory ~/bin/ort-conan containing the file 'setup_conan.sh' renamed 'conan' and made executable. + * - two pyenv venvs 'conan' and 'conan2' containing respectively the conan 1 and conan 2 installations. + * Then the test can be run with PATH set to ~/bin/ort-conan:$PATH (and the PATH of the pyenv installation). * * A word of caution about Conan 2 tests: If there is no lockfile, when Conan resolves the dependencies it read its * cache and relies on the package name, ignoring the version. This means, for instance, that if a test reported diff --git a/plugins/package-managers/conan/src/main/kotlin/Conan.kt b/plugins/package-managers/conan/src/main/kotlin/Conan.kt index 848455f8f8fd2..a96ce025575cd 100644 --- a/plugins/package-managers/conan/src/main/kotlin/Conan.kt +++ b/plugins/package-managers/conan/src/main/kotlin/Conan.kt @@ -56,6 +56,7 @@ import org.ossreviewtoolkit.plugins.api.OrtPlugin import org.ossreviewtoolkit.plugins.api.OrtPluginOption import org.ossreviewtoolkit.plugins.api.PluginDescriptor import org.ossreviewtoolkit.utils.common.CommandLineTool +import org.ossreviewtoolkit.utils.common.ProcessCapture import org.ossreviewtoolkit.utils.common.alsoIfNull import org.ossreviewtoolkit.utils.common.masked import org.ossreviewtoolkit.utils.common.safeDeleteRecursively @@ -69,7 +70,7 @@ import org.semver4j.RangesList import org.semver4j.RangesListFactory internal class ConanCommand(private val useConan2: Boolean = false) : CommandLineTool { - override fun command(workingDir: File?) = if (useConan2) "conan2" else "conan" + override fun command(workingDir: File?) = "conan" override fun transformVersion(output: String) = // Conan could report version strings like: @@ -78,8 +79,15 @@ internal class ConanCommand(private val useConan2: Boolean = false) : CommandLin override fun getVersionRequirement(): RangesList = RangesListFactory.create(">=1.44.0 <3.0") - override fun run(vararg args: CharSequence, workingDir: File?, environment: Map) = - super.run(args = args, workingDir, environment + ("CONAN_NON_INTERACTIVE" to "1")) + override fun run(vararg args: CharSequence, workingDir: File?, environment: Map): ProcessCapture = + super.run( + args = args, + workingDir, + environment + mapOf( + "CONAN_NON_INTERACTIVE" to "1", + "CONAN_MAJOR_VERSION" to if (useConan2) "2" else "1" + ) + ) } data class ConanConfig( @@ -114,12 +122,6 @@ class Conan( private val config: ConanConfig ) : PackageManager("Conan") { companion object { - internal val DUMMY_COMPILER_SETTINGS = arrayOf( - "-s", "compiler=gcc", - "-s", "compiler.libcxx=libstdc++", - "-s", "compiler.version=11.1" - ) - internal const val SCOPE_NAME_DEPENDENCIES = "requires" internal const val SCOPE_NAME_DEV_DEPENDENCIES = "build_requires" internal const val SCOPE_NAME_TEST_DEPENDENCIES = "test_requires" @@ -214,6 +216,8 @@ class Conan( config.lockfileName?.let { hasLockfile(workingDir.resolve(it).path) } == true } + handler.createConanProfileIfNeeded() + val handlerResults = handler.process(definitionFile, config.lockfileName) val result = with(handlerResults) { diff --git a/plugins/package-managers/conan/src/main/kotlin/ConanV1Handler.kt b/plugins/package-managers/conan/src/main/kotlin/ConanV1Handler.kt index 1614ab6d4731f..4756d345db142 100644 --- a/plugins/package-managers/conan/src/main/kotlin/ConanV1Handler.kt +++ b/plugins/package-managers/conan/src/main/kotlin/ConanV1Handler.kt @@ -30,7 +30,6 @@ import org.ossreviewtoolkit.model.PackageReference import org.ossreviewtoolkit.model.RemoteArtifact import org.ossreviewtoolkit.model.Scope import org.ossreviewtoolkit.model.VcsInfo -import org.ossreviewtoolkit.plugins.packagemanagers.conan.Conan.Companion.DUMMY_COMPILER_SETTINGS import org.ossreviewtoolkit.plugins.packagemanagers.conan.Conan.Companion.SCOPE_NAME_DEPENDENCIES import org.ossreviewtoolkit.plugins.packagemanagers.conan.Conan.Companion.SCOPE_NAME_DEV_DEPENDENCIES import org.ossreviewtoolkit.utils.common.Os @@ -44,6 +43,12 @@ import org.ossreviewtoolkit.utils.ort.createOrtTempDir internal class ConanV1Handler(private val conan: Conan) : ConanVersionHandler { override fun getConanHome(): File = Os.userHomeDirectory.resolve(".conan") + override fun createConanProfileIfNeeded() { + if (!getConanHome().resolve("profiles/ort-default").isFile) { + conan.command.run("profile", "new", "ort-default", "--detect").requireSuccess() + } + } + override fun getConanStoragePath(): File = getConanHome().resolve("data") override fun process(definitionFile: File, lockfileName: String?): HandlerResults { @@ -64,7 +69,8 @@ internal class ConanV1Handler(private val conan: Conan) : ConanVersionHandler { definitionFile.name, "--json", jsonFile.absolutePath, - *DUMMY_COMPILER_SETTINGS + "--profile", + "ort-default" ).requireSuccess() } diff --git a/plugins/package-managers/conan/src/main/kotlin/ConanV2Handler.kt b/plugins/package-managers/conan/src/main/kotlin/ConanV2Handler.kt index 8aabd0319ec98..d22dcd974961c 100644 --- a/plugins/package-managers/conan/src/main/kotlin/ConanV2Handler.kt +++ b/plugins/package-managers/conan/src/main/kotlin/ConanV2Handler.kt @@ -32,7 +32,6 @@ import org.ossreviewtoolkit.model.PackageReference import org.ossreviewtoolkit.model.RemoteArtifact import org.ossreviewtoolkit.model.Scope import org.ossreviewtoolkit.model.VcsInfo -import org.ossreviewtoolkit.plugins.packagemanagers.conan.Conan.Companion.DUMMY_COMPILER_SETTINGS import org.ossreviewtoolkit.plugins.packagemanagers.conan.Conan.Companion.SCOPE_NAME_DEPENDENCIES import org.ossreviewtoolkit.plugins.packagemanagers.conan.Conan.Companion.SCOPE_NAME_DEV_DEPENDENCIES import org.ossreviewtoolkit.plugins.packagemanagers.conan.Conan.Companion.SCOPE_NAME_TEST_DEPENDENCIES @@ -48,16 +47,17 @@ import org.ossreviewtoolkit.utils.ort.createOrtTempDir internal class ConanV2Handler(private val conan: Conan) : ConanVersionHandler { override fun getConanHome(): File = Os.userHomeDirectory.resolve(".conan2") + override fun createConanProfileIfNeeded() { + if (!getConanHome().resolve("profiles/ort-default").isFile) { + conan.command.run("profile", "detect", "--name", "ort-default").requireSuccess() + } + } + override fun getConanStoragePath(): File = getConanHome().resolve("p") override fun process(definitionFile: File, lockfileName: String?): HandlerResults { val workingDir = definitionFile.parentFile - // Create a default build profile. - if (!getConanHome().resolve("profiles/default").isFile) { - conan.command.run(workingDir, "profile", "detect") - } - val jsonFile = createOrtTempDir().resolve("info.json") if (lockfileName != null) { conan.verifyLockfileBelongsToProject(workingDir, lockfileName) @@ -71,7 +71,8 @@ internal class ConanV2Handler(private val conan: Conan) : ConanVersionHandler { lockfileName, "--out-file", jsonFile.absolutePath, - *DUMMY_COMPILER_SETTINGS, + "--profile:all", + "ort-default", definitionFile.name ).requireSuccess() } else { @@ -83,7 +84,8 @@ internal class ConanV2Handler(private val conan: Conan) : ConanVersionHandler { "json", "--out-file", jsonFile.absolutePath, - *DUMMY_COMPILER_SETTINGS, + "--profile:all", + "ort-default", definitionFile.name ).requireSuccess() } diff --git a/plugins/package-managers/conan/src/main/kotlin/ConanVersionHandler.kt b/plugins/package-managers/conan/src/main/kotlin/ConanVersionHandler.kt index 4913bb6b0a833..c90a3a86b29be 100644 --- a/plugins/package-managers/conan/src/main/kotlin/ConanVersionHandler.kt +++ b/plugins/package-managers/conan/src/main/kotlin/ConanVersionHandler.kt @@ -35,6 +35,11 @@ internal interface ConanVersionHandler { */ fun getConanHome(): File + /** + * Create a default ORT Conan profile if it does not exist yet. This profile will contain the complication flags. + */ + fun createConanProfileIfNeeded() + /** * Get the Conan storage path, i.e. the location where Conan caches downloaded packages. */ diff --git a/scripts/setup_conan.sh b/scripts/setup_conan.sh new file mode 100644 index 0000000000000..d803d1feea2fb --- /dev/null +++ b/scripts/setup_conan.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# +# Copyright (C) 2025 The ORT Project Authors (see ) +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# License-Filename: LICENSE +# + +conan_option=${CONAN_MAJOR_VERSION:-2} + +# Since this script is installed with the name "conan", there is a risk of infinite recursion if pyenv is not available +# on the PATH, which can occur when setting up a development environment. To prevent this, check for recursive calls. +if [[ "$CONAN_RECURSIVE_CALL" -eq 1 ]]; then + echo "Recursive call detected. Exiting." + exit 1 +fi + +# Setup pyenv +eval "$(pyenv init - --no-rehash bash)" +eval "$(pyenv virtualenv-init -)" + +# Setting up Conan 1.x +if [[ "$conan_option" -eq 1 ]]; then # Setting up Conan 1.x series + pyenv activate conan + # Docker has modern libc + CONAN_RECURSIVE_CALL=1 conan profile update settings.compiler.libcxx=libstdc++11 ort-default +elif [[ "$conan_option" -eq 2 ]]; then # Setting up Conan 2.x series + pyenv activate conan2 +fi + +# Runs conan from activated profile +CONAN_RECURSIVE_CALL=1 conan "$@" +