Skip to content

Commit

Permalink
custom global generators in cache extensions/generators folder (conan…
Browse files Browse the repository at this point in the history
…-io#13718)

* global generators in cache

* add test
  • Loading branch information
memsharded authored Apr 20, 2023
1 parent 9d18bae commit 31ab039
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 5 deletions.
2 changes: 1 addition & 1 deletion conan/api/subapi/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,4 @@ def install_consumer(self, deps_graph, generators=None, source_folder=None, outp

conanfile.generators = list(set(conanfile.generators).union(generators or []))
app = ConanApp(self.conan_api.cache_folder)
write_generators(conanfile, app.hook_manager)
write_generators(conanfile, app)
4 changes: 4 additions & 0 deletions conans/client/cache/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,10 @@ def settings_path(self):
def custom_commands_path(self):
return os.path.join(self.cache_folder, EXTENSIONS_FOLDER, CUSTOM_COMMANDS_FOLDER)

@property
def custom_generators_path(self):
return os.path.join(self.cache_folder, EXTENSIONS_FOLDER, "generators")

@property
def plugins_path(self):
return os.path.join(self.cache_folder, EXTENSIONS_FOLDER, PLUGINS_FOLDER)
Expand Down
26 changes: 24 additions & 2 deletions conans/client/generators/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import inspect
import os
import traceback
import importlib
Expand Down Expand Up @@ -45,10 +46,30 @@ def _get_generator_class(generator_name):
f"inside module {generator_class}") from e


def write_generators(conanfile, hook_manager):
def load_cache_generators(path):
from conans.client.loader import load_python_file
result = {} # Name of the generator: Class
if not os.path.isdir(path):
return result
for f in os.listdir(path):
if not f.endswith(".py") or f.startswith("_"):
continue
full_path = os.path.join(path, f)
mod, _ = load_python_file(full_path)
for name, value in inspect.getmembers(mod):
if inspect.isclass(value) and not name.startswith("_"):
result = {name: value}
return result


def write_generators(conanfile, app):
new_gen_folder = conanfile.generators_folder
_receive_conf(conanfile)

hook_manager = app.hook_manager
cache = app.cache
# TODO: Optimize this, so the global generators are not loaded every call to write_generators
global_generators = load_cache_generators(cache.custom_generators_path)
hook_manager.execute("pre_generate", conanfile=conanfile)

if conanfile.generators:
Expand All @@ -60,7 +81,8 @@ def write_generators(conanfile, hook_manager):
conanfile.generators = []
try:
for generator_name in old_generators:
generator_class = _get_generator_class(generator_name)
global_generator = global_generators.get(generator_name)
generator_class = global_generator or _get_generator_class(generator_name)
if generator_class:
try:
generator = generator_class(conanfile)
Expand Down
4 changes: 2 additions & 2 deletions conans/client/installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def _copy_sources(conanfile, source_folder, build_folder):
raise ConanException("%s\nError copying sources to build folder" % msg)

def _build(self, conanfile, pref):
write_generators(conanfile, self._hook_manager)
write_generators(conanfile, self._app)

try:
run_build_method(conanfile, self._hook_manager)
Expand Down Expand Up @@ -351,7 +351,7 @@ def _handle_node_editable(self, install_node):
output = conanfile.output
output.info("Rewriting files of editable package "
"'{}' at '{}'".format(conanfile.name, conanfile.generators_folder))
write_generators(conanfile, self._hook_manager)
write_generators(conanfile, self._app)

if node.binary == BINARY_EDITABLE_BUILD:
run_build_method(conanfile, self._hook_manager)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import os
import textwrap

from conans.test.assets.genconanfile import GenConanfile
from conans.test.utils.tools import TestClient
from conans.util.files import save


def test_custom_global_generator():
c = TestClient()
generator = textwrap.dedent("""
class MyCustomGenerator:
def __init__(self, conanfile):
self._conanfile = conanfile
def generate(self):
pkg = self._conanfile.dependencies["pkg"].ref
self._conanfile.output.info(f"DEP: {pkg}!!")
""")
save(os.path.join(c.cache.custom_generators_path, "mygen.py"), generator)
conanfile = textwrap.dedent("""
[requires]
pkg/0.1
[generators]
MyCustomGenerator
""")
c.save({"pkg/conanfile.py": GenConanfile("pkg", "0.1"),
"conanfile.txt": conanfile})
c.run("create pkg")
c.run("install .")
assert "conanfile.txt: Generator 'MyCustomGenerator' calling 'generate()'" in c.out
assert "conanfile.txt: DEP: pkg/0.1!!" in c.out

# By CLI also works
conanfile = textwrap.dedent("""
[requires]
pkg/0.1
""")
c.save({"conanfile.txt": conanfile})
c.run("install . -g MyCustomGenerator")
assert "conanfile.txt: Generator 'MyCustomGenerator' calling 'generate()'" in c.out
assert "conanfile.txt: DEP: pkg/0.1!!" in c.out

# In conanfile.py also works
conanfile = textwrap.dedent("""
from conan import ConanFile
class MyPkg(ConanFile):
requires = "pkg/0.1"
generators = "MyCustomGenerator"
""")
c.save({"conanfile.py": conanfile}, clean_first=True)
c.run("install . ")
assert "conanfile.py: Generator 'MyCustomGenerator' calling 'generate()'" in c.out
assert "conanfile.py: DEP: pkg/0.1!!" in c.out


def test_custom_global_generator_imports():
"""
our custom generator can use python imports
"""
c = TestClient()
generator = textwrap.dedent("""
from _myfunc import mygenerate
class MyCustomGenerator:
def __init__(self, conanfile):
self._conanfile = conanfile
def generate(self):
mygenerate(self._conanfile)
""")
myaux = textwrap.dedent("""
def mygenerate(conanfile):
conanfile.output.info("MYGENERATE WORKS!!")
""")
save(os.path.join(c.cache.custom_generators_path, "mygen.py"), generator)
save(os.path.join(c.cache.custom_generators_path, "_myfunc.py"), myaux)

c.save({"conanfile.txt": ""})
c.run("install . -g MyCustomGenerator")
assert "conanfile.txt: Generator 'MyCustomGenerator' calling 'generate()'" in c.out
assert "conanfile.txt: MYGENERATE WORKS!!" in c.out

0 comments on commit 31ab039

Please sign in to comment.