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

Split C and C++ compilation #1366

Merged
merged 9 commits into from Apr 18, 2018
Merged

Split C and C++ compilation #1366

merged 9 commits into from Apr 18, 2018

Conversation

steeve
Copy link
Contributor

@steeve steeve commented Mar 5, 2018

What it does

This PR splits C and C++ CGo compilation into separate steps. The main idea behind this is for specifying per language options (such as std=c++14 for C++).

For that, the pack builder has been modified to take multiple archives (C and C++) and fuse them.

This is also a required step for adding ObjC support.

I have also added a commit which tries to make relative CC options absolute (via whitelisting)

@jayconrod
Copy link
Contributor

I took a quick look, but I won't be able to do a detailed review until tomorrow. I have a couple concerns before then though:

  • Could you explain a bit more about what the ObjC support will look like? I guess some sources would need to be compiled by objc_library or something like that.
  • Bazel treats the C / C++ toolchain as mostly one thing, configured with CROSSTOOL. I think Objective C / C++ is also configured with CROSSTOOL, but I don't know how the flags are separated. In general, I think CROSSTOOL is the right place for language-specific flags. go_library copts should be reserved for library-specific flags like -D, -I, -framework.
  • If C / C++ / ObjC sources and headers are separated into different cc_library / objc_library rules, how will they compile if there are dependencies between sources in different languages?
  • I mentioned this in the other PR, but there is ongoing work to expose C++ toolchains to Skylark. I'm not sure whether ObjC is part of that effort, but that would let us avoid cc_library and objc_library rules altogether. This is a big enough change that it might be better to wait until that work is complete.

@steeve
Copy link
Contributor Author

steeve commented Mar 5, 2018

Thanks for being so fast.

Here are some answers:

  1. For ObjC support, you can check out the early work on this at https://github.com/znly/rules_go/commit/30ee77332fe195d6819c3b8ff6b9233b20ae985a, but meat is is at https://github.com/znly/rules_go/commit/30ee77332fe195d6819c3b8ff6b9233b20ae985a#diff-1d1e845efd5e9ec6b7a69dcecb01ed38R446
  2. You're right, but if you're building something else than C, setting some flags will contaminate the CGoCodeGen pure C compilation flags and fail the whole thing. In our project, for instance, we're using C++14 in cpp files in Go packages, and setting CXXFLAGS is the only way to make it work. However, if we add -std=c++14 to a pure C compilation, clang will fail.
  3. For now I'm propagating the deps to all the languages. I don't think it's that bad really? Wether or not one uses the deps is up to them but at least Bazel makes them available to the library.
  4. I'd love for this to happen! It would be the cleanest solution for sure. Last I checked though the spec is expected to land for Q1, then there is the implementation. Wet finger estimate point to at least may-june before it's in master. Or maybe you have some insider information ? 😄

@jayconrod
Copy link
Contributor

For c++14, it looks like the recommended way is to pass --cxxopt='-std=c++14' on the command line. You can put this in your tools/bazel.rc file so Bazel picks it up automatically. That will also apply to other cc_library rules.

About C/C++ sandwich: @mhlopko, how's it going? Will ObjC be supported, too? For context, it would be useful to build mixed C/C++/ObjC sources that appear alongside cgo code. Right now, we build C/C++ as a cc_library.

@steeve
Copy link
Contributor Author

steeve commented Mar 5, 2018

For c++14, it looks like the recommended way is to pass --cxxopt='-std=c++14' on the command line. You can put this in your tools/bazel.rc file so Bazel picks it up automatically. That will also apply to other cc_library rules.

Indeed, I saw that flag but I figure it was easier just splitting and doing it package by package and also because Go supports setting CFLAGS and CXXFLAGS separately, too.

@jayconrod
Copy link
Contributor

I'll wait until #1365 is merged before diving into this.

@steeve
Copy link
Contributor Author

steeve commented Mar 6, 2018

Sure thing!

@hlopko
Copy link
Member

hlopko commented Mar 7, 2018

Sorry for the slow reply. We are working on 2 things in C++ rules:

  • Skylark API to C++ toolchain (tracking issue) - so you can ask cc_toolchain to give you the same command line that cc_library/binary would use
  • Skylark API to C++ rules (C++ sandwich, tracking issue) - so you can access C++ providers and interoperate with c++ rules from other Skylark rules.

I don't know much about open source go rules, but I assume you'll want to use the sandwich. That will allow you to consume C++ providers, dig objects out, and use them with cgo. @oquenchil can comment on timeframes.

Re ObjC, they will use their own providers, but tbh I don't know much about that. Maybe @c-parsons has more information.

@steeve
Copy link
Contributor Author

steeve commented Mar 7, 2018

@mhlopko thanks for the reply

Indeed we'd like to be able to return cc providers to be consumed by cc_ rules. Right now we rely on macros and filegroups to do so, and in the end are forced to name them differently.

See for instance at https://github.com/bazelbuild/rules_go/pull/1365/files#diff-dfaef6484291891479c192a6169bc232R90

