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

Feature/relativize generators #13607

Merged
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion conan/tools/cmake/cmakedeps/cmakedeps.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from conan.tools.cmake.cmakedeps.templates.target_configuration import TargetConfigurationTemplate
from conan.tools.cmake.cmakedeps.templates.target_data import ConfigDataTemplate
from conan.tools.cmake.cmakedeps.templates.targets import TargetsTemplate
from conans.client.generators import relativize_generated_file
from conans.errors import ConanException
from conans.util.files import save

Expand Down Expand Up @@ -94,7 +95,9 @@ def _generate_files(self, require, dep, ret, find_module_mode):
ret[config_version.filename] = config_version.render()

data_target = ConfigDataTemplate(self, require, dep, find_module_mode)
ret[data_target.filename] = data_target.render()
data_content = relativize_generated_file(data_target.render(), self._conanfile,
"${CMAKE_CURRENT_LIST_DIR}")
ret[data_target.filename] = data_content

target_configuration = TargetConfigurationTemplate(self, require, dep, find_module_mode)
ret[target_configuration.filename] = target_configuration.render()
Expand Down
8 changes: 1 addition & 7 deletions conan/tools/cmake/cmakedeps/templates/target_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,7 @@ def context(self):

# Make the root_folder relative to the generated xxx-data.cmake file
root_folder = self._root_folder
generators_folder = self.cmakedeps._conanfile.generators_folder
if os.path.commonpath([root_folder, generators_folder]) == generators_folder:
rel_path = os.path.relpath(root_folder, generators_folder)
rel_path = rel_path.replace('\\', '/').replace('$', '\\$').replace('"', '\\"')
root_folder = f"${{CMAKE_CURRENT_LIST_DIR}}/{rel_path}"
else:
root_folder = root_folder.replace('\\', '/').replace('$', '\\$').replace('"', '\\"')
root_folder = root_folder.replace('\\', '/').replace('$', '\\$').replace('"', '\\"')

return {"global_cpp": global_cpp,
"has_components": self.conanfile.cpp_info.has_components,
Expand Down
9 changes: 3 additions & 6 deletions conan/tools/cmake/toolchain/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -520,12 +520,9 @@ def context(self):
pkg_config = self._conanfile.conf.get("tools.gnu:pkg_config", check_type=str)
if pkg_config:
pkg_config = pkg_config.replace("\\", "/")
pkg_config_path = self._conanfile.generators_folder
if pkg_config_path:
# hardcoding scope as "build"
subsystem = deduce_subsystem(self._conanfile, "build")
pathsep = ":" if subsystem != WINDOWS else ";"
pkg_config_path = pkg_config_path.replace("\\", "/") + pathsep
subsystem = deduce_subsystem(self._conanfile, "build")
pathsep = ":" if subsystem != WINDOWS else ";"
pkg_config_path = "${CMAKE_CURRENT_LIST_DIR}" + pathsep
return {"pkg_config": pkg_config,
"pkg_config_path": pkg_config_path}

Expand Down
2 changes: 2 additions & 0 deletions conan/tools/cmake/toolchain/toolchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from conan.tools.intel import IntelCC
from conan.tools.microsoft import VCVars
from conan.tools.microsoft.visual import vs_ide_version
from conans.client.generators import relativize_generated_file
from conans.errors import ConanException
from conans.model.options import _PackageOption
from conans.util.files import save
Expand Down Expand Up @@ -171,6 +172,7 @@ def _context(self):
def content(self):
context = self._context()
content = Template(self._template, trim_blocks=True, lstrip_blocks=True).render(**context)
content = relativize_generated_file(content, self._conanfile, "${CMAKE_CURRENT_LIST_DIR}")
return content

def generate(self):
Expand Down
4 changes: 2 additions & 2 deletions conan/tools/env/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from collections import OrderedDict
from contextlib import contextmanager

from conans.client.generators import relativize_generated_file
from conans.client.subsystems import deduce_subsystem, WINDOWS, subsystem_path
from conans.errors import ConanException
from conans.model.recipe_ref import ref_matches
Expand Down Expand Up @@ -414,14 +415,13 @@ def save_bat(self, file_location, generate_deactivate=True):
{deactivate}
""").format(deactivate=deactivate if generate_deactivate else "")
result = [capture]
location = os.path.abspath(os.path.dirname(file_location))
for varname, varvalues in self._values.items():
value = varvalues.get_str("%{name}%", subsystem=self._subsystem, pathsep=self._pathsep)
# To make the script relocatable
value = value.replace(location, "%~dp0")
result.append('set "{}={}"'.format(varname, value))

content = "\n".join(result)
content = relativize_generated_file(content, self._conanfile, "%~dp0")
# It is very important to save it correctly with utf-8, the Conan util save() is broken
os.makedirs(os.path.dirname(os.path.abspath(file_location)), exist_ok=True)
open(file_location, "w", encoding="utf-8").write(content)
Expand Down
12 changes: 12 additions & 0 deletions conans/client/generators/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,3 +171,15 @@ def ps1_content(files):
if generated:
conanfile.output.highlight("Generating aggregated env files")
conanfile.output.info(f"Generated aggregated env files: {generated}")


def relativize_generated_file(content, conanfile, placeholder):
abs_base_path = conanfile.folders._base_generators
if not abs_base_path or not os.path.isabs(abs_base_path):
return content
generators_folder = conanfile.generators_folder
rel_path = os.path.relpath(abs_base_path, generators_folder)
new_path = placeholder if rel_path == "." else os.path.join(placeholder, rel_path)
content = content.replace(abs_base_path, new_path)
content = content.replace(abs_base_path.replace("\\", "/"), new_path.replace("\\", "/"))
return content
105 changes: 85 additions & 20 deletions conans/test/functional/command/test_install_deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,47 @@
from conans.util.files import save


@pytest.mark.tool("cmake")
def test_install_deploy():
@pytest.fixture(scope="module")
def _client():
c = TestClient()
c.run("new cmake_lib -d name=hello -d version=0.1")
c.run("create . -o *:shared=True -tf=")
conanfile = textwrap.dedent("""
import os
from conan import ConanFile
from conan.tools.files import save
class Tool(ConanFile):
name = "tool"
version = "1.0"
def package(self):
save(self, os.path.join(self.package_folder, "build", "my_tools.cmake"),
'set(MY_TOOL_VARIABLE "Hello world!")')

def package_info(self):
self.cpp_info.includedirs = []
self.cpp_info.libdirs = []
self.cpp_info.bindirs = []
path_build_modules = os.path.join("build", "my_tools.cmake")
self.cpp_info.set_property("cmake_build_modules", [path_build_modules])
""")
import os
from conan import ConanFile
from conan.tools.files import save
class Tool(ConanFile):
name = "tool"
version = "1.0"
def package(self):
save(self, os.path.join(self.package_folder, "build", "my_tools.cmake"),
'set(MY_TOOL_VARIABLE "Hello world!")')

def package_info(self):
self.cpp_info.includedirs = []
self.cpp_info.libdirs = []
self.cpp_info.bindirs = []
path_build_modules = os.path.join("build", "my_tools.cmake")
self.cpp_info.set_property("cmake_build_modules", [path_build_modules])
""")
c.save({"conanfile.py": conanfile}, clean_first=True)
c.run("create .")
return c


