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

Conan (third party) support for ctypes native libraries #5998

Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
19b2824
reimplement a previous PR -- ignore this
cosmicexplorer Apr 25, 2018
91641b7
Fix git history...v2
Jun 22, 2018
5898ce8
Merge with master
Jun 22, 2018
cb79337
Fix broken int test
Jun 22, 2018
41067f8
Add workaround for linux machines
Jun 22, 2018
70cb412
Add a conan requirement class, refactor conan subsystem, rename fetch…
Jun 22, 2018
3ea6586
Make use of PythonInterpreter.get() explicit
Jun 22, 2018
afb45c8
Refactor task product into its own class, allow multiple conan remote…
Jun 25, 2018
8bc0e29
Rename third party verbiage
Jun 25, 2018
1dab9c0
Add a thirdparty project that links a library file
Jun 26, 2018
8631b9c
Return linker settings to normal
Jun 26, 2018
6a95c66
Add __dso_handle workaround to attempt to pass CI
Jun 26, 2018
d6dde33
Add stdlib c++ to linker commandline for debugging
Jun 26, 2018
932a8af
Refactor names
Jun 26, 2018
bc50b16
Remove all usages of std lib in cpp examples to pass travis CI
Jun 26, 2018
bab73bb
Remove cruft
Jun 27, 2018
bd8f792
First round of cosmicexplorer's comments
Jun 27, 2018
abaef25
Merge branch 'master' of github.com:pantsbuild/pants into clivingston…
Jul 9, 2018
47994c4
Change name of c_library target to debug lint failure
Jul 9, 2018
67eae3f
Address cosmicexplorer's comments and apply patch for fixing include …
Jul 9, 2018
579e8dd
Fix integration test for osx by using clang++ instead of g++
Jul 10, 2018
7439e18
Fix integration test for osx by using clang++ instead of g++
Jul 10, 2018
4c41611
Remove cruft from tests and add clarifying comments
Jul 10, 2018
a9cc38f
Remove C code from 3rdparty testprojects
Jul 10, 2018
ac050f3
debug travis
Jul 10, 2018
9bc4c35
Revert regex refactor
Jul 10, 2018
b1ecc81
Debug patch for clang-based tests in CI
Jul 10, 2018
611579f
Travis debug 2
Jul 10, 2018
05c2cf7
Travis debug 3
Jul 10, 2018
40b60bb
Dummy change to refresh test sources in CI
Jul 10, 2018
1bf69da
just try
cosmicexplorer Jul 12, 2018
ee0fa06
Merge branch 'master' into native-thirdparty-passes-unit-tests
cosmicexplorer Jul 12, 2018
0047140
revert changes to llvm.py and test_native_toolchain.py
cosmicexplorer Jul 12, 2018
7fc20ac
add back g++ hack
cosmicexplorer Jul 12, 2018
529f58c
Change int test
Jul 13, 2018
0818064
Merge branch 'master' of github.com:pantsbuild/pants into clivingston…
Jul 13, 2018
5803352
Add cmdline slicing for conan calls due to new WrappedPex functionality
Jul 14, 2018
5fcc69b
Remove pdb statement
Jul 16, 2018
abdffa6
Merge branch 'master' of github.com:pantsbuild/pants into clivingston…
Jul 16, 2018
e0e772a
Dummy change to trigger CI
Jul 16, 2018
8d028c0
Fix osx test failure
Jul 16, 2018
cf64653
Merge branch 'master' of github.com:pantsbuild/pants into clivingston…
Jul 16, 2018
51d4829
Add github issue link to fixme in llvm.py
Jul 17, 2018
b7b5916
Add Danny's patch to fix broken unit tests
Jul 17, 2018
4a8bedb
Add github issue link and comment on why it makes sense to slice the …
Jul 17, 2018
ea4c701
Cosmicexplorer final comments
Jul 17, 2018
5dbaea4
Add more extensive testing
Jul 17, 2018
84be507
Add 1 issue link
Jul 18, 2018
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
4 changes: 4 additions & 0 deletions src/python/pants/backend/native/register.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@
from pants.backend.native.subsystems.binaries.llvm import create_llvm_rules
from pants.backend.native.subsystems.native_toolchain import create_native_toolchain_rules
from pants.backend.native.subsystems.xcode_cli_tools import create_xcode_cli_tools_rules
from pants.backend.native.targets.external_native_library import ExternalNativeLibrary
from pants.backend.native.targets.native_artifact import NativeArtifact
from pants.backend.native.targets.native_library import CLibrary, CppLibrary
from pants.backend.native.tasks.c_compile import CCompile
from pants.backend.native.tasks.cpp_compile import CppCompile
from pants.backend.native.tasks.link_shared_libraries import LinkSharedLibraries
from pants.backend.native.tasks.native_external_library_fetch import NativeExternalLibraryFetch
from pants.build_graph.build_file_aliases import BuildFileAliases
from pants.goal.task_registrar import TaskRegistrar as task

