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 example of editable package with custom src folder layout #156

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import platform
import os

from conan import conan_version

from test.examples_tools import run, chdir, replace


print("- Editable packages (cmake_layout with separate build folders) -")


# FIXME: remove once 2.0-beta10 is out
prefix_preset_name = "" if "beta9" in str(conan_version) else "conan-"
editable_add_argument = "say/1.0" if "beta9" in str(conan_version) else "--name=say --version=1.0"
editable_remove_argument = "say/1.0" if "beta9" in str(conan_version) else "--refs=say/1.0"

run(f"conan editable add --output-folder build_say say {editable_add_argument}")

# Set up the conan generators folders
# Use the consumer to generate the ../build_say/*/generators folders
with chdir("hello"):
if platform.system() == "Windows":
run("conan install . -s build_type=Release --output-folder ../build_hello")
run("conan install . -s build_type=Debug --output-folder ../build_hello")
else:
run("conan install . -s build_type=Release --output-folder ../build_hello")

# Build the dependency explicitly (no need for conan-install, that was done by consumer in previous step)
# This step could be skipped if conan install (above) included --build=editable
with chdir("say"):
if platform.system() == "Windows":
# This step was done by hello ... run("conan install . -s build_type=Release")
# This step was done by hello ... run("conan install . -s build_type=Debug")
run(f"cmake --preset {prefix_preset_name}default")
run(f"cmake --build --preset {prefix_preset_name}release")
run(f"cmake --build --preset {prefix_preset_name}debug")
else:
# This step was done by hello ... run("conan install . -s build_type=Release")
run(f"cmake --preset {prefix_preset_name}release")
run(f"cmake --build --preset {prefix_preset_name}release")

# Build the consumer
with chdir("hello"):
if platform.system() == "Windows":
run(f"cmake --preset {prefix_preset_name}default")
run(f"cmake --build --preset {prefix_preset_name}release")
run(f"cmake --build --preset {prefix_preset_name}debug")
cmd_out = run("../build_hello/Release/hello.exe")
assert "say/1.0: Hello World Release!" in cmd_out
cmd_out = run("../build_hello/Debug/hello.exe")
assert "say/1.0: Hello World Debug!" in cmd_out
else:
run(f"cmake --preset {prefix_preset_name}release")
run(f"cmake --build --preset {prefix_preset_name}release")
cmd_out = run("../build_hello/Release/hello")
assert "say/1.0: Hello World Release!" in cmd_out

# Edit 'say' source code
with chdir("say"):
replace(os.path.join("thelib/src", "say.cpp"), "Hello World", "Bye World")
if platform.system() == "Windows":
run(f"cmake --build --preset {prefix_preset_name}release")
run(f"cmake --build --preset {prefix_preset_name}debug")
else:
run(f"cmake --build --preset {prefix_preset_name}release")

with chdir("hello"):
if platform.system() == "Windows":
run(f"cmake --build --preset {prefix_preset_name}release")
run(f"cmake --build --preset {prefix_preset_name}debug")
cmd_out = run("../build_hello/Release/hello.exe")
assert "say/1.0: Bye World Release!" in cmd_out
cmd_out = run("../build_hello/Debug/hello.exe")
assert "say/1.0: Bye World Debug!" in cmd_out
else:
run(f"cmake --build --preset {prefix_preset_name}release")
cmd_out = run("../build_hello/Release/hello")
assert "say/1.0: Bye World Release!" in cmd_out

run(f"conan editable remove {editable_remove_argument}")
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
cmake_minimum_required(VERSION 3.15)

project(Hello)

find_package(say REQUIRED)

add_executable(hello src/hello.cpp)
target_link_libraries(hello say::say)
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from conan import ConanFile
from conan.tools.cmake import cmake_layout, CMake


class HelloConan(ConanFile):
name = "hello"
version = "1.0"

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

generators = "CMakeToolchain", "CMakeDeps"
requires = "say/1.0"

def layout(self):
cmake_layout(self)

def configure(self):
self.options["say"].something_custom = True

def build(self):
cmake = CMake(self)
cmake.configure()
cmake.build()
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#include "say.h"