@pytest.fixture()
def client(_client):
""" this is much faster than creating and uploading everythin
"""
client = TestClient(default_server_user=True)
shutil.rmtree(client.cache_folder)
shutil.copytree(_client.cache_folder, client.cache_folder)
return client


@pytest.mark.tool("cmake")
def test_install_deploy(client):
c = client
custom_content = 'message(STATUS "MY_TOOL_VARIABLE=${MY_TOOL_VARIABLE}!")'
cmake = gen_cmakelists(appname="my_app", appsources=["main.cpp"], find_package=["hello", "tool"],
custom_content=custom_content)
Expand All @@ -59,7 +75,6 @@ def deploy(graph, output_folder, **kwargs):
clean_first=True)
c.run("install . -o *:shared=True "
"--deploy=deploy.py -of=mydeploy -g CMakeToolchain -g CMakeDeps")
print(c.out)
c.run("remove * -c") # Make sure the cache is clean, no deps there
arch = c.get_default_host_profile().settings['arch']
deps = c.load(f"mydeploy/hello-release-{arch}-data.cmake")
Expand All @@ -84,6 +99,56 @@ def deploy(graph, output_folder, **kwargs):
assert "hello/0.1: Hello World Release!" in c2.out


@pytest.mark.tool("cmake")
def test_install_full_deploy_layout(client):
c = client
custom_content = 'message(STATUS "MY_TOOL_VARIABLE=${MY_TOOL_VARIABLE}!")'
cmake = gen_cmakelists(appname="my_app", appsources=["main.cpp"], find_package=["hello", "tool"],
custom_content=custom_content)
conanfile = textwrap.dedent("""
[requires]
hello/0.1
tool/1.0
[generators]
CMakeDeps
CMakeToolchain
[layout]
cmake_layout
""")
c.save({"conanfile.txt": conanfile,
"CMakeLists.txt": cmake,
"main.cpp": gen_function_cpp(name="main", includes=["hello"], calls=["hello"])},
clean_first=True)
c.run("install . -o *:shared=True --deploy=full_deploy.py")
c.run("remove * -c") # Make sure the cache is clean, no deps there
arch = c.get_default_host_profile().settings['arch']
folder = "/Release" if platform.system() != "Windows" else ""
rel_path = "../../" if platform.system() == "Windows" else "../../../"
deps = c.load(f"build{folder}/generators/hello-release-{arch}-data.cmake")
assert 'set(hello_PACKAGE_FOLDER_RELEASE "${CMAKE_CURRENT_LIST_DIR}/' \
f'{rel_path}host/hello/0.1/Release/{arch}")' in deps
assert 'set(hello_INCLUDE_DIRS_RELEASE "${hello_PACKAGE_FOLDER_RELEASE}/include")' in deps
assert 'set(hello_LIB_DIRS_RELEASE "${hello_PACKAGE_FOLDER_RELEASE}/lib")' in deps