Expand All @@ -25,6 +27,7 @@ def build_file_aliases():
targets={
CLibrary.alias(): CLibrary,
CppLibrary.alias(): CppLibrary,
ExternalNativeLibrary.alias(): ExternalNativeLibrary
Copy link
Contributor

Choose a reason for hiding this comment

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

We should have consistent formatting of commas, at least in the native backend. I'll use this style in the future so we don't have any conflict, but also, it's generally a very good idea to maintain the same style as the rest of the file/directory, no matter where it is. Style changes aren't usually big enough to require a change on their own, and many people may not point it out for fear of being nitpicky, and it's really helpful if you can make sure to uphold whatever style is used in the current file so people in the future editing this file are clear on which to use.

},
objects={
NativeArtifact.alias(): NativeArtifact,
Expand All @@ -35,6 +38,7 @@ def build_file_aliases():
def register_goals():
# FIXME(#5962): register these under the 'compile' goal when we eliminate the product transitive
# dependency from export -> compile.
task(name='native-third-party-fetch', action=NativeExternalLibraryFetch).install('native-compile')
task(name='c-for-ctypes', action=CCompile).install('native-compile')
task(name='cpp-for-ctypes', action=CppCompile).install('native-compile')
task(name='shared-libraries', action=LinkSharedLibraries).install('link')
Expand Down
91 changes: 91 additions & 0 deletions src/python/pants/backend/native/subsystems/conan.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# coding=utf-8
# Copyright 2018 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from __future__ import (absolute_import, division, generators, nested_scopes, print_function,
unicode_literals, with_statement)

import logging
import os

from pex.interpreter import PythonInterpreter
from pex.pex import PEX
from pex.pex_builder import PEXBuilder
from pex.pex_info import PexInfo

from pants.backend.python.python_requirement import PythonRequirement
from pants.backend.python.tasks.pex_build_util import dump_requirements
from pants.backend.python.tasks.wrapped_pex import WrappedPEX
from pants.base.build_environment import get_pants_cachedir
from pants.subsystem.subsystem import Subsystem
from pants.util.dirutil import safe_concurrent_creation


log = logging.getLogger(__name__)
Copy link
Contributor

Choose a reason for hiding this comment

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

This is always called logger, typically, and changing the name makes it less clear that this is the canonical normal logger that every other file is using.

Copy link
Contributor Author

Choose a reason for hiding this comment

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



class Conan(Subsystem):
"""Pex binary for the conan package manager."""
options_scope = 'conan'

@staticmethod
def default_conan_requirements():
Copy link
Contributor

Choose a reason for hiding this comment

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

I would make this a class field.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

return (
'conan==1.4.4',
'PyJWT>=1.4.0, <2.0.0',
'requests>=2.7.0, <3.0.0',
'colorama>=0.3.3, <0.4.0',
'PyYAML>=3.11, <3.13.0',
'patch==1.16',
'fasteners>=0.14.1',
'six>=1.10.0',
'node-semver==0.2.0',
'distro>=1.0.2, <1.2.0',
'pylint>=1.8.1, <1.9.0',
'future==0.16.0',
'pygments>=2.0, <3.0',
'astroid>=1.6, <1.7',
'deprecation>=2.0, <2.1'
)

@classmethod
def register_options(cls, register):
super(Conan, cls).register_options(register)
register('--conan-requirements', type=list, default=cls.default_conan_requirements(),
advanced=True, help='The requirements used to build to conan client pex.')
Copy link
Contributor

Choose a reason for hiding this comment

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

I generally prefer to have the help on a new line, but this isn't followed a lot and isn't that important.

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, this should read The requirements used to build the conan client pex..

Copy link
Contributor Author

