1#!/usr/local/bin/python3.8
2
3# Copyright (C) 2013 Steven Watanabe
4# Distributed under the Boost Software License, Version 1.0.
5# (See accompanying file LICENSE_1_0.txt or copy at
6# http://www.boost.org/LICENSE_1_0.txt)
7
8import sys
9
10def create(t):
11  t.write('''mockinfo.py''', '''
12from __future__ import print_function
13import re
14import optparse
15import os
16
17parser = optparse.OptionParser()
18parser.add_option('-o', dest="output_file")
19parser.add_option('-x', dest="language")
20parser.add_option('-c', dest="compile", action="store_true")
21parser.add_option('-I', dest="includes", action="append")
22parser.add_option('-D', dest="defines", action="append")
23parser.add_option('-L', dest="library_path", action="append")
24parser.add_option('--dll', dest="dll", action="store_true")
25parser.add_option('--archive', dest="archive", action="store_true")
26parser.add_option('--static-lib', dest="static_libraries", action="append")
27parser.add_option('--shared-lib', dest="shared_libraries", action="append")
28
29cwd = os.environ["JAM_CWD"]
30
31class MockInfo(object):
32  def __init__(self, verbose=False):
33    self.files = dict()
34    self.commands = list()
35    self.verbose = verbose
36  def source_file(self, name, pattern):
37    self.files[name] = pattern
38  def action(self, command, status=0):
39    if isinstance(command, str):
40      command = command.split()
41    self.commands.append((command, status))
42  def check(self, command):
43    print("Testing command", command)
44    for (raw, status) in self.commands:
45      if self.matches(raw, command):
46        return status
47  def matches(self, raw, command):
48    (expected_options, expected_args) = parser.parse_args(raw)
49    options = command[0]
50    input_files = list(command[1])
51    if self.verbose:
52      print("  - matching against", (expected_options, expected_args))
53    if len(expected_args) != len(input_files):
54      if self.verbose:
55        print("  argument list sizes differ")
56      return False
57    for arg in expected_args:
58      if arg.startswith('$'):
59        fileid = arg[1:]
60        pattern = self.files[fileid] if fileid in self.files else fileid
61        matching_file = None
62        for input_file in input_files:
63          with open(input_file, 'r') as f:
64            contents = f.read()
65          if pattern == contents:
66            matching_file = input_file
67            break
68        if matching_file is not None:
69          input_files.remove(matching_file)
70        else:
71          if self.verbose:
72            print("    Failed to match input file contents: %s" % arg)
73          return False
74      else:
75        if arg in input_files:
76          input_files.remove(arg)
77        else:
78          if self.verbose:
79            print("    Failed to match input file: %s" % arg)
80          return False
81
82    if options.language != expected_options.language:
83      if self.verbose:
84        print("    Failed to match -c")
85      return False
86
87    if options.compile != expected_options.compile:
88      if self.verbose:
89        print("    Failed to match -x")
90      return False
91
92    # Normalize a path for comparison purposes
93    def adjust_path(p):
94      return os.path.normcase(os.path.normpath(os.path.join(cwd, p)))
95
96    # order matters
97    if options.includes is None:
98      options.includes = []
99    if expected_options.includes is None:
100      expected_options.includes = []
101    if list(map(adjust_path, options.includes)) != \
102        list(map(adjust_path, expected_options.includes)):
103      if self.verbose:
104        print("    Failed to match -I ",  list(map(adjust_path, options.includes)), \
105          " != ", list(map(adjust_path, expected_options.includes)))
106      return False
107
108    if options.defines is None:
109      options.defines = []
110    if expected_options.defines is None:
111      expected_options.defines = []
112    if options.defines != expected_options.defines:
113      if self.verbose:
114        print("    Failed to match -I ",  options.defines, \
115          " != ", expected_options.defines)
116      return False
117
118    if options.library_path is None:
119      options.library_path = []
120    if expected_options.library_path is None:
121      expected_options.library_path = []
122    if list(map(adjust_path, options.library_path)) != \
123        list(map(adjust_path, expected_options.library_path)):
124      if self.verbose:
125        print("    Failed to match -L ",  list(map(adjust_path, options.library_path)), \
126          " != ", list(map(adjust_path, expected_options.library_path)))
127      return False
128
129    if options.static_libraries != expected_options.static_libraries:
130      if self.verbose:
131        print("    Failed to match --static-lib")
132      return False
133
134    if options.shared_libraries != expected_options.shared_libraries:
135      if self.verbose:
136        print("    Failed to match --shared-lib")
137      return False
138
139    if options.dll != expected_options.dll:
140      if self.verbose:
141        print("    Failed to match --dll")
142      return False
143
144    if options.archive != expected_options.archive:
145      if self.verbose:
146        print("    Failed to match --archive")
147      return False
148
149    # The output must be handled after everything else
150    # is validated
151    if expected_options.output_file is not None:
152      if options.output_file is not None:
153        if expected_options.output_file.startswith('$'):
154          fileid = expected_options.output_file[1:]
155          if fileid not in self.files:
156            self.files[fileid] = fileid
157          else:
158            assert(self.files[fileid] == fileid)
159          with open(options.output_file, 'w') as output:
160            output.write(fileid)
161      else:
162        if self.verbose:
163          print("Failed to match -o")
164        return False
165    elif options.output_file is not None:
166      if self.verbose:
167        print("Failed to match -o")
168      return False
169
170    # if we've gotten here, then everything matched
171    if self.verbose:
172      print("    Matched")
173    return True
174''')
175
176  t.write('mock.py', '''
177from __future__ import print_function
178import mockinfo
179import markup
180import sys
181
182status = markup.info.check(mockinfo.parser.parse_args())
183if status is not None:
184  exit(status)
185else:
186  print("Unrecognized command: " + ' '.join(sys.argv))
187  exit(1)
188''')
189
190  t.write('mock.jam', '''
191import feature ;
192import toolset ;
193import path ;
194import modules ;
195import common ;
196import type ;
197
198.python-cmd = "\"%s\"" ;
199
200# Behave the same as gcc on Windows, because that's what
201# the test system expects
202type.set-generated-target-prefix SHARED_LIB : <toolset>mock <target-os>windows : lib ;
203type.set-generated-target-suffix STATIC_LIB : <toolset>mock <target-os>windows : a ;
204
205rule init ( )
206{
207    local here = [ path.make [ modules.binding $(__name__) ] ] ;
208    here = [ path.native [ path.root [ path.parent $(here) ] [ path.pwd ] ] ] ;
209    .config-cmd = [ common.variable-setting-command JAM_CWD : $(here) ] $(.python-cmd) -B ;
210}
211
212feature.extend toolset : mock ;
213
214generators.register-c-compiler mock.compile.c++ : CPP : OBJ : <toolset>mock ;
215generators.register-c-compiler mock.compile.c : C : OBJ : <toolset>mock ;
216
217generators.register-linker mock.link : LIB OBJ : EXE : <toolset>mock ;
218generators.register-linker mock.link.dll : LIB OBJ : SHARED_LIB : <toolset>mock ;
219generators.register-archiver mock.archive : OBJ : STATIC_LIB : <toolset>mock ;
220
221toolset.flags mock.compile OPTIONS <link>shared : -fPIC ;
222toolset.flags mock.compile INCLUDES : <include> ;
223toolset.flags mock.compile DEFINES : <define> ;
224
225actions compile.c
226{
227   $(.config-cmd) mock.py -c -x c -I"$(INCLUDES)" -D"$(DEFINES)" "$(>)" -o "$(<)"
228}
229
230actions compile.c++
231{
232    $(.config-cmd) mock.py -c -x c++ -I"$(INCLUDES)" -D"$(DEFINES)" "$(>)" -o "$(<)"
233}
234
235toolset.flags mock.link USER_OPTIONS <linkflags> ;
236toolset.flags mock.link FINDLIBS-STATIC <find-static-library> ;
237toolset.flags mock.link FINDLIBS-SHARED <find-shared-library> ;
238toolset.flags mock.link LINK_PATH <library-path> ;
239toolset.flags mock.link LIBRARIES <library-file> ;
240
241actions link
242{
243    $(.config-cmd) mock.py "$(>)" -o "$(<)" $(USER_OPTIONS) -L"$(LINK_PATH)" --static-lib=$(FINDLIBS-STATIC) --shared-lib=$(FINDLIBS-SHARED)
244}
245
246actions archive
247{
248    $(.config-cmd) mock.py --archive "$(>)" -o "$(<)" $(USER_OPTIONS)
249}
250
251actions link.dll
252{
253    $(.config-cmd) mock.py --dll "$(>)" -o "$(<)" $(USER_OPTIONS) -L"$(LINK_PATH)" --static-lib=$(FINDLIBS-STATIC) --shared-lib=$(FINDLIBS-SHARED)
254}
255
256''' % sys.executable.replace('\\', '\\\\'))
257
258def set_expected(t, markup):
259  verbose = "True" if t.verbose else "False"
260  t.write('markup.py', '''
261import mockinfo
262info = mockinfo.MockInfo(%s)
263def source_file(name, contents):
264  info.source_file(name, contents)
265def action(command, status=0):
266  info.action(command, status)
267''' % (verbose) + markup)
268