We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Remove ".py" report
Part of #111 

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `pytype/tools/analyze_project/pytype_runner.py`
Content:
```
1 """Use pytype to analyze and infer types for an entire project."""
2 
3 from __future__ import print_function
4 
5 import logging
6 import os
7 import subprocess
8 
9 from pytype import file_utils
10 from pytype import module_utils
11 from pytype.tools.analyze_project import config
12 import six
13 
14 
15 # Generate a default pyi for builtin and system dependencies.
16 DEFAULT_PYI = """
17 from typing import Any
18 def __getattr__(name) -> Any: ...
19 """
20 
21 
22 class Action(object):
23   CHECK = 'check'
24   INFER = 'infer'
25   GENERATE_DEFAULT = 'generate default'
26 
27 
28 class Stage(object):
29   SINGLE_PASS = 'single pass'
30   FIRST_PASS = 'first pass'
31   SECOND_PASS = 'second pass'
32 
33 
34 FIRST_PASS_SUFFIX = '-1'
35 
36 
37 def resolved_file_to_module(f):
38   """Turn an importlab ResolvedFile into a pytype Module."""
39   full_path = f.path
40   target = f.short_path
41   path = full_path[:-len(target)]
42   name = f.module_name
43   # We want to preserve __init__ in the module_name for pytype.
44   if os.path.basename(full_path) == '__init__.py':
45     name += '.__init__'
46   return module_utils.Module(
47       path=path, target=target, name=name, kind=f.__class__.__name__)
48 
49 
50 def deps_from_import_graph(import_graph):
51   """Construct PytypeRunner args from an importlab.ImportGraph instance.
52 
53   Kept as a separate function so PytypeRunner can be tested independently of
54   importlab.
55 
56   Args:
57     import_graph: An importlab.ImportGraph instance.
58 
59   Returns:
60     List of (tuple of source modules, tuple of direct deps) in dependency order.
61   """
62   def get_filenames(node):
63     if isinstance(node, six.string_types):
64       return (node,)
65     else:
66       # Make the build as deterministic as possible to minimize rebuilds.
67       return tuple(sorted(node.nodes))
68   def make_module(filename):
69     return resolved_file_to_module(import_graph.provenance[filename])
70   modules = []
71   for node, deps in reversed(import_graph.deps_list()):
72     files = tuple(make_module(f) for f in get_filenames(node))
73     # flatten and dedup
74     seen = set()
75     deps = tuple(make_module(d) for dep in deps for d in get_filenames(dep)
76                  if d not in seen and not seen.add(d))
77     modules.append((files, deps))
78   return modules
79 
80 
81 def _module_to_output_path(mod):
82   """Convert a module to an output path."""
83   path, _ = os.path.splitext(mod.target)
84   if path.replace(os.path.sep, '.').endswith(mod.name):
85     # Preferentially use the short path.
86     return path[-len(mod.name):]
87   else:
88     # Fall back to computing the output path from the name, which is a last
89     # resort because it messes up hidden files. Since such files aren't valid
90     # python packages anyway, we preserve any leading '.' in order to not
91     # create a file directly in / (which would likely cause a crash with a
92     # permission error) and let the rest of the path be mangled.
93     return mod.name[0] + mod.name[1:].replace('.', os.path.sep)
94 
95 
96 def get_imports_map(deps, module_to_imports_map, module_to_output):
97   """Get a short path -> full path map for the given deps."""
98   imports_map = {}
99   for m in deps:
100     if m in module_to_imports_map:
101       imports_map.update(module_to_imports_map[m])
102     imports_map[_module_to_output_path(m)] = module_to_output[m]
103   return imports_map
104 
105 
106 class PytypeRunner(object):
107   """Runs pytype over an import graph."""
108 
109   def __init__(self, conf, sorted_sources):
110     self.filenames = set(conf.inputs)  # files to type-check
111     # all source modules as a sequence of (module, direct_deps)
112     self.sorted_sources = sorted_sources
113     self.python_version = conf.python_version
114     self.pyi_dir = os.path.join(conf.output, 'pyi')
115     self.imports_dir = os.path.join(conf.output, 'imports')
116     self.ninja_file = os.path.join(conf.output, 'build.ninja')
117     self.custom_options = [
118         (k, getattr(conf, k)) for k in set(conf.__slots__) - set(config.ITEMS)]
119     self.keep_going = conf.keep_going
120 
121   def set_custom_options(self, flags_with_values, binary_flags):
122     """Merge self.custom_options into flags_with_values and binary_flags."""
123     for dest, value in self.custom_options:
124       arg_info = config.get_pytype_single_item(dest).arg_info
125       if arg_info.to_command_line:
126         value = arg_info.to_command_line(value)
127       if isinstance(value, bool):
128         if value:
129           binary_flags.add(arg_info.flag)
130         else:
131           binary_flags.discard(arg_info.flag)
132       elif value:
133         flags_with_values[arg_info.flag] = str(value)
134 
135   def get_pytype_command_for_ninja(self, report_errors):
136     """Get the command line for running pytype."""
137     exe = 'pytype-single'
138     flags_with_values = {
139         '--imports_info': '$imports',
140         '-V': self.python_version,
141         '-o': '$out',
142         '--module-name': '$module',
143     }
144     binary_flags = {
145         '--quick',
146         '--analyze-annotated' if report_errors else '--no-report-errors',
147         '--nofail',
148     }
149     if report_errors:
150       self.set_custom_options(flags_with_values, binary_flags)
151     # Order the flags so that ninja recognizes commands across runs.
152     return (
153         [exe] +
154         list(sum(sorted(flags_with_values.items()), ())) +
155         sorted(binary_flags) +
156         ['$in']
157     )
158 
159   def make_imports_dir(self):
160     try:
161       file_utils.makedirs(self.imports_dir)
162     except OSError:
163       logging.error('Could not create imports directory: %s', self.imports_dir)
164       return False
165     return True
166 
167   def write_default_pyi(self):
168     """Write a default pyi file."""
169     output = os.path.join(self.imports_dir, 'default.pyi')
170     with open(output, 'w') as f:
171       f.write(DEFAULT_PYI)
172     return output
173 
174   def write_imports(self, module_name, imports_map, suffix):
175     """Write a .imports file."""
176     output = os.path.join(self.imports_dir, module_name + '.imports' + suffix)
177     with open(output, 'w') as f:
178       for item in imports_map.items():
179         f.write('%s %s\n' % item)
180     return output
181 
182   def get_module_action(self, module):
183     """Get the action for the given module.
184 
185     Args:
186       module: A module_utils.Module object.
187 
188     Returns:
189       An Action object, or None for a non-Python file.
190     """
191     f = module.full_path
192     # Report errors for files we are analysing directly.
193     if f in self.filenames:
194       action = Action.CHECK
195       report = logging.warning
196     else:
197       action = Action.INFER
198       report = logging.info
199     if not f.endswith('.py'):
200       report('skipped: non-Python file %s', f)
201       return None
202     # For builtin and system files, do not attempt to generate a pyi.
203     if module.kind in ('Builtin', 'System'):
204       action = Action.GENERATE_DEFAULT
205       report('%s: %s module %s', action, module.kind, module.name)
206     return action
207 
208   def yield_sorted_modules(self):
209     """Yield modules from our sorted source files."""
210     for group, deps in self.sorted_sources:
211       modules = []
212       for module in group:
213         action = self.get_module_action(module)
214         if action:
215           modules.append((module, action))
216       if len(modules) == 1:
217         yield modules[0] + (deps, Stage.SINGLE_PASS)
218       else:
219         # If we have a cycle we run pytype over the files twice. So that we
220         # don't fail on missing dependencies, we'll ignore errors the first
221         # time and add the cycle itself to the dependencies the second time.
222         second_pass_deps = []
223         for module, action in modules:
224           second_pass_deps.append(module)
225           if action == Action.CHECK:
226             action = Action.INFER
227           yield module, action, deps, Stage.FIRST_PASS
228         deps += tuple(second_pass_deps)
229         for module, action in modules:
230           # We don't need to run generate_default twice
231           if action != Action.GENERATE_DEFAULT:
232             yield module, action, deps, Stage.SECOND_PASS
233 
234   def write_ninja_preamble(self):
235     """Write out the pytype-single commands that the build will call."""
236     with open(self.ninja_file, 'w') as f:
237       for action, report_errors in ((Action.INFER, False),
238                                     (Action.CHECK, True)):
239         command = ' '.join(
240             self.get_pytype_command_for_ninja(report_errors=report_errors))
241         logging.info('%s command: %s', action, command)
242         f.write('rule %s\n  command = %s\n' % (action, command))
243 
244   def write_build_statement(self, module, action, deps, imports, suffix):
245     """Write a build statement for the given module.
246 
247     Args:
248       module: A module_utils.Module object.
249       action: An Action object.
250       deps: The module's dependencies.
251       imports: An imports file.
252       suffix: An output file suffix.
253 
254     Returns:
255       The expected output of the build statement.
256     """
257     output = os.path.join(self.pyi_dir,
258                           _module_to_output_path(module) + '.pyi' + suffix)
259     logging.info('%s %s\n  imports: %s\n  deps: %s\n  output: %s',
260                  action, module.name, imports, deps, output)
261     with open(self.ninja_file, 'a') as f:
262       f.write('build {output}: {action} {input}{deps}\n'
263               '  imports = {imports}\n'
264               '  module = {module}\n'.format(
265                   output=output,
266                   action=action,
267                   input=module.full_path,
268                   deps=' | ' + ' '.join(deps) if deps else '',
269                   imports=imports,
270                   module=module.name))
271     return output
272 
273   def setup_build(self):
274     """Write out the full build.ninja file.
275 
276     Returns:
277       All files with build statements.
278     """
279     if not self.make_imports_dir():
280       return set()
281     default_output = self.write_default_pyi()
282     self.write_ninja_preamble()
283     files = set()
284     module_to_imports_map = {}
285     module_to_output = {}
286     for module, action, deps, stage in self.yield_sorted_modules():
287       if files >= self.filenames:
288         logging.info('skipped: %s %s (%s)', action, module.name, stage)
289         continue
290       if action == Action.GENERATE_DEFAULT:
291         module_to_output[module] = default_output
292         continue
293       if stage == Stage.SINGLE_PASS:
294         files.add(module.full_path)
295         suffix = ''
296       elif stage == Stage.FIRST_PASS:
297         suffix = FIRST_PASS_SUFFIX
298       else:
299         assert stage == Stage.SECOND_PASS
300         files.add(module.full_path)
301         suffix = ''
302       imports_map = module_to_imports_map[module] = get_imports_map(
303           deps, module_to_imports_map, module_to_output)
304       imports = self.write_imports(module.name, imports_map, suffix)
305       # Don't depend on default.pyi, since it's regenerated every time.
306       deps = tuple(module_to_output[m] for m in deps
307                    if module_to_output[m] != default_output)
308       module_to_output[module] = self.write_build_statement(
309           module, action, deps, imports, suffix)
310     return files
311 
312   def build(self):
313     """Execute the build.ninja file."""
314     # -k N     keep going until N jobs fail (0 means infinity)
315     # -C DIR   change to DIR before doing anything else
316     k = '0' if self.keep_going else '1'
317     c = os.path.dirname(self.ninja_file)
318     return subprocess.call(['ninja', '-k', k, '-C', c])
319 
320   def run(self):
321     """Run pytype over the project."""
322     logging.info('------------- Starting pytype run. -------------')
323     files_to_analyze = self.setup_build()
324     num_sources = len(self.filenames & files_to_analyze)
325     print('Analyzing %d sources with %d local dependencies' %
326           (num_sources, len(files_to_analyze) - num_sources))
327     ret = self.build()
328     if not ret:
329       print('Success: no errors found')
330     return ret
331
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/pytype/tools/analyze_project/pytype_runner.py b/pytype/tools/analyze_project/pytype_runner.py
--- a/pytype/tools/analyze_project/pytype_runner.py
+++ b/pytype/tools/analyze_project/pytype_runner.py
@@ -196,9 +196,6 @@
     else:
       action = Action.INFER
       report = logging.info
-    if not f.endswith('.py'):
-      report('skipped: non-Python file %s', f)
-      return None
     # For builtin and system files, do not attempt to generate a pyi.
     if module.kind in ('Builtin', 'System'):
       action = Action.GENERATE_DEFAULT

{"golden_diff": "diff --git a/pytype/tools/analyze_project/pytype_runner.py b/pytype/tools/analyze_project/pytype_runner.py\n--- a/pytype/tools/analyze_project/pytype_runner.py\n+++ b/pytype/tools/analyze_project/pytype_runner.py\n@@ -196,9 +196,6 @@\n     else:\n       action = Action.INFER\n       report = logging.info\n-    if not f.endswith('.py'):\n-      report('skipped: non-Python file %s', f)\n-      return None\n     # For builtin and system files, do not attempt to generate a pyi.\n     if module.kind in ('Builtin', 'System'):\n       action = Action.GENERATE_DEFAULT\n", "issue": "Remove \".py\" report\nPart of #111 \n", "before_files": [{"content": "\"\"\"Use pytype to analyze and infer types for an entire project.\"\"\"\n\nfrom __future__ import print_function\n\nimport logging\nimport os\nimport subprocess\n\nfrom pytype import file_utils\nfrom pytype import module_utils\nfrom pytype.tools.analyze_project import config\nimport six\n\n\n# Generate a default pyi for builtin and system dependencies.\nDEFAULT_PYI = \"\"\"\nfrom typing import Any\ndef __getattr__(name) -> Any: ...\n\"\"\"\n\n\nclass Action(object):\n  CHECK = 'check'\n  INFER = 'infer'\n  GENERATE_DEFAULT = 'generate default'\n\n\nclass Stage(object):\n  SINGLE_PASS = 'single pass'\n  FIRST_PASS = 'first pass'\n  SECOND_PASS = 'second pass'\n\n\nFIRST_PASS_SUFFIX = '-1'\n\n\ndef resolved_file_to_module(f):\n  \"\"\"Turn an importlab ResolvedFile into a pytype Module.\"\"\"\n  full_path = f.path\n  target = f.short_path\n  path = full_path[:-len(target)]\n  name = f.module_name\n  # We want to preserve __init__ in the module_name for pytype.\n  if os.path.basename(full_path) == '__init__.py':\n    name += '.__init__'\n  return module_utils.Module(\n      path=path, target=target, name=name, kind=f.__class__.__name__)\n\n\ndef deps_from_import_graph(import_graph):\n  \"\"\"Construct PytypeRunner args from an importlab.ImportGraph instance.\n\n  Kept as a separate function so PytypeRunner can be tested independently of\n  importlab.\n\n  Args:\n    import_graph: An importlab.ImportGraph instance.\n\n  Returns:\n    List of (tuple of source modules, tuple of direct deps) in dependency order.\n  \"\"\"\n  def get_filenames(node):\n    if isinstance(node, six.string_types):\n      return (node,)\n    else:\n      # Make the build as deterministic as possible to minimize rebuilds.\n      return tuple(sorted(node.nodes))\n  def make_module(filename):\n    return resolved_file_to_module(import_graph.provenance[filename])\n  modules = []\n  for node, deps in reversed(import_graph.deps_list()):\n    files = tuple(make_module(f) for f in get_filenames(node))\n    # flatten and dedup\n    seen = set()\n    deps = tuple(make_module(d) for dep in deps for d in get_filenames(dep)\n                 if d not in seen and not seen.add(d))\n    modules.append((files, deps))\n  return modules\n\n\ndef _module_to_output_path(mod):\n  \"\"\"Convert a module to an output path.\"\"\"\n  path, _ = os.path.splitext(mod.target)\n  if path.replace(os.path.sep, '.').endswith(mod.name):\n    # Preferentially use the short path.\n    return path[-len(mod.name):]\n  else:\n    # Fall back to computing the output path from the name, which is a last\n    # resort because it messes up hidden files. Since such files aren't valid\n    # python packages anyway, we preserve any leading '.' in order to not\n    # create a file directly in / (which would likely cause a crash with a\n    # permission error) and let the rest of the path be mangled.\n    return mod.name[0] + mod.name[1:].replace('.', os.path.sep)\n\n\ndef get_imports_map(deps, module_to_imports_map, module_to_output):\n  \"\"\"Get a short path -> full path map for the given deps.\"\"\"\n  imports_map = {}\n  for m in deps:\n    if m in module_to_imports_map:\n      imports_map.update(module_to_imports_map[m])\n    imports_map[_module_to_output_path(m)] = module_to_output[m]\n  return imports_map\n\n\nclass PytypeRunner(object):\n  \"\"\"Runs pytype over an import graph.\"\"\"\n\n  def __init__(self, conf, sorted_sources):\n    self.filenames = set(conf.inputs)  # files to type-check\n    # all source modules as a sequence of (module, direct_deps)\n    self.sorted_sources = sorted_sources\n    self.python_version = conf.python_version\n    self.pyi_dir = os.path.join(conf.output, 'pyi')\n    self.imports_dir = os.path.join(conf.output, 'imports')\n    self.ninja_file = os.path.join(conf.output, 'build.ninja')\n    self.custom_options = [\n        (k, getattr(conf, k)) for k in set(conf.__slots__) - set(config.ITEMS)]\n    self.keep_going = conf.keep_going\n\n  def set_custom_options(self, flags_with_values, binary_flags):\n    \"\"\"Merge self.custom_options into flags_with_values and binary_flags.\"\"\"\n    for dest, value in self.custom_options:\n      arg_info = config.get_pytype_single_item(dest).arg_info\n      if arg_info.to_command_line:\n        value = arg_info.to_command_line(value)\n      if isinstance(value, bool):\n        if value:\n          binary_flags.add(arg_info.flag)\n        else:\n          binary_flags.discard(arg_info.flag)\n      elif value:\n        flags_with_values[arg_info.flag] = str(value)\n\n  def get_pytype_command_for_ninja(self, report_errors):\n    \"\"\"Get the command line for running pytype.\"\"\"\n    exe = 'pytype-single'\n    flags_with_values = {\n        '--imports_info': '$imports',\n        '-V': self.python_version,\n        '-o': '$out',\n        '--module-name': '$module',\n    }\n    binary_flags = {\n        '--quick',\n        '--analyze-annotated' if report_errors else '--no-report-errors',\n        '--nofail',\n    }\n    if report_errors:\n      self.set_custom_options(flags_with_values, binary_flags)\n    # Order the flags so that ninja recognizes commands across runs.\n    return (\n        [exe] +\n        list(sum(sorted(flags_with_values.items()), ())) +\n        sorted(binary_flags) +\n        ['$in']\n    )\n\n  def make_imports_dir(self):\n    try:\n      file_utils.makedirs(self.imports_dir)\n    except OSError:\n      logging.error('Could not create imports directory: %s', self.imports_dir)\n      return False\n    return True\n\n  def write_default_pyi(self):\n    \"\"\"Write a default pyi file.\"\"\"\n    output = os.path.join(self.imports_dir, 'default.pyi')\n    with open(output, 'w') as f:\n      f.write(DEFAULT_PYI)\n    return output\n\n  def write_imports(self, module_name, imports_map, suffix):\n    \"\"\"Write a .imports file.\"\"\"\n    output = os.path.join(self.imports_dir, module_name + '.imports' + suffix)\n    with open(output, 'w') as f:\n      for item in imports_map.items():\n        f.write('%s %s\\n' % item)\n    return output\n\n  def get_module_action(self, module):\n    \"\"\"Get the action for the given module.\n\n    Args:\n      module: A module_utils.Module object.\n\n    Returns:\n      An Action object, or None for a non-Python file.\n    \"\"\"\n    f = module.full_path\n    # Report errors for files we are analysing directly.\n    if f in self.filenames:\n      action = Action.CHECK\n      report = logging.warning\n    else:\n      action = Action.INFER\n      report = logging.info\n    if not f.endswith('.py'):\n      report('skipped: non-Python file %s', f)\n      return None\n    # For builtin and system files, do not attempt to generate a pyi.\n    if module.kind in ('Builtin', 'System'):\n      action = Action.GENERATE_DEFAULT\n      report('%s: %s module %s', action, module.kind, module.name)\n    return action\n\n  def yield_sorted_modules(self):\n    \"\"\"Yield modules from our sorted source files.\"\"\"\n    for group, deps in self.sorted_sources:\n      modules = []\n      for module in group:\n        action = self.get_module_action(module)\n        if action:\n          modules.append((module, action))\n      if len(modules) == 1:\n        yield modules[0] + (deps, Stage.SINGLE_PASS)\n      else:\n        # If we have a cycle we run pytype over the files twice. So that we\n        # don't fail on missing dependencies, we'll ignore errors the first\n        # time and add the cycle itself to the dependencies the second time.\n        second_pass_deps = []\n        for module, action in modules:\n          second_pass_deps.append(module)\n          if action == Action.CHECK:\n            action = Action.INFER\n          yield module, action, deps, Stage.FIRST_PASS\n        deps += tuple(second_pass_deps)\n        for module, action in modules:\n          # We don't need to run generate_default twice\n          if action != Action.GENERATE_DEFAULT:\n            yield module, action, deps, Stage.SECOND_PASS\n\n  def write_ninja_preamble(self):\n    \"\"\"Write out the pytype-single commands that the build will call.\"\"\"\n    with open(self.ninja_file, 'w') as f:\n      for action, report_errors in ((Action.INFER, False),\n                                    (Action.CHECK, True)):\n        command = ' '.join(\n            self.get_pytype_command_for_ninja(report_errors=report_errors))\n        logging.info('%s command: %s', action, command)\n        f.write('rule %s\\n  command = %s\\n' % (action, command))\n\n  def write_build_statement(self, module, action, deps, imports, suffix):\n    \"\"\"Write a build statement for the given module.\n\n    Args:\n      module: A module_utils.Module object.\n      action: An Action object.\n      deps: The module's dependencies.\n      imports: An imports file.\n      suffix: An output file suffix.\n\n    Returns:\n      The expected output of the build statement.\n    \"\"\"\n    output = os.path.join(self.pyi_dir,\n                          _module_to_output_path(module) + '.pyi' + suffix)\n    logging.info('%s %s\\n  imports: %s\\n  deps: %s\\n  output: %s',\n                 action, module.name, imports, deps, output)\n    with open(self.ninja_file, 'a') as f:\n      f.write('build {output}: {action} {input}{deps}\\n'\n              '  imports = {imports}\\n'\n              '  module = {module}\\n'.format(\n                  output=output,\n                  action=action,\n                  input=module.full_path,\n                  deps=' | ' + ' '.join(deps) if deps else '',\n                  imports=imports,\n                  module=module.name))\n    return output\n\n  def setup_build(self):\n    \"\"\"Write out the full build.ninja file.\n\n    Returns:\n      All files with build statements.\n    \"\"\"\n    if not self.make_imports_dir():\n      return set()\n    default_output = self.write_default_pyi()\n    self.write_ninja_preamble()\n    files = set()\n    module_to_imports_map = {}\n    module_to_output = {}\n    for module, action, deps, stage in self.yield_sorted_modules():\n      if files >= self.filenames:\n        logging.info('skipped: %s %s (%s)', action, module.name, stage)\n        continue\n      if action == Action.GENERATE_DEFAULT:\n        module_to_output[module] = default_output\n        continue\n      if stage == Stage.SINGLE_PASS:\n        files.add(module.full_path)\n        suffix = ''\n      elif stage == Stage.FIRST_PASS:\n        suffix = FIRST_PASS_SUFFIX\n      else:\n        assert stage == Stage.SECOND_PASS\n        files.add(module.full_path)\n        suffix = ''\n      imports_map = module_to_imports_map[module] = get_imports_map(\n          deps, module_to_imports_map, module_to_output)\n      imports = self.write_imports(module.name, imports_map, suffix)\n      # Don't depend on default.pyi, since it's regenerated every time.\n      deps = tuple(module_to_output[m] for m in deps\n                   if module_to_output[m] != default_output)\n      module_to_output[module] = self.write_build_statement(\n          module, action, deps, imports, suffix)\n    return files\n\n  def build(self):\n    \"\"\"Execute the build.ninja file.\"\"\"\n    # -k N     keep going until N jobs fail (0 means infinity)\n    # -C DIR   change to DIR before doing anything else\n    k = '0' if self.keep_going else '1'\n    c = os.path.dirname(self.ninja_file)\n    return subprocess.call(['ninja', '-k', k, '-C', c])\n\n  def run(self):\n    \"\"\"Run pytype over the project.\"\"\"\n    logging.info('------------- Starting pytype run. -------------')\n    files_to_analyze = self.setup_build()\n    num_sources = len(self.filenames & files_to_analyze)\n    print('Analyzing %d sources with %d local dependencies' %\n          (num_sources, len(files_to_analyze) - num_sources))\n    ret = self.build()\n    if not ret:\n      print('Success: no errors found')\n    return ret\n", "path": "pytype/tools/analyze_project/pytype_runner.py"}], "after_files": [{"content": "\"\"\"Use pytype to analyze and infer types for an entire project.\"\"\"\n\nfrom __future__ import print_function\n\nimport logging\nimport os\nimport subprocess\n\nfrom pytype import file_utils\nfrom pytype import module_utils\nfrom pytype.tools.analyze_project import config\nimport six\n\n\n# Generate a default pyi for builtin and system dependencies.\nDEFAULT_PYI = \"\"\"\nfrom typing import Any\ndef __getattr__(name) -> Any: ...\n\"\"\"\n\n\nclass Action(object):\n  CHECK = 'check'\n  INFER = 'infer'\n  GENERATE_DEFAULT = 'generate default'\n\n\nclass Stage(object):\n  SINGLE_PASS = 'single pass'\n  FIRST_PASS = 'first pass'\n  SECOND_PASS = 'second pass'\n\n\nFIRST_PASS_SUFFIX = '-1'\n\n\ndef resolved_file_to_module(f):\n  \"\"\"Turn an importlab ResolvedFile into a pytype Module.\"\"\"\n  full_path = f.path\n  target = f.short_path\n  path = full_path[:-len(target)]\n  name = f.module_name\n  # We want to preserve __init__ in the module_name for pytype.\n  if os.path.basename(full_path) == '__init__.py':\n    name += '.__init__'\n  return module_utils.Module(\n      path=path, target=target, name=name, kind=f.__class__.__name__)\n\n\ndef deps_from_import_graph(import_graph):\n  \"\"\"Construct PytypeRunner args from an importlab.ImportGraph instance.\n\n  Kept as a separate function so PytypeRunner can be tested independently of\n  importlab.\n\n  Args:\n    import_graph: An importlab.ImportGraph instance.\n\n  Returns:\n    List of (tuple of source modules, tuple of direct deps) in dependency order.\n  \"\"\"\n  def get_filenames(node):\n    if isinstance(node, six.string_types):\n      return (node,)\n    else:\n      # Make the build as deterministic as possible to minimize rebuilds.\n      return tuple(sorted(node.nodes))\n  def make_module(filename):\n    return resolved_file_to_module(import_graph.provenance[filename])\n  modules = []\n  for node, deps in reversed(import_graph.deps_list()):\n    files = tuple(make_module(f) for f in get_filenames(node))\n    # flatten and dedup\n    seen = set()\n    deps = tuple(make_module(d) for dep in deps for d in get_filenames(dep)\n                 if d not in seen and not seen.add(d))\n    modules.append((files, deps))\n  return modules\n\n\ndef _module_to_output_path(mod):\n  \"\"\"Convert a module to an output path.\"\"\"\n  path, _ = os.path.splitext(mod.target)\n  if path.replace(os.path.sep, '.').endswith(mod.name):\n    # Preferentially use the short path.\n    return path[-len(mod.name):]\n  else:\n    # Fall back to computing the output path from the name, which is a last\n    # resort because it messes up hidden files. Since such files aren't valid\n    # python packages anyway, we preserve any leading '.' in order to not\n    # create a file directly in / (which would likely cause a crash with a\n    # permission error) and let the rest of the path be mangled.\n    return mod.name[0] + mod.name[1:].replace('.', os.path.sep)\n\n\ndef get_imports_map(deps, module_to_imports_map, module_to_output):\n  \"\"\"Get a short path -> full path map for the given deps.\"\"\"\n  imports_map = {}\n  for m in deps:\n    if m in module_to_imports_map:\n      imports_map.update(module_to_imports_map[m])\n    imports_map[_module_to_output_path(m)] = module_to_output[m]\n  return imports_map\n\n\nclass PytypeRunner(object):\n  \"\"\"Runs pytype over an import graph.\"\"\"\n\n  def __init__(self, conf, sorted_sources):\n    self.filenames = set(conf.inputs)  # files to type-check\n    # all source modules as a sequence of (module, direct_deps)\n    self.sorted_sources = sorted_sources\n    self.python_version = conf.python_version\n    self.pyi_dir = os.path.join(conf.output, 'pyi')\n    self.imports_dir = os.path.join(conf.output, 'imports')\n    self.ninja_file = os.path.join(conf.output, 'build.ninja')\n    self.custom_options = [\n        (k, getattr(conf, k)) for k in set(conf.__slots__) - set(config.ITEMS)]\n    self.keep_going = conf.keep_going\n\n  def set_custom_options(self, flags_with_values, binary_flags):\n    \"\"\"Merge self.custom_options into flags_with_values and binary_flags.\"\"\"\n    for dest, value in self.custom_options:\n      arg_info = config.get_pytype_single_item(dest).arg_info\n      if arg_info.to_command_line:\n        value = arg_info.to_command_line(value)\n      if isinstance(value, bool):\n        if value:\n          binary_flags.add(arg_info.flag)\n        else:\n          binary_flags.discard(arg_info.flag)\n      elif value:\n        flags_with_values[arg_info.flag] = str(value)\n\n  def get_pytype_command_for_ninja(self, report_errors):\n    \"\"\"Get the command line for running pytype.\"\"\"\n    exe = 'pytype-single'\n    flags_with_values = {\n        '--imports_info': '$imports',\n        '-V': self.python_version,\n        '-o': '$out',\n        '--module-name': '$module',\n    }\n    binary_flags = {\n        '--quick',\n        '--analyze-annotated' if report_errors else '--no-report-errors',\n        '--nofail',\n    }\n    if report_errors:\n      self.set_custom_options(flags_with_values, binary_flags)\n    # Order the flags so that ninja recognizes commands across runs.\n    return (\n        [exe] +\n        list(sum(sorted(flags_with_values.items()), ())) +\n        sorted(binary_flags) +\n        ['$in']\n    )\n\n  def make_imports_dir(self):\n    try:\n      file_utils.makedirs(self.imports_dir)\n    except OSError:\n      logging.error('Could not create imports directory: %s', self.imports_dir)\n      return False\n    return True\n\n  def write_default_pyi(self):\n    \"\"\"Write a default pyi file.\"\"\"\n    output = os.path.join(self.imports_dir, 'default.pyi')\n    with open(output, 'w') as f:\n      f.write(DEFAULT_PYI)\n    return output\n\n  def write_imports(self, module_name, imports_map, suffix):\n    \"\"\"Write a .imports file.\"\"\"\n    output = os.path.join(self.imports_dir, module_name + '.imports' + suffix)\n    with open(output, 'w') as f:\n      for item in imports_map.items():\n        f.write('%s %s\\n' % item)\n    return output\n\n  def get_module_action(self, module):\n    \"\"\"Get the action for the given module.\n\n    Args:\n      module: A module_utils.Module object.\n\n    Returns:\n      An Action object, or None for a non-Python file.\n    \"\"\"\n    f = module.full_path\n    # Report errors for files we are analysing directly.\n    if f in self.filenames:\n      action = Action.CHECK\n      report = logging.warning\n    else:\n      action = Action.INFER\n      report = logging.info\n    # For builtin and system files, do not attempt to generate a pyi.\n    if module.kind in ('Builtin', 'System'):\n      action = Action.GENERATE_DEFAULT\n      report('%s: %s module %s', action, module.kind, module.name)\n    return action\n\n  def yield_sorted_modules(self):\n    \"\"\"Yield modules from our sorted source files.\"\"\"\n    for group, deps in self.sorted_sources:\n      modules = []\n      for module in group:\n        action = self.get_module_action(module)\n        if action:\n          modules.append((module, action))\n      if len(modules) == 1:\n        yield modules[0] + (deps, Stage.SINGLE_PASS)\n      else:\n        # If we have a cycle we run pytype over the files twice. So that we\n        # don't fail on missing dependencies, we'll ignore errors the first\n        # time and add the cycle itself to the dependencies the second time.\n        second_pass_deps = []\n        for module, action in modules:\n          second_pass_deps.append(module)\n          if action == Action.CHECK:\n            action = Action.INFER\n          yield module, action, deps, Stage.FIRST_PASS\n        deps += tuple(second_pass_deps)\n        for module, action in modules:\n          # We don't need to run generate_default twice\n          if action != Action.GENERATE_DEFAULT:\n            yield module, action, deps, Stage.SECOND_PASS\n\n  def write_ninja_preamble(self):\n    \"\"\"Write out the pytype-single commands that the build will call.\"\"\"\n    with open(self.ninja_file, 'w') as f:\n      for action, report_errors in ((Action.INFER, False),\n                                    (Action.CHECK, True)):\n        command = ' '.join(\n            self.get_pytype_command_for_ninja(report_errors=report_errors))\n        logging.info('%s command: %s', action, command)\n        f.write('rule %s\\n  command = %s\\n' % (action, command))\n\n  def write_build_statement(self, module, action, deps, imports, suffix):\n    \"\"\"Write a build statement for the given module.\n\n    Args:\n      module: A module_utils.Module object.\n      action: An Action object.\n      deps: The module's dependencies.\n      imports: An imports file.\n      suffix: An output file suffix.\n\n    Returns:\n      The expected output of the build statement.\n    \"\"\"\n    output = os.path.join(self.pyi_dir,\n                          _module_to_output_path(module) + '.pyi' + suffix)\n    logging.info('%s %s\\n  imports: %s\\n  deps: %s\\n  output: %s',\n                 action, module.name, imports, deps, output)\n    with open(self.ninja_file, 'a') as f:\n      f.write('build {output}: {action} {input}{deps}\\n'\n              '  imports = {imports}\\n'\n              '  module = {module}\\n'.format(\n                  output=output,\n                  action=action,\n                  input=module.full_path,\n                  deps=' | ' + ' '.join(deps) if deps else '',\n                  imports=imports,\n                  module=module.name))\n    return output\n\n  def setup_build(self):\n    \"\"\"Write out the full build.ninja file.\n\n    Returns:\n      All files with build statements.\n    \"\"\"\n    if not self.make_imports_dir():\n      return set()\n    default_output = self.write_default_pyi()\n    self.write_ninja_preamble()\n    files = set()\n    module_to_imports_map = {}\n    module_to_output = {}\n    for module, action, deps, stage in self.yield_sorted_modules():\n      if files >= self.filenames:\n        logging.info('skipped: %s %s (%s)', action, module.name, stage)\n        continue\n      if action == Action.GENERATE_DEFAULT:\n        module_to_output[module] = default_output\n        continue\n      if stage == Stage.SINGLE_PASS:\n        files.add(module.full_path)\n        suffix = ''\n      elif stage == Stage.FIRST_PASS:\n        suffix = FIRST_PASS_SUFFIX\n      else:\n        assert stage == Stage.SECOND_PASS\n        files.add(module.full_path)\n        suffix = ''\n      imports_map = module_to_imports_map[module] = get_imports_map(\n          deps, module_to_imports_map, module_to_output)\n      imports = self.write_imports(module.name, imports_map, suffix)\n      # Don't depend on default.pyi, since it's regenerated every time.\n      deps = tuple(module_to_output[m] for m in deps\n                   if module_to_output[m] != default_output)\n      module_to_output[module] = self.write_build_statement(\n          module, action, deps, imports, suffix)\n    return files\n\n  def build(self):\n    \"\"\"Execute the build.ninja file.\"\"\"\n    # -k N     keep going until N jobs fail (0 means infinity)\n    # -C DIR   change to DIR before doing anything else\n    k = '0' if self.keep_going else '1'\n    c = os.path.dirname(self.ninja_file)\n    return subprocess.call(['ninja', '-k', k, '-C', c])\n\n  def run(self):\n    \"\"\"Run pytype over the project.\"\"\"\n    logging.info('------------- Starting pytype run. -------------')\n    files_to_analyze = self.setup_build()\n    num_sources = len(self.filenames & files_to_analyze)\n    print('Analyzing %d sources with %d local dependencies' %\n          (num_sources, len(files_to_analyze) - num_sources))\n    ret = self.build()\n    if not ret:\n      print('Success: no errors found')\n    return ret\n", "path": "pytype/tools/analyze_project/pytype_runner.py"}]}