From 77e981fe1950ad0852d8faadd42446adbf264e14 Mon Sep 17 00:00:00 2001 From: Francisco Ramirez de Anton Date: Wed, 18 Oct 2023 10:27:23 +0200 Subject: [PATCH 01/24] BazelDeps refactor --- conan/tools/google/bazeldeps.py | 757 +++++++++++++++++++++----------- 1 file changed, 510 insertions(+), 247 deletions(-) diff --git a/conan/tools/google/bazeldeps.py b/conan/tools/google/bazeldeps.py index 7c6527f033d..c16cb7a318b 100644 --- a/conan/tools/google/bazeldeps.py +++ b/conan/tools/google/bazeldeps.py @@ -1,278 +1,541 @@ import os -import platform import textwrap +from collections import namedtuple -from jinja2 import Template +from jinja2 import Template, StrictUndefined -from conan.tools._check_build_profile import check_using_build_profile -from conans.errors import ConanException +from conan.errors import ConanException +from conan.internal import check_duplicated_generator +from conans.model.dependencies import get_transitive_requires from conans.util.files import save -class BazelDeps(object): - def __init__(self, conanfile): - self._conanfile = conanfile - check_using_build_profile(self._conanfile) - - def generate(self): - local_repositories = [] - generators_folder = self._conanfile.generators_folder - - for build_dependency in self._conanfile.dependencies.direct_build.values(): - content = self._get_build_dependency_buildfile_content(build_dependency) - filename = self._save_dependency_buildfile(build_dependency, content, - generators_folder) - - local_repository = self._create_new_local_repository(build_dependency, filename) - local_repositories.append(local_repository) - - for dependency in self._conanfile.dependencies.host.values(): - content = self._get_dependency_buildfile_content(dependency) - if not content: +_DepInfo = namedtuple("DepInfo", ['name', 'requires', 'cpp_info']) +_LibInfo = namedtuple("LibInfo", ['name', 'is_shared', 'lib_path', 'interface_path']) + + +def _get_name_with_namespace(namespace, name): + """ + Build a name with a namespace, e.g., openssl-crypto + """ + return f"{namespace}-{name}" + + +def _get_package_reference_name(dep): + """ + Get the reference name for the given package + """ + return dep.ref.name + + +def _get_package_name(dep): + pkg_name = dep.cpp_info.get_property("bazel_module_name") or _get_package_reference_name(dep) + return pkg_name + + +def _get_component_name(dep, comp_ref_name): + pkg_name = _get_package_name(dep) + if comp_ref_name not in dep.cpp_info.components: + # foo::foo might be referencing the root cppinfo + if _get_package_reference_name(dep) == comp_ref_name: + return pkg_name + raise ConanException("Component '{name}::{cname}' not found in '{name}' " + "package requirement".format(name=_get_package_reference_name(dep), + cname=comp_ref_name)) + comp_name = dep.cpp_info.components[comp_ref_name].get_property("bazel_module_name") + # If user did not set bazel_module_name, let's create a component name + # with a namespace, e.g., dep-comp1 + return comp_name or _get_name_with_namespace(pkg_name, comp_ref_name) + + +# FIXME: This function should be a common one to be used by PkgConfigDeps, CMakeDeps?, etc. +def get_requirements(conanfile, build_context_activated): + """ + Simply save the activated requirements (host + build + test), and the deactivated ones + """ + # All the requirements + host_req = conanfile.dependencies.host + build_req = conanfile.dependencies.build # tool_requires + test_req = conanfile.dependencies.test + + for require, dep in list(host_req.items()) + list(build_req.items()) + list(test_req.items()): + # Require is not used at the moment, but its information could be used, + # and will be used in Conan 2.0 + # Filter the build_requires not activated with self.build_context_activated + if require.build and dep.ref.name not in build_context_activated: + continue + yield require, dep + + +def _get_libs(conanfile, cpp_info, is_shared=False, relative_to_path=None) -> list: + """ + Get the static/shared library paths + + :param conanfile: The current recipe object. + :param dep: of the dependency. + :param cpp_info: of the component. + :param relative_to_path: base path to prune from each library path + :return: dict of static/shared library absolute paths -> + {lib_name: (IS_SHARED, LIB_PATH, DLL_PATH)} + Note: LIB_PATH could be both static and shared ones in case of UNIX. If Windows it'll be + the interface library xxx.lib linked to the DLL_PATH one. + """ + cpp_info = cpp_info + libdirs = cpp_info.libdirs + bindirs = cpp_info.bindirs + libs = cpp_info.libs[:] # copying the values + ret = {} + interfaces = {} + + for libdir in (libdirs + bindirs): + lib = "" + if not os.path.exists(libdir): + conanfile.output.debug("The library folder doesn't exist: {}".format(libdir)) + continue + files = os.listdir(libdir) + lib_path = None + for f in files: + full_path = os.path.join(libdir, f) + if not os.path.isfile(full_path): # Make sure that directories are excluded continue - filename = self._save_dependency_buildfile(dependency, content, generators_folder) + # Users may not name their libraries in a conventional way. For example, directly + # use the basename of the lib file as lib name. + if f in libs: + lib = f + libs.remove(f) + lib_path = full_path + break + name, ext = os.path.splitext(f) + if name not in libs and name.startswith("lib"): + name = name[3:] + if name in libs: + # FIXME: Should it read a conf variable to know unexpected extensions? + if (is_shared and ext in (".so", ".dylib", ".lib", ".dll")) or \ + (not is_shared and ext in (".a", ".lib")): + lib = name + libs.remove(name) + lib_path = full_path + break + if lib_path is not None: + _, ext = os.path.splitext(lib_path) + if is_shared and ext == ".lib": # Windows interface library + interfaces[lib] = _relativize_path(lib_path, relative_to_path=relative_to_path) + else: + ret[lib] = _relativize_path(lib_path, relative_to_path=relative_to_path) + + libraries = [] + for lib, lib_path in ret.items(): + interface_library = None + if lib_path.endswith(".dll"): + if lib not in interfaces: + raise ConanException(f"Windows needs a .lib for link-time and .dll for runtime." + f"Only found {lib_path}") + interface_library = interfaces.pop(lib) + libraries.append(_LibInfo(lib, is_shared, lib_path, interface_library)) + # TODO: Would we want to manage the cases where DLLs are provided by the system? + conanfile.output.warning(f"The library/ies {libs} cannot be found in the dependency") + return libraries + + +def _get_headers(cpp_info, package_folder_path): + return ', '.join('"{}/**"'.format(_relativize_path(path, package_folder_path)) + for path in cpp_info.includedirs) + + +def _get_includes(cpp_info, package_folder_path): + return ', '.join('"{}"'.format(_relativize_path(path, package_folder_path)) + for path in cpp_info.includedirs) + + +def _get_defines(cpp_info): + return ', '.join('"{}"'.format(define.replace('"', '\\' * 3 + '"')) + for define in cpp_info.defines) + + +def _get_linkopts(cpp_info, os_build): + link_opt = '"/DEFAULTLIB:{}"' if os_build == "Windows" else '"-l{}"' + system_libs = [link_opt.format(lib) for lib in cpp_info.system_libs] + shared_flags = cpp_info.sharedlinkflags + cpp_info.exelinkflags + return ", ".join(system_libs + shared_flags) + + +def _get_copts(cpp_info): + # FIXME: long discussions between copts (-Iflag) vs includes in Bazel. Not sure yet + # includedirsflags = ['"-I{}"'.format(_relativize_path(d, package_folder_path)) + # for d in cpp_info.includedirs] + cxxflags = [var.replace('"', '\\"') for var in cpp_info.cxxflags] + cflags = [var.replace('"', '\\"') for var in cpp_info.cflags] + return ", ".join(cxxflags + cflags) + + +# FIXME: Very fragile. Need UTs, and, maybe, move it to conan.tools.files +def _relativize_path(path, relative_to_path): + """ + Relativize the path with regard to a given base path + + :param path: path to relativize + :param relative_to_path: base path to prune from the path + :return: Unix-like path relative to ``relative_to_path``. Otherwise, it returns + the Unix-like ``path``. + """ + if not path or not relative_to_path: + return path + p = path.replace("\\", "/") + rel = relative_to_path.replace("\\", "/") + if p.startswith(rel): + return p[len(rel):].lstrip("/") + elif rel in p: + return p.split(rel)[-1].lstrip("/") + else: + return p.lstrip("/") + + +class _BazelDependenciesBZLGenerator: + """ + Bazel needs to know all the dependencies for its current project. So, the only way + to do that is to tell the WORKSPACE file how to load all the Conan ones. This is the goal + of the function created by this class, the ``load_conan_dependencies`` one. + + More information: + * https://bazel.build/reference/be/workspace#new_local_repository + """ + + filename = "dependencies.bzl" + template = textwrap.dedent("""\ + # This Bazel module should be loaded by your WORKSPACE file. + # Add these lines to your WORKSPACE one (assuming that you're using the "bazel_layout"): + # load("@//bazel-conan-tools:dependencies.bzl", "load_conan_dependencies") + # load_conan_dependencies() + + {% macro new_local_repository(pkg_name, pkg_folder, pkg_build_file_path) %} + native.new_local_repository( + name="{{pkg_name}}", + path="{{pkg_folder}}", + build_file="{{pkg_build_file_path}}", + ) + {% endmacro %} + def load_conan_dependencies(): + {% for pkg_name, pkg_folder, pkg_build_file_path in dependencies %} + {{ new_local_repository(pkg_name, pkg_folder, pkg_build_file_path) }} + {% endfor %} + """) - local_repository = self._create_new_local_repository(dependency, filename) - local_repositories.append(local_repository) + def __init__(self, conanfile, dependencies): + self._conanfile = conanfile + self._dependencies = dependencies - content = self._get_main_buildfile_content(local_repositories) - self._save_main_buildfiles(content, self._conanfile.generators_folder) + def generate(self): + template = Template(self.template, trim_blocks=True, lstrip_blocks=True, + undefined=StrictUndefined) + content = template.render(dependencies=self._dependencies) + # Saving the BUILD (empty) and dependencies.bzl files + save(self.filename, content) + save("BUILD.bazel", "# This is an empty BUILD file to be able to load the " + "dependencies.bzl one.") + + +class _BazelBUILDGenerator: + """ + This class creates the BUILD.bazel for each dependency where it's declared all the + necessary information to load the libraries + """ + + # If both files exist, BUILD.bazel takes precedence over BUILD + # https://bazel.build/concepts/build-files + filename = "BUILD.bazel" + template = textwrap.dedent("""\ + {% macro cc_import_macro(libs) %} + {% for lib_info in libs %} + cc_import( + name = "{{ lib_info.name }}_precompiled", + {% if lib_info.is_shared %} + shared_library = "{{ lib_info.lib_path }}", + {% else %} + static_library = "{{ lib_info.lib_path }}", + {% endif %} + {% if lib_info.interface_path %} + interface_library = "{{ lib_info.interface_path }}", + {% endif %} + ) + {% endfor %} + {% endmacro %} + {% macro cc_library_macro(obj) %} + cc_library( + name = "{{ obj["name"] }}", + {% if obj["headers"] %} + hdrs = glob([ + {{ obj["headers"] }} + ]), + {% endif %} + {% if obj["includes"] %} + includes = [ + {{ obj["includes"] }} + ], + {% endif %} + {% if obj["defines"] %} + defines = [ + {{ obj["defines"] }} + ], + {% endif %} + {% if obj["linkopts"] %} + linkopts = [ + {{ obj["linkopts"] }} + ], + {% endif %} + {% if obj["copts"] %} + copts = [ + {{ obj["copts"] }} + ], + {% endif %} + visibility = ["//visibility:public"], + {% if obj["libs"] or obj["dependencies"] %} + deps = [ + {% for lib in obj["libs"] %} + ":{{ lib.name }}_precompiled", + {% endfor %} + {% for name in obj["component_names"] %} + ":{{ name }}", + {% endfor %} + {% for dep in obj["dependencies"] %} + "{{ dep }}", + {% endfor %} + ], + {% endif %} + ) + {% endmacro %} + load("@rules_cc//cc:defs.bzl", "cc_import", "cc_library") + + # Components precompiled libs + {% for component in components %} + {{ cc_import_macro(component["libs"]) }} + {% endfor %} + # Root package precompiled libs + {{ cc_import_macro(root["libs"]) }} + # Components libraries declaration + {% for component in components %} + {{ cc_library_macro(component) }} + {% endfor %} + # Package library declaration + {{ cc_library_macro(root) }} + """) + + def __init__(self, conanfile, dep, root_package_info, components_info): + self._conanfile = conanfile + self._dep = dep + self._root_package_info = root_package_info + self._components_info = components_info - def _save_dependency_buildfile(self, dependency, buildfile_content, conandeps): - filename = '{}/{}/BUILD'.format(conandeps, dependency.ref.name) - save(filename, buildfile_content) - return filename + @property + def _is_shared_dependency(self): + """ + Checking traits and shared option + """ + default_value = self._dep.options.get_safe("shared") if self._dep.options else False + return {"shared-library": True, + "static-library": False}.get(str(self._dep.package_type), default_value) - def _get_build_dependency_buildfile_content(self, dependency): - filegroup = textwrap.dedent(""" - filegroup( - name = "{}_binaries", - data = glob(["**"]), - visibility = ["//visibility:public"], - ) + @property + def build_file_pah(self): + """ + Returns the absolute path to the BUILD file created by Conan + """ + return os.path.join(self._root_package_info.name, self.filename) - """).format(dependency.ref.name) + @property + def absolute_build_file_pah(self): + """ + Returns the absolute path to the BUILD file created by Conan + """ + return os.path.join(self._conanfile.generators_folder, self.build_file_pah) - return filegroup + @property + def package_folder(self): + """ + Returns the package folder path + """ + # If editable, package_folder can be None + root_folder = self._dep.recipe_folder if self._dep.package_folder is None \ + else self._dep.package_folder + return root_folder.replace("\\", "/") - def _get_dependency_buildfile_content(self, dependency): - template = textwrap.dedent("""\ - load("@rules_cc//cc:defs.bzl", "cc_import", "cc_library") + @property + def package_name(self): + """ + Wrapper to get the final name used for the root dependency cc_library declaration + """ + return self._root_package_info.name + + def _get_context(self): + def fill_info(info): + ret = { + "name": info.name, # package name and components name + "libs": {}, + "headers": "", + "includes": "", + "defines": "", + "linkopts": "", + "copts": "", + "dependencies": info.requires, + "component_names": [] # filled only by the root + } + if info.cpp_info is not None: + cpp_info = info.cpp_info + headers = _get_headers(cpp_info, package_folder_path) + includes = _get_includes(cpp_info, package_folder_path) + copts = _get_copts(cpp_info) + defines = _get_defines(cpp_info) + linkopts = _get_linkopts(cpp_info, self._dep.settings_build.get_safe("os")) + libs = _get_libs(self._conanfile, cpp_info, is_shared=is_shared, + relative_to_path=package_folder_path) + ret.update({ + "libs": libs, + "headers": headers, + "includes": includes, + "defines": defines, + "linkopts": linkopts, + "copts": copts + }) + return ret + + is_shared = self._is_shared_dependency + package_folder_path = self.package_folder + context = dict() + context["root"] = fill_info(self._root_package_info) + context["components"] = [] + for component in self._components_info: + component_context = fill_info(component) + context["components"].append(component_context) + context["root"]["component_names"].append(component_context["name"]) + return context - {%- for libname, filepath in libs.items() %} - cc_import( - name = "{{ libname }}_precompiled", - {{ library_type }} = "{{ filepath }}", - ) - {%- endfor %} + def generate(self): + context = self._get_context() + template = Template(self.template, trim_blocks=True, lstrip_blocks=True, + undefined=StrictUndefined) + content = template.render(context) + save(self.build_file_pah, content) - {%- for libname, (lib_path, dll_path) in shared_with_interface_libs.items() %} - cc_import( - name = "{{ libname }}_precompiled", - interface_library = "{{ lib_path }}", - shared_library = "{{ dll_path }}", - ) - {%- endfor %} - - cc_library( - name = "{{ name }}", - {%- if headers %} - hdrs = glob([{{ headers }}]), - {%- endif %} - {%- if includes %} - includes = [{{ includes }}], - {%- endif %} - {%- if defines %} - defines = [{{ defines }}], - {% endif %} - {%- if linkopts %} - linkopts = [{{ linkopts }}], - {%- endif %} - visibility = ["//visibility:public"], - {%- if libs or shared_with_interface_libs %} - deps = [ - # do not sort - {%- for lib in libs %} - ":{{ lib }}_precompiled", - {%- endfor %} - {%- for lib in shared_with_interface_libs %} - ":{{ lib }}_precompiled", - {%- endfor %} - {%- for dep in dependencies %} - "@{{ dep }}", - {%- endfor %} - ], - {%- endif %} - ) - """) +class _InfoGenerator: - cpp_info = dependency.cpp_info.aggregated_components() + def __init__(self, conanfile, dep, build_context_activated=None): + self._conanfile = conanfile + self._dep = dep + self._transitive_reqs = get_transitive_requires(self._conanfile, dep) + self._build_context_activated = build_context_activated or [] - if not cpp_info.libs and not cpp_info.includedirs: - return None + def _get_cpp_info_requires_names(self, cpp_info): + """ + Get all the valid names from the requirements ones given a CppInfo object. - headers = [] - includes = [] + For instance, those requirements could be coming from: - def _relativize_path(p, base_path): - # TODO: Very fragile, to test more - if p.startswith(base_path): - return p[len(base_path):].replace("\\", "/").lstrip("/") - return p.replace("\\", "/").lstrip("/") + ```python + from conan import ConanFile + class PkgConan(ConanFile): + requires = "other/1.0" - # TODO: This only works for package_folder, but not editable - package_folder = dependency.package_folder - for path in cpp_info.includedirs: - headers.append('"{}/**"'.format(_relativize_path(path, package_folder))) - includes.append('"{}"'.format(_relativize_path(path, package_folder))) + def package_info(self): + self.cpp_info.requires = ["other::cmp1"] - headers = ', '.join(headers) - includes = ', '.join(includes) + # Or: - defines = ('"{}"'.format(define.replace('"', '\\' * 3 + '"')) - for define in cpp_info.defines) - defines = ', '.join(defines) + def package_info(self): + self.cpp_info.components["cmp"].requires = ["other::cmp1"] + ``` + """ + dep_ref_name = _get_package_reference_name(self._dep) + ret = [] + for req in cpp_info.requires: + pkg_ref_name, comp_ref_name = req.split("::") if "::" in req else (dep_ref_name, req) + prefix = ":" # Requirements declared in the same BUILD file + # For instance, dep == "hello/1.0" and req == "other::cmp1" -> hello != other + if dep_ref_name != pkg_ref_name: + try: + req_conanfile = self._transitive_reqs[pkg_ref_name] + # Requirements declared in another dependency BUILD file + prefix = f"@{_get_package_name(req_conanfile)}//:" + except KeyError: + continue # If the dependency is not in the transitive, might be skipped + else: # For instance, dep == "hello/1.0" and req == "hello::cmp1" -> hello == hello + req_conanfile = self._dep + comp_name = _get_component_name(req_conanfile, comp_ref_name) + ret.append(f"{prefix}{comp_name}") + return ret + + @property + def components_info(self): + """ + Get the whole package and its components information like their own requires, names and even + the cpp_info for each component. - linkopts = [] - for system_lib in cpp_info.system_libs: - # FIXME: Replace with settings_build - if platform.system() == "Windows": - linkopts.append('"/DEFAULTLIB:{}"'.format(system_lib)) - else: - linkopts.append('"-l{}"'.format(system_lib)) - - linkopts = ', '.join(linkopts) - lib_dir = 'lib' - - dependencies = [] - for dep in dependency.dependencies.direct_host.values(): - dependencies.append(dep.ref.name) - - shared_library = dependency.options.get_safe("shared") if dependency.options else False - - libs = {} - shared_with_interface_libs = {} - for lib in cpp_info.libs: - lib_path, dll_path = self._get_lib_file_paths(shared_library, - cpp_info.libdirs, - cpp_info.bindirs, - lib) - if lib_path and dll_path: - shared_with_interface_libs[lib] = ( - _relativize_path(lib_path, package_folder), - _relativize_path(dll_path, package_folder)) - elif lib_path: - libs[lib] = _relativize_path(lib_path, package_folder) - - context = { - "name": dependency.ref.name, - "libs": libs, - "shared_with_interface_libs": shared_with_interface_libs, - "libdir": lib_dir, - "headers": headers, - "includes": includes, - "defines": defines, - "linkopts": linkopts, - "library_type": "shared_library" if shared_library else "static_library", - "dependencies": dependencies, - } - content = Template(template).render(**context) - return content - - def _get_dll_file_paths(self, bindirs, expected_file): - """Find a given dll file in bin directories. If found return the full - path, otherwise return None. + :return: `list` of `_PCInfo` objects with all the components information """ - for each_bin in bindirs: - if not os.path.exists(each_bin): - self._conanfile.output.warning("The bin folder doesn't exist: {}".format(each_bin)) - continue - files = os.listdir(each_bin) - for f in files: - full_path = os.path.join(each_bin, f) - if not os.path.isfile(full_path): - continue - if f == expected_file: - return full_path - return None - - def _get_lib_file_paths(self, shared, libdirs, bindirs, lib): - for libdir in libdirs: - if not os.path.exists(libdir): - self._conanfile.output.warning("The library folder doesn't exist: {}".format(libdir)) - continue - files = os.listdir(libdir) - lib_basename = None - lib_path = None - for f in files: - full_path = os.path.join(libdir, f) - if not os.path.isfile(full_path): # Make sure that directories are excluded - continue - # Users may not name their libraries in a conventional way. For example, directly - # use the basename of the lib file as lib name. - if f == lib: - lib_basename = f - lib_path = full_path - break - name, ext = os.path.splitext(f) - if ext in (".so", ".lib", ".a", ".dylib", ".bc"): - if ext != ".lib" and name.startswith("lib"): - name = name[3:] - if lib == name: - lib_basename = f - lib_path = full_path - break - if lib_path is not None: - dll_path = None - name, ext = os.path.splitext(lib_basename) - if shared and ext == ".lib": - dll_path = self._get_dll_file_paths(bindirs, name+".dll") - return lib_path, dll_path - self._conanfile.output.warning("The library {} cannot be found in the " - "dependency".format(lib)) - return None, None - - def _create_new_local_repository(self, dependency, dependency_buildfile_name): - if dependency.package_folder is None: - # The local repository path should be the base of every declared cc_library, - # this is potentially incompatible with editables where there is no package_folder - # and the build_folder and the source_folder might be different, so there is no common - # base. - raise ConanException("BazelDeps doesn't support editable packages") - snippet = textwrap.dedent(""" - native.new_local_repository( - name="{}", - path="{}", - build_file="{}", - ) - """).format( - dependency.ref.name, - # FIXME: This shouldn't use package_folder, at editables it doesn't exists - dependency.package_folder.replace("\\", "/"), - dependency_buildfile_name.replace("\\", "/") - ) - - return snippet - - def _get_main_buildfile_content(self, local_repositories): - template = textwrap.dedent(""" - def load_conan_dependencies(): - {} - """) + if not self._dep.cpp_info.has_components: + return [] + components_info = [] + # Loop through all the package's components + for comp_ref_name, cpp_info in self._dep.cpp_info.get_sorted_components().items(): + # At first, let's check if we have defined some components requires, e.g., "dep::cmp1" + comp_requires_names = self._get_cpp_info_requires_names(cpp_info) + comp_name = _get_component_name(self._dep, comp_ref_name) + # Save each component information + components_info.append(_DepInfo(comp_name, comp_requires_names, cpp_info)) + return components_info + + @property + def root_package_info(self): + """ + Get the whole package information - if local_repositories: - function_content = "\n".join(local_repositories) - function_content = ' '.join(line for line in function_content.splitlines(True)) - else: - function_content = ' pass' + :return: `_PCInfo` object with the package information + """ + pkg_name = _get_package_name(self._dep) + # At first, let's check if we have defined some global requires, e.g., "other::cmp1" + requires = self._get_cpp_info_requires_names(self._dep.cpp_info) + # If we have found some component requires it would be enough + if not requires: + # If no requires were found, let's try to get all the direct dependencies, + # e.g., requires = "other_pkg/1.0" + for req in self._transitive_reqs.values(): + req_name = _get_package_name(req) + requires.append(f"@{req_name}//:{req_name}") + cpp_info = self._dep.cpp_info + return _DepInfo(pkg_name, requires, cpp_info) + + +class BazelDeps: - content = template.format(function_content) + def __init__(self, conanfile): + self._conanfile = conanfile + # Activate the build context for the specified libraries + self.build_context_activated = [] - return content + def generate(self): + """ + Save all the targets BUILD files and the dependencies.bzl one. - def _save_main_buildfiles(self, content, generators_folder): - # A BUILD file must exist, even if it's empty, in order for Bazel - # to detect it as a Bazel package and to allow to load the .bzl files - save("{}/BUILD".format(generators_folder), "") - save("{}/dependencies.bzl".format(generators_folder), content) + Important! The dependencies.bzl file should be loaded by the WORKSPACE Bazel file. + """ + check_duplicated_generator(self, self._conanfile) + requirements = get_requirements(self._conanfile, self.build_context_activated) + deps_info = [] + for require, dep in requirements: + # Bazel info generator + info_generator = _InfoGenerator(self._conanfile, dep) + root_package_info = info_generator.root_package_info + components_info = info_generator.components_info + # Generating single BUILD files per dependency + bazel_generator = _BazelBUILDGenerator(self._conanfile, dep, + root_package_info, components_info) + bazel_generator.generate() + # Saving pieces of information from each BUILD file + deps_info.append(( + bazel_generator.package_name, # package name + bazel_generator.package_folder, # path to dependency folder + bazel_generator.absolute_build_file_pah # path to the BUILD file created + )) + # dependencies.bzl has all the information about where to look for the dependencies + bazel_dependencies_module_generator = _BazelDependenciesBZLGenerator(self._conanfile, + deps_info) + bazel_dependencies_module_generator.generate() From e310e99538862d96745cf73ee79a5fca7fd13160 Mon Sep 17 00:00:00 2001 From: Francisco Ramirez de Anton Date: Wed, 18 Oct 2023 10:27:56 +0200 Subject: [PATCH 02/24] BazelToolchain refactor --- conan/tools/google/toolchain.py | 166 ++++++++++++++++++++++++++++---- 1 file changed, 148 insertions(+), 18 deletions(-) diff --git a/conan/tools/google/toolchain.py b/conan/tools/google/toolchain.py index fc0c2717cf4..cbb924d7050 100644 --- a/conan/tools/google/toolchain.py +++ b/conan/tools/google/toolchain.py @@ -1,24 +1,154 @@ -from conan.tools._check_build_profile import check_using_build_profile -from conan.tools.files.files import save_toolchain_args +import textwrap +from jinja2 import Template -class BazelToolchain(object): +from conan.internal import check_duplicated_generator +from conan.tools.apple import to_apple_arch, is_apple_os +from conan.tools.build.cross_building import cross_building +from conan.tools.build.flags import cppstd_flag +from conan.tools.files import save - def __init__(self, conanfile, namespace=None): + +def _get_cpu_name(conanfile): + host_os = conanfile.settings.get_safe('os').lower() + host_arch = conanfile.settings.get_safe('arch') + if is_apple_os(conanfile): + host_os = "darwin" if host_os == "macos" else host_os + host_arch = to_apple_arch(conanfile) + # FIXME: Probably it's going to fail, but let's try it because it normally follows this syntax + return f"{host_os}_{host_arch}" + + +# FIXME: In the future, it could be BazelPlatform instead? Check https://bazel.build/concepts/platforms +class BazelToolchain: + """ + Creates a simple conan_bzl.rc file which defines a conan-config configuration with all the + attributes defined by the consumer. Bear in mind that this is not a complete toolchain, it + only fills some common CLI attributes and save them in a *.rc file. + + Important: Maybe, this toolchain should create a new Conan platform with the user + constraints, but it's not the goal for now as Bazel has tons of platforms and toolchains + already available in its bazel_tools repo. For now, it only admits a list of platforms defined + by the user. + More information related: + * Toolchains: https://bazel.build/extending/toolchains (deprecated) + * Platforms: https://bazel.build/concepts/platforms (new default since Bazel 7.x) + * Migrating to platforms: https://bazel.build/concepts/platforms + * Issue related: https://github.com/bazelbuild/bazel/issues/6516 + + Others: + CROOSTOOL: https://github.com/bazelbuild/bazel/blob/cb0fb033bad2a73e0457f206afb87e195be93df2/tools/cpp/CROSSTOOL + Cross-compiling with Bazel: https://ltekieli.com/cross-compiling-with-bazel/ + bazelrc files: https://bazel.build/run/bazelrc + CLI options: https://bazel.build/reference/command-line-reference + User manual: https://bazel.build/docs/user-manual + """ + + bazelrc_name = "conan_bzl.rc" + bazelrc_config = "conan-config" + bazelrc_template = textwrap.dedent(""" + # Automatic bazelrc file created by Conan + {% if copt %}build:conan-config {{copt}}{% endif %} + {% if conlyopt %}build:conan-config {{conlyopt}}{% endif %} + {% if cxxopt %}build:conan-config {{cxxopt}}{% endif %} + {% if linkopt %}build:conan-config {{linkopt}}{% endif %} + {% if force_pic %}build:conan-config --force_pic={{force_pic}}{% endif %} + {% if dynamic_mode %}build:conan-config --dynamic_mode={{dynamic_mode}}{% endif %} + {% if strip %}build:conan-config --strip={{strip}}{% endif %} + {% if compilation_mode %}build:conan-config --compilation_mode={{compilation_mode}}{% endif %} + {% if compiler %}build:conan-config --compiler={{compiler}}{% endif %} + {% if cpu %}build:conan-config --cpu={{cpu}}{% endif %} + {% if crosstool_top %}build:conan-config --crosstool_top={{crosstool_top}}{% endif %}""") + + def __init__(self, conanfile): self._conanfile = conanfile - self._namespace = namespace - check_using_build_profile(self._conanfile) + + # Flags + # TODO: Should we read the buildenv to get flags? + self.extra_cxxflags = [] + self.extra_cflags = [] + self.extra_ldflags = [] + self.extra_defines = [] + + # Bazel build parameters + shared = self._conanfile.options.get_safe("shared") + fpic = self._conanfile.options.get_safe("fPIC") + self.force_pic = fpic if (not shared and fpic is not None) else None + # FIXME: Keeping this option but it's not working as expected. It's not creating the shared + # libraries at all. + self.dynamic_mode = "fully" if shared else "off" + self.cppstd = cppstd_flag(self._conanfile.settings) + self.copt = [] + self.conlyopt = [] + self.cxxopt = [] + self.linkopt = [] + self.strip = 'sometimes' # 'always', 'sometimes', 'never' + self.compilation_mode = "fastbuild" # 'fastbuild', 'dbg', 'opt' + # Be aware that this parameter does not admit a compiler absolute path + # If you want to add it, you will have to use a specific Bazel toolchain + self.compiler = None + # cpu is the target architecture, and it's a bit tricky. If it's not a cross-compilation, + # let Bazel guess it. + self.cpu = None + # TODO: cross-compilation process is so powerless. Needs to use the new platforms. + if cross_building(self._conanfile): + # Bazel is using those toolchains/platforms by default. + # It's better to let it configure the project in that case + self.cpu = _get_cpu_name(conanfile) + # This is itself a toolchain but just in case + self.crosstool_top = None + # TODO: Have a look at https://bazel.build/reference/be/make-variables + # FIXME: Missing host_xxxx options. When are they needed? Cross-compilation? + + @staticmethod + def _filter_list_empty_fields(v): + return list(filter(bool, v)) + + @property + def cxxflags(self): + ret = [self.cppstd] + conf_flags = self._conanfile.conf.get("tools.build:cxxflags", default=[], check_type=list) + ret = ret + conf_flags + self.extra_cxxflags + return self._filter_list_empty_fields(ret) + + @property + def cflags(self): + conf_flags = self._conanfile.conf.get("tools.build:cflags", default=[], check_type=list) + ret = conf_flags + self.extra_cflags + return self._filter_list_empty_fields(ret) + + @property + def ldflags(self): + conf_flags = self._conanfile.conf.get("tools.build:sharedlinkflags", default=[], + check_type=list) + conf_flags.extend(self._conanfile.conf.get("tools.build:exelinkflags", default=[], + check_type=list)) + linker_scripts = self._conanfile.conf.get("tools.build:linker_scripts", default=[], check_type=list) + conf_flags.extend(["-T'" + linker_script + "'" for linker_script in linker_scripts]) + ret = conf_flags + self.extra_ldflags + return self._filter_list_empty_fields(ret) + + def _context(self): + return { + "copt": " ".join(f"--copt={flag}" for flag in self.copt), + "conlyopt": " ".join(f"--conlyopt={flag}" for flag in (self.conlyopt + self.cflags)), + "cxxopt": " ".join(f"--cxxopt={flag}" for flag in (self.cxxopt + self.cxxflags)), + "linkopt": " ".join(f"--linkopt={flag}" for flag in (self.linkopt + self.ldflags)), + "force_pic": self.force_pic, + "dynamic_mode": self.dynamic_mode, + "strip": self.strip, + "compilation_mode": self.compilation_mode, + "compiler": self.compiler, + "cpu": self.cpu, + "crosstool_top": self.crosstool_top, + } + + @property + def _content(self): + context = self._context() + content = Template(self.bazelrc_template).render(context) + return content def generate(self): - content = {} - configs = ",".join(self._conanfile.conf.get("tools.google.bazel:configs", - default=[], - check_type=list)) - if configs: - content["bazel_configs"] = configs - - bazelrc = self._conanfile.conf.get("tools.google.bazel:bazelrc_path") - if bazelrc: - content["bazelrc_path"] = bazelrc - - save_toolchain_args(content, namespace=self._namespace) + check_duplicated_generator(self, self._conanfile) + save(self._conanfile, BazelToolchain.bazelrc_name, self._content) From 349f4ee1c84f6db052a5249b302adcb74cc1d1aa Mon Sep 17 00:00:00 2001 From: Francisco Ramirez de Anton Date: Wed, 18 Oct 2023 10:28:42 +0200 Subject: [PATCH 03/24] bazel_layout refactor --- conan/tools/google/layout.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/conan/tools/google/layout.py b/conan/tools/google/layout.py index 76e750dbd7c..561e629d0ea 100644 --- a/conan/tools/google/layout.py +++ b/conan/tools/google/layout.py @@ -1,11 +1,13 @@ +import os -def bazel_layout(conanfile, generator=None, src_folder="."): - """The layout for bazel is very limited, it builds in the root folder even specifying - "bazel --output_base=xxx" in the other hand I don't know how to inject a custom path so - the build can load the dependencies.bzl from the BazelDeps""" - conanfile.folders.build = "" - conanfile.folders.generators = "" - # used in test package for example, to know where the binaries are (editables not supported yet) - conanfile.cpp.build.bindirs = ["bazel-bin"] - conanfile.cpp.build.libdirs = ["bazel-bin"] +def bazel_layout(conanfile, src_folder="."): + """Bazel layout is so limited. It does not allow to create its special symlinks in other + folder. See more information in https://bazel.build/remote/output-directories""" + subproject = conanfile.folders.subproject + conanfile.folders.source = src_folder if not subproject else os.path.join(subproject, src_folder) + conanfile.folders.build = "." # Bazel always build the whole project in the root folder + conanfile.folders.generators = "bazel-conan-tools" # one + # FIXME: used in test package for example, to know where the binaries are (editables not supported yet)? + conanfile.cpp.build.bindirs = [os.path.join(conanfile.folders.build, "bazel-bin")] + conanfile.cpp.build.libdirs = [os.path.join(conanfile.folders.build, "bazel-bin")] From 92eeb02f7e42e625091dff1184c7a62be8836a40 Mon Sep 17 00:00:00 2001 From: Francisco Ramirez de Anton Date: Wed, 18 Oct 2023 10:29:20 +0200 Subject: [PATCH 04/24] Bazel helper refactor --- conan/tools/google/bazel.py | 86 +++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 37 deletions(-) diff --git a/conan/tools/google/bazel.py b/conan/tools/google/bazel.py index 90c64e467ee..9b94af9c00a 100644 --- a/conan/tools/google/bazel.py +++ b/conan/tools/google/bazel.py @@ -1,44 +1,56 @@ -from conan.tools.files.files import load_toolchain_args +import os +import platform +from conan.tools.google import BazelToolchain -class Bazel(object): - def __init__(self, conanfile, namespace=None): - self._conanfile = conanfile - self._namespace = namespace - self._get_bazel_project_configuration() - - def configure(self, args=None): - pass - def build(self, args=None, label=None): - # TODO: Change the directory where bazel builds the project (by default, /var/tmp/_bazel_ ) - - bazelrc_path = '--bazelrc={}'.format(self._bazelrc_path) if self._bazelrc_path else '' - bazel_config = " ".join(['--config={}'.format(conf) for conf in self._bazel_config]) +class Bazel(object): - # arch = self._conanfile.settings.get_safe("arch") - # cpu = { - # "armv8": "arm64", - # "x86_64": "" - # }.get(arch, arch) - # - # command = 'bazel {} build --sandbox_debug --subcommands=pretty_print --cpu={} {} {}'.format( - # bazelrc_path, - # cpu, - # bazel_config, - # label - # ) - command = 'bazel {} build {} {}'.format( - bazelrc_path, - bazel_config, - label - ) + def __init__(self, conanfile): + self._conanfile = conanfile + def build(self, target=None, cli_args=None): + """ + Runs "bazel build " + """ + # Use BazelToolchain generated file if exists + conan_bazelrc = os.path.join(self._conanfile.generators_folder, BazelToolchain.bazelrc_name) + use_conan_config = os.path.exists(conan_bazelrc) + bazelrc_paths = [] + bazelrc_configs = [] + if use_conan_config: + bazelrc_paths.append(conan_bazelrc) + bazelrc_configs.append(BazelToolchain.bazelrc_config) + # User bazelrc paths have more prio than Conan one + # See more info in https://bazel.build/run/bazelrc + bazelrc_paths.extend(self._conanfile.conf.get("tools.google.bazel:bazelrc_path", + default=[], check_type=list)) + command = "bazel" + for rc in bazelrc_paths: + command += f" --bazelrc='{rc}'" + command += " build" + bazelrc_configs.extend(self._conanfile.conf.get("tools.google.bazel:configs", + default=[], check_type=list)) + for config in bazelrc_configs: + command += f" --config={config}" + if cli_args: + command += " ".join(f" {arg}" for arg in cli_args) + command += f" {target}" self._conanfile.run(command) + # This is very important for Windows, as otherwise the bazel server locks files + if platform.system() == "Windows": + self._conanfile.run("bazel shutdown") + + def install(self): + """ + Bazel does not have any install process as it installs everything in its own cache + """ + pass - def _get_bazel_project_configuration(self): - toolchain_file_content = load_toolchain_args(self._conanfile.generators_folder, - namespace=self._namespace) - configs = toolchain_file_content.get("bazel_configs") - self._bazel_config = configs.split(",") if configs else [] - self._bazelrc_path = toolchain_file_content.get("bazelrc_path") + def test(self, target=None): + """ + Runs "bazel test " + """ + if self._conanfile.conf.get("tools.build:skip_test", check_type=bool) or target is None: + return + self._conanfile.run(f'bazel test {target}') From 485510f8c61d0d6ab4b7c336f70c4c7582cc1ac6 Mon Sep 17 00:00:00 2001 From: Francisco Ramirez de Anton Date: Wed, 18 Oct 2023 11:02:20 +0200 Subject: [PATCH 05/24] Reverted BazelDeps --- conan/tools/google/bazeldeps.py | 757 +++++++++++--------------------- 1 file changed, 247 insertions(+), 510 deletions(-) diff --git a/conan/tools/google/bazeldeps.py b/conan/tools/google/bazeldeps.py index c16cb7a318b..7c6527f033d 100644 --- a/conan/tools/google/bazeldeps.py +++ b/conan/tools/google/bazeldeps.py @@ -1,541 +1,278 @@ import os +import platform import textwrap -from collections import namedtuple -from jinja2 import Template, StrictUndefined +from jinja2 import Template -from conan.errors import ConanException -from conan.internal import check_duplicated_generator -from conans.model.dependencies import get_transitive_requires +from conan.tools._check_build_profile import check_using_build_profile +from conans.errors import ConanException from conans.util.files import save -_DepInfo = namedtuple("DepInfo", ['name', 'requires', 'cpp_info']) -_LibInfo = namedtuple("LibInfo", ['name', 'is_shared', 'lib_path', 'interface_path']) - - -def _get_name_with_namespace(namespace, name): - """ - Build a name with a namespace, e.g., openssl-crypto - """ - return f"{namespace}-{name}" - - -def _get_package_reference_name(dep): - """ - Get the reference name for the given package - """ - return dep.ref.name - - -def _get_package_name(dep): - pkg_name = dep.cpp_info.get_property("bazel_module_name") or _get_package_reference_name(dep) - return pkg_name - - -def _get_component_name(dep, comp_ref_name): - pkg_name = _get_package_name(dep) - if comp_ref_name not in dep.cpp_info.components: - # foo::foo might be referencing the root cppinfo - if _get_package_reference_name(dep) == comp_ref_name: - return pkg_name - raise ConanException("Component '{name}::{cname}' not found in '{name}' " - "package requirement".format(name=_get_package_reference_name(dep), - cname=comp_ref_name)) - comp_name = dep.cpp_info.components[comp_ref_name].get_property("bazel_module_name") - # If user did not set bazel_module_name, let's create a component name - # with a namespace, e.g., dep-comp1 - return comp_name or _get_name_with_namespace(pkg_name, comp_ref_name) - - -# FIXME: This function should be a common one to be used by PkgConfigDeps, CMakeDeps?, etc. -def get_requirements(conanfile, build_context_activated): - """ - Simply save the activated requirements (host + build + test), and the deactivated ones - """ - # All the requirements - host_req = conanfile.dependencies.host - build_req = conanfile.dependencies.build # tool_requires - test_req = conanfile.dependencies.test - - for require, dep in list(host_req.items()) + list(build_req.items()) + list(test_req.items()): - # Require is not used at the moment, but its information could be used, - # and will be used in Conan 2.0 - # Filter the build_requires not activated with self.build_context_activated - if require.build and dep.ref.name not in build_context_activated: - continue - yield require, dep - - -def _get_libs(conanfile, cpp_info, is_shared=False, relative_to_path=None) -> list: - """ - Get the static/shared library paths - - :param conanfile: The current recipe object. - :param dep: of the dependency. - :param cpp_info: of the component. - :param relative_to_path: base path to prune from each library path - :return: dict of static/shared library absolute paths -> - {lib_name: (IS_SHARED, LIB_PATH, DLL_PATH)} - Note: LIB_PATH could be both static and shared ones in case of UNIX. If Windows it'll be - the interface library xxx.lib linked to the DLL_PATH one. - """ - cpp_info = cpp_info - libdirs = cpp_info.libdirs - bindirs = cpp_info.bindirs - libs = cpp_info.libs[:] # copying the values - ret = {} - interfaces = {} - - for libdir in (libdirs + bindirs): - lib = "" - if not os.path.exists(libdir): - conanfile.output.debug("The library folder doesn't exist: {}".format(libdir)) - continue - files = os.listdir(libdir) - lib_path = None - for f in files: - full_path = os.path.join(libdir, f) - if not os.path.isfile(full_path): # Make sure that directories are excluded - continue - # Users may not name their libraries in a conventional way. For example, directly - # use the basename of the lib file as lib name. - if f in libs: - lib = f - libs.remove(f) - lib_path = full_path - break - name, ext = os.path.splitext(f) - if name not in libs and name.startswith("lib"): - name = name[3:] - if name in libs: - # FIXME: Should it read a conf variable to know unexpected extensions? - if (is_shared and ext in (".so", ".dylib", ".lib", ".dll")) or \ - (not is_shared and ext in (".a", ".lib")): - lib = name - libs.remove(name) - lib_path = full_path - break - if lib_path is not None: - _, ext = os.path.splitext(lib_path) - if is_shared and ext == ".lib": # Windows interface library - interfaces[lib] = _relativize_path(lib_path, relative_to_path=relative_to_path) - else: - ret[lib] = _relativize_path(lib_path, relative_to_path=relative_to_path) - - libraries = [] - for lib, lib_path in ret.items(): - interface_library = None - if lib_path.endswith(".dll"): - if lib not in interfaces: - raise ConanException(f"Windows needs a .lib for link-time and .dll for runtime." - f"Only found {lib_path}") - interface_library = interfaces.pop(lib) - libraries.append(_LibInfo(lib, is_shared, lib_path, interface_library)) - # TODO: Would we want to manage the cases where DLLs are provided by the system? - conanfile.output.warning(f"The library/ies {libs} cannot be found in the dependency") - return libraries - - -def _get_headers(cpp_info, package_folder_path): - return ', '.join('"{}/**"'.format(_relativize_path(path, package_folder_path)) - for path in cpp_info.includedirs) - - -def _get_includes(cpp_info, package_folder_path): - return ', '.join('"{}"'.format(_relativize_path(path, package_folder_path)) - for path in cpp_info.includedirs) - - -def _get_defines(cpp_info): - return ', '.join('"{}"'.format(define.replace('"', '\\' * 3 + '"')) - for define in cpp_info.defines) - - -def _get_linkopts(cpp_info, os_build): - link_opt = '"/DEFAULTLIB:{}"' if os_build == "Windows" else '"-l{}"' - system_libs = [link_opt.format(lib) for lib in cpp_info.system_libs] - shared_flags = cpp_info.sharedlinkflags + cpp_info.exelinkflags - return ", ".join(system_libs + shared_flags) - - -def _get_copts(cpp_info): - # FIXME: long discussions between copts (-Iflag) vs includes in Bazel. Not sure yet - # includedirsflags = ['"-I{}"'.format(_relativize_path(d, package_folder_path)) - # for d in cpp_info.includedirs] - cxxflags = [var.replace('"', '\\"') for var in cpp_info.cxxflags] - cflags = [var.replace('"', '\\"') for var in cpp_info.cflags] - return ", ".join(cxxflags + cflags) - - -# FIXME: Very fragile. Need UTs, and, maybe, move it to conan.tools.files -def _relativize_path(path, relative_to_path): - """ - Relativize the path with regard to a given base path - - :param path: path to relativize - :param relative_to_path: base path to prune from the path - :return: Unix-like path relative to ``relative_to_path``. Otherwise, it returns - the Unix-like ``path``. - """ - if not path or not relative_to_path: - return path - p = path.replace("\\", "/") - rel = relative_to_path.replace("\\", "/") - if p.startswith(rel): - return p[len(rel):].lstrip("/") - elif rel in p: - return p.split(rel)[-1].lstrip("/") - else: - return p.lstrip("/") - - -class _BazelDependenciesBZLGenerator: - """ - Bazel needs to know all the dependencies for its current project. So, the only way - to do that is to tell the WORKSPACE file how to load all the Conan ones. This is the goal - of the function created by this class, the ``load_conan_dependencies`` one. - - More information: - * https://bazel.build/reference/be/workspace#new_local_repository - """ - - filename = "dependencies.bzl" - template = textwrap.dedent("""\ - # This Bazel module should be loaded by your WORKSPACE file. - # Add these lines to your WORKSPACE one (assuming that you're using the "bazel_layout"): - # load("@//bazel-conan-tools:dependencies.bzl", "load_conan_dependencies") - # load_conan_dependencies() - - {% macro new_local_repository(pkg_name, pkg_folder, pkg_build_file_path) %} - native.new_local_repository( - name="{{pkg_name}}", - path="{{pkg_folder}}", - build_file="{{pkg_build_file_path}}", - ) - {% endmacro %} - def load_conan_dependencies(): - {% for pkg_name, pkg_folder, pkg_build_file_path in dependencies %} - {{ new_local_repository(pkg_name, pkg_folder, pkg_build_file_path) }} - {% endfor %} - """) - - def __init__(self, conanfile, dependencies): +class BazelDeps(object): + def __init__(self, conanfile): self._conanfile = conanfile - self._dependencies = dependencies + check_using_build_profile(self._conanfile) def generate(self): - template = Template(self.template, trim_blocks=True, lstrip_blocks=True, - undefined=StrictUndefined) - content = template.render(dependencies=self._dependencies) - # Saving the BUILD (empty) and dependencies.bzl files - save(self.filename, content) - save("BUILD.bazel", "# This is an empty BUILD file to be able to load the " - "dependencies.bzl one.") - - -class _BazelBUILDGenerator: - """ - This class creates the BUILD.bazel for each dependency where it's declared all the - necessary information to load the libraries - """ - - # If both files exist, BUILD.bazel takes precedence over BUILD - # https://bazel.build/concepts/build-files - filename = "BUILD.bazel" - template = textwrap.dedent("""\ - {% macro cc_import_macro(libs) %} - {% for lib_info in libs %} - cc_import( - name = "{{ lib_info.name }}_precompiled", - {% if lib_info.is_shared %} - shared_library = "{{ lib_info.lib_path }}", - {% else %} - static_library = "{{ lib_info.lib_path }}", - {% endif %} - {% if lib_info.interface_path %} - interface_library = "{{ lib_info.interface_path }}", - {% endif %} - ) - {% endfor %} - {% endmacro %} - {% macro cc_library_macro(obj) %} - cc_library( - name = "{{ obj["name"] }}", - {% if obj["headers"] %} - hdrs = glob([ - {{ obj["headers"] }} - ]), - {% endif %} - {% if obj["includes"] %} - includes = [ - {{ obj["includes"] }} - ], - {% endif %} - {% if obj["defines"] %} - defines = [ - {{ obj["defines"] }} - ], - {% endif %} - {% if obj["linkopts"] %} - linkopts = [ - {{ obj["linkopts"] }} - ], - {% endif %} - {% if obj["copts"] %} - copts = [ - {{ obj["copts"] }} - ], - {% endif %} - visibility = ["//visibility:public"], - {% if obj["libs"] or obj["dependencies"] %} - deps = [ - {% for lib in obj["libs"] %} - ":{{ lib.name }}_precompiled", - {% endfor %} - {% for name in obj["component_names"] %} - ":{{ name }}", - {% endfor %} - {% for dep in obj["dependencies"] %} - "{{ dep }}", - {% endfor %} - ], - {% endif %} - ) - {% endmacro %} - load("@rules_cc//cc:defs.bzl", "cc_import", "cc_library") - - # Components precompiled libs - {% for component in components %} - {{ cc_import_macro(component["libs"]) }} - {% endfor %} - # Root package precompiled libs - {{ cc_import_macro(root["libs"]) }} - # Components libraries declaration - {% for component in components %} - {{ cc_library_macro(component) }} - {% endfor %} - # Package library declaration - {{ cc_library_macro(root) }} - """) - - def __init__(self, conanfile, dep, root_package_info, components_info): - self._conanfile = conanfile - self._dep = dep - self._root_package_info = root_package_info - self._components_info = components_info + local_repositories = [] + generators_folder = self._conanfile.generators_folder - @property - def _is_shared_dependency(self): - """ - Checking traits and shared option - """ - default_value = self._dep.options.get_safe("shared") if self._dep.options else False - return {"shared-library": True, - "static-library": False}.get(str(self._dep.package_type), default_value) + for build_dependency in self._conanfile.dependencies.direct_build.values(): + content = self._get_build_dependency_buildfile_content(build_dependency) + filename = self._save_dependency_buildfile(build_dependency, content, + generators_folder) - @property - def build_file_pah(self): - """ - Returns the absolute path to the BUILD file created by Conan - """ - return os.path.join(self._root_package_info.name, self.filename) + local_repository = self._create_new_local_repository(build_dependency, filename) + local_repositories.append(local_repository) - @property - def absolute_build_file_pah(self): - """ - Returns the absolute path to the BUILD file created by Conan - """ - return os.path.join(self._conanfile.generators_folder, self.build_file_pah) + for dependency in self._conanfile.dependencies.host.values(): + content = self._get_dependency_buildfile_content(dependency) + if not content: + continue + filename = self._save_dependency_buildfile(dependency, content, generators_folder) - @property - def package_folder(self): - """ - Returns the package folder path - """ - # If editable, package_folder can be None - root_folder = self._dep.recipe_folder if self._dep.package_folder is None \ - else self._dep.package_folder - return root_folder.replace("\\", "/") + local_repository = self._create_new_local_repository(dependency, filename) + local_repositories.append(local_repository) - @property - def package_name(self): - """ - Wrapper to get the final name used for the root dependency cc_library declaration - """ - return self._root_package_info.name - - def _get_context(self): - def fill_info(info): - ret = { - "name": info.name, # package name and components name - "libs": {}, - "headers": "", - "includes": "", - "defines": "", - "linkopts": "", - "copts": "", - "dependencies": info.requires, - "component_names": [] # filled only by the root - } - if info.cpp_info is not None: - cpp_info = info.cpp_info - headers = _get_headers(cpp_info, package_folder_path) - includes = _get_includes(cpp_info, package_folder_path) - copts = _get_copts(cpp_info) - defines = _get_defines(cpp_info) - linkopts = _get_linkopts(cpp_info, self._dep.settings_build.get_safe("os")) - libs = _get_libs(self._conanfile, cpp_info, is_shared=is_shared, - relative_to_path=package_folder_path) - ret.update({ - "libs": libs, - "headers": headers, - "includes": includes, - "defines": defines, - "linkopts": linkopts, - "copts": copts - }) - return ret - - is_shared = self._is_shared_dependency - package_folder_path = self.package_folder - context = dict() - context["root"] = fill_info(self._root_package_info) - context["components"] = [] - for component in self._components_info: - component_context = fill_info(component) - context["components"].append(component_context) - context["root"]["component_names"].append(component_context["name"]) - return context + content = self._get_main_buildfile_content(local_repositories) + self._save_main_buildfiles(content, self._conanfile.generators_folder) - def generate(self): - context = self._get_context() - template = Template(self.template, trim_blocks=True, lstrip_blocks=True, - undefined=StrictUndefined) - content = template.render(context) - save(self.build_file_pah, content) + def _save_dependency_buildfile(self, dependency, buildfile_content, conandeps): + filename = '{}/{}/BUILD'.format(conandeps, dependency.ref.name) + save(filename, buildfile_content) + return filename + def _get_build_dependency_buildfile_content(self, dependency): + filegroup = textwrap.dedent(""" + filegroup( + name = "{}_binaries", + data = glob(["**"]), + visibility = ["//visibility:public"], + ) -class _InfoGenerator: + """).format(dependency.ref.name) - def __init__(self, conanfile, dep, build_context_activated=None): - self._conanfile = conanfile - self._dep = dep - self._transitive_reqs = get_transitive_requires(self._conanfile, dep) - self._build_context_activated = build_context_activated or [] + return filegroup - def _get_cpp_info_requires_names(self, cpp_info): - """ - Get all the valid names from the requirements ones given a CppInfo object. + def _get_dependency_buildfile_content(self, dependency): + template = textwrap.dedent("""\ + load("@rules_cc//cc:defs.bzl", "cc_import", "cc_library") - For instance, those requirements could be coming from: + {%- for libname, filepath in libs.items() %} + cc_import( + name = "{{ libname }}_precompiled", + {{ library_type }} = "{{ filepath }}", + ) + {%- endfor %} - ```python - from conan import ConanFile - class PkgConan(ConanFile): - requires = "other/1.0" + {%- for libname, (lib_path, dll_path) in shared_with_interface_libs.items() %} + cc_import( + name = "{{ libname }}_precompiled", + interface_library = "{{ lib_path }}", + shared_library = "{{ dll_path }}", + ) + {%- endfor %} + + cc_library( + name = "{{ name }}", + {%- if headers %} + hdrs = glob([{{ headers }}]), + {%- endif %} + {%- if includes %} + includes = [{{ includes }}], + {%- endif %} + {%- if defines %} + defines = [{{ defines }}], + {% endif %} + {%- if linkopts %} + linkopts = [{{ linkopts }}], + {%- endif %} + visibility = ["//visibility:public"], + {%- if libs or shared_with_interface_libs %} + deps = [ + # do not sort + {%- for lib in libs %} + ":{{ lib }}_precompiled", + {%- endfor %} + {%- for lib in shared_with_interface_libs %} + ":{{ lib }}_precompiled", + {%- endfor %} + {%- for dep in dependencies %} + "@{{ dep }}", + {%- endfor %} + ], + {%- endif %} + ) - def package_info(self): - self.cpp_info.requires = ["other::cmp1"] + """) - # Or: + cpp_info = dependency.cpp_info.aggregated_components() - def package_info(self): - self.cpp_info.components["cmp"].requires = ["other::cmp1"] - ``` - """ - dep_ref_name = _get_package_reference_name(self._dep) - ret = [] - for req in cpp_info.requires: - pkg_ref_name, comp_ref_name = req.split("::") if "::" in req else (dep_ref_name, req) - prefix = ":" # Requirements declared in the same BUILD file - # For instance, dep == "hello/1.0" and req == "other::cmp1" -> hello != other - if dep_ref_name != pkg_ref_name: - try: - req_conanfile = self._transitive_reqs[pkg_ref_name] - # Requirements declared in another dependency BUILD file - prefix = f"@{_get_package_name(req_conanfile)}//:" - except KeyError: - continue # If the dependency is not in the transitive, might be skipped - else: # For instance, dep == "hello/1.0" and req == "hello::cmp1" -> hello == hello - req_conanfile = self._dep - comp_name = _get_component_name(req_conanfile, comp_ref_name) - ret.append(f"{prefix}{comp_name}") - return ret - - @property - def components_info(self): - """ - Get the whole package and its components information like their own requires, names and even - the cpp_info for each component. + if not cpp_info.libs and not cpp_info.includedirs: + return None - :return: `list` of `_PCInfo` objects with all the components information - """ - if not self._dep.cpp_info.has_components: - return [] - components_info = [] - # Loop through all the package's components - for comp_ref_name, cpp_info in self._dep.cpp_info.get_sorted_components().items(): - # At first, let's check if we have defined some components requires, e.g., "dep::cmp1" - comp_requires_names = self._get_cpp_info_requires_names(cpp_info) - comp_name = _get_component_name(self._dep, comp_ref_name) - # Save each component information - components_info.append(_DepInfo(comp_name, comp_requires_names, cpp_info)) - return components_info - - @property - def root_package_info(self): - """ - Get the whole package information + headers = [] + includes = [] - :return: `_PCInfo` object with the package information - """ - pkg_name = _get_package_name(self._dep) - # At first, let's check if we have defined some global requires, e.g., "other::cmp1" - requires = self._get_cpp_info_requires_names(self._dep.cpp_info) - # If we have found some component requires it would be enough - if not requires: - # If no requires were found, let's try to get all the direct dependencies, - # e.g., requires = "other_pkg/1.0" - for req in self._transitive_reqs.values(): - req_name = _get_package_name(req) - requires.append(f"@{req_name}//:{req_name}") - cpp_info = self._dep.cpp_info - return _DepInfo(pkg_name, requires, cpp_info) - - -class BazelDeps: + def _relativize_path(p, base_path): + # TODO: Very fragile, to test more + if p.startswith(base_path): + return p[len(base_path):].replace("\\", "/").lstrip("/") + return p.replace("\\", "/").lstrip("/") - def __init__(self, conanfile): - self._conanfile = conanfile - # Activate the build context for the specified libraries - self.build_context_activated = [] + # TODO: This only works for package_folder, but not editable + package_folder = dependency.package_folder + for path in cpp_info.includedirs: + headers.append('"{}/**"'.format(_relativize_path(path, package_folder))) + includes.append('"{}"'.format(_relativize_path(path, package_folder))) - def generate(self): - """ - Save all the targets BUILD files and the dependencies.bzl one. + headers = ', '.join(headers) + includes = ', '.join(includes) + + defines = ('"{}"'.format(define.replace('"', '\\' * 3 + '"')) + for define in cpp_info.defines) + defines = ', '.join(defines) - Important! The dependencies.bzl file should be loaded by the WORKSPACE Bazel file. + linkopts = [] + for system_lib in cpp_info.system_libs: + # FIXME: Replace with settings_build + if platform.system() == "Windows": + linkopts.append('"/DEFAULTLIB:{}"'.format(system_lib)) + else: + linkopts.append('"-l{}"'.format(system_lib)) + + linkopts = ', '.join(linkopts) + lib_dir = 'lib' + + dependencies = [] + for dep in dependency.dependencies.direct_host.values(): + dependencies.append(dep.ref.name) + + shared_library = dependency.options.get_safe("shared") if dependency.options else False + + libs = {} + shared_with_interface_libs = {} + for lib in cpp_info.libs: + lib_path, dll_path = self._get_lib_file_paths(shared_library, + cpp_info.libdirs, + cpp_info.bindirs, + lib) + if lib_path and dll_path: + shared_with_interface_libs[lib] = ( + _relativize_path(lib_path, package_folder), + _relativize_path(dll_path, package_folder)) + elif lib_path: + libs[lib] = _relativize_path(lib_path, package_folder) + + context = { + "name": dependency.ref.name, + "libs": libs, + "shared_with_interface_libs": shared_with_interface_libs, + "libdir": lib_dir, + "headers": headers, + "includes": includes, + "defines": defines, + "linkopts": linkopts, + "library_type": "shared_library" if shared_library else "static_library", + "dependencies": dependencies, + } + content = Template(template).render(**context) + return content + + def _get_dll_file_paths(self, bindirs, expected_file): + """Find a given dll file in bin directories. If found return the full + path, otherwise return None. """ - check_duplicated_generator(self, self._conanfile) - requirements = get_requirements(self._conanfile, self.build_context_activated) - deps_info = [] - for require, dep in requirements: - # Bazel info generator - info_generator = _InfoGenerator(self._conanfile, dep) - root_package_info = info_generator.root_package_info - components_info = info_generator.components_info - # Generating single BUILD files per dependency - bazel_generator = _BazelBUILDGenerator(self._conanfile, dep, - root_package_info, components_info) - bazel_generator.generate() - # Saving pieces of information from each BUILD file - deps_info.append(( - bazel_generator.package_name, # package name - bazel_generator.package_folder, # path to dependency folder - bazel_generator.absolute_build_file_pah # path to the BUILD file created - )) - # dependencies.bzl has all the information about where to look for the dependencies - bazel_dependencies_module_generator = _BazelDependenciesBZLGenerator(self._conanfile, - deps_info) - bazel_dependencies_module_generator.generate() + for each_bin in bindirs: + if not os.path.exists(each_bin): + self._conanfile.output.warning("The bin folder doesn't exist: {}".format(each_bin)) + continue + files = os.listdir(each_bin) + for f in files: + full_path = os.path.join(each_bin, f) + if not os.path.isfile(full_path): + continue + if f == expected_file: + return full_path + return None + + def _get_lib_file_paths(self, shared, libdirs, bindirs, lib): + for libdir in libdirs: + if not os.path.exists(libdir): + self._conanfile.output.warning("The library folder doesn't exist: {}".format(libdir)) + continue + files = os.listdir(libdir) + lib_basename = None + lib_path = None + for f in files: + full_path = os.path.join(libdir, f) + if not os.path.isfile(full_path): # Make sure that directories are excluded + continue + # Users may not name their libraries in a conventional way. For example, directly + # use the basename of the lib file as lib name. + if f == lib: + lib_basename = f + lib_path = full_path + break + name, ext = os.path.splitext(f) + if ext in (".so", ".lib", ".a", ".dylib", ".bc"): + if ext != ".lib" and name.startswith("lib"): + name = name[3:] + if lib == name: + lib_basename = f + lib_path = full_path + break + if lib_path is not None: + dll_path = None + name, ext = os.path.splitext(lib_basename) + if shared and ext == ".lib": + dll_path = self._get_dll_file_paths(bindirs, name+".dll") + return lib_path, dll_path + self._conanfile.output.warning("The library {} cannot be found in the " + "dependency".format(lib)) + return None, None + + def _create_new_local_repository(self, dependency, dependency_buildfile_name): + if dependency.package_folder is None: + # The local repository path should be the base of every declared cc_library, + # this is potentially incompatible with editables where there is no package_folder + # and the build_folder and the source_folder might be different, so there is no common + # base. + raise ConanException("BazelDeps doesn't support editable packages") + snippet = textwrap.dedent(""" + native.new_local_repository( + name="{}", + path="{}", + build_file="{}", + ) + """).format( + dependency.ref.name, + # FIXME: This shouldn't use package_folder, at editables it doesn't exists + dependency.package_folder.replace("\\", "/"), + dependency_buildfile_name.replace("\\", "/") + ) + + return snippet + + def _get_main_buildfile_content(self, local_repositories): + template = textwrap.dedent(""" + def load_conan_dependencies(): + {} + """) + + if local_repositories: + function_content = "\n".join(local_repositories) + function_content = ' '.join(line for line in function_content.splitlines(True)) + else: + function_content = ' pass' + + content = template.format(function_content) + + return content + + def _save_main_buildfiles(self, content, generators_folder): + # A BUILD file must exist, even if it's empty, in order for Bazel + # to detect it as a Bazel package and to allow to load the .bzl files + save("{}/BUILD".format(generators_folder), "") + save("{}/dependencies.bzl".format(generators_folder), content) From 3fe380a8018a9de1dd74fd0459a039ab813a5f56 Mon Sep 17 00:00:00 2001 From: Francisco Ramirez de Anton Date: Wed, 18 Oct 2023 12:54:32 +0200 Subject: [PATCH 06/24] Applying suggestions --- conan/tools/google/bazel.py | 6 ------ conan/tools/google/layout.py | 2 +- conans/assets/templates/new_v2_bazel.py | 11 ++++------- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/conan/tools/google/bazel.py b/conan/tools/google/bazel.py index 9b94af9c00a..9337dd15e1e 100644 --- a/conan/tools/google/bazel.py +++ b/conan/tools/google/bazel.py @@ -41,12 +41,6 @@ def build(self, target=None, cli_args=None): if platform.system() == "Windows": self._conanfile.run("bazel shutdown") - def install(self): - """ - Bazel does not have any install process as it installs everything in its own cache - """ - pass - def test(self, target=None): """ Runs "bazel test " diff --git a/conan/tools/google/layout.py b/conan/tools/google/layout.py index 561e629d0ea..00b216045be 100644 --- a/conan/tools/google/layout.py +++ b/conan/tools/google/layout.py @@ -7,7 +7,7 @@ def bazel_layout(conanfile, src_folder="."): subproject = conanfile.folders.subproject conanfile.folders.source = src_folder if not subproject else os.path.join(subproject, src_folder) conanfile.folders.build = "." # Bazel always build the whole project in the root folder - conanfile.folders.generators = "bazel-conan-tools" # one + conanfile.folders.generators = "conan" # one # FIXME: used in test package for example, to know where the binaries are (editables not supported yet)? conanfile.cpp.build.bindirs = [os.path.join(conanfile.folders.build, "bazel-bin")] conanfile.cpp.build.libdirs = [os.path.join(conanfile.folders.build, "bazel-bin")] diff --git a/conans/assets/templates/new_v2_bazel.py b/conans/assets/templates/new_v2_bazel.py index 9a3c836659d..92c0c9868a1 100644 --- a/conans/assets/templates/new_v2_bazel.py +++ b/conans/assets/templates/new_v2_bazel.py @@ -28,8 +28,7 @@ def layout(self): def build(self): bazel = Bazel(self) - bazel.configure() - bazel.build(label="//main:{name}") + bazel.build(target="//main:{name}") def package(self): dest_lib = os.path.join(self.package_folder, "lib") @@ -66,8 +65,7 @@ def requirements(self): def build(self): bazel = Bazel(self) - bazel.configure() - bazel.build(label="//main:example") + bazel.build(target="//main:example") def layout(self): bazel_layout(self) @@ -104,7 +102,7 @@ def test(self): _bazel_workspace = "" _test_bazel_workspace = """ -load("@//:dependencies.bzl", "load_conan_dependencies") +load("@//conan:dependencies.bzl", "load_conan_dependencies") load_conan_dependencies() """ @@ -133,8 +131,7 @@ def layout(self): def build(self): bazel = Bazel(self) - bazel.configure() - bazel.build(label="//main:{name}") + bazel.build(target="//main:{name}") def package(self): dest_bin = os.path.join(self.package_folder, "bin") From 53688ed32895cdef74e1c9c3d760bf796c4f9b13 Mon Sep 17 00:00:00 2001 From: Francisco Ramirez de Anton Date: Wed, 18 Oct 2023 12:58:02 +0200 Subject: [PATCH 07/24] Applying suggestions --- conan/tools/google/toolchain.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/conan/tools/google/toolchain.py b/conan/tools/google/toolchain.py index cbb924d7050..ec5176ab4cf 100644 --- a/conan/tools/google/toolchain.py +++ b/conan/tools/google/toolchain.py @@ -83,7 +83,9 @@ def __init__(self, conanfile): self.cxxopt = [] self.linkopt = [] self.strip = 'sometimes' # 'always', 'sometimes', 'never' - self.compilation_mode = "fastbuild" # 'fastbuild', 'dbg', 'opt' + self.compilation_mode = {'Release': 'opt', 'Debug': 'dbg'}.get( + self._conanfile.settings.get_safe("build_type"), "fastbuild" # Bazel default + ) # Be aware that this parameter does not admit a compiler absolute path # If you want to add it, you will have to use a specific Bazel toolchain self.compiler = None From 2864c59a67e6245f4a32e8467b2653b504ce37af Mon Sep 17 00:00:00 2001 From: Francisco Ramirez de Anton Date: Wed, 18 Oct 2023 15:06:23 +0200 Subject: [PATCH 08/24] removed Conan 2 imports --- conan/tools/google/toolchain.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/conan/tools/google/toolchain.py b/conan/tools/google/toolchain.py index ec5176ab4cf..b69925d95c5 100644 --- a/conan/tools/google/toolchain.py +++ b/conan/tools/google/toolchain.py @@ -2,10 +2,9 @@ from jinja2 import Template -from conan.internal import check_duplicated_generator +from conan.tools._compilers import cppstd_flag from conan.tools.apple import to_apple_arch, is_apple_os from conan.tools.build.cross_building import cross_building -from conan.tools.build.flags import cppstd_flag from conan.tools.files import save @@ -152,5 +151,5 @@ def _content(self): return content def generate(self): - check_duplicated_generator(self, self._conanfile) + # check_duplicated_generator(self, self._conanfile) # uncomment for Conan 2.x save(self._conanfile, BazelToolchain.bazelrc_name, self._content) From 1c4e0990bf538d6e844755f23347e59c0eb7f73a Mon Sep 17 00:00:00 2001 From: Francisco Ramirez de Anton Date: Thu, 19 Oct 2023 12:37:47 +0200 Subject: [PATCH 09/24] Testing BazelToolchain and bazel_layout --- .../toolchains/google/test_bazel.py | 18 ++- .../test_bazeltoolchain_cross_compilation.py | 45 +++++++ .../toolchains/test_bazel_toolchain.py | 42 ------- .../{test_bazel.py => test_bazeldeps.py} | 0 .../toolchains/google/test_bazeltoolchain.py | 110 ++++++++++++++++++ .../toolchains/test_toolchain_namespaces.py | 95 --------------- .../test/unittests/tools/google/test_bazel.py | 41 ++----- 7 files changed, 174 insertions(+), 177 deletions(-) create mode 100644 conans/test/functional/toolchains/google/test_bazeltoolchain_cross_compilation.py delete mode 100644 conans/test/functional/toolchains/test_bazel_toolchain.py rename conans/test/integration/toolchains/google/{test_bazel.py => test_bazeldeps.py} (100%) create mode 100644 conans/test/integration/toolchains/google/test_bazeltoolchain.py diff --git a/conans/test/functional/toolchains/google/test_bazel.py b/conans/test/functional/toolchains/google/test_bazel.py index 5967c8b04cc..0d1dab86494 100644 --- a/conans/test/functional/toolchains/google/test_bazel.py +++ b/conans/test/functional/toolchains/google/test_bazel.py @@ -26,7 +26,7 @@ def base_profile(): build_type={build_type} [conf] - tools.google.bazel:bazelrc_path={curdir}/mybazelrc + tools.google.bazel:bazelrc_path=["{curdir}/mybazelrc"] tools.google.bazel:configs=["{build_type}", "withTimeStamps"] """) @@ -34,7 +34,7 @@ def base_profile(): @pytest.fixture(scope="module") def client_exe(bazelrc): client = TestClient(path_with_spaces=False) - client.run("new myapp/1.0 --template bazel_exe") + client.run("new bazel_exe -d name=myapp -d version=1.0") # The build: define several configurations that can be activated by passing # the bazel config with tools.google.bazel:configs client.save({"mybazelrc": bazelrc}) @@ -44,7 +44,7 @@ def client_exe(bazelrc): @pytest.fixture(scope="module") def client_lib(bazelrc): client = TestClient(path_with_spaces=False) - client.run("new mylib/1.0 --template bazel_lib") + client.run("new bazel_lib -d name=mylib -d version=1.0") # The build: define several configurations that can be activated by passing # the bazel config with tools.google.bazel:configs client.save({"mybazelrc": bazelrc}) @@ -91,7 +91,7 @@ def test_transitive_consuming(): client = TestClient(path_with_spaces=False) # A regular library made with CMake with client.chdir("zlib"): - client.run("new zlib/1.2.11 --template cmake_lib") + client.run("new cmake_lib -d name=zlib -d version=1.2.11") conanfile = client.load("conanfile.py") conanfile += """ self.cpp_info.defines.append("MY_DEFINE=\\"MY_VALUE\\"") @@ -127,8 +127,7 @@ def layout(self): def build(self): bazel = Bazel(self) - bazel.configure() - bazel.build(label="//main:openssl") + bazel.build(target="//main:openssl") def package(self): dest_bin = os.path.join(self.package_folder, "bin") @@ -187,7 +186,7 @@ def package_info(self): """) bazel_workspace = textwrap.dedent(""" - load("@//:dependencies.bzl", "load_conan_dependencies") + load("@//conan:dependencies.bzl", "load_conan_dependencies") load_conan_dependencies() """) @@ -207,8 +206,6 @@ def package_info(self): class OpenSSLTestConan(ConanFile): settings = "os", "compiler", "build_type", "arch" - # VirtualBuildEnv and VirtualRunEnv can be avoided if "tools.env.virtualenv:auto_use" is defined - # (it will be defined in Conan 2.0) generators = "BazelToolchain", "BazelDeps", "VirtualBuildEnv", "VirtualRunEnv" apply_env = False @@ -217,8 +214,7 @@ def requirements(self): def build(self): bazel = Bazel(self) - bazel.configure() - bazel.build(label="//main:example") + bazel.build(target="//main:example") def layout(self): bazel_layout(self) diff --git a/conans/test/functional/toolchains/google/test_bazeltoolchain_cross_compilation.py b/conans/test/functional/toolchains/google/test_bazeltoolchain_cross_compilation.py new file mode 100644 index 00000000000..0bf08952108 --- /dev/null +++ b/conans/test/functional/toolchains/google/test_bazeltoolchain_cross_compilation.py @@ -0,0 +1,45 @@ +import os +import platform +import textwrap + +import pytest + +from conans.test.utils.tools import TestClient + + +@pytest.mark.skipif(platform.system() != "Darwin", reason="Only for Darwin") +@pytest.mark.tool("bazel") +def test_bazel_simple_cross_compilation(): + profile = textwrap.dedent(""" + [settings] + arch=x86_64 + build_type=Release + compiler=apple-clang + compiler.cppstd=gnu17 + compiler.libcxx=libc++ + compiler.version=13.0 + os=Macos + """) + profile_host = textwrap.dedent(""" + [settings] + arch=armv8 + build_type=Release + compiler=apple-clang + compiler.cppstd=gnu17 + compiler.libcxx=libc++ + compiler.version=13.0 + os=Macos + """) + client = TestClient(path_with_spaces=False) + # client.run("new bazel_lib -d name=myapp -d version=1.0") + client.run("new myapp/1.0 -m bazel_lib") + client.save({ + "profile": profile, + "profile_host": profile_host + }) + # client.run("build . -pr:h profile_host -pr:b profile") + client.run("install . -pr:h profile_host -pr:b profile") + client.run("build .") + libmyapp = os.path.join(client.current_folder, "bazel-bin", "main", "libmyapp.a") + client.run_command(f'otool -hv {libmyapp}') + assert "ARM64" in client.out diff --git a/conans/test/functional/toolchains/test_bazel_toolchain.py b/conans/test/functional/toolchains/test_bazel_toolchain.py deleted file mode 100644 index 7adcc3bc697..00000000000 --- a/conans/test/functional/toolchains/test_bazel_toolchain.py +++ /dev/null @@ -1,42 +0,0 @@ -import textwrap - -from conan.tools.files.files import load_toolchain_args -from conans.test.assets.genconanfile import GenConanfile -from conans.test.utils.tools import TestClient - - -def test_toolchain_empty_config(): - client = TestClient(path_with_spaces=False) - - conanfile = GenConanfile().with_settings("os", "compiler", "build_type", "arch").\ - with_generator("BazelToolchain") - - client.save({"conanfile.py": conanfile}) - client.run("install .") - - config = load_toolchain_args(client.current_folder) - assert not config - - -def test_toolchain_loads_config_from_profile(): - client = TestClient(path_with_spaces=False) - - profile = textwrap.dedent(""" - include(default) - [conf] - tools.google.bazel:configs=["test_config", "test_config2"] - tools.google.bazel:bazelrc_path=/path/to/bazelrc - """) - - conanfile = GenConanfile().with_settings("os", "compiler", "build_type", "arch").\ - with_generator("BazelToolchain") - - client.save({ - "conanfile.py": conanfile, - "test_profile": profile - }) - client.run("install . -pr=test_profile") - - config = load_toolchain_args(client.current_folder) - assert config['bazel_configs'] == "test_config,test_config2" - assert config['bazelrc_path'] == "/path/to/bazelrc" diff --git a/conans/test/integration/toolchains/google/test_bazel.py b/conans/test/integration/toolchains/google/test_bazeldeps.py similarity index 100% rename from conans/test/integration/toolchains/google/test_bazel.py rename to conans/test/integration/toolchains/google/test_bazeldeps.py diff --git a/conans/test/integration/toolchains/google/test_bazeltoolchain.py b/conans/test/integration/toolchains/google/test_bazeltoolchain.py new file mode 100644 index 00000000000..9da55e39ec3 --- /dev/null +++ b/conans/test/integration/toolchains/google/test_bazeltoolchain.py @@ -0,0 +1,110 @@ +import os +import textwrap + +import pytest + +from conan.tools.files import load +from conan.tools.google import BazelToolchain +from conans.test.utils.tools import TestClient + + +@pytest.fixture(scope="module") +def conanfile(): + return textwrap.dedent(""" + from conan import ConanFile + from conan.tools.google import BazelToolchain, bazel_layout + class ExampleConanIntegration(ConanFile): + settings = "os", "arch", "build_type", "compiler" + options = {"shared": [True, False], "fPIC": [True, False]} + default_options = {"shared": False, "fPIC": True} + generators = 'BazelToolchain' + def layout(self): + bazel_layout(self) + """) + + +def test_default_bazel_toolchain(conanfile): + profile = textwrap.dedent(""" + [settings] + arch=x86_64 + build_type=Release + compiler=apple-clang + compiler.cppstd=gnu17 + compiler.libcxx=libc++ + compiler.version=13.0 + os=Macos + """) + + c = TestClient() + c.save({"conanfile.py": conanfile, + "profile": profile}) + c.run("install . -pr profile") + content = load(c, os.path.join(c.current_folder, "conan", BazelToolchain.bazelrc_name)) + assert "build:conan-config --cxxopt=-std=gnu++17" in content + assert "build:conan-config --force_pic=True" in content + assert "build:conan-config --dynamic_mode=off" in content + assert "build:conan-config --strip=sometimes" in content + assert "build:conan-config --compilation_mode=opt" in content + + +def test_bazel_toolchain_and_flags(conanfile): + profile = textwrap.dedent(""" + [settings] + arch=x86_64 + build_type=Release + compiler=apple-clang + compiler.cppstd=gnu17 + compiler.libcxx=libc++ + compiler.version=13.0 + os=Macos + [options] + shared=True + [conf] + tools.build:cxxflags=["--flag1", "--flag2"] + tools.build:cflags+=["--flag3", "--flag4"] + tools.build:sharedlinkflags+=["--flag5"] + tools.build:exelinkflags+=["--flag6"] + tools.build:linker_scripts+=["myscript.sh"] + """) + c = TestClient() + c.save({"conanfile.py": conanfile, + "profile": profile}) + c.run("install . -pr profile") + content = load(c, os.path.join(c.current_folder, "conan", BazelToolchain.bazelrc_name)) + assert "build:conan-config --conlyopt=--flag3 --conlyopt=--flag4" in content + assert "build:conan-config --cxxopt=-std=gnu++17 --cxxopt=--flag1 --cxxopt=--flag2" in content + assert "build:conan-config --linkopt=--flag5 --linkopt=--flag6 --linkopt=-T'myscript.sh'" in content + assert "build:conan-config --force_pic=True" not in content + assert "build:conan-config --dynamic_mode=fully" in content + assert "build:conan-config --strip=sometimes" in content + assert "build:conan-config --compilation_mode=opt" in content + + +def test_bazel_toolchain_and_cross_compilation(conanfile): + profile = textwrap.dedent(""" + [settings] + arch=x86_64 + build_type=Release + compiler=apple-clang + compiler.cppstd=gnu17 + compiler.libcxx=libc++ + compiler.version=13.0 + os=Macos + """) + profile_host = textwrap.dedent(""" + [settings] + arch=armv8 + build_type=Release + compiler=apple-clang + compiler.cppstd=gnu17 + compiler.libcxx=libc++ + compiler.version=13.0 + os=Macos + """) + c = TestClient() + c.save({"conanfile.py": conanfile, + "profile": profile, + "profile_host": profile_host}) + c.run("install . -pr:b profile -pr:h profile_host") + content = load(c, os.path.join(c.current_folder, "conan", BazelToolchain.bazelrc_name)) + assert "build:conan-config --cpu=darwin_arm64" in content diff --git a/conans/test/integration/toolchains/test_toolchain_namespaces.py b/conans/test/integration/toolchains/test_toolchain_namespaces.py index b98f8b1aadc..cd56b101256 100644 --- a/conans/test/integration/toolchains/test_toolchain_namespaces.py +++ b/conans/test/integration/toolchains/test_toolchain_namespaces.py @@ -6,45 +6,6 @@ from conans.test.utils.tools import TestClient -def test_bazel_namespace(): - client = TestClient() - namespace = "somename" - conanfile = textwrap.dedent(""" - from conans import ConanFile - from conan.tools.google import BazelToolchain, Bazel - - class Conan(ConanFile): - settings = "os", "arch", "compiler", "build_type" - def generate(self): - bazel = BazelToolchain(self, namespace='{0}') - bazel.generate() - def build(self): - bazel = Bazel(self, namespace='{0}') - self.output.info(bazel._bazel_config) - self.output.info(bazel._bazelrc_path) - """.format(namespace)) - - profile = textwrap.dedent(""" - include(default) - [conf] - tools.google.bazel:configs=["test_config"] - tools.google.bazel:bazelrc_path=/path/to/bazelrc - """) - - client.save({"test_profile": profile}) - - client.save({"conanfile.py": conanfile}) - client.run("install . -pr test_profile") - assert os.path.isfile(os.path.join(client.current_folder, - "{}_{}".format(namespace, CONAN_TOOLCHAIN_ARGS_FILE))) - content = load_toolchain_args(generators_folder=client.current_folder, namespace=namespace) - bazel_config = content.get("bazel_configs") - bazelrc_path = content.get("bazelrc_path") - client.run("build .") - assert bazel_config in client.out - assert bazelrc_path in client.out - - def test_autotools_namespace(): client = TestClient() namespace = "somename" @@ -75,59 +36,3 @@ def build(self): client.run("build .") assert at_configure_args in client.out assert at_make_args in client.out - - -def test_multiple_toolchains_one_recipe(): - # https://github.com/conan-io/conan/issues/9376 - client = TestClient() - namespaces = ["autotools", "bazel"] - conanfile = textwrap.dedent(""" - from conans import ConanFile - from conan.tools.gnu import AutotoolsToolchain, Autotools - from conan.tools.google import BazelToolchain, Bazel - - class Conan(ConanFile): - settings = "os", "arch", "compiler", "build_type" - def generate(self): - autotools = AutotoolsToolchain(self, namespace='{0}') - autotools.configure_args = ['a', 'b'] - autotools.make_args = ['c', 'd'] - autotools.generate() - bazel = BazelToolchain(self, namespace='{1}') - bazel.generate() - - def build(self): - autotools = Autotools(self, namespace='{0}') - self.output.info(autotools._configure_args) - self.output.info(autotools._make_args) - bazel = Bazel(self, namespace='{1}') - self.output.info(bazel._bazel_config) - self.output.info(bazel._bazelrc_path) - """.format(*namespaces)) - - client.save({"conanfile.py": conanfile}) - - profile = textwrap.dedent(""" - include(default) - [conf] - tools.google.bazel:configs=["test_config"] - tools.google.bazel:bazelrc_path=/path/to/bazelrc - """) - - client.save({"test_profile": profile}) - - client.run("install . -pr test_profile") - check_args = { - "autotools": ["configure_args", "make_args"], - "bazel": ["bazel_configs", "bazelrc_path"], - } - checks = [] - for namespace in namespaces: - assert os.path.isfile(os.path.join(client.current_folder, - "{}_{}".format(namespace, CONAN_TOOLCHAIN_ARGS_FILE))) - content = load_toolchain_args(generators_folder=client.current_folder, namespace=namespace) - for arg in check_args.get(namespace): - checks.append(content.get(arg)) - client.run("build .") - for check in checks: - assert check in client.out diff --git a/conans/test/unittests/tools/google/test_bazel.py b/conans/test/unittests/tools/google/test_bazel.py index d01c932c11a..1cb89685abd 100644 --- a/conans/test/unittests/tools/google/test_bazel.py +++ b/conans/test/unittests/tools/google/test_bazel.py @@ -1,41 +1,24 @@ -import textwrap -import os -from conan.tools import CONAN_TOOLCHAIN_ARGS_SECTION, CONAN_TOOLCHAIN_ARGS_FILE -from conans.util.files import save, remove - from conan.tools.google import Bazel -from conans.model.conf import ConfDefinition from conans.test.utils.mocks import ConanFileMock def test_bazel_command_with_empty_config(): conanfile = ConanFileMock() - args_file = os.path.join(conanfile.generators_folder, CONAN_TOOLCHAIN_ARGS_FILE) - save(args_file, - textwrap.dedent("""\ - [%s] - bazel_configs= - bazelrc_path= - """ % CONAN_TOOLCHAIN_ARGS_SECTION)) - bazel = Bazel(conanfile) - bazel.build(label='//test:label') - # TODO: Create a context manager to remove the file - remove(args_file) - assert 'bazel build //test:label' == str(conanfile.command) + bazel.build(target='//test:label') + # Uncomment Conan 2.x + # assert 'bazel build //test:label' in conanfile.commands + assert 'bazel build //test:label' == str(conanfile.command) def test_bazel_command_with_config_values(): conanfile = ConanFileMock() - args_file = os.path.join(conanfile.generators_folder, CONAN_TOOLCHAIN_ARGS_FILE) - save(args_file, - textwrap.dedent("""\ - [%s] - bazel_configs=config,config2 - bazelrc_path=/path/to/bazelrc - """ % CONAN_TOOLCHAIN_ARGS_SECTION)) + conanfile.conf.define("tools.google.bazel:configs", ["config", "config2"]) + conanfile.conf.define("tools.google.bazel:bazelrc_path", ["/path/to/bazelrc"]) bazel = Bazel(conanfile) - bazel.build(label='//test:label') - # TODO: Create a context manager to remove the file - remove(args_file) - assert 'bazel --bazelrc=/path/to/bazelrc build --config=config --config=config2 //test:label' == str(conanfile.command) + bazel.build(target='//test:label') + # Uncomment Conan 2.x + # assert "bazel --bazelrc='/path/to/bazelrc' build " \ + # "--config=config --config=config2 //test:label" in conanfile.commands + assert "bazel --bazelrc='/path/to/bazelrc' build " \ + "--config=config --config=config2 //test:label" == str(conanfile.command) From 42e00eff15dc58fd969b393abd0c17407ff94457 Mon Sep 17 00:00:00 2001 From: Francisco Ramirez de Anton Date: Thu, 19 Oct 2023 16:45:44 +0200 Subject: [PATCH 10/24] Fixed tests on macos --- conan/tools/google/toolchain.py | 3 +++ conans/test/functional/toolchains/google/test_bazel.py | 9 ++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/conan/tools/google/toolchain.py b/conan/tools/google/toolchain.py index b69925d95c5..7f4939bd356 100644 --- a/conan/tools/google/toolchain.py +++ b/conan/tools/google/toolchain.py @@ -2,6 +2,7 @@ from jinja2 import Template +from conan.tools._check_build_profile import check_using_build_profile from conan.tools._compilers import cppstd_flag from conan.tools.apple import to_apple_arch, is_apple_os from conan.tools.build.cross_building import cross_building @@ -61,6 +62,8 @@ class BazelToolchain: def __init__(self, conanfile): self._conanfile = conanfile + # TODO: Remove in Conan 2.x + check_using_build_profile(self._conanfile) # Flags # TODO: Should we read the buildenv to get flags? diff --git a/conans/test/functional/toolchains/google/test_bazel.py b/conans/test/functional/toolchains/google/test_bazel.py index 0d1dab86494..40d713fe782 100644 --- a/conans/test/functional/toolchains/google/test_bazel.py +++ b/conans/test/functional/toolchains/google/test_bazel.py @@ -34,7 +34,8 @@ def base_profile(): @pytest.fixture(scope="module") def client_exe(bazelrc): client = TestClient(path_with_spaces=False) - client.run("new bazel_exe -d name=myapp -d version=1.0") + # client.run("new bazel_exe -d name=myapp -d version=1.0") + client.run("new myapp/1.0 -m bazel_exe") # The build: define several configurations that can be activated by passing # the bazel config with tools.google.bazel:configs client.save({"mybazelrc": bazelrc}) @@ -44,7 +45,8 @@ def client_exe(bazelrc): @pytest.fixture(scope="module") def client_lib(bazelrc): client = TestClient(path_with_spaces=False) - client.run("new bazel_lib -d name=mylib -d version=1.0") + # client.run("new bazel_lib -d name=mylib -d version=1.0") + client.run("new mylib/1.0 -m bazel_lib") # The build: define several configurations that can be activated by passing # the bazel config with tools.google.bazel:configs client.save({"mybazelrc": bazelrc}) @@ -91,7 +93,8 @@ def test_transitive_consuming(): client = TestClient(path_with_spaces=False) # A regular library made with CMake with client.chdir("zlib"): - client.run("new cmake_lib -d name=zlib -d version=1.2.11") + # client.run("new cmake_lib -d name=zlib -d version=1.2.11") + client.run("new zlib/1.2.11 -m cmake_lib") conanfile = client.load("conanfile.py") conanfile += """ self.cpp_info.defines.append("MY_DEFINE=\\"MY_VALUE\\"") From 0b1aa4e917acd2b775815156256e642a6b2e3f43 Mon Sep 17 00:00:00 2001 From: Francisco Ramirez de Anton Date: Thu, 19 Oct 2023 17:01:13 +0200 Subject: [PATCH 11/24] Skipping windows --- conans/test/unittests/tools/google/test_bazel.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/conans/test/unittests/tools/google/test_bazel.py b/conans/test/unittests/tools/google/test_bazel.py index 1cb89685abd..b77f3a1f1a0 100644 --- a/conans/test/unittests/tools/google/test_bazel.py +++ b/conans/test/unittests/tools/google/test_bazel.py @@ -1,7 +1,13 @@ +import platform + +import pytest + from conan.tools.google import Bazel from conans.test.utils.mocks import ConanFileMock +@pytest.mark.skipif(platform.system() == "Windows", reason="Remove this skip for Conan 2.x" + "Needs conanfile.commands") def test_bazel_command_with_empty_config(): conanfile = ConanFileMock() bazel = Bazel(conanfile) @@ -11,6 +17,8 @@ def test_bazel_command_with_empty_config(): assert 'bazel build //test:label' == str(conanfile.command) +@pytest.mark.skipif(platform.system() == "Windows", reason="Remove this skip for Conan 2.x." + "Needs conanfile.commands") def test_bazel_command_with_config_values(): conanfile = ConanFileMock() conanfile.conf.define("tools.google.bazel:configs", ["config", "config2"]) From 59f6b9dd8edcf4c36ca5aeeff537e7fcf3200595 Mon Sep 17 00:00:00 2001 From: Francisco Ramirez de Anton Date: Thu, 19 Oct 2023 17:21:58 +0200 Subject: [PATCH 12/24] reordering imports --- .../toolchains/google/test_bazeltoolchain_cross_compilation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conans/test/functional/toolchains/google/test_bazeltoolchain_cross_compilation.py b/conans/test/functional/toolchains/google/test_bazeltoolchain_cross_compilation.py index 0bf08952108..2f394071354 100644 --- a/conans/test/functional/toolchains/google/test_bazeltoolchain_cross_compilation.py +++ b/conans/test/functional/toolchains/google/test_bazeltoolchain_cross_compilation.py @@ -7,8 +7,8 @@ from conans.test.utils.tools import TestClient -@pytest.mark.skipif(platform.system() != "Darwin", reason="Only for Darwin") @pytest.mark.tool("bazel") +@pytest.mark.skipif(platform.system() != "Darwin", reason="Only for Darwin") def test_bazel_simple_cross_compilation(): profile = textwrap.dedent(""" [settings] From d179f4a112784e7061a8e2a92fdecd653ebb5c97 Mon Sep 17 00:00:00 2001 From: Francisco Ramirez de Anton Date: Thu, 19 Oct 2023 19:00:03 +0200 Subject: [PATCH 13/24] Building all the targets by default --- conan/tools/google/bazel.py | 2 +- conans/assets/templates/new_v2_bazel.py | 6 +++--- .../google/test_bazeltoolchain_cross_compilation.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/conan/tools/google/bazel.py b/conan/tools/google/bazel.py index 9337dd15e1e..f5d56d62459 100644 --- a/conan/tools/google/bazel.py +++ b/conan/tools/google/bazel.py @@ -9,7 +9,7 @@ class Bazel(object): def __init__(self, conanfile): self._conanfile = conanfile - def build(self, target=None, cli_args=None): + def build(self, target="//...", cli_args=None): """ Runs "bazel build " """ diff --git a/conans/assets/templates/new_v2_bazel.py b/conans/assets/templates/new_v2_bazel.py index 92c0c9868a1..5709849dcae 100644 --- a/conans/assets/templates/new_v2_bazel.py +++ b/conans/assets/templates/new_v2_bazel.py @@ -28,7 +28,7 @@ def layout(self): def build(self): bazel = Bazel(self) - bazel.build(target="//main:{name}") + bazel.build() def package(self): dest_lib = os.path.join(self.package_folder, "lib") @@ -65,7 +65,7 @@ def requirements(self): def build(self): bazel = Bazel(self) - bazel.build(target="//main:example") + bazel.build() def layout(self): bazel_layout(self) @@ -131,7 +131,7 @@ def layout(self): def build(self): bazel = Bazel(self) - bazel.build(target="//main:{name}") + bazel.build() def package(self): dest_bin = os.path.join(self.package_folder, "bin") diff --git a/conans/test/functional/toolchains/google/test_bazeltoolchain_cross_compilation.py b/conans/test/functional/toolchains/google/test_bazeltoolchain_cross_compilation.py index 2f394071354..0bf08952108 100644 --- a/conans/test/functional/toolchains/google/test_bazeltoolchain_cross_compilation.py +++ b/conans/test/functional/toolchains/google/test_bazeltoolchain_cross_compilation.py @@ -7,8 +7,8 @@ from conans.test.utils.tools import TestClient -@pytest.mark.tool("bazel") @pytest.mark.skipif(platform.system() != "Darwin", reason="Only for Darwin") +@pytest.mark.tool("bazel") def test_bazel_simple_cross_compilation(): profile = textwrap.dedent(""" [settings] From d12be74984d31bcbd8f6b3d4f492a1c04ee70ef9 Mon Sep 17 00:00:00 2001 From: Francisco Ramirez de Anton Date: Mon, 23 Oct 2023 09:44:24 +0200 Subject: [PATCH 14/24] 1.x conftest tools --- conans/test/functional/toolchains/google/test_bazel.py | 6 +++--- .../google/test_bazeltoolchain_cross_compilation.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/conans/test/functional/toolchains/google/test_bazel.py b/conans/test/functional/toolchains/google/test_bazel.py index 40d713fe782..a0088500ce1 100644 --- a/conans/test/functional/toolchains/google/test_bazel.py +++ b/conans/test/functional/toolchains/google/test_bazel.py @@ -56,7 +56,7 @@ def client_lib(bazelrc): @pytest.mark.parametrize("build_type", ["Debug", "Release", "RelWithDebInfo", "MinSizeRel"]) @pytest.mark.skipif(platform.system() != "Linux", reason="FIXME: Darwin keeps failing randomly " "and win is suspect too") -@pytest.mark.tool("bazel") +@pytest.mark.tool_bazel def test_basic_exe(client_exe, build_type, base_profile): profile = base_profile.format(build_type=build_type, curdir=client_exe.current_folder) @@ -72,7 +72,7 @@ def test_basic_exe(client_exe, build_type, base_profile): @pytest.mark.parametrize("build_type", ["Debug", "Release", "RelWithDebInfo", "MinSizeRel"]) @pytest.mark.skipif(platform.system() != "Linux", reason="FIXME: Darwin keeps failing randomly " "and win is suspect too") -@pytest.mark.tool("bazel") +@pytest.mark.tool_bazel def test_basic_lib(client_lib, build_type, base_profile): profile = base_profile.format(build_type=build_type, curdir=client_lib.current_folder) @@ -87,7 +87,7 @@ def test_basic_lib(client_lib, build_type, base_profile): @pytest.mark.skipif(platform.system() != "Linux", reason="FIXME: Darwin keeps failing randomly " "and win is suspect too") -@pytest.mark.tool("bazel") +@pytest.mark.tool_bazel def test_transitive_consuming(): client = TestClient(path_with_spaces=False) diff --git a/conans/test/functional/toolchains/google/test_bazeltoolchain_cross_compilation.py b/conans/test/functional/toolchains/google/test_bazeltoolchain_cross_compilation.py index 0bf08952108..4c030215583 100644 --- a/conans/test/functional/toolchains/google/test_bazeltoolchain_cross_compilation.py +++ b/conans/test/functional/toolchains/google/test_bazeltoolchain_cross_compilation.py @@ -8,7 +8,7 @@ @pytest.mark.skipif(platform.system() != "Darwin", reason="Only for Darwin") -@pytest.mark.tool("bazel") +@pytest.mark.tool_bazel def test_bazel_simple_cross_compilation(): profile = textwrap.dedent(""" [settings] From 5412c1ee84f70263f1e9108f9e3263a703c8747a Mon Sep 17 00:00:00 2001 From: Francisco Ramirez de Anton Date: Mon, 23 Oct 2023 11:56:46 +0200 Subject: [PATCH 15/24] test refactored --- .../test_bazeltoolchain_cross_compilation.py | 40 +++++++++++++++++-- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/conans/test/functional/toolchains/google/test_bazeltoolchain_cross_compilation.py b/conans/test/functional/toolchains/google/test_bazeltoolchain_cross_compilation.py index 4c030215583..88ce3790f39 100644 --- a/conans/test/functional/toolchains/google/test_bazeltoolchain_cross_compilation.py +++ b/conans/test/functional/toolchains/google/test_bazeltoolchain_cross_compilation.py @@ -4,6 +4,7 @@ import pytest +from conans.test.assets.sources import gen_function_cpp, gen_function_h from conans.test.utils.tools import TestClient @@ -30,12 +31,45 @@ def test_bazel_simple_cross_compilation(): compiler.version=13.0 os=Macos """) + conanfile = textwrap.dedent(""" + from conan import ConanFile + from conan.tools.google import Bazel, bazel_layout + + class MyappConan(ConanFile): + name = "myapp" + version = "1.0" + settings = "os", "compiler", "build_type", "arch" + generators = "BazelToolchain" + + def config_options(self): + if self.settings.os == "Windows": + del self.options.fPIC + + def layout(self): + bazel_layout(self) + + def build(self): + bazel = Bazel(self) + bazel.build() + """) + BUILD = textwrap.dedent(""" + load("@rules_cc//cc:defs.bzl", "cc_library") + cc_library( + name = "myapp", + srcs = ["myapp.cpp"], + hdrs = ["myapp.h"], + ) + """) client = TestClient(path_with_spaces=False) - # client.run("new bazel_lib -d name=myapp -d version=1.0") - client.run("new myapp/1.0 -m bazel_lib") client.save({ "profile": profile, - "profile_host": profile_host + "profile_host": profile_host, + "conanfile.py": conanfile, + "WORKSPACE": "", + "main/BUILD": BUILD, + "main/myapp.cpp": gen_function_cpp(name="myapp"), + "main/myapp.h": gen_function_h(name="myapp"), + }) # client.run("build . -pr:h profile_host -pr:b profile") client.run("install . -pr:h profile_host -pr:b profile") From d043d13cc09f298ee31e629ebe9baca090127288 Mon Sep 17 00:00:00 2001 From: Francisco Ramirez de Anton Date: Thu, 26 Oct 2023 10:09:17 +0200 Subject: [PATCH 16/24] Keeping baward compatibility. Safer command runner. Fixed Windows rc paths --- conan/tools/google/bazel.py | 42 ++++++++++++++----- conans/assets/templates/new_v2_bazel.py | 4 ++ .../test/unittests/tools/google/test_bazel.py | 4 +- 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/conan/tools/google/bazel.py b/conan/tools/google/bazel.py index f5d56d62459..fe4636ffcab 100644 --- a/conan/tools/google/bazel.py +++ b/conan/tools/google/bazel.py @@ -9,10 +9,33 @@ class Bazel(object): def __init__(self, conanfile): self._conanfile = conanfile - def build(self, target="//...", cli_args=None): + def configure(self, args=None): + # TODO: Remove in Conan 2.x. Keeping it backward compatible + pass + + def _safe_run_command(self, command): + """ + Windows is having problems for stopping bazel processes, so it ends up locking + some files if something goes wrong. Better to shut down the Bazel server after running + each command. """ - Runs "bazel build " + try: + self._conanfile.run(command) + finally: + if platform.system() == "Windows": + self._conanfile.run("bazel shutdown") + + def build(self, args=None, label=None, target="//..."): + """ + Runs "bazel build " + + :param label: DEPRECATED: It'll disappear in Conan 2.x. It is the target label + :param target: It is the target label + :param args: list of extra arguments + :return: """ + # TODO: Remove in Conan 2.x. Superseded by target + label = label or target # Use BazelToolchain generated file if exists conan_bazelrc = os.path.join(self._conanfile.generators_folder, BazelToolchain.bazelrc_name) use_conan_config = os.path.exists(conan_bazelrc) @@ -27,19 +50,16 @@ def build(self, target="//...", cli_args=None): default=[], check_type=list)) command = "bazel" for rc in bazelrc_paths: - command += f" --bazelrc='{rc}'" + command += f" --bazelrc={rc}" command += " build" bazelrc_configs.extend(self._conanfile.conf.get("tools.google.bazel:configs", default=[], check_type=list)) for config in bazelrc_configs: command += f" --config={config}" - if cli_args: - command += " ".join(f" {arg}" for arg in cli_args) - command += f" {target}" - self._conanfile.run(command) - # This is very important for Windows, as otherwise the bazel server locks files - if platform.system() == "Windows": - self._conanfile.run("bazel shutdown") + if args: + command += " ".join(f" {arg}" for arg in args) + command += f" {label}" + self._safe_run_command(command) def test(self, target=None): """ @@ -47,4 +67,4 @@ def test(self, target=None): """ if self._conanfile.conf.get("tools.build:skip_test", check_type=bool) or target is None: return - self._conanfile.run(f'bazel test {target}') + self._safe_run_command(f'bazel test {target}') diff --git a/conans/assets/templates/new_v2_bazel.py b/conans/assets/templates/new_v2_bazel.py index 5709849dcae..067d6cb3825 100644 --- a/conans/assets/templates/new_v2_bazel.py +++ b/conans/assets/templates/new_v2_bazel.py @@ -28,6 +28,8 @@ def layout(self): def build(self): bazel = Bazel(self) + # TODO: configure function is deprecated + bazel.configure() bazel.build() def package(self): @@ -65,6 +67,8 @@ def requirements(self): def build(self): bazel = Bazel(self) + # TODO: configure function is deprecated + bazel.configure() bazel.build() def layout(self): diff --git a/conans/test/unittests/tools/google/test_bazel.py b/conans/test/unittests/tools/google/test_bazel.py index b77f3a1f1a0..7f2dfa1fc31 100644 --- a/conans/test/unittests/tools/google/test_bazel.py +++ b/conans/test/unittests/tools/google/test_bazel.py @@ -26,7 +26,7 @@ def test_bazel_command_with_config_values(): bazel = Bazel(conanfile) bazel.build(target='//test:label') # Uncomment Conan 2.x - # assert "bazel --bazelrc='/path/to/bazelrc' build " \ + # assert "bazel --bazelrc=/path/to/bazelrc build " \ # "--config=config --config=config2 //test:label" in conanfile.commands - assert "bazel --bazelrc='/path/to/bazelrc' build " \ + assert "bazel --bazelrc=/path/to/bazelrc build " \ "--config=config --config=config2 //test:label" == str(conanfile.command) From a9207b5b9dbfe5804f2cda8dd65841aabc57fa0c Mon Sep 17 00:00:00 2001 From: Francisco Ramirez de Anton Date: Thu, 26 Oct 2023 10:10:14 +0200 Subject: [PATCH 17/24] Better comment --- conans/assets/templates/new_v2_bazel.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conans/assets/templates/new_v2_bazel.py b/conans/assets/templates/new_v2_bazel.py index 067d6cb3825..59803b98a39 100644 --- a/conans/assets/templates/new_v2_bazel.py +++ b/conans/assets/templates/new_v2_bazel.py @@ -28,7 +28,7 @@ def layout(self): def build(self): bazel = Bazel(self) - # TODO: configure function is deprecated + # DeprecationWarning: "bazel.configure()" function is deprecated bazel.configure() bazel.build() @@ -67,7 +67,7 @@ def requirements(self): def build(self): bazel = Bazel(self) - # TODO: configure function is deprecated + # DeprecationWarning: "bazel.configure()" function is deprecated bazel.configure() bazel.build() From e5e10b9a383d64ab720512fe3b79ed4f21835f3b Mon Sep 17 00:00:00 2001 From: Francisco Ramirez de Anton Date: Thu, 26 Oct 2023 10:22:46 +0200 Subject: [PATCH 18/24] Removed configure from templates. Useless --- conans/assets/templates/new_v2_bazel.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/conans/assets/templates/new_v2_bazel.py b/conans/assets/templates/new_v2_bazel.py index 59803b98a39..5709849dcae 100644 --- a/conans/assets/templates/new_v2_bazel.py +++ b/conans/assets/templates/new_v2_bazel.py @@ -28,8 +28,6 @@ def layout(self): def build(self): bazel = Bazel(self) - # DeprecationWarning: "bazel.configure()" function is deprecated - bazel.configure() bazel.build() def package(self): @@ -67,8 +65,6 @@ def requirements(self): def build(self): bazel = Bazel(self) - # DeprecationWarning: "bazel.configure()" function is deprecated - bazel.configure() bazel.build() def layout(self): From fe897523775e9202938690fd0ef36edb4f2b60bb Mon Sep 17 00:00:00 2001 From: Francisco Ramirez de Anton Date: Thu, 26 Oct 2023 13:28:31 +0200 Subject: [PATCH 19/24] removed strip attr and fastbuild as default value --- conan/tools/google/toolchain.py | 9 ++++----- .../integration/toolchains/google/test_bazeltoolchain.py | 2 -- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/conan/tools/google/toolchain.py b/conan/tools/google/toolchain.py index 7f4939bd356..895140fe8cf 100644 --- a/conan/tools/google/toolchain.py +++ b/conan/tools/google/toolchain.py @@ -84,9 +84,8 @@ def __init__(self, conanfile): self.conlyopt = [] self.cxxopt = [] self.linkopt = [] - self.strip = 'sometimes' # 'always', 'sometimes', 'never' self.compilation_mode = {'Release': 'opt', 'Debug': 'dbg'}.get( - self._conanfile.settings.get_safe("build_type"), "fastbuild" # Bazel default + self._conanfile.settings.get_safe("build_type") ) # Be aware that this parameter does not admit a compiler absolute path # If you want to add it, you will have to use a specific Bazel toolchain @@ -112,13 +111,13 @@ def _filter_list_empty_fields(v): def cxxflags(self): ret = [self.cppstd] conf_flags = self._conanfile.conf.get("tools.build:cxxflags", default=[], check_type=list) - ret = ret + conf_flags + self.extra_cxxflags + ret = ret + self.extra_cxxflags + conf_flags return self._filter_list_empty_fields(ret) @property def cflags(self): conf_flags = self._conanfile.conf.get("tools.build:cflags", default=[], check_type=list) - ret = conf_flags + self.extra_cflags + ret = self.extra_cflags + conf_flags return self._filter_list_empty_fields(ret) @property @@ -129,7 +128,7 @@ def ldflags(self): check_type=list)) linker_scripts = self._conanfile.conf.get("tools.build:linker_scripts", default=[], check_type=list) conf_flags.extend(["-T'" + linker_script + "'" for linker_script in linker_scripts]) - ret = conf_flags + self.extra_ldflags + ret = self.extra_ldflags + conf_flags return self._filter_list_empty_fields(ret) def _context(self): diff --git a/conans/test/integration/toolchains/google/test_bazeltoolchain.py b/conans/test/integration/toolchains/google/test_bazeltoolchain.py index 9da55e39ec3..9c35b9904c0 100644 --- a/conans/test/integration/toolchains/google/test_bazeltoolchain.py +++ b/conans/test/integration/toolchains/google/test_bazeltoolchain.py @@ -43,7 +43,6 @@ def test_default_bazel_toolchain(conanfile): assert "build:conan-config --cxxopt=-std=gnu++17" in content assert "build:conan-config --force_pic=True" in content assert "build:conan-config --dynamic_mode=off" in content - assert "build:conan-config --strip=sometimes" in content assert "build:conan-config --compilation_mode=opt" in content @@ -76,7 +75,6 @@ def test_bazel_toolchain_and_flags(conanfile): assert "build:conan-config --linkopt=--flag5 --linkopt=--flag6 --linkopt=-T'myscript.sh'" in content assert "build:conan-config --force_pic=True" not in content assert "build:conan-config --dynamic_mode=fully" in content - assert "build:conan-config --strip=sometimes" in content assert "build:conan-config --compilation_mode=opt" in content From 27411d30504682a84772f8fd839fa69b895f1a19 Mon Sep 17 00:00:00 2001 From: Francisco Ramirez de Anton Date: Thu, 26 Oct 2023 14:30:44 +0200 Subject: [PATCH 20/24] removed strip attr --- conan/tools/google/toolchain.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/conan/tools/google/toolchain.py b/conan/tools/google/toolchain.py index 895140fe8cf..0ebcc2df92f 100644 --- a/conan/tools/google/toolchain.py +++ b/conan/tools/google/toolchain.py @@ -54,7 +54,6 @@ class BazelToolchain: {% if linkopt %}build:conan-config {{linkopt}}{% endif %} {% if force_pic %}build:conan-config --force_pic={{force_pic}}{% endif %} {% if dynamic_mode %}build:conan-config --dynamic_mode={{dynamic_mode}}{% endif %} - {% if strip %}build:conan-config --strip={{strip}}{% endif %} {% if compilation_mode %}build:conan-config --compilation_mode={{compilation_mode}}{% endif %} {% if compiler %}build:conan-config --compiler={{compiler}}{% endif %} {% if cpu %}build:conan-config --cpu={{cpu}}{% endif %} @@ -139,7 +138,6 @@ def _context(self): "linkopt": " ".join(f"--linkopt={flag}" for flag in (self.linkopt + self.ldflags)), "force_pic": self.force_pic, "dynamic_mode": self.dynamic_mode, - "strip": self.strip, "compilation_mode": self.compilation_mode, "compiler": self.compiler, "cpu": self.cpu, From ce5081c987bc2bad78974b1fe4f00bf4eba381bd Mon Sep 17 00:00:00 2001 From: Francisco Ramirez de Anton Date: Thu, 26 Oct 2023 15:53:13 +0200 Subject: [PATCH 21/24] Added validate step --- conans/assets/templates/new_v2_bazel.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/conans/assets/templates/new_v2_bazel.py b/conans/assets/templates/new_v2_bazel.py index 5709849dcae..a349818603e 100644 --- a/conans/assets/templates/new_v2_bazel.py +++ b/conans/assets/templates/new_v2_bazel.py @@ -3,6 +3,7 @@ conanfile_sources_v2 = """ import os from conan import ConanFile +from conan.errors import ConanException from conan.tools.google import Bazel, bazel_layout from conan.tools.files import copy @@ -23,6 +24,12 @@ def config_options(self): if self.settings.os == "Windows": del self.options.fPIC + def validate(self): + if self.settings.os in ("Windows", "Macos") and self.options.shared: + raise ConanException("Windows and Macos needs extra BUILD configuration to be able " + "to create a shared library. Please, check this reference to " + "know more about it: https://bazel.build/reference/be/c-cpp") + def layout(self): bazel_layout(self) From 81956f3501d8d15a15e3bf58b619aeb28ff4606b Mon Sep 17 00:00:00 2001 From: Francisco Ramirez de Anton Date: Thu, 26 Oct 2023 15:57:38 +0200 Subject: [PATCH 22/24] Validate test --- .../test/functional/toolchains/google/test_bazel.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/conans/test/functional/toolchains/google/test_bazel.py b/conans/test/functional/toolchains/google/test_bazel.py index a0088500ce1..f559c7c2ec0 100644 --- a/conans/test/functional/toolchains/google/test_bazel.py +++ b/conans/test/functional/toolchains/google/test_bazel.py @@ -53,6 +53,17 @@ def client_lib(bazelrc): return client +@pytest.mark.skipif(platform.system() == "Linux", reason="Validate exception raises " + "only for Macos/Windows") +@pytest.mark.tool_bazel +def test_basic_exe(): + client = TestClient(path_with_spaces=False) + # client.run("new bazel_lib -d name=mylib -d version=1.0") + client.run("new mylib/1.0 -m bazel_lib") + client.run("create . -o '*:shared=True'", assert_error=True) + assert "Windows and Macos needs extra BUILD configuration" in client.out + + @pytest.mark.parametrize("build_type", ["Debug", "Release", "RelWithDebInfo", "MinSizeRel"]) @pytest.mark.skipif(platform.system() != "Linux", reason="FIXME: Darwin keeps failing randomly " "and win is suspect too") From d33c0d68008e89e93f0d5c82b2b4b6e94364078a Mon Sep 17 00:00:00 2001 From: Francisco Ramirez de Anton Date: Mon, 30 Oct 2023 12:47:15 +0100 Subject: [PATCH 23/24] Keeping backward-compatibility. Added deprecated warnings --- conan/tools/google/bazel.py | 30 ++++++++--- conan/tools/google/layout.py | 4 +- conan/tools/google/toolchain.py | 9 +++- conans/assets/templates/new_v2_bazel.py | 6 +++ conans/model/conf.py | 1 + .../toolchains/google/test_bazel.py | 9 ++-- .../toolchains/google/test_bazel_layout.py | 50 +++++++++++++++++++ .../toolchains/google/test_bazeltoolchain.py | 3 ++ 8 files changed, 99 insertions(+), 13 deletions(-) create mode 100644 conans/test/integration/toolchains/google/test_bazel_layout.py diff --git a/conan/tools/google/bazel.py b/conan/tools/google/bazel.py index fe4636ffcab..75f3b30ba84 100644 --- a/conan/tools/google/bazel.py +++ b/conan/tools/google/bazel.py @@ -6,11 +6,19 @@ class Bazel(object): - def __init__(self, conanfile): + def __init__(self, conanfile, namespace=None): self._conanfile = conanfile + # TODO: Remove namespace in Conan 2.x + if namespace: + self._conanfile.output.warn("In Bazel() call, namespace param has been " + "deprecated as it's not used anymore. Use the bazel_layout()" + " and the 'tools.google.bazel_layout:generators_folder=xxxx' configuration" + " to specify another folder for the Conan-generated-files.") def configure(self, args=None): # TODO: Remove in Conan 2.x. Keeping it backward compatible + self._conanfile.output.warn("Bazel.configure() function has been deprecated." + " Removing in Conan 2.x.") pass def _safe_run_command(self, command): @@ -35,7 +43,10 @@ def build(self, args=None, label=None, target="//..."): :return: """ # TODO: Remove in Conan 2.x. Superseded by target - label = label or target + if label: + self._conanfile.output.warn("In Bazel.build() call, label param has been deprecated." + " Migrating to target.") + target = label # Use BazelToolchain generated file if exists conan_bazelrc = os.path.join(self._conanfile.generators_folder, BazelToolchain.bazelrc_name) use_conan_config = os.path.exists(conan_bazelrc) @@ -46,19 +57,24 @@ def build(self, args=None, label=None, target="//..."): bazelrc_configs.append(BazelToolchain.bazelrc_config) # User bazelrc paths have more prio than Conan one # See more info in https://bazel.build/run/bazelrc - bazelrc_paths.extend(self._conanfile.conf.get("tools.google.bazel:bazelrc_path", - default=[], check_type=list)) + # TODO: Legacy Bazel allowed only one value. Remove for Conan 2.x and check list-type. + rc_paths = self._conanfile.conf.get("tools.google.bazel:bazelrc_path", default=[]) + rc_paths = [rc_paths.strip()] if isinstance(rc_paths, str) else rc_paths + bazelrc_paths.extend(rc_paths) command = "bazel" for rc in bazelrc_paths: command += f" --bazelrc={rc}" command += " build" - bazelrc_configs.extend(self._conanfile.conf.get("tools.google.bazel:configs", - default=[], check_type=list)) + # TODO: Legacy Bazel allowed only one value or several ones separate by commas. + # Remove for Conan 2.x and check list-type. + configs = self._conanfile.conf.get("tools.google.bazel:configs", default=[]) + configs = [c.strip() for c in configs.split(",")] if isinstance(configs, str) else configs + bazelrc_configs.extend(configs) for config in bazelrc_configs: command += f" --config={config}" if args: command += " ".join(f" {arg}" for arg in args) - command += f" {label}" + command += f" {target}" self._safe_run_command(command) def test(self, target=None): diff --git a/conan/tools/google/layout.py b/conan/tools/google/layout.py index 00b216045be..5486a5d526f 100644 --- a/conan/tools/google/layout.py +++ b/conan/tools/google/layout.py @@ -7,7 +7,9 @@ def bazel_layout(conanfile, src_folder="."): subproject = conanfile.folders.subproject conanfile.folders.source = src_folder if not subproject else os.path.join(subproject, src_folder) conanfile.folders.build = "." # Bazel always build the whole project in the root folder - conanfile.folders.generators = "conan" # one + # FIXME: Keeping backward-compatibility. Defaulting to "conan" in Conan 2.x. + conanfile.folders.generators = conanfile.conf.get("tools.google.bazel_layout:generators_folder", + default=".", check_type=str) # FIXME: used in test package for example, to know where the binaries are (editables not supported yet)? conanfile.cpp.build.bindirs = [os.path.join(conanfile.folders.build, "bazel-bin")] conanfile.cpp.build.libdirs = [os.path.join(conanfile.folders.build, "bazel-bin")] diff --git a/conan/tools/google/toolchain.py b/conan/tools/google/toolchain.py index 0ebcc2df92f..2fe1f32ec70 100644 --- a/conan/tools/google/toolchain.py +++ b/conan/tools/google/toolchain.py @@ -59,9 +59,14 @@ class BazelToolchain: {% if cpu %}build:conan-config --cpu={{cpu}}{% endif %} {% if crosstool_top %}build:conan-config --crosstool_top={{crosstool_top}}{% endif %}""") - def __init__(self, conanfile): + def __init__(self, conanfile, namespace=None): self._conanfile = conanfile - # TODO: Remove in Conan 2.x + # TODO: Remove namespace and check_using_build_profile in Conan 2.x + if namespace: + self._conanfile.output.warn("In BazelToolchain() call, namespace param has been " + "deprecated as it's not used anymore. Use the bazel_layout()" + " and the 'tools.google.bazel_layout:generators_folder=xxxx' configuration" + " to specify another folder for the Conan-generated-files.") check_using_build_profile(self._conanfile) # Flags diff --git a/conans/assets/templates/new_v2_bazel.py b/conans/assets/templates/new_v2_bazel.py index a349818603e..17fbdbe9126 100644 --- a/conans/assets/templates/new_v2_bazel.py +++ b/conans/assets/templates/new_v2_bazel.py @@ -32,6 +32,8 @@ def validate(self): def layout(self): bazel_layout(self) + # DEPRECATED: Default generators folder will be "conan" in Conan 2.x + self.folders.generators = "conan" def build(self): bazel = Bazel(self) @@ -76,6 +78,8 @@ def build(self): def layout(self): bazel_layout(self) + # DEPRECATED: Default generators folder will be "conan" in Conan 2.x + self.folders.generators = "conan" def test(self): if not cross_building(self): @@ -135,6 +139,8 @@ class {package_name}Conan(ConanFile): def layout(self): bazel_layout(self) + # DEPRECATED: Default generators folder will be "conan" in Conan 2.x + self.folders.generators = "conan" def build(self): bazel = Bazel(self) diff --git a/conans/model/conf.py b/conans/model/conf.py index fcad44d9613..4d71ff4e05e 100644 --- a/conans/model/conf.py +++ b/conans/model/conf.py @@ -32,6 +32,7 @@ "tools.gnu:host_triplet": "Custom host triplet to pass to Autotools scripts", "tools.google.bazel:configs": "Define Bazel config file", "tools.google.bazel:bazelrc_path": "Defines Bazel rc-path", + "tools.google.bazel_layout:generators_folder": "Define the generators folder for the bazel_layout(). Defaulted to 'conan' in Conan 2.x", "tools.microsoft.msbuild:verbosity": "Verbosity level for MSBuild: 'Quiet', 'Minimal', 'Normal', 'Detailed', 'Diagnostic'", "tools.microsoft.msbuild:vs_version": "Defines the IDE version when using the new msvc compiler", "tools.microsoft.msbuild:max_cpu_count": "Argument for the /m when running msvc to build parallel projects", diff --git a/conans/test/functional/toolchains/google/test_bazel.py b/conans/test/functional/toolchains/google/test_bazel.py index f559c7c2ec0..f8791f3ed25 100644 --- a/conans/test/functional/toolchains/google/test_bazel.py +++ b/conans/test/functional/toolchains/google/test_bazel.py @@ -26,8 +26,11 @@ def base_profile(): build_type={build_type} [conf] - tools.google.bazel:bazelrc_path=["{curdir}/mybazelrc"] - tools.google.bazel:configs=["{build_type}", "withTimeStamps"] + # FIXME: Conan 2.x + # tools.google.bazel:bazelrc_path=["{curdir}/mybazelrc"] + # tools.google.bazel:configs=["{build_type}", "withTimeStamps"] + tools.google.bazel:bazelrc_path={curdir}/mybazelrc + tools.google.bazel:configs="{build_type}", "withTimeStamps" """) @@ -200,7 +203,7 @@ def package_info(self): """) bazel_workspace = textwrap.dedent(""" - load("@//conan:dependencies.bzl", "load_conan_dependencies") + load("@//:dependencies.bzl", "load_conan_dependencies") load_conan_dependencies() """) diff --git a/conans/test/integration/toolchains/google/test_bazel_layout.py b/conans/test/integration/toolchains/google/test_bazel_layout.py new file mode 100644 index 00000000000..928dd5355eb --- /dev/null +++ b/conans/test/integration/toolchains/google/test_bazel_layout.py @@ -0,0 +1,50 @@ +import os +import textwrap + +import pytest + +from conan.tools.files import load +from conan.tools.google import BazelToolchain +from conans.test.utils.tools import TestClient + + +@pytest.fixture(scope="module") +def conanfile(): + return textwrap.dedent(""" + from conan import ConanFile + from conan.tools.google import BazelToolchain, bazel_layout + class ExampleConanIntegration(ConanFile): + settings = "os", "arch", "build_type", "compiler" + options = {"shared": [True, False], "fPIC": [True, False]} + default_options = {"shared": False, "fPIC": True} + generators = 'BazelToolchain' + def layout(self): + bazel_layout(self) + """) + + +@pytest.mark.parametrize("generators_folder", [None, "other"]) +def test_bazel_layout_generators_folder_conf(generators_folder, conanfile): + profile = textwrap.dedent(""" + [settings] + arch=x86_64 + build_type=Release + compiler=apple-clang + compiler.cppstd=gnu17 + compiler.libcxx=libc++ + compiler.version=13.0 + os=Macos + {} + """) + conf = textwrap.dedent(""" + [conf] + tools.google.bazel_layout:generators_folder={} + """) + profile = profile.format("" if generators_folder is None else conf.format(generators_folder)) + c = TestClient() + c.save({"conanfile.py": conanfile, + "profile": profile}) + c.run("install . -pr profile") + # FIXME: "conan" is the default one in Conan 2.x + # assert load(c, os.path.join(c.current_folder, generators_folder or "conan", BazelToolchain.bazelrc_name)) + assert load(c, os.path.join(c.current_folder, generators_folder or ".", BazelToolchain.bazelrc_name)) diff --git a/conans/test/integration/toolchains/google/test_bazeltoolchain.py b/conans/test/integration/toolchains/google/test_bazeltoolchain.py index 9c35b9904c0..7dcf5d1b112 100644 --- a/conans/test/integration/toolchains/google/test_bazeltoolchain.py +++ b/conans/test/integration/toolchains/google/test_bazeltoolchain.py @@ -20,6 +20,8 @@ class ExampleConanIntegration(ConanFile): generators = 'BazelToolchain' def layout(self): bazel_layout(self) + # Remove in Conan 2.x + self.folders.generators = "conan" """) @@ -98,6 +100,7 @@ def test_bazel_toolchain_and_cross_compilation(conanfile): compiler.libcxx=libc++ compiler.version=13.0 os=Macos + """) c = TestClient() c.save({"conanfile.py": conanfile, From ad05e2763b8bfd7697d27dfd4e9c0db8bbb6b0c0 Mon Sep 17 00:00:00 2001 From: Francisco Ramirez de Anton Date: Mon, 30 Oct 2023 15:01:24 +0100 Subject: [PATCH 24/24] Keeping original bazel_layout. Applied suggestions --- conan/tools/google/bazel.py | 10 ++-- conan/tools/google/layout.py | 5 +- conan/tools/google/toolchain.py | 6 +-- conans/model/conf.py | 1 - .../toolchains/google/test_bazel_layout.py | 50 ------------------- 5 files changed, 9 insertions(+), 63 deletions(-) delete mode 100644 conans/test/integration/toolchains/google/test_bazel_layout.py diff --git a/conan/tools/google/bazel.py b/conan/tools/google/bazel.py index 75f3b30ba84..f68661f4bb0 100644 --- a/conan/tools/google/bazel.py +++ b/conan/tools/google/bazel.py @@ -10,14 +10,12 @@ def __init__(self, conanfile, namespace=None): self._conanfile = conanfile # TODO: Remove namespace in Conan 2.x if namespace: - self._conanfile.output.warn("In Bazel() call, namespace param has been " - "deprecated as it's not used anymore. Use the bazel_layout()" - " and the 'tools.google.bazel_layout:generators_folder=xxxx' configuration" - " to specify another folder for the Conan-generated-files.") + self._conanfile.output.warning("In Bazel() call, namespace param has been " + "deprecated as it's not used anymore.") def configure(self, args=None): # TODO: Remove in Conan 2.x. Keeping it backward compatible - self._conanfile.output.warn("Bazel.configure() function has been deprecated." + self._conanfile.output.warning("Bazel.configure() function has been deprecated." " Removing in Conan 2.x.") pass @@ -44,7 +42,7 @@ def build(self, args=None, label=None, target="//..."): """ # TODO: Remove in Conan 2.x. Superseded by target if label: - self._conanfile.output.warn("In Bazel.build() call, label param has been deprecated." + self._conanfile.output.warning("In Bazel.build() call, label param has been deprecated." " Migrating to target.") target = label # Use BazelToolchain generated file if exists diff --git a/conan/tools/google/layout.py b/conan/tools/google/layout.py index 5486a5d526f..95bec4a78a8 100644 --- a/conan/tools/google/layout.py +++ b/conan/tools/google/layout.py @@ -8,8 +8,9 @@ def bazel_layout(conanfile, src_folder="."): conanfile.folders.source = src_folder if not subproject else os.path.join(subproject, src_folder) conanfile.folders.build = "." # Bazel always build the whole project in the root folder # FIXME: Keeping backward-compatibility. Defaulting to "conan" in Conan 2.x. - conanfile.folders.generators = conanfile.conf.get("tools.google.bazel_layout:generators_folder", - default=".", check_type=str) + conanfile.output.warning("In bazel_layout() call, generators folder changes its default value " + "from './' to './conan/' in Conan 2.x") + conanfile.folders.generators = "." # FIXME: used in test package for example, to know where the binaries are (editables not supported yet)? conanfile.cpp.build.bindirs = [os.path.join(conanfile.folders.build, "bazel-bin")] conanfile.cpp.build.libdirs = [os.path.join(conanfile.folders.build, "bazel-bin")] diff --git a/conan/tools/google/toolchain.py b/conan/tools/google/toolchain.py index 2fe1f32ec70..05a661a5b4b 100644 --- a/conan/tools/google/toolchain.py +++ b/conan/tools/google/toolchain.py @@ -63,10 +63,8 @@ def __init__(self, conanfile, namespace=None): self._conanfile = conanfile # TODO: Remove namespace and check_using_build_profile in Conan 2.x if namespace: - self._conanfile.output.warn("In BazelToolchain() call, namespace param has been " - "deprecated as it's not used anymore. Use the bazel_layout()" - " and the 'tools.google.bazel_layout:generators_folder=xxxx' configuration" - " to specify another folder for the Conan-generated-files.") + self._conanfile.output.warning("In BazelToolchain() call, namespace param has been " + "deprecated as it's not used anymore.") check_using_build_profile(self._conanfile) # Flags diff --git a/conans/model/conf.py b/conans/model/conf.py index 4d71ff4e05e..fcad44d9613 100644 --- a/conans/model/conf.py +++ b/conans/model/conf.py @@ -32,7 +32,6 @@ "tools.gnu:host_triplet": "Custom host triplet to pass to Autotools scripts", "tools.google.bazel:configs": "Define Bazel config file", "tools.google.bazel:bazelrc_path": "Defines Bazel rc-path", - "tools.google.bazel_layout:generators_folder": "Define the generators folder for the bazel_layout(). Defaulted to 'conan' in Conan 2.x", "tools.microsoft.msbuild:verbosity": "Verbosity level for MSBuild: 'Quiet', 'Minimal', 'Normal', 'Detailed', 'Diagnostic'", "tools.microsoft.msbuild:vs_version": "Defines the IDE version when using the new msvc compiler", "tools.microsoft.msbuild:max_cpu_count": "Argument for the /m when running msvc to build parallel projects", diff --git a/conans/test/integration/toolchains/google/test_bazel_layout.py b/conans/test/integration/toolchains/google/test_bazel_layout.py deleted file mode 100644 index 928dd5355eb..00000000000 --- a/conans/test/integration/toolchains/google/test_bazel_layout.py +++ /dev/null @@ -1,50 +0,0 @@ -import os -import textwrap - -import pytest - -from conan.tools.files import load -from conan.tools.google import BazelToolchain -from conans.test.utils.tools import TestClient - - -@pytest.fixture(scope="module") -def conanfile(): - return textwrap.dedent(""" - from conan import ConanFile - from conan.tools.google import BazelToolchain, bazel_layout - class ExampleConanIntegration(ConanFile): - settings = "os", "arch", "build_type", "compiler" - options = {"shared": [True, False], "fPIC": [True, False]} - default_options = {"shared": False, "fPIC": True} - generators = 'BazelToolchain' - def layout(self): - bazel_layout(self) - """) - - -@pytest.mark.parametrize("generators_folder", [None, "other"]) -def test_bazel_layout_generators_folder_conf(generators_folder, conanfile): - profile = textwrap.dedent(""" - [settings] - arch=x86_64 - build_type=Release - compiler=apple-clang - compiler.cppstd=gnu17 - compiler.libcxx=libc++ - compiler.version=13.0 - os=Macos - {} - """) - conf = textwrap.dedent(""" - [conf] - tools.google.bazel_layout:generators_folder={} - """) - profile = profile.format("" if generators_folder is None else conf.format(generators_folder)) - c = TestClient() - c.save({"conanfile.py": conanfile, - "profile": profile}) - c.run("install . -pr profile") - # FIXME: "conan" is the default one in Conan 2.x - # assert load(c, os.path.join(c.current_folder, generators_folder or "conan", BazelToolchain.bazelrc_name)) - assert load(c, os.path.join(c.current_folder, generators_folder or ".", BazelToolchain.bazelrc_name))