#!/usr/bin/env python
# coding: utf-8

# Copyright 2014 The Crashpad Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import re
import subprocess
import sys

def FixUserImplementation(implementation):
  """Rewrites a MIG-generated user implementation (.c) file.

  Rewrites the file at |implementation| by adding “__attribute__((unused))” to
  the definition of any structure typedefed as “__Reply” by searching for the
  pattern unique to those structure definitions. These structures are in fact
  unused in the user implementation file, and this will trigger a
  -Wunused-local-typedefs warning in gcc unless removed or marked with the
  “unused” attribute.
  """

  file = open(implementation, 'r+')
  contents = file.read()

  pattern = re.compile('^(\t} __Reply);$', re.MULTILINE)
  contents = pattern.sub(r'\1 __attribute__((unused));', contents)

  file.seek(0)
  file.truncate()
  file.write(contents)
  file.close()

def FixServerImplementation(implementation):
  """Rewrites a MIG-generated server implementation (.c) file.

  Rewrites the file at |implementation| by replacing “mig_internal” with
  “mig_external” on functions that begin with “__MIG_check__”. This makes these
  functions available to other callers outside this file from a linkage
  perspective. It then returns, as a list of lines, declarations that can be
  added to a header file, so that other files that include that header file will
  have access to these declarations from a compilation perspective.
  """

  file = open(implementation, 'r+')
  contents = file.read()

  # Find interesting declarations.
  declaration_pattern = \
      re.compile('^mig_internal (kern_return_t __MIG_check__.*)$',
                 re.MULTILINE)
  declarations = declaration_pattern.findall(contents)

  # Remove “__attribute__((__unused__))” from the declarations, and call them
  # “mig_external” or “extern” depending on whether “mig_external” is defined.
  attribute_pattern = re.compile(r'__attribute__\(\(__unused__\)\) ')
  declarations = ['#ifdef mig_external\nmig_external\n#else\nextern\n#endif\n' +
                  attribute_pattern.sub('', x) +
                  ';\n' for x in declarations]

  # Rewrite the declarations in this file as “mig_external”.
  contents = declaration_pattern.sub(r'mig_external \1', contents);

  file.seek(0)
  file.truncate()
  file.write(contents)
  file.close()
  return declarations

def FixHeader(header, declarations=[]):
  """Rewrites a MIG-generated header (.h) file.

  Rewrites the file at |header| by placing it inside an “extern "C"” block, so
  that it declares things properly when included by a C++ compilation unit.
  |declarations| can be a list of additional declarations to place inside the
  “extern "C"” block after the original contents of |header|.
  """

  file = open(header, 'r+')
  contents = file.read()
  declarations_text = ''.join(declarations)
  contents = '''\
#ifdef __cplusplus
extern "C" {
#endif

%s
%s
#ifdef __cplusplus
}
#endif
''' % (contents, declarations_text)
  file.seek(0)
  file.truncate()
  file.write(contents)
  file.close()

def main(args):
  assert len(args) == 5
  (defs_file, user_c, server_c, user_h, server_h) = args
  subprocess.check_call(['mig',
                         '-user', user_c,
                         '-server', server_c,
                         '-header', user_h,
                         '-sheader', server_h,
                         defs_file])
  FixUserImplementation(user_c)
  server_declarations = FixServerImplementation(server_c)
  FixHeader(user_h)
  FixHeader(server_h, server_declarations)

if __name__ == '__main__':
  sys.exit(main(sys.argv[1:]))