@steeve
Copy link
Contributor Author

steeve commented Mar 7, 2018

Changed pack to ensure object names are unique among the multiple archives in dd11da3

@jayconrod
Copy link
Contributor

@mhlopko I'm actually most interested in the toolchain being exposed in Skylark. How far along is that? #4571 doesn't have much information. We'd like to be able to invoke a C / C++ / ObjC compiler and linker on some generated code during execution without creating cc_library / objc_library rules. I think we can get the compiler command and flags right now, but I'm guessing the includes and libraries and other toolchain files won't be visible in the sandbox.

The sandwich is also useful for other things, but I'm mainly interested in the toolchain right now.

@steeve
Copy link
Contributor Author

steeve commented Mar 8, 2018

Correct me if I'm wrong, but why not do a pure skylark cc_ rules when #4571 lands ?

@jayconrod
Copy link
Contributor

I think that's the long-term plan, but the C/C++ rules are really complicated, having accumulated a decade's worth of features and idiosyncrasies within Google. A full migration will take years. I hope a simplified version that serves most common cases will be available sooner than that though.

@hlopko
Copy link
Member

hlopko commented Mar 9, 2018

Steeve: that's the end goal, to migrate existing rules into Skylark. But there are things that C++ rules do that are not supported in Skylark yet, plus we want to use the same rules internally and externally, and for that we need to do small incremental steps, making sure google doesn't break.

Jay: Since I still know very little about external go rules let me make sure at least you have enough information to decide what's best :) Part of the sandwich (spoiler alert :) are functions cc_common.compile(...) and cc_common.link(...). AFAIK this is what you need, give C++ rules sources, compile them, make a .a of them, and propagate providers. Toolchain api will only enable you to get the command line flags that C++ rules would use. That's definitely something you need for internal go rules though.

The work is ongoing, optimistically EOQ1 for test runs behind a whitelist. I'll keep you updated.

@oquenchil
Copy link

The work is ongoing, optimistically EOQ1 for test runs behind a whitelist. I'll keep you updated.

This timeframe sounds correct. This should be tested internally first where we have more control on who's using the API and we can change it more easily if necessary.

@steeve
Copy link
Contributor Author

steeve commented Mar 23, 2018

Please hold on this one, I'm going to properly rebase it on top of master.

@steeve
Copy link
Contributor Author

steeve commented Apr 9, 2018

I've properly rebased it and added tests.
@jayconrod your move

@steeve
Copy link
Contributor Author

steeve commented Apr 9, 2018

The test failure is due to the Travis machine apparently not having a C++14 compiler installed.

https://travis-ci.org/bazelbuild/rules_go/jobs/363343765#L2495

ERROR: /home/travis/build/bazelbuild/rules_go/tests/core/cxx/BUILD.bazel:3:1: C++ compilation of rule '//tests/core/cxx:go_default_library.cgo_cxx_lib' failed (Exit 1): gcc failed: error executing command 

https://travis-ci.org/bazelbuild/rules_go/jobs/363343765#L2504

gcc: error: unrecognized command line option '-std=c++14'

I can change the test to use C++11. But I was using C++14 specific feaures to highlight it, but oh well.

Copy link
Contributor

@jayconrod jayconrod left a comment

Choose a reason for hiding this comment

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

Thanks for rebasing and sorry for the slow response; I just got back from vacation.

I have a few comments on specifics, but this seems like the right approach given our current constraints. I hope that when Bazel lets us build C/C++/ObjC command lines, we can simplify this and stop using separate cc_library rules.

@@ -283,7 +295,7 @@ def _cgo_collect_info_impl(ctx):
gen_go_srcs = codegen.gen_go + import_files,
cgo_deps = codegen.deps,
cgo_exports = codegen.exports,
cgo_archive = _select_archive(ctx.files.lib),
cgo_archives = _select_archives(ctx.files.libs),
Copy link
Contributor

Choose a reason for hiding this comment

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

Please update documentation for GoSource in go/providers.rst to reflect this change.

FYI, I'm okay with small API breaking changes like this in providers between major versions. I doubt many people are using this yet. In the future, we may need to be more strict about this though.

@@ -16,7 +16,7 @@ def emit_pack(go,
in_lib = None,
out_lib = None,
objects = [],
archive = None):
archives = []):
Copy link
Contributor

Choose a reason for hiding this comment

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

Please update documentation in go/toolchains.rst#pack to reflect this change.

@@ -78,20 +78,23 @@ def _c_filter_options(options, blacklist):
return [opt for opt in options
if not any([opt.startswith(prefix) for prefix in blacklist])]

def _select_archive(files):
def _select_archives(files):
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this is safe. See the documentation below. cc_library may produce multiple output files, and we want exactly one of them.

Instead of calling this once on ctx.files.libs, call for each target, i.e., [_select_archive(lib.files) for lib in ctx.attr.libs]

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i still need to check that one out

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok got it, i've pushed a fix