# We can fully move it to another folder, and still works
tmp = os.path.join(temp_folder(), "relocated")
shutil.copytree(c.current_folder, tmp)
shutil.rmtree(c.current_folder)
c2 = TestClient(current_folder=tmp)
with c2.chdir(f"build{folder}"):
# I can totally build without errors with deployed
cmakelist = "../.." if platform.system() != "Windows" else ".."
c2.run_command(f"cmake {cmakelist} -DCMAKE_TOOLCHAIN_FILE=generators/conan_toolchain.cmake "
"-DCMAKE_BUILD_TYPE=Release")
assert "MY_TOOL_VARIABLE=Hello world!!" in c2.out
c2.run_command("cmake --build . --config Release")
if platform.system() == "Windows": # Only the .bat env-generators are relocatable atm
cmd = r"generators\conanrun.bat && Release\my_app.exe"
# For Lunux: cmd = ". mydeploy/conanrun.sh && ./my_app"
c2.run_command(cmd)
assert "hello/0.1: Hello World Release!" in c2.out


def test_copy_files_deploy():
c = TestClient()
deploy = textwrap.dedent("""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -727,7 +727,7 @@ def test_pkg_config_block():
assert 'set(PKG_CONFIG_EXECUTABLE /usr/local/bin/pkg-config CACHE FILEPATH ' in toolchain
pathsep = ":" if os_ != "Windows" else ";"
pkg_config_path_set = 'set(ENV{PKG_CONFIG_PATH} "%s$ENV{PKG_CONFIG_PATH}")' % \
(client.current_folder.replace("\\", "/") + pathsep)
("${CMAKE_CURRENT_LIST_DIR}" + pathsep)
assert pkg_config_path_set in toolchain


Expand Down
8 changes: 4 additions & 4 deletions conans/test/unittests/tools/cmake/test_cmakedeps.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def test_cpp_info_name_cmakedeps():
"arch": ["x86"]})
conanfile.settings.build_type = "Release"
conanfile.settings.arch = "x86"
conanfile.folders.set_base_generators("/")
conanfile.folders.set_base_generators("/some/abs/path") # non-existing to not relativize

cpp_info = CppInfo(set_defaults=True)
cpp_info.set_property("cmake_target_name", "MySuperPkg1::MySuperPkg1")
Expand Down Expand Up @@ -63,7 +63,7 @@ def test_cpp_info_name_cmakedeps_components():
"arch": ["x86", "x64"]})
conanfile.settings.build_type = "Debug"
conanfile.settings.arch = "x64"
conanfile.folders.set_base_generators("/")
conanfile.folders.set_base_generators("/some/abs/path") # non-existing to not relativize

cpp_info = CppInfo()
cpp_info.set_property("cmake_file_name", "ComplexFileName1")
Expand Down Expand Up @@ -113,7 +113,7 @@ def test_cmake_deps_links_flags():
"arch": ["x86"]})
conanfile.settings.build_type = "Release"
conanfile.settings.arch = "x86"
conanfile.folders.set_base_generators("/")
conanfile.folders.set_base_generators("/some/abs/path") # non-existing to not relativize

cpp_info = CppInfo()
# https://github.com/conan-io/conan/issues/8811 regression, fix with explicit - instead of /
Expand Down Expand Up @@ -161,7 +161,7 @@ def test_component_name_same_package():
"arch": ["x86"]})
conanfile.settings.build_type = "Release"
conanfile.settings.arch = "x86"
conanfile.folders.set_base_generators("/")
conanfile.folders.set_base_generators("/some/abs/path") # non-existing to not relativize

cpp_info = CppInfo(set_defaults=True)

Expand Down
4 changes: 2 additions & 2 deletions conans/test/unittests/tools/cmake/test_cmaketoolchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def conanfile():
c.settings.os = "Windows"
c.conf = Conf()
c.conf.define("tools.cmake.cmaketoolchain:system_name", "potato")
c.folders.set_base_generators(".")
c.folders.set_base_generators("/some/abs/path") # non-existing to not relativize
c._conan_node = Mock()
c._conan_node.transitive_deps = {}
return c
Expand Down Expand Up @@ -146,7 +146,7 @@ def conanfile_apple():
c.settings.os.version = "10.15"
c.settings_build = c.settings
c.conf = Conf()
c.folders.set_base_generators(".")
c.folders.set_base_generators("/some/abs/path") # non-existing to not relativize
c._conan_node = Mock()
c._conan_node.dependencies = []
c._conan_node.transitive_deps = {}
Expand Down