Add streamexecutor-config

Summary:
Similar to llvm-config, gets command-line flags that are needed to build
applications linking against StreamExecutor.

Reviewers: jprice, jlebar

Subscribers: beanz, parallel_libs-commits

Differential Revision: https://reviews.llvm.org/D24302

llvm-svn: 280955
GitOrigin-RevId: 5755bb42ffedba7805d74a938a86e65eea702a3a
diff --git a/streamexecutor/CMakeLists.txt b/streamexecutor/CMakeLists.txt
index 8baf976..fde628f 100644
--- a/streamexecutor/CMakeLists.txt
+++ b/streamexecutor/CMakeLists.txt
@@ -2,6 +2,7 @@
 
 option(STREAM_EXECUTOR_UNIT_TESTS "enable unit tests" ON)
 option(STREAM_EXECUTOR_ENABLE_DOXYGEN "enable StreamExecutor doxygen" ON)
+option(STREAM_EXECUTOR_ENABLE_CONFIG_TOOL "enable building streamexecutor-config tool" ON)
 
 # First find includes relative to the streamexecutor top-level source path.
 include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}/include)
@@ -64,6 +65,10 @@
 add_subdirectory(lib)
 add_subdirectory(examples)
 
+if(STREAM_EXECUTOR_ENABLE_CONFIG_TOOL )
+    add_subdirectory(tools/streamexecutor-config)
+endif(STREAM_EXECUTOR_ENABLE_CONFIG_TOOL )
+
 install(DIRECTORY include/ DESTINATION include)
 
 if (STREAM_EXECUTOR_ENABLE_DOXYGEN)
diff --git a/streamexecutor/Doxyfile.in b/streamexecutor/Doxyfile.in
index 8ca45f1..0b23734 100644
--- a/streamexecutor/Doxyfile.in
+++ b/streamexecutor/Doxyfile.in
@@ -794,7 +794,7 @@
 # Note that the wildcards are matched against the file with absolute path, so to
 # exclude all test directories for example use the pattern */test/*
 