int main() {
say();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
cmake_minimum_required(VERSION 3.15)
project(say CXX)

add_subdirectory(thelib/src)
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from conan import ConanFile
from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout


class SayConan(ConanFile):
name = "say"
version = "1.0"

# Binary configuration
settings = "os", "compiler", "build_type", "arch"
options = {"shared": [True, False], "fPIC": [True, False], "something_custom": [True, False]}
default_options = {"shared": False, "fPIC": True, "something_custom": False}

# Sources are located in the same place as this recipe, copy them to the recipe
exports_sources = "CMakeLists.txt", "src/*", "include/*"

def config_options(self):
if self.settings.os == "Windows":
del self.options.fPIC

def validate(self):
# ensure the consumer's options affect this dependency
if not self.options.something_custom:
raise ConanInvalidConfiguration("The consumer should set something_custom=True")
Comment on lines +21 to +24
Copy link
Member

Choose a reason for hiding this comment

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

This validate() seems unrelated to the example, I'd say it should be simplified and removed, and the example focused on the custom layout.

Copy link
Author

Choose a reason for hiding this comment

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

The point of validate() was to ensure the build of "say" had the flag set. That flag was set by the consumer.
The original example built "say" without any influence from the consumer.
If you attempted to build this example with the same procedure as the other example (ie conan-install "say" directly), then it should fail.
Perhaps that should be a different example?

Copy link
Author

Choose a reason for hiding this comment

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

Re simplification, I got rid of the Debug build. It was there in the original example.
Can I also get rid of the "beta9" stuff?
I didn't test on Windows, do I really need the cmake --preset conan-default windows specialisation?
Seems weird that it is different on Windows.

Copy link
Author

Choose a reason for hiding this comment

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

Can you give me a place where it should be moved?
Are you able to help with the HTML linking?

Copy link
Member

Choose a reason for hiding this comment

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

The point of validate() was to ensure the build of "say" had the flag set. That flag was set by the consumer.

I might still be missing something. The option is not used at all for the build, is it? If it doesn't have any effect on the layout, the build or anything, it still doesn't seem related to an example that is focused on the custom source folder. This would be a completely different use case, which is setting options from consumers, but this still doesn't seem related to the current example of the layout().


def layout(self):
cmake_layout(self)

self.cpp.source.includedirs = ["thelib/src"]
self.cpp.build.libdirs = ["thelib/src"]
Copy link
Member

Choose a reason for hiding this comment

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

This doesn't seem a valid cpp.build.libdirs, I don't think the final say.lib goes in thelib/src/say.lib.

Copy link
Author

Choose a reason for hiding this comment

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

It sure does.

$ ls build_say/build/Release/thelib/src/
CMakeFiles  cmake_install.cmake  libsay.a

This matches the way a real library is built.
The source is not at the top level, there are several directory levels with documentation and other things before it gets to the source, and that is where cmake builds the library.

A key thing that is demonstrated here is the libraries will end up in a very different path to after it is packaged.
ie after packaging, it is in the sensible package/lib folder. Before, libraries may be getting built in all sorts of places.

Copy link
Contributor

Choose a reason for hiding this comment

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

@paulharris keep in mind that people use different platforms and generators. cmake does do different build folder layouts depending on that.

i've been using the following expression usually:

# this sets self.cpp.build.libdirs[0] (and bindirs) to a reasonable value based on build context
# it usually becomes "." or self.build_type
cmake_layout(self)

# example below expects something like
# /CMakeLists.txt -> add_subdirectory(relative/path/to/add_library/call)
# /relative/path/to/add_library/call/CMakeLists.txt -> add_library(foo)
self.cpp.build.libdirs = [
  os.path.join("relative", "path", "to", "add_library", "call", self.cpp.build.libdirs[0])
]
self.cpp.build.bindirs = [
  os.path.join("relative", "path", "to", "add_library", "call", self.cpp.build.bindirs[0])
]
self.cpp.build.libs = ["foo"]


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 = ["say"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
add_library(say say.cpp)
# target_include_directories(say PUBLIC include)

# set_target_properties(say PROPERTIES PUBLIC_HEADER "include/say.h")
install(TARGETS say)
install(FILES say.h DESTINATION "include/say")
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#include <iostream>
#include "say.h"

void say(){
#ifdef NDEBUG
std::cout << "say/1.0: Hello World Release!\n";
#else
std::cout << "say/1.0: Hello World Debug!\n";
#endif
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#pragma once

#ifdef WIN32
#define say_EXPORT __declspec(dllexport)
#else
#define say_EXPORT
#endif

say_EXPORT void say();