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

Add compatibility with module packages on CMake #17718

Open
wants to merge 8 commits into
base: develop2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions conan/internal/model/pkg_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class PackageType(Enum):
PYTHON = "python-require"
CONF = "configuration"
UNKNOWN = "unknown"
MODULE = "module"

def __str__(self):
return self.value
Expand Down
7 changes: 6 additions & 1 deletion conan/internal/model/requires.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,11 @@ def set_if_none(field, value):
set_if_none("_libs", False)
set_if_none("_headers", False)
set_if_none("_visible", False) # Conflicts might be allowed for this kind of package
elif pkg_type is PackageType.MODULE:
set_if_none("_run", True)
set_if_none("_libs", False)
set_if_none("_headers", False)
set_if_none("_visible", False)

src_pkg_type = src_node.conanfile.package_type
if src_pkg_type is PackageType.HEADER:
Expand Down Expand Up @@ -294,7 +299,7 @@ def transform_downstream(self, pkg_type, require, dep_pkg_type):

# Regular and test requires
if dep_pkg_type is PackageType.SHARED or dep_pkg_type is PackageType.STATIC:
if pkg_type is PackageType.SHARED:
if pkg_type is PackageType.SHARED or pkg_type is PackageType.MODULE:
downstream_require = Requirement(require.ref, headers=False, libs=False, run=require.run)
elif pkg_type is PackageType.STATIC:
downstream_require = Requirement(require.ref, headers=False, libs=require.libs, run=require.run)
Expand Down
4 changes: 4 additions & 0 deletions conan/tools/cmake/cmakedeps2/target_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,10 @@ def _get_cmake_lib(self, info, components, pkg_folder, pkg_folder_var):
link_languages = ["CXX" if c == "C++" else c for c in link_languages]
target["link_languages"] = link_languages

if self._conanfile.package_type == PackageType.MODULE:
target = {"type": "module"}


return target

def _add_root_lib_target(self, libs, pkg_name, cpp_info):
Expand Down
Empty file.
268 changes: 268 additions & 0 deletions test/functional/module/test_cmake_module.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
import pytest
import textwrap

from conan.test.assets.genconanfile import GenConanfile
from conan.test.utils.tools import TestClient
from conan.test.assets.sources import gen_function_cpp, gen_function_h


def test_module_project():
# app ---> module1 ---> mylib

client = TestClient()

# -- Generate MyLib
cmakelists = textwrap.dedent("""
cmake_minimum_required(VERSION 3.15)
project(MyLib CXX)

add_library(MyLib mylib.h mylib.cpp)
install(FILES mylib.h DESTINATION include)
install(TARGETS MyLib DESTINATION "."
RUNTIME DESTINATION bin
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
)
""")

conanfile = textwrap.dedent("""
from conan import ConanFile
from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout


class pkgRecipe(ConanFile):
name = "mylib"
package_type = "library"

settings = "os", "compiler", "build_type", "arch"
options = {
"shared": [True, False],
"fPIC": [True, False],
}
default_options = {
"shared": True,
"fPIC": True,
}
implements = ["auto_shared_fpic"]

exports_sources = "CMakeLists.txt", "mylib.h", "mylib.cpp"

def layout(self):
cmake_layout(self)

def generate(self):
tc = CMakeToolchain(self)
tc.generate()

def build(self):
cmake = CMake(self)
cmake.configure()
cmake.build()

def package(self):
cmake = CMake(self)
cmake.install()

def package_info(self):
self.cpp_info.libs = ["MyLib"]
""")

client.save({"mylib/conanfile.py": conanfile,
"mylib/mylib.h": gen_function_h(name="mylib"),
"mylib/mylib.cpp": gen_function_cpp(name="mylib"),
"mylib/CMakeLists.txt": cmakelists}, clean_first=True)
client.run("create mylib --version=0.1 -o='*/*:shared=True'")

# -- Generate MyModule
cmakelists = textwrap.dedent("""
cmake_minimum_required(VERSION 3.15)
project(MyModule CXX)

find_package(mylib REQUIRED)

add_library(MyModule MODULE main.cpp)
target_link_libraries(MyModule mylib::mylib)

install(TARGETS MyModule DESTINATION "."
RUNTIME DESTINATION bin
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
)
""")

conanfile = textwrap.dedent("""
from conan import ConanFile
from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout, CMakeDeps


class pkgRecipe(ConanFile):
name = "mymodule"
package_type = "module"

settings = "os", "compiler", "build_type", "arch"
options = {
"shared": [True, False],
"fPIC": [True, False],
}
default_options = {
"shared": False,
"fPIC": True,
}
implements = ["auto_shared_fpic"]

exports_sources = "CMakeLists.txt", "main.cpp"

def layout(self):
cmake_layout(self)

def generate(self):
deps = CMakeDeps(self)
deps.generate()
tc = CMakeToolchain(self)
tc.generate()

def requirements(self):
self.requires("mylib/0.1")

def build(self):
cmake = CMake(self)
cmake.configure()
cmake.build()

def package(self):
cmake = CMake(self)
cmake.install()

def package_info(self):
self.cpp_info.bindirs.append("lib")
""")
cpp = textwrap.dedent("""

#include <iostream>
#include "mylib.h"

#ifdef _WIN32
#define DLL_EXPORT __declspec(dllexport)
#else
#define DLL_EXPORT
#endif

extern "C" DLL_EXPORT int moduleFunction(){
mylib();
return 0;
}
""")