-EXCLUDE_PATTERNS       = */examples/* */unittests/*
+EXCLUDE_PATTERNS       = */examples/* */tools/* */unittests/*
 
 # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
 # (namespaces, classes, functions, etc.) that should be excluded from the
diff --git a/streamexecutor/tools/streamexecutor-config/CMakeLists.txt b/streamexecutor/tools/streamexecutor-config/CMakeLists.txt
new file mode 100644
index 0000000..7c0e5b0
--- /dev/null
+++ b/streamexecutor/tools/streamexecutor-config/CMakeLists.txt
@@ -0,0 +1,3 @@
+find_package(PythonInterp REQUIRED)
+configure_file(streamexecutor-config.in streamexecutor-config)
+install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/streamexecutor-config DESTINATION bin)
diff --git a/streamexecutor/tools/streamexecutor-config/streamexecutor-config.in b/streamexecutor/tools/streamexecutor-config/streamexecutor-config.in
new file mode 100755
index 0000000..6fb4bec
--- /dev/null
+++ b/streamexecutor/tools/streamexecutor-config/streamexecutor-config.in
@@ -0,0 +1,206 @@
+#!@PYTHON_EXECUTABLE@
+#
+#===- streamexecutor-config - Build config script for SE -----*- python -*--===#
+#
+#                     The LLVM Compiler Infrastructure
+#
+# This file is distributed under the University of Illinois Open Source
+# License. See LICENSE.TXT for details.
+#
+#===------------------------------------------------------------------------===#
+
+r"""
+Get configuration info needed to compile programs which use StreamExecutor.
+
+Runs llvm-config and adds StreamExecutor-specific flags to the output. Supports
+only the subset of llvm-config flags that are relevant for applications
+compiling against StreamExecutor.
+
+This utility will typically be used to construct a compile command line for an
+application which depends on the StreamExecutor library.
+
+For example:
+  c++ example.cpp -o example \
+      $(streamexecutor-config \
+          --cppflags --cxxflags --ldflags --libs --system-libs)
+"""
+
+import argparse
+import errno
+import os
+import shlex
+import subprocess
+import sys
+
+def get_llvm_config_dir():
+  """Gets the path to the llvm-config executable.
+
+  Surrounding the cmake replacement with triple quotes should allow for paths
+  that contain quotes."""
+  return r"""@LLVM_BINARY_DIR@/bin"""
+
+def get_cmake_install_prefix():
+  """Gets the value of the cmake variable CMAKE_INSTALL_PREFIX.
+
+  Surrounding the cmake replacement with triple quotes should allow for paths
+  that contain quotes."""
+  return r"""@CMAKE_INSTALL_PREFIX@"""
+
+def cuddle_flag(flag, tokens):
+  """If flag appears by itself in tokens, combines it with the next token.
+
+  >>> tokens = ['-I', '/usr/include']
+  >>> cuddle_flag('-I', tokens)
+  >>> tokens
+  ['-I/usr/include']
+
+  >>> tokens = ['-L', '/usr/lib']
+  >>> cuddle_flag('-L', tokens)
+  >>> tokens
+  ['-L/usr/lib']
+
+  >>> tokens = ['-I']
+  >>> cuddle_flag('-I', tokens)
+  >>> tokens
+  ['-I']
+
+  >>> tokens = ['-I', '/usr/include', '-I', '/usr/local/include']
+  >>> cuddle_flag('-I', tokens)
+  >>> tokens
+  ['-I/usr/include', '-I/usr/local/include']
+  """
+  start = 0
+  while True:
+    try:
+      index = tokens.index(flag, start)
+    except ValueError:
+      return
+    if index + 1 < len(tokens):
+      follower = tokens.pop(index + 1)
+      tokens[index] = flag + follower
+    start = index + 1
+
+def get_llvm_config_output_for_dir(llvm_config_dir, flags_string):
+  """Calls llvm-config at the given path and returns the output with -I and -L
+  flags cuddled."""
+  output = subprocess.check_output(
+      ['%s/llvm-config' % llvm_config_dir] + flags_string.split()).strip()
+  tokens = shlex.split(output)
+  cuddle_flag('-I', tokens)
+  cuddle_flag('-L', tokens)
+  return ' '.join(tokens)
+
+def has_token(token, string):
+  """Checks if the given token appears in the string.
+
+  The token argument must be a single shell token.
+
+  >>> string = '-I/usr/include -L"/usr/lib"'
+  >>> has_token('-I/usr/include', string)
+  True
+  >>> has_token('-I/usr/local/include', string)
+  False
+  >>> has_token('-I"/usr/include"', string)
+  True
+  >>> has_token('-L"/usr/lib"', string)
+  True
+  >>> has_token('-L/usr/lib', string)
+  True
+  """
+  split_token = shlex.split(token)
+  if len(split_token) > 1:
+    raise ValueError('has_token called with a multi-token token: ' + token)
+  escaped_token = split_token[0]
+  return escaped_token in shlex.split(string)
+
+def main():
+  parser = argparse.ArgumentParser(
+      prog='streamexecutor-config',
+      formatter_class=argparse.RawDescriptionHelpFormatter,
+      description=__doc__)
+
+  parser.add_argument(
+      '--cppflags',
+      action='store_true',
+      help=
+        'C preprocessor flags for files that include StreamExecutor headers.')
+
+  parser.add_argument(
+      '--cxxflags',
+      action='store_true',
+      help='C++ compiler flags for files that include StreamExecutor headers.')
+
+  parser.add_argument(
+      '--ldflags',
+      action='store_true',
+      help='Print linker flags.')
+
+  parser.add_argument(
+      '--libs',
+      action='store_true',
+      help='Libraries needed to link against StreamExecutor.')
+
+  parser.add_argument(
+      '--system-libs',
+      action='store_true',
+      help='System libraries needed to link against StreamExecutor.')
+
+  parser.add_argument(
+      '--llvm-config-dir',
+      default=get_llvm_config_dir(),
+      help='Directory containing the llvm-config executable. '\
+          'If not specified, defaults to the cmake-configured location')
+
+  args = parser.parse_args()
+
+  # Print the help message if the user did not pass any flag arguments.
+  if not any(
+      getattr(args, flag)
+        for flag in ('cppflags', 'cxxflags', 'ldflags', 'libs', 'system_libs')):
+    parser.print_help()
+    sys.exit(1)
+
+  # Check for the presence of the llvm-config executable.
+  if not os.path.isfile('%s/llvm-config' % args.llvm_config_dir):
+    sys.exit('llvm-config not found in: ' + args.llvm_config_dir)
+  if not os.access('%s/llvm-config' % args.llvm_config_dir, os.X_OK):
+    sys.exit('llvm-config not executable in: ' + args.llvm_config_dir)
+
+  # We will always use args.llvm_config_dir as the second argument to
+  # get_llvm_config_output_for_path.
+  get_llvm_config_output = lambda flags : get_llvm_config_output_for_dir(
+      args.llvm_config_dir, flags)
+
+  all_flags = []
+
+  if args.cppflags:
+    llvm_flags = get_llvm_config_output('--cppflags')
+    all_flags.append(llvm_flags)
+    se_flag = "-I%s/include" % get_cmake_install_prefix()
+    if not has_token(token=se_flag, string=llvm_flags):
+      all_flags.append(se_flag)
+
+  if args.cxxflags:
+    all_flags.append(get_llvm_config_output('--cxxflags'))
+
+  if args.ldflags:
+    llvm_flags = get_llvm_config_output('--ldflags')
+    all_flags.append(llvm_flags)
+    se_flag = "-L%s/lib" % get_cmake_install_prefix()
+    if not has_token(token=se_flag, string=llvm_flags):
+      all_flags.append(se_flag)
+
+  if args.libs:
+    llvm_flags = get_llvm_config_output('--libs support symbolize')
+    se_flag = '-lstreamexecutor'
+    if not has_token(token=se_flag, string=llvm_flags):
+      all_flags.append(se_flag)
+    all_flags.append(llvm_flags)
+
+  if args.system_libs:
+    all_flags.append(get_llvm_config_output('--system-libs'))
+
+  print(' '.join(all_flags))
+
+if __name__ == '__main__':
+  main()