Choose a reason for hiding this comment

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


class ConanBinary(object):
Copy link
Contributor

Choose a reason for hiding this comment

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

I would make this subclass datatype(['pex']), which would allow you to remove the entire body of this class.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

"""A `conan` PEX binary."""

def __init__(self, pex):
self._pex = pex

@property
def pex(self):
"""Return the conan binary PEX.

:rtype: :class:`pex.pex.PEX`
"""
return self._pex

@classmethod
Copy link
Contributor

Choose a reason for hiding this comment

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

I usually try to move all overrides of Task methods to the top of the class definition wherever possible, as this makes it easier to see what strange behavior the task might have.

Also, typically implementation versions have started at 0, not 1, but that's not important.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

def implementation_version(cls):
return super(Conan, cls).implementation_version() + [('Conan', 1)]

def bootstrap_conan(self):
Copy link
Contributor

Choose a reason for hiding this comment

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

This is neat - one day we should generalize it in a manner similar to JVMTools.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed, this is really neat!

pex_info = PexInfo.default()
pex_info.entry_point = 'conans.conan'
conan_bootstrap_dir = os.path.join(get_pants_cachedir(), 'conan_support')
conan_pex_path = os.path.join(conan_bootstrap_dir, 'conan_binary')
interpreter = PythonInterpreter.get()
if os.path.exists(conan_pex_path):
conan_binary = WrappedPEX(PEX(conan_pex_path, interpreter), interpreter)
return self.ConanBinary(conan_binary)
else:
Copy link
Contributor

Choose a reason for hiding this comment

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

This line has trailing whitespace.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

with safe_concurrent_creation(conan_pex_path) as safe_path:
builder = PEXBuilder(safe_path, interpreter, pex_info=pex_info)
reqs = [PythonRequirement(req) for req in self.get_options().conan_requirements]
dump_requirements(builder, interpreter, reqs, log)
builder.freeze()
conan_binary = WrappedPEX(PEX(conan_pex_path, interpreter), interpreter)
return self.ConanBinary(conan_binary)
42 changes: 42 additions & 0 deletions src/python/pants/backend/native/targets/external_native_library.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# coding=utf-8
# Copyright 2018 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from __future__ import (absolute_import, division, generators, nested_scopes, print_function,
unicode_literals, with_statement)

from pants.base.payload import Payload
from pants.base.payload_field import PrimitiveField
from pants.base.validation import assert_list
from pants.build_graph.target import Target


class ExternalNativeLibrary(Target):
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a good name for this target at this point in time! It may be worth revisiting later, along with the C/C++ library target names.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

"""A set of Conan package strings to be passed to the Conan package manager."""

@classmethod
def alias(cls):
return 'external_native_library'

def __init__(self, payload=None, packages=None, **kwargs):
"""
:param packages: a list of Conan-style package strings

Example:
lzo/2.10@twitter/stable
"""
payload = payload or Payload()

assert_list(packages, key_arg='packages')
payload.add_fields({
'packages': PrimitiveField(packages),
})
super(ExternalNativeLibrary, self).__init__(payload=payload, **kwargs)

@property
def packages(self):
return self.payload.packages

@property
def lib_names(self):
return [pkg_name.split('/')[0] for pkg_name in self.payload.packages]
Copy link
Contributor

Choose a reason for hiding this comment

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

This should use re.match(r'^([^/]+)/', pkg_name).group(1). In general, it's much, much easier to understand regexes than manual string manipulation, and I would strongly advise spending time to find the right regex to solve these sorts of problems. One page I found especially useful was https://regex101.com/, but I will regularly open up a python prompt and sanity test any regex I ever write before using it, which makes it easier to avoid errors.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

