1#!/usr/bin/env python
2# Copyright (c) 2012 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""This script should be run manually on occasion to make sure the gyp file and
7the includes tests are up to date.
8
9It does the following:
10 - Verifies that all source code is in ppapi.gyp
11 - Verifies that all sources in ppapi.gyp really do exist
12 - Generates tests/test_c_includes.c
13 - Generates tests/test_cpp_includes.cc
14These tests are checked in to SVN.
15"""
16# TODO(dmichael):  Make this script execute as a gyp action, move the include
17#                  tests to some 'generated' area, and remove them from version
18#                  control.
19
20import re
21import os
22import sys
23import posixpath
24
25# A simple regular expression that should match source files for C++ and C.
26SOURCE_FILE_RE = re.compile('.+\.(cc|c|h)$')
27
28# IGNORE_RE is a regular expression that matches directories which contain
29# source that we don't (currently) expect to be in ppapi.gyp.  This script will
30# not check whether source files under these directories are in the gyp file.
31# TODO(dmichael): Put examples back in the build.
32# TODO(brettw): Put proxy in the build when it's ready.
33IGNORE_RE = re.compile('^(examples|GLES2|proxy|tests\/clang).*')
34
35GYP_TARGETS_KEY = 'targets'
36GYP_SOURCES_KEY = 'sources'
37GYP_TARGET_NAME_KEY = 'target_name'
38
39
40# Return a set containing all source files found given an object read from a gyp
41# file.
42def GetAllGypSources(gyp_file_data):
43  sources = set([])
44  for target in gyp_file_data[GYP_TARGETS_KEY]:
45    # Get a list of sources in the target that are not ignored, and 'normalize'
46    # them.  The main reason for this is to turn the forward slashes in the gyp
47    # file in to backslashes when the script is run on Windows.
48    source_list = [posixpath.normpath(src) for src in target[GYP_SOURCES_KEY]
49                   if not IGNORE_RE.match(src)]
50    sources |= set(source_list)
51  return sources
52
53
54# Search the directory named start_root and all its subdirectories for source
55# files.
56# Return a set containing the string names of all the source files found,
57# relative to start_root.
58def GetFileSources(start_root):
59  file_set = set([])
60  for root, dirs, files in os.walk(start_root):
61    relative_root = os.path.relpath(root, start_root)
62    if not IGNORE_RE.match(relative_root):
63      for source in files:
64        if SOURCE_FILE_RE.match(source):
65          file_set |= set([os.path.join(relative_root, source)])
66  return file_set
67
68
69# Make sure all source files are in the given gyp object (evaluated from a gyp
70# file), and that all source files listed in the gyp object exist in the
71# directory.
72def VerifyGypFile(gyp_file_data):
73  gyp_sources = GetAllGypSources(gyp_file_data)
74  file_sources = GetFileSources('.')
75  in_gyp_not_file = gyp_sources - file_sources
76  in_file_not_gyp = file_sources - gyp_sources
77  if len(in_gyp_not_file):
78    print 'Found source file(s) in ppapi.gyp but not in the directory:', \
79      in_gyp_not_file
80  if len(in_file_not_gyp):
81    print 'Found source file(s) in the directory but not in ppapi.gyp:', \
82      in_file_not_gyp
83  error_count = len(in_gyp_not_file) + len(in_file_not_gyp)
84  if error_count:
85    sys.exit(error_count)
86
87
88def WriteLines(filename, lines):
89  outfile = open(filename, 'w')
90  for line in lines:
91    outfile.write(line)
92  outfile.write('\n')
93
94
95COPYRIGHT_STRING_C = \
96"""/* Copyright (c) 2010 The Chromium Authors. All rights reserved.
97 * Use of this source code is governed by a BSD-style license that can be
98 * found in the LICENSE file.
99 *
100 * This test simply includes all the C headers to ensure they compile with a C
101 * compiler.  If it compiles, it passes.
102 */
103"""
104
105COPYRIGHT_STRING_CC = \
106"""// Copyright (c) 2010 The Chromium Authors. All rights reserved.
107// Use of this source code is governed by a BSD-style license that can be
108// found in the LICENSE file.
109//
110// This test simply includes all the C++ headers to ensure they compile with a
111// C++ compiler.  If it compiles, it passes.
112//
113"""
114
115
116# Get the source file names out of the given gyp file data object (as evaluated
117# from a gyp file) for the given target name.  Return the string names in
118# sorted order.
119def GetSourcesForTarget(target_name, gyp_file_data):
120  for target in gyp_file_data[GYP_TARGETS_KEY]:
121    if target[GYP_TARGET_NAME_KEY] == target_name:
122      sources = target[GYP_SOURCES_KEY]
123      sources.sort()
124      return sources
125  print 'Warning: no target named ', target, ' found.'
126  return []
127
128
129# Generate all_c_includes.h, which includes all C headers.  This is part of
130# tests/test_c_sizes.c, which includes all C API files to ensure that all
131# the headers in ppapi/c can be compiled with a C compiler, and also asserts
132# (with compile-time assertions) that all structs and enums are a particular
133# size.
134def GenerateCIncludeTest(gyp_file_data):
135  c_sources = GetSourcesForTarget('ppapi_c', gyp_file_data)
136  lines = [COPYRIGHT_STRING_C]
137  lines.append('#ifndef PPAPI_TESTS_ALL_C_INCLUDES_H_\n')
138  lines.append('#define PPAPI_TESTS_ALL_C_INCLUDES_H_\n\n')
139  for source in c_sources:
140    lines.append('#include "ppapi/' + source + '"\n')
141  lines.append('\n#endif  /* PPAPI_TESTS_ALL_C_INCLUDES_H_ */\n')
142  WriteLines('tests/all_c_includes.h', lines)
143
144
145# Generate all_cpp_includes.h, which is used by test_cpp_includes.cc to ensure
146# that all the headers in ppapi/cpp can be compiled with a C++ compiler.
147def GenerateCCIncludeTest(gyp_file_data):
148  cc_sources = GetSourcesForTarget('ppapi_cpp_objects', gyp_file_data)
149  header_re = re.compile('.+\.h$')
150  lines = [COPYRIGHT_STRING_CC]
151  lines.append('#ifndef PPAPI_TESTS_ALL_CPP_INCLUDES_H_\n')
152  lines.append('#define PPAPI_TESTS_ALL_CPP_INCLUDES_H_\n\n')
153  for source in cc_sources:
154    if header_re.match(source):
155      lines.append('#include "ppapi/' + source + '"\n')
156  lines.append('\n#endif  // PPAPI_TESTS_ALL_CPP_INCLUDES_H_\n')
157  WriteLines('tests/all_cpp_includes.h', lines)
158
159
160def main():
161  ppapi_gyp_file_name = 'ppapi.gyp'
162  gyp_file_contents = open(ppapi_gyp_file_name).read()
163  gyp_file_data = eval(gyp_file_contents)
164  VerifyGypFile(gyp_file_data)
165  GenerateCIncludeTest(gyp_file_data)
166  GenerateCCIncludeTest(gyp_file_data)
167  return 0
168
169
170if __name__ == '__main__':
171    sys.exit(main())
172