client.save({"mymodule/conanfile.py": conanfile,
"mymodule/main.cpp": cpp,
"mymodule/CMakeLists.txt": cmakelists}, clean_first=True)
client.run("create mymodule --version=0.1 -v -c tools.cmake.cmakedeps:new='will_break_next'")


# -- Generate Main app
cmake = textwrap.dedent("""
cmake_minimum_required(VERSION 3.15)
project(MainApp CXX)

add_executable(MainApp main.cpp)
install(TARGETS MainApp DESTINATION "."
RUNTIME DESTINATION bin
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
)
""")
app_cpp = textwrap.dedent("""
#include <iostream>
#ifdef _WIN32
#include <windows.h>
#else
#include <dlfcn.h>
#endif

int main() {
std::cout << "Hello from App" << std::endl;


#ifdef _WIN32

typedef int (__cdecl *MYPROC)();
HINSTANCE hinstLib = LoadLibrary(TEXT("MyModule.dll"));
if (!hinstLib){
std::cerr << "Error loading plugin: " << __FILE__ << __LINE__ << std::endl;
return 1;
}
MYPROC ProcAdd = (MYPROC) GetProcAddress(hinstLib, "moduleFunction");
if (!ProcAdd) {
std::cerr << "Error finding function 'moduleFunction': " << __FILE__ << __LINE__ << std::endl;
FreeLibrary(hinstLib);
return 1;
}
(ProcAdd)();
FreeLibrary(hinstLib);

#else
void* handle = dlopen("libMyModule.so", RTLD_LAZY);
if (!handle) {
std::cerr << "Error loading plugin: " << dlerror() << std::endl;
return 1;
}

typedef int (*moduleFunction)(void);
moduleFunction customFunction;
*(void**)(&customFunction) = dlsym(handle, "moduleFunction");

if (!customFunction) {
std::cerr << "Error finding function 'moduleFunction': " << dlerror() << std::endl;
dlclose(handle);
return 1;
}

customFunction();
dlclose(handle);
#endif

return 0;
}
""")
conanfile = textwrap.dedent("""
from conan import ConanFile
from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout, CMakeDeps
import os


class pkgRecipe(ConanFile):
name = "mainapp"
package_type = "application"

settings = "os", "compiler", "build_type", "arch"

exports_sources = "CMakeLists.txt", "main.cpp"

def layout(self):
cmake_layout(self)

def generate(self):
deps = CMakeDeps(self)
deps.generate()
tc = CMakeToolchain(self)
tc.generate()

def requirements(self):
self.requires("mymodule/0.1")

def build(self):
cmake = CMake(self)
cmake.configure()
cmake.build()
if self.settings.os == "Windows":
my_path = self.cpp.build.bindir
self.run(os.path.join(my_path, "MainApp"), env="conanrun")
else:
self.run(f"'{os.path.join(self.build_folder, 'MainApp')}'", env="conanrun")
""")
client.save(
{"mainapp/conanfile.py": conanfile,
"mainapp/main.cpp": app_cpp,
"mainapp/CMakeLists.txt": cmake}, clean_first=True)
client.run("create mainapp --version=0.1 -c tools.cmake.cmakedeps:new='will_break_next'")
assert "mylib: Release!" in client.out
38 changes: 38 additions & 0 deletions test/functional/toolchains/cmake/test_cmakedeps2_modules.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from conan.test.utils.tools import TestClient, GenConanfile


def test_create_consume_module():

# main -> mod -> dep/1.0
# -> dep/2.0
tc = TestClient()
tc.save({"conanfile.py": GenConanfile("dep", "1.0").with_generator("CMakeDeps").with_settings("build_type").with_package_type("shared-library")})
tc.run('create -c tools.cmake.cmakedeps:new="will_break_next" .')
tc.save({"conanfile.py": GenConanfile("dep", "2.0").with_generator("CMakeDeps").with_settings("build_type").with_package_type("shared-library")})
tc.run('create -c tools.cmake.cmakedeps:new="will_break_next" .')
tc.save({"conanfile.py": GenConanfile("mod", "1.0").with_requirement("dep/1.0").with_package_type("module").with_generator("CMakeDeps").with_settings("build_type")})
tc.run('create -c tools.cmake.cmakedeps:new="will_break_next" .')
conanfile = GenConanfile("main", "1.0").with_requirement("dep/2.0").with_requirement("mod/1.0").with_generator("CMakeDeps").with_settings("build_type")
tc.save({"conanfile.py": conanfile})
tc.run('create -c tools.cmake.cmakedeps:new="will_break_next" .', assert_error=True) #Not valid

def test_help():
a = """
from conan import ConanFile
class MyModule(ConanFile):
name = 'mod'
version = '1.0'
package_type = 'module'
generators = "CMakeDeps"

def requirements(self):
self.requires("dep/1.0", visible=False)
settings = "build_type"
"""
tc = TestClient()
tc.save({"conanfile.py": GenConanfile("dep", "1.0").with_generator("CMakeDeps").with_settings("build_type")})
tc.run('create -c tools.cmake.cmakedeps:new="will_break_next" .')
tc.save({"conanfile.py": a})
tc.run('create -c tools.cmake.cmakedeps:new="will_break_next" .')
assert True