13 changes: 13 additions & 0 deletions src/python/pants/backend/native/tasks/link_shared_libraries.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from pants.backend.native.subsystems.native_toolchain import NativeToolchain
from pants.backend.native.targets.native_library import NativeLibrary
from pants.backend.native.tasks.native_compile import NativeTargetDependencies, ObjectFiles
from pants.backend.native.tasks.native_external_library_fetch import NativeExternalLibraryFetch
from pants.backend.native.tasks.native_task import NativeTask
from pants.base.exceptions import TaskError
from pants.base.workunit import WorkUnit, WorkUnitLabel
Expand Down Expand Up @@ -142,6 +143,16 @@ def _make_link_request(self, vt, compiled_objects_product, native_target_deps_pr
def _get_shared_lib_cmdline_args(self):
return self.linker.platform.resolve_platform_specific(self._SHARED_CMDLINE_ARGS)

def _get_third_party_lib_args(self):
lib_args = []
tp_files_product = self.context.products.get_data(NativeExternalLibraryFetch.NativeExternalLibraryFiles)
Copy link
Contributor

Choose a reason for hiding this comment

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

This product dependency needs to be required in a prepare() method for this class.

In general I really prefer to fetch all products in the execute() method, because it makes it much easier to see what products a task depends on, and where, and I would suggest you do that here (and just pass it in as an argument).

Copy link
Contributor Author

@CMLivingston CMLivingston Jun 27, 2018

Choose a reason for hiding this comment

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

if tp_files_product.lib_names:
for lib_name in tp_files_product.lib_names:
lib_args.append('-l{}'.format(lib_name))
lib_dir_arg = '-L{}'.format(tp_files_product.lib)
Copy link
Contributor

@cosmicexplorer cosmicexplorer Jun 27, 2018

Choose a reason for hiding this comment

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

I think this logic (generating command-line arguments from lib and lib_names) should be moved to the NativeExternalLibraryFetch.NativeExternalLibraryFiles class. That way you could remove this method entirely.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

lib_args.append(lib_dir_arg)
return lib_args

def _execute_link_request(self, link_request):
object_files = link_request.object_files

Expand All @@ -156,6 +167,8 @@ def _execute_link_request(self, link_request):
# We are executing in the results_dir, so get absolute paths for everything.
cmd = ([linker.exe_filename] +
self._get_shared_lib_cmdline_args() +
self._get_third_party_lib_args() +
['-lstdc++'] +
Copy link
Contributor

Choose a reason for hiding this comment

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

The -lstdc++ is going to be removed, right?

['-o', os.path.abspath(resulting_shared_lib_path)] +
[os.path.abspath(obj) for obj in object_files])

Expand Down
12 changes: 12 additions & 0 deletions src/python/pants/backend/native/tasks/native_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from pants.backend.native.config.environment import Executable
from pants.backend.native.targets.native_library import NativeLibrary
from pants.backend.native.tasks.native_external_library_fetch import NativeExternalLibraryFetch
from pants.backend.native.tasks.native_task import NativeTask
from pants.base.exceptions import TaskError
from pants.base.workunit import WorkUnit, WorkUnitLabel
Expand Down Expand Up @@ -55,6 +56,7 @@ class NativeCompile(NativeTask, AbstractClass):
# `NativeCompile` will use `workunit_label` as the name of the workunit when executing the
# compiler process. `workunit_label` must be set to a string.
workunit_label = None
workunit_name = 'native-compile'
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this used anywhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yup, on line 278 in the NativeCompileError.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, that was a mistake, it's supposed to be workunit_label. Could you make that change?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks!


@classmethod
def product_types(cls):
Expand Down Expand Up @@ -185,10 +187,20 @@ def get_compiler(self):
def _compiler(self):
return self.get_compiler()

def get_third_party_include_dirs(self):
inc_dir = []
tp_files_product = self.context.products.get_data(NativeExternalLibraryFetch.NativeExternalLibraryFiles)
Copy link
Contributor

Choose a reason for hiding this comment

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

Again, it's usually much more clear to fetch products in the body of the execute() method, and if that is done this method can be removed entirely as well (making it into a separate method actually obscures the logic here a little).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

directory = tp_files_product.include
if directory:
inc_dir = [directory]
return inc_dir

def _make_compile_request(self, versioned_target, dependencies):
target = versioned_target.target
include_dirs = [self._include_dirs_for_target(dep_tgt) for dep_tgt in dependencies]
Copy link
Contributor

Choose a reason for hiding this comment

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

Generally, if I'm mutating a variable, I isolate that along with the initialization of the variable and surround it by at least one line of whitespace on both sides, a habit I learned the hard way, and I would recommend doing that here and anywhere else this occurs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

include_dirs.extend(self.get_third_party_include_dirs())
sources_and_headers = self.get_sources_headers_for_target(target)

return NativeCompileRequest(
compiler=self._compiler,
include_dirs=include_dirs,
Expand Down
Loading