Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bazel][1.x][Part 1]: Bazel + BazelToolchain refactor #14958

Merged
merged 24 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
77e981f
BazelDeps refactor
franramirez688 Oct 18, 2023
e310e99
BazelToolchain refactor
franramirez688 Oct 18, 2023
349f4ee
bazel_layout refactor
franramirez688 Oct 18, 2023
92eeb02
Bazel helper refactor
franramirez688 Oct 18, 2023
485510f
Reverted BazelDeps
franramirez688 Oct 18, 2023
3fe380a
Applying suggestions
franramirez688 Oct 18, 2023
53688ed
Applying suggestions
franramirez688 Oct 18, 2023
2864c59
removed Conan 2 imports
franramirez688 Oct 18, 2023
1c4e099
Testing BazelToolchain and bazel_layout
franramirez688 Oct 19, 2023
42e00ef
Fixed tests on macos
franramirez688 Oct 19, 2023
0b1aa4e
Skipping windows
franramirez688 Oct 19, 2023
59f6b9d
reordering imports
franramirez688 Oct 19, 2023
d179f4a
Building all the targets by default
franramirez688 Oct 19, 2023
d12be74
1.x conftest tools
franramirez688 Oct 23, 2023
5412c1e
test refactored
franramirez688 Oct 23, 2023
d043d13
Keeping baward compatibility. Safer command runner. Fixed Windows rc …
franramirez688 Oct 26, 2023
a9207b5
Better comment
franramirez688 Oct 26, 2023
e5e10b9
Removed configure from templates. Useless
franramirez688 Oct 26, 2023
fe89752
removed strip attr and fastbuild as default value
franramirez688 Oct 26, 2023
27411d3
removed strip attr
franramirez688 Oct 26, 2023
ce5081c
Added validate step
franramirez688 Oct 26, 2023
81956f3
Validate test
franramirez688 Oct 26, 2023
d33c0d6
Keeping backward-compatibility. Added deprecated warnings
franramirez688 Oct 30, 2023
ad05e27
Keeping original bazel_layout. Applied suggestions
franramirez688 Oct 30, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 75 additions & 35 deletions conan/tools/google/bazel.py
Original file line number Diff line number Diff line change
@@ -1,44 +1,84 @@
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
franramirez688 marked this conversation as resolved.
Show resolved Hide resolved
self._get_bazel_project_configuration()
# TODO: Remove namespace in Conan 2.x
if namespace:
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.warning("Bazel.configure() function has been deprecated."
" Removing in Conan 2.x.")
pass

def build(self, args=None, label=None):
# TODO: Change the directory where bazel builds the project (by default, /var/tmp/_bazel_<username> )

bazelrc_path = '--bazelrc={}'.format(self._bazelrc_path) if self._bazelrc_path else ''
bazel_config = " ".join(['--config={}'.format(conf) for conf in self._bazel_config])

# 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
)

self._conanfile.run(command)

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 _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.
"""
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 <rcpaths> build <configs> <args> <targets>"

: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
if label:
self._conanfile.output.warning("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)
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
# 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"
# 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" {target}"
self._safe_run_command(command)

def test(self, target=None):
"""
Runs "bazel test <target>"
"""
if self._conanfile.conf.get("tools.build:skip_test", check_type=bool) or target is None:
return
self._safe_run_command(f'bazel test {target}')
23 changes: 14 additions & 9 deletions conan/tools/google/layout.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
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
# FIXME: Keeping backward-compatibility. Defaulting to "conan" in Conan 2.x.
conanfile.output.warning("In bazel_layout() call, generators folder changes its default value "
"from './' to './conan/' in Conan 2.x")
Comment on lines +11 to +12
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we can make here a explicit recomendation of setting self.folders.generators_folder = "conan" so that users can have a clue on what to do, but we can change that in the next PR

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")]
franramirez688 marked this conversation as resolved.
Show resolved Hide resolved
conanfile.cpp.build.libdirs = [os.path.join(conanfile.folders.build, "bazel-bin")]
164 changes: 149 additions & 15 deletions conan/tools/google/toolchain.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,158 @@
import textwrap

from jinja2 import Template

from conan.tools._check_build_profile import check_using_build_profile
from conan.tools.files.files import save_toolchain_args
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.files import save


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}"


class BazelToolchain(object):
# 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 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, namespace=None):
self._conanfile = conanfile
self._namespace = namespace
# TODO: Remove namespace and check_using_build_profile in Conan 2.x
if namespace:
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
# 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.compilation_mode = {'Release': 'opt', 'Debug': 'dbg'}.get(
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
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 + 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 = self.extra_cflags + conf_flags
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 = self.extra_ldflags + conf_flags
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,
"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) # uncomment for Conan 2.x
save(self._conanfile, BazelToolchain.bazelrc_name, self._content)
24 changes: 17 additions & 7 deletions conans/assets/templates/new_v2_bazel.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -23,13 +24,20 @@ 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")

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The next PR solves this issue. That one introduces changes in the template to let users create/consume shared libraries in all the platforms.

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)
bazel.configure()
bazel.build(label="//main:{name}")
bazel.build()
franramirez688 marked this conversation as resolved.
Show resolved Hide resolved
franramirez688 marked this conversation as resolved.
Show resolved Hide resolved

def package(self):
dest_lib = os.path.join(self.package_folder, "lib")
Expand Down Expand Up @@ -66,11 +74,12 @@ def requirements(self):

def build(self):
bazel = Bazel(self)
bazel.configure()
bazel.build(label="//main:example")
bazel.build()

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):
Expand Down Expand Up @@ -104,7 +113,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()
"""

Expand All @@ -130,11 +139,12 @@ 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)
bazel.configure()
bazel.build(label="//main:{name}")
bazel.build()

def package(self):
dest_bin = os.path.join(self.package_folder, "bin")
Expand Down
Loading