@@ -325,13 +337,17 @@ def setup_cgo_library(name, srcs, cdeps, copts, clinkopts):
"external/" + REPOSITORY_NAME[1:] if len(REPOSITORY_NAME) > 1 else "",
PACKAGE_NAME)
copts = copts + ["-I", base_dir]
cxxopts = cxxopts + ["-I", base_dir]
cppopts = cppopts + ["-I", base_dir]
Copy link
Contributor

Choose a reason for hiding this comment

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

Since we're separating copts and cppopts, this flag should only be added to cppopts.

Please also double-check all mentions of copts in this file. There are several places in _cgo_codegen_impl where it should be changed to cppopts. Basically anything that isn't specific to C or C++ should be changed.

@@ -25,6 +25,8 @@ _CGO_ATTRS = {
"srcs": None,
"cdeps": [],
"copts": [],
"cxxopts": [],
"cppopts": [],
Copy link
Contributor

Choose a reason for hiding this comment

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

Please update attribute tables in go/core.rst to include the new attributes.

This may break people that have C++ code that currently depends on options in copts. Hopefully this won't affect many people. I'll file an issue on Gazelle to separate these.

@@ -0,0 +1,19 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
Copy link
Contributor

Choose a reason for hiding this comment

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

Several test comments:

  1. In terms of test organization, I'd prefer this to be tests/core/cgo, since this is really testing cgo functionality. Could be divided further into language subdirectories (tests/core/cgo/cxx).
  2. We really want to verify that the right set of options get passed to each language. copts should be C-only, cxxopts should be C++-only, cppopts should be both. Easiest way to do that is probably with -D flags, then checking at build time with #if and #error. This would also fix the error on Travis CI due not having a C++14 compiler available.
  3. Please create a README.rst describing the tests, and run tests/update.py.

@steeve
Copy link
Contributor Author

steeve commented Apr 15, 2018

I'm in a bit of a dilemma over copts vs cppopts. When one wants to use -D they now need to put it into cppopts or else it won't work. While is this correct, this is rather not friendly imho...

@steeve
Copy link
Contributor Author

steeve commented Apr 15, 2018

PR updated with most of the advices.
See 8ee185c for what I'm describing

@steeve
Copy link
Contributor Author

steeve commented Apr 16, 2018

Never mind I think I solved it by a41c767

Copy link
Contributor

@jayconrod jayconrod left a comment

Choose a reason for hiding this comment

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

Getting close. A few more minor comments on changes.

outs = []
for lib in libs:
archive = _select_archive(lib.files)
if archive:
Copy link
Contributor

Choose a reason for hiding this comment

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

When would a cc_library not produce any usable archives? Does that happen if there are no source files?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This can happen if there are only header files (which happens if C++ files are missing, for instance).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I realize I could have been clearer. In the case of a regular C compilation, the cc_library responsible of building the C++ files will not produce any archive.


def _cgo_codegen_impl(ctx):
go = go_context(ctx)
if not go.cgo_tools:
fail("Go toolchain does not support cgo")
linkopts = ctx.attr.linkopts[:]
copts = go.cgo_tools.c_options + go.cgo_tools.compiler_options + ctx.attr.copts
cppopts = go.cgo_tools.c_options + go.cgo_tools.compiler_options + ctx.attr.copts + ctx.attr.cppopts
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's keep cppopts and copts separate here for clarity.

cppopts = go.cgo_tools.compiler_options +ctx.attr.cppopts
copts = go.cgo_tools.c_options + ctx.attr.copts

Copy link
Contributor Author

Choose a reason for hiding this comment

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

got it

@@ -161,10 +181,10 @@ def _cgo_codegen_impl(ctx):
# The first -- below is to stop the cgo from processing args, the
# second is an actual arg to forward to the underlying go tool
args.add(["--", "--"])
args.add(copts)
args.add(cppopts)
Copy link
Contributor

Choose a reason for hiding this comment

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

Both cppopts and copts can be added here.

cgo = True,
copts = ["-DRULES_GO_C"],
cxxopts = ["-DRULES_GO_CPP"],
srcs = [
Copy link
Contributor

Choose a reason for hiding this comment

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

Define something in cppopts and check that define is visible in both add.c and add.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.

👍

@@ -392,6 +392,18 @@ Attributes
| Subject to `"Make variable"`_ substitution and `Bourne shell tokenization`_. |
| Only valid if :param:`cgo` = :value:`True`. |
+----------------------------+-----------------------------+---------------------------------------+
| :param:`cxxopts` | :type:`string_list` | :value:`[]` |
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks good, but copy to go_library and go_test.

@steeve
Copy link
Contributor Author

steeve commented Apr 17, 2018

I've updated the PR, thank you for the feedback!

@steeve
Copy link
Contributor Author

steeve commented Apr 18, 2018

@jayconrod do you want me to rebase instead ?

@jayconrod jayconrod merged commit dcaae02 into bazelbuild:master Apr 18, 2018
@jayconrod
Copy link
Contributor

@steeve No need, I was just waiting for CI to run again after resolving the merge conflict.

Thanks again for working on this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants