Skip to content

Add testproject with C/C++ interdependent targets, fix native backend design mistake - #6628

Merged
cosmicexplorer merged 7 commits into
pantsbuild:masterfrom
cosmicexplorer:add-dependencies-ctypes-example
Oct 18, 2018
Merged

Add testproject with C/C++ interdependent targets, fix native backend design mistake#6628
cosmicexplorer merged 7 commits into
pantsbuild:masterfrom
cosmicexplorer:add-dependencies-ctypes-example

Conversation

@cosmicexplorer

@cosmicexplorer cosmicexplorer commented Oct 14, 2018

Copy link
Copy Markdown
Contributor

Problem

This probably blocks #6486 and #6492.

As @CMLivingston and I realized sometime this week (to my immense dismay), we had no tests of native library targets depending on each other. When making these examples, I realized two things:

  1. --strict-deps doesn't really make sense as two separate options for C and C++ targets which can depend on each other.
  2. The implementation of --strict-deps in the native backend ("caching" native target interdependencies in a product from the compiler tasks) was absurd and unnecessary.

I think I vaguely recall that the fact that LinkSharedLibraries previously did not have a subsystem dependency on the CCompileSettings or CppCompileSettings subsystems may have led to a subtle caching bug in the link task when options in those subsystems were modified, but I don't remember all the details right now. Regardless, that bug would have been fixed with this PR -- see the below:

Solution

  • Move dependency calculation into a single NativeBuildSettings subsystem which is consumed by
    compile and link tasks, and use it to calculate the dependencies in both tasks.
    • Drop the very unnecessary NativeTargetDependencies product.
  • Move a ton of logic into NativeTask that really should have been there instead of NativeCompile, but couldn't because of the hacked together dependency calculation.
  • Add a testproject with C and C++ targets depending on each other, and an integration test showing the above all works.

Result

We have a working example of C and C++ targets depending on each other, resulting in idiomatic enough C and C++, which also work extremely effectively in a python_dist() target.

@stuhood stuhood left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nice cleanup. Thanks!

"""Any settings relevant to a compiler invocation."""
class NativeBuildSettings(Subsystem):
"""Any settings relevant to a compiler and/or linker invocation."""
options_scope = 'native-build-settings'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Going from independent C/CPP subsystems to a merged one is a slightly odd, but maybe understandable step. I'm guessing you initially modeled this after the Java and Scala subsystems. If people think of both languages as interchangeable (my guess is that they don't?) then this might be fine... otherwise, doing a bit of work to identify which language a target is building and apply slightly different settings could be valuable in the long run (it's hard to split them later).

Anecdotally: Java and Scala having different strict_deps settings has been a lifesaver, because Scala's compiler has drastically different dep requirements, and it's a much larger step to enable strict deps there.

@cosmicexplorer cosmicexplorer Oct 14, 2018

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think people don't think of the two languages as interchangeable because it has literally not been possible to formulate code as such without sacrificing a goat or two, even in the case of mixed C and C++ code. The reason for the merged subsystem was to make it clear that C and C++ are going to depend on each other the exact same way -- my goal is 0% to let it become a kitchen sink. I think --fatal-warnings is an example of something that could be split back into C and C++-specific subsystems (I'll do that now). Does that address your concern in:

otherwise, doing a bit of work to identify which language a target is building and apply slightly different settings could be valuable in the long run (it's hard to split them later).

I think that --strict-deps as a concept doesn't really exist in traditional C/C++ build systems, and separately, I think if we can support zero-(user-)overhead interdependence of C and C++ targets, it would be nice to uphold. I can make it clear in the docstring that NativeBuildSettings is for "pants stuff and not things like compiler arguments".

Anecdotally: Java and Scala having different strict_deps settings has been a lifesaver, because Scala's compiler has drastically different dep requirements, and it's a much larger step to enable strict deps there.

Could you elaborate on this? It is currently my impression (as in the above paragraph) that because --strict-deps is already this new thing we are introducing into C/C++, and is entirely managed by pants, it makes more sense to merge that setting. What does "drastically different dep requirements" mean?

@cosmicexplorer cosmicexplorer Oct 14, 2018

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Also, I wouldn't really call this "modelled" after the Java and Scala subsystems -- I had already made up a quick subsystem base class to calculate subsystem/target mirrored options, I just added a bool option to it, and used the existing strict deps calculation methods. I had originally done this before you did the zinc upgrade and literally made a new DependencyContext base class like I think you did or whatever, regardless that merge conflict was fun.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I want you to know that it took all of my strength to not implement --compiler-option-sets just now but I saved that for later because this PR otherwise works.

Separately, the test failure (in testprojects integration) is because we don't currently have a way to allow any command-line option to supersede a target option like --strict-deps (which was the idea, but it makes it hard to test). I will figure out a low-overhead solution that allows us to have these building in that integration test.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think I'll just rewrite that BUILD file temporarily in the test, that's easy.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done, that test passes locally.

@cosmicexplorer
cosmicexplorer force-pushed the add-dependencies-ctypes-example branch from 030d62b to 8367461 Compare October 14, 2018 20:20
cosmicexplorer added a commit that referenced this pull request Oct 15, 2018
…6630)

Takeover of #6492 (which has completely passed review) as it was blocked by progress on two other PRs I have up (#6486, #6628) due to potential merge conflicts, which I can resolve when they come up for each of these PRs to unblock landing them in parallel.

The body of #6492 was:

### Problem

As described in #6178, the `NativeExternalLibraryFiles` products of a `conan` resolve are not currently partitioned by target, which means it isn't possible to expose individual 3rdparty deps to only their declared dependents.

### Solution

Partition the `NativeExternalLibraryFiles` product using `UnionProduct` while producing it in `NativeExternalLibraryFetch` (and switch to using isolated `vt.results_dir` directories per `external_native_library` target), and consume the split product in `NativeCompile` and `LinkSharedLibraries`.

### Result

Only declared dependents have access to 3rdparty libraries. Fixes #6178.
@cosmicexplorer
cosmicexplorer force-pushed the add-dependencies-ctypes-example branch from 8367461 to a4e3b90 Compare October 15, 2018 04:25
The indirect method of "caching" dependent target calculation we had previously resulted in
`LinkSharedLibraries` not incorporating the value of e.g. `--strict-deps` into its task
fingerprint. This fell apart because although we allow C and C++ targets to depend on each
other (and therefore `--strict-deps` being different for those target types is undefined -- this led
to errors).

We collapse the `NativeCompileSettings` subclasses (which take care of calculating dependencies given
the value of `--strict-deps`) into a single `NativeBuildSettings` subsystem, which is consumed by
compile and link tasks, and calculate the dependencies twice (which there was no reason we shouldn't
have been doing before).
@cosmicexplorer
cosmicexplorer force-pushed the add-dependencies-ctypes-example branch from a4e3b90 to 71bda6a Compare October 15, 2018 05:00
super(NativeBuildSettings, cls).register_options(register)

register('--strict-deps', type=bool, default=True, fingerprint=True, advanced=True,
help='The default for the "strict_deps" argument for targets of this language.')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This help message does not really explain anything to the user who doesn't know about strict deps. Is the full explanation included anywhere in a help message?

@cosmicexplorer cosmicexplorer Oct 16, 2018

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is a trash message that I distinctly remember telling myself to fix a long time ago. Will fix, thanks a lot for catching this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Should be fixed!

@CMLivingston CMLivingston left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Just one small comment - Great work, ship it!

return NativeToolchain.scoped_instance(self)

def get_compile_settings(self):
def _cpp_compile_settings(self):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Any reason not to inline the @memoized_property and CppCompileSettings.scoped_instance(self) to get_compile_settings?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

None! Will remove.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done!


@memoized_property
def _cpp_toolchain(self):
return self._request_single(LLVMCppToolchain, self._native_toolchain).cpp_toolchain

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This still confuses me mightily (we're in the context of CppCompile and both LLVMCppToolchain and GCCCppToolchain exist, but LLVMCppToolchain is picked here unconditionally and without explanation), but out of scope.

@cosmicexplorer cosmicexplorer Oct 16, 2018

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That's true. Since we now have testing using both compilers (I think not having this was the initial reason for the unconditional selection which nobody else called me out on -- I don't remember why that wasn't resolved the last time you brought this up) this should probably be fixed.

@cosmicexplorer cosmicexplorer Oct 16, 2018

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'm thinking of an enum option --toolchain-type for GCC vs LLVM on NativeBuildSettings which is the easiest thing in the entire world to do.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Left a comment in the thread -- I have a WIP PR that does exactly that but would depend on this getting merged first. It's not that many lines.

mapping this subsystem's options attribute name (with underscores) to the corresponding target's
keyword argument name.
"""
mirrored_option_to_kwarg_map = None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In NativeCompile below you use

@classproperty
def workunit_label(cls):
  raise NotImplementedError(...)

Any reason not to use this here as well both to gain a method to carry the doc and a bit more robustness?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I literally made the docstring above by moving comments from above that field -- will make this change.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed -- if there's a useful value for what's inside the NotImplementedError(...) I can edit that -- just couldn't think of anything.

def get_compile_settings(self):
"""Return a subclass of NativeBuildStepSettingsBase.

NB: the result of this method is cached in self._compile_settings and therefore it is only

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

"...in self._compile_settings" - too much detail. How about just:
"NB: Subclasses will be queried for the compile settings once and the result cached."

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Will do, this was a lazy docstring cleanup.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done!

def get_compiler(self):
"""An instance of `Executable` which can be invoked to compile files.

NB: the result of this method is cached in self._compiler and therefore it is only called once!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ditto. Referring a subclass to internal methods they do not need not know about instead of the behavior they may need to know about.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Will fix, noted.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed!

# to compile, and the `dependent_target_constraint` to determine which dependent targets to
# operate on for `strict_deps` calculation.
# NB: `source_target_constraint` must be overridden.
source_target_constraint = None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Another instance of indecision on how to do class properties that must be over-ridden. Pick a style.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

= None is a placeholder and always annoyed me with its inflexibility but the prospect of a classproperty hadn't occurred to me. Also noting the indecision, maintaining style across files in a pr isn't something I currently check enough in self-review.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed! Also unclear if there is a useful string to pass to NotImplementedError(...) but otherwise fixed.

@cosmicexplorer

Copy link
Copy Markdown
Contributor Author

I broke out #6638 to track some errors which might be occurring locally, but which shouldn't affect this PR. I'm going to address the last comment on toolchain selection with a WIP PR that I will create immediately after this lands.

"""
raise NotImplementedError()

@memoized_classproperty

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm happy with this change if you are, but I was not bothered by dependent_target_constraint since it just looked like a class constant. It was only source_target_constraint which was commented as needing an override that seemed out of line with what you had done elsewhere for class-level bits that needed to be overridden.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I was thinking it could be useful to have the docstring right there instead of in NativeTask. It certainly doesn't need to be memoized.

def _c_toolchain(self):
return self._request_single(LLVMCToolchain, self._native_toolchain).c_toolchain

def get_compile_settings(self):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Just checking you mean to drop the memoization when inlining here and in cpp_compile.py below.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This bit was intentional! This was in response to clearing up the docstrings for these abstractmethods in NativeTask to make it clear that this method is only called once (see NativeTask). If it would seem to make more sense to let the leaf tasks handle the memoization, we can move it there.

@cosmicexplorer
cosmicexplorer merged commit f5120a2 into pantsbuild:master Oct 18, 2018
@stuhood stuhood added this to the 1.11.x milestone Oct 18, 2018
@stuhood stuhood removed the needs-cherrypick [CI] label Oct 18, 2018
@stuhood stuhood removed this from the 1.11.x milestone Oct 18, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants