• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (C) 2020 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#      http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14"""Test manifest split."""
15
16import json
17import os
18import re
19import subprocess
20import tempfile
21import unittest
22import unittest.mock
23import xml.etree.ElementTree as ET
24
25import manifest_split
26
27
28class ManifestSplitTest(unittest.TestCase):
29
30  def test_read_config(self):
31    with tempfile.NamedTemporaryFile('w+t') as test_config:
32      test_config.write("""
33        <config>
34          <add_project name="add1" />
35          <add_project name="add2" />
36          <remove_project name="remove1" />
37          <remove_project name="remove2" />
38          <path_mapping pattern="p1.*" sub="$0" />
39        </config>""")
40      test_config.flush()
41      config = manifest_split.ManifestSplitConfig.from_config_files(
42          [test_config.name])
43      self.assertEqual(config.remove_projects, {
44          'remove1': test_config.name,
45          'remove2': test_config.name
46      })
47      self.assertEqual(config.add_projects, {
48          'add1': test_config.name,
49          'add2': test_config.name
50      })
51      self.assertEqual(config.path_mappings, [
52          manifest_split.PathMappingConfig(re.compile('p1.*'), '$0'),
53      ])
54
55  def test_get_repo_projects_from_manifest(self):
56    manifest_contents = """
57      <manifest>
58        <project name="platform/project1" path="system/project1" />
59        <project name="platform/project2" path="system/project2" />
60        <project name="platform/project3" path="system/project3" />
61      </manifest>"""
62    manifest = ET.ElementTree(ET.fromstring(manifest_contents))
63    projects = manifest_split.get_repo_projects(
64        None, manifest, path_mappings=[])
65    self.assertDictEqual(
66        {
67            'system/project1': 'platform/project1',
68            'system/project2': 'platform/project2',
69            'system/project3': 'platform/project3',
70        }, projects)
71
72
73  def test_get_repo_projects(self):
74    with tempfile.NamedTemporaryFile('w+t') as repo_list_file:
75      repo_list_file.write("""
76        system/project1 : platform/project1
77        system/project2 : platform/project2""")
78      repo_list_file.flush()
79      repo_projects = manifest_split.get_repo_projects(
80          repo_list_file.name, None, path_mappings=[])
81      self.assertEqual(
82          repo_projects, {
83              'system/project1': 'platform/project1',
84              'system/project2': 'platform/project2',
85          })
86
87  def test_get_repo_projects_with_mappings(self):
88    with tempfile.NamedTemporaryFile('w+t') as repo_list_file:
89      repo_list_file.write("""
90        overlay/system/project1 : platform/project1
91        system/project2 : platform/project2
92        hide/this/one : platform/project3""")
93      repo_list_file.flush()
94      path_mappings = [
95          manifest_split.PathMappingConfig(re.compile('^overlay/(.*)'), '\\1'),
96          manifest_split.PathMappingConfig(re.compile('^hide/this/one.*'), ''),
97      ]
98
99      repo_projects = manifest_split.get_repo_projects(repo_list_file.name,
100                                                       None,
101                                                       path_mappings)
102      self.assertEqual(
103          repo_projects, {
104              'system/project1': 'platform/project1',
105              'system/project2': 'platform/project2',
106          })
107
108  def test_get_module_info(self):
109    with tempfile.NamedTemporaryFile('w+t') as module_info_file:
110      module_info_file.write("""{
111        "target1a": { "class": ["EXECUTABLES"], "path": ["system/project1"], "dependencies": ["target2"] },
112        "target1b": { "class": ["EXECUTABLES"], "path": ["system/project1"], "dependencies": ["target3", "target42"] },
113        "target2": { "class": ["SHARED_LIBRARIES"], "path": ["out/project2"], "dependencies": [] },
114        "target3": { "class": ["SHARED_LIBRARIES"], "path": ["vendor/google/project3"], "dependencies": ["x", "y", "z"] },
115        "target4a": { "class": ["APPS"], "path": ["system/project4"], "dependencies": ["out/target/common/obj/JAVA_LIBRARIES/target4b_intermediates/classes-header.jar"] },
116        "target4b": { "class": ["JAVA_LIBRARIES"],  "path": ["system/project4"], "dependencies": [] }
117      }""")
118      module_info_file.flush()
119      repo_projects = {
120          'system/project1': 'platform/project1',
121          'system/project4': 'platform/project4',
122          'vendor/google/project3': 'vendor/project3',
123      }
124      ignore_paths = set(['out/'])
125      module_info = manifest_split.ModuleInfo(module_info_file.name,
126                                              repo_projects, ignore_paths)
127      self.assertEqual(
128          module_info.project_modules, {
129              'platform/project1': set(['target1a', 'target1b']),
130              'platform/project4': set(['target4a', 'target4b']),
131              'vendor/project3': set(['target3']),
132          })
133      self.assertEqual(
134          module_info.module_project, {
135              'target1a': 'platform/project1',
136              'target1b': 'platform/project1',
137              'target3': 'vendor/project3',
138              'target4a': 'platform/project4',
139              'target4b': 'platform/project4',
140          })
141      self.assertEqual(
142          module_info.module_class, {
143              'target1a': 'EXECUTABLES',
144              'target1b': 'EXECUTABLES',
145              'target2': 'SHARED_LIBRARIES',
146              'target3': 'SHARED_LIBRARIES',
147              'target4a': 'APPS',
148              'target4b': 'JAVA_LIBRARIES',
149          })
150      self.assertEqual(
151          module_info.module_deps, {
152              'target1a': ['target2'],
153              'target1b': ['target3', 'target42'],
154              'target2': [],
155              'target3': ['x', 'y', 'z'],
156              'target4a': ['target4b'],
157              'target4b': [],
158          })
159
160  def test_get_module_info_raises_on_unknown_module_path(self):
161    with tempfile.NamedTemporaryFile('w+t') as module_info_file:
162      module_info_file.write("""{
163        "target1": { "class": ["EXECUTABLES"], "path": ["system/unknown/project1"], "dependencies": [] }
164      }""")
165      module_info_file.flush()
166      repo_projects = {}
167      ignore_paths = set()
168      with self.assertRaisesRegex(ValueError,
169                                  'Unknown module path for module target1'):
170        manifest_split.ModuleInfo(module_info_file.name, repo_projects,
171                                  ignore_paths)
172
173  @unittest.mock.patch.object(subprocess, 'check_output', autospec=True)
174  def test_get_ninja_inputs(self, mock_check_output):
175    mock_check_output.return_value = b"""
176    path/to/input1
177    path/to/input2
178    path/to/TEST_MAPPING
179    path/to/MODULE_LICENSE_GPL
180    """
181
182    inputs = manifest_split.get_ninja_inputs('unused', 'unused', ['droid'])
183    self.assertEqual(inputs, {'path/to/input1', 'path/to/input2'})
184
185  @unittest.mock.patch.object(subprocess, 'check_output', autospec=True)
186  def test_get_ninja_inputs_includes_test_mapping(self, mock_check_output):
187    mock_check_output.return_value = b"""
188    path/to/input1
189    path/to/input2
190    path/to/TEST_MAPPING
191    """
192
193    inputs = manifest_split.get_ninja_inputs('unused', 'unused',
194                                             ['droid', 'test_mapping'])
195    self.assertEqual(
196        inputs, {'path/to/input1', 'path/to/input2', 'path/to/TEST_MAPPING'})
197
198  @unittest.mock.patch.object(subprocess, 'check_output', autospec=True)
199  def test_get_kati_makefiles(self, mock_check_output):
200    with tempfile.TemporaryDirectory() as temp_dir:
201      os.chdir(temp_dir)
202
203      makefiles = [
204          'device/oem1/product1.mk',
205          'device/oem2/product2.mk',
206          'device/google/google_product.mk',
207          'overlays/oem_overlay/device/oem3/product3.mk',
208          'packages/apps/Camera/Android.mk',
209      ]
210      for makefile in makefiles:
211        os.makedirs(os.path.dirname(makefile))
212        os.mknod(makefile)
213
214      symlink_src = os.path.join(temp_dir, 'vendor/oem4/symlink_src.mk')
215      os.makedirs(os.path.dirname(symlink_src))
216      os.mknod(symlink_src)
217      symlink_dest = 'device/oem4/symlink_dest.mk'
218      os.makedirs(os.path.dirname(symlink_dest))
219      os.symlink(symlink_src, symlink_dest)
220      # Only append the symlink destination, not where the symlink points to.
221      # (The Kati stamp file does not resolve symlink sources.)
222      makefiles.append(symlink_dest)
223
224      # Mock the output of ckati_stamp_dump:
225      mock_check_output.return_value = '\n'.join(makefiles).encode()
226
227      kati_makefiles = manifest_split.get_kati_makefiles(
228          'stamp-file', ['overlays/oem_overlay/'])
229      self.assertEqual(
230          kati_makefiles,
231          set([
232              # Regular product makefiles
233              'device/oem1/product1.mk',
234              'device/oem2/product2.mk',
235              # Product makefile remapped from an overlay
236              'device/oem3/product3.mk',
237              # Product makefile symlink and its source
238              'device/oem4/symlink_dest.mk',
239              'vendor/oem4/symlink_src.mk',
240          ]))
241
242  def test_scan_repo_projects(self):
243    repo_projects = {
244        'system/project1': 'platform/project1',
245        'system/project2': 'platform/project2',
246    }
247    self.assertEqual(
248        manifest_split.scan_repo_projects(repo_projects,
249                                          'system/project1/path/to/file.h'),
250        'system/project1')
251    self.assertEqual(
252        manifest_split.scan_repo_projects(
253            repo_projects, 'system/project2/path/to/another_file.cc'),
254        'system/project2')
255    self.assertIsNone(
256        manifest_split.scan_repo_projects(
257            repo_projects, 'system/project3/path/to/unknown_file.h'))
258
259  def test_get_input_projects(self):
260    repo_projects = {
261        'system/project1': 'platform/project1',
262        'system/project2': 'platform/project2',
263        'system/project4': 'platform/project4',
264    }
265    inputs = [
266        'system/project1/path/to/file.h',
267        'out/path/to/out/file.h',
268        'system/project2/path/to/another_file.cc',
269        'system/project3/path/to/unknown_file.h',
270        '/tmp/absolute/path/file.java',
271    ]
272    self.assertEqual(
273        manifest_split.get_input_projects(repo_projects, inputs), {
274            'platform/project1': ['system/project1/path/to/file.h'],
275            'platform/project2': ['system/project2/path/to/another_file.cc'],
276        })
277
278  def test_update_manifest(self):
279    manifest_contents = """
280      <manifest>
281        <project name="platform/project1" path="system/project1" />
282        <project name="platform/project2" path="system/project2" />
283        <project name="platform/project3" path="system/project3" />
284      </manifest>"""
285    input_projects = set(['platform/project1', 'platform/project3'])
286    remove_projects = set(['platform/project3'])
287    manifest = manifest_split.update_manifest(
288        ET.ElementTree(ET.fromstring(manifest_contents)), input_projects,
289        remove_projects)
290
291    projects = manifest.getroot().findall('project')
292    self.assertEqual(len(projects), 1)
293    self.assertEqual(
294        ET.tostring(projects[0]).strip().decode(),
295        '<project name="platform/project1" path="system/project1" />')
296
297  @unittest.mock.patch.object(subprocess, 'check_output', autospec=True)
298  def test_create_split_manifest(self, mock_check_output):
299    with tempfile.NamedTemporaryFile('w+t') as repo_list_file, \
300      tempfile.NamedTemporaryFile('w+t') as manifest_file, \
301      tempfile.NamedTemporaryFile('w+t') as module_info_file, \
302      tempfile.NamedTemporaryFile('w+t') as config_file, \
303      tempfile.NamedTemporaryFile('w+t') as split_manifest_file, \
304      tempfile.TemporaryDirectory() as temp_dir:
305
306      os.chdir(temp_dir)
307
308      repo_list_file.write("""
309        system/project1 : platform/project1
310        system/project2 : platform/project2
311        system/project3 : platform/project3
312        system/project4 : platform/project4
313        system/project5 : platform/project5
314        system/project6 : platform/project6
315        system/project7 : platform/project7
316        system/project8 : platform/project8
317        system/project9 : platform/project9
318        vendor/project1 : vendor/project1""")
319      repo_list_file.flush()
320
321      manifest_file.write("""
322        <manifest>
323          <project name="platform/project1" path="system/project1" />
324          <project name="platform/project2" path="system/project2" />
325          <project name="platform/project3" path="system/project3" />
326          <project name="platform/project4" path="system/project4" />
327          <project name="platform/project5" path="system/project5" />
328          <project name="platform/project6" path="system/project6" />
329          <project name="platform/project7" path="system/project7" />
330          <project name="platform/project8" path="system/project8" />
331          <project name="platform/project9" path="system/project9" />
332          <project name="vendor/project1" path="vendor/project1" />
333        </manifest>""")
334      manifest_file.flush()
335
336      module_info_file.write("""{
337        "droid": { "class": ["EXECUTABLES"], "path": ["system/project1"], "dependencies": [] },
338        "target_a": { "class": ["EXECUTABLES"], "path": ["out/project2"], "dependencies": ["unknown_module_a"] },
339        "target_b": { "class": ["EXECUTABLES"], "path": ["system/project3"], "dependencies": ["target_f", "unknown_module_b"] },
340        "target_c": { "class": ["EXECUTABLES"], "path": ["system/project4"], "dependencies": [] },
341        "target_d": { "class": ["EXECUTABLES"], "path": ["system/project5"], "dependencies": [] },
342        "target_e": { "class": ["EXECUTABLES"], "path": ["system/project6"], "dependencies": [] },
343        "target_f": { "class": ["HEADER_LIBRARIES"], "path": ["system/project7"], "dependencies": [] },
344        "target_g": { "class": ["SHARED_LIBRARIES"], "path": ["system/project8"], "dependencies": ["target_h"] },
345        "target_h": { "class": ["HEADER_LIBRARIES"], "path": ["system/project9"], "dependencies": [] }
346      }""")
347      module_info_file.flush()
348
349      # droid needs inputs from project1 and project3
350      ninja_inputs_droid = b"""
351      system/project1/file1
352      system/project1/file2
353      system/project3/file1
354      """
355
356      # target_b (indirectly included due to being in project3) needs inputs
357      # from project3 and project4
358      ninja_inputs_target_b = b"""
359      system/project3/file2
360      system/project4/file1
361      """
362
363      # target_c (indirectly included due to being in project4) needs inputs
364      # from only project4
365      ninja_inputs_target_c = b"""
366      system/project4/file2
367      system/project4/file3
368      """
369
370      product_makefile = 'vendor/project1/product.mk'
371      os.makedirs(os.path.dirname(product_makefile))
372      os.mknod(product_makefile)
373      kati_stamp_dump = product_makefile.encode()
374
375      mock_check_output.side_effect = [
376          ninja_inputs_droid,
377          kati_stamp_dump,
378          ninja_inputs_target_b,
379          ninja_inputs_target_c,
380      ]
381
382      # The config file says to manually include project6
383      config_file.write("""
384        <config>
385          <add_project name="platform/project6" />
386        </config>""")
387      config_file.flush()
388
389      debug_file = os.path.join(temp_dir, 'debug.json')
390
391      manifest_split.create_split_manifest(
392          ['droid'], manifest_file.name, split_manifest_file.name,
393          [config_file.name], repo_list_file.name, 'build-target.ninja',
394          'ninja', module_info_file.name, 'unused kati stamp',
395          ['unused overlay'], [], debug_file)
396      split_manifest = ET.parse(split_manifest_file.name)
397      split_manifest_projects = [
398          child.attrib['name']
399          for child in split_manifest.getroot().findall('project')
400      ]
401      self.assertEqual(
402          split_manifest_projects,
403          [
404              # From droid
405              'platform/project1',
406              # From droid
407              'platform/project3',
408              # From target_b (module within project3, indirect dependency)
409              'platform/project4',
410              # Manual inclusion from config file
411              'platform/project6',
412              # From target_b (depends on target_f header library)
413              'platform/project7',
414              # Inclusion from the Kati makefile stamp
415              'vendor/project1',
416          ])
417
418      with open(debug_file) as debug_fp:
419        debug_data = json.load(debug_fp)
420
421        # Dependency for droid, but no other adjacent modules
422        self.assertTrue(debug_data['platform/project1']['direct_input'])
423        self.assertFalse(debug_data['platform/project1']['adjacent_input'])
424        self.assertFalse(debug_data['platform/project1']['deps_input'])
425
426        # Dependency for droid and an adjacent module
427        self.assertTrue(debug_data['platform/project3']['direct_input'])
428        self.assertTrue(debug_data['platform/project3']['adjacent_input'])
429        self.assertFalse(debug_data['platform/project3']['deps_input'])
430
431        # Dependency only for an adjacent module
432        self.assertFalse(debug_data['platform/project4']['direct_input'])
433        self.assertTrue(debug_data['platform/project4']['adjacent_input'])
434        self.assertFalse(debug_data['platform/project4']['deps_input'])
435
436        # Included via header library
437        self.assertFalse(debug_data['platform/project7']['direct_input'])
438        self.assertFalse(debug_data['platform/project7']['adjacent_input'])
439        self.assertTrue(debug_data['platform/project7']['deps_input'])
440
441        # Included due to the config file
442        self.assertEqual(
443            debug_data['platform/project6']['manual_add_config'],
444            config_file.name)
445
446        # Included due to the Kati makefile stamp
447        self.assertEqual(debug_data['vendor/project1']['kati_makefiles'][0],
448                         product_makefile)
449
450  @unittest.mock.patch.object(manifest_split, 'get_ninja_inputs', autospec=True)
451  @unittest.mock.patch.object(manifest_split, 'get_kati_makefiles', autospec=True)
452  @unittest.mock.patch.object(manifest_split.ModuleInfo, '__init__', autospec=True)
453  def test_create_split_manifest_skip_kati_module_info(self, mock_init,
454                                                       mock_get_kati_makefiles,
455                                                       mock_get_ninja_inputs):
456    with tempfile.NamedTemporaryFile('w+t') as repo_list_file, \
457            tempfile.NamedTemporaryFile('w+t') as manifest_file, \
458            tempfile.NamedTemporaryFile('w+t') as module_info_file, \
459            tempfile.NamedTemporaryFile('w+t') as config_file, \
460            tempfile.NamedTemporaryFile('w+t') as split_manifest_file, \
461            tempfile.TemporaryDirectory() as temp_dir:
462
463      os.chdir(temp_dir)
464
465      manifest_file.write("""
466        <manifest>
467        </manifest>""")
468      manifest_file.flush()
469
470      manifest_split.create_split_manifest(
471          targets=['droid'],
472          manifest_file=manifest_file.name,
473          split_manifest_file=split_manifest_file.name,
474          config_files=[],
475          repo_list_file=repo_list_file.name,
476          ninja_build_file='build-target.ninja',
477          ninja_binary='ninja',
478          kati_stamp_file=None,
479          module_info_file=None,
480          overlays=[],
481          installed_prebuilts=[],
482          debug_file=None)
483
484    mock_get_ninja_inputs.assert_called_with(
485        'ninja', 'build-target.ninja', ['droid'])
486    mock_get_kati_makefiles.assert_not_called()
487    mock_init.assert_not_called()
488
489  @unittest.mock.patch.object(subprocess, 'check_output', autospec=True)
490  def test_create_split_manifest_installed_prebuilt(self, mock_check_output):
491
492    # The purpose of this test is to verify that create_split_manifests treats
493    # installed prebuilts as projects, even though the installed prebuilts are
494    # not in the manifest. This use case occurs when installed prebuilts
495    # contribute modules to the build, but the installed prebuilts themselves
496    # aren't sourced from the manifest.
497
498    with tempfile.NamedTemporaryFile('w+t') as repo_list_file, \
499      tempfile.NamedTemporaryFile('w+t') as manifest_file, \
500      tempfile.NamedTemporaryFile('w+t') as module_info_file, \
501      tempfile.NamedTemporaryFile('w+t') as split_manifest_file, \
502      tempfile.TemporaryDirectory() as temp_dir:
503
504      os.chdir(temp_dir)
505
506      repo_list_file.write("""
507        system/project1 : platform/project1
508        vendor/project1 : vendor/project1""")
509      repo_list_file.flush()
510
511      # Here we have small manifest that does not include "prebuilt/project3"
512      # or "prebuilt/project4".
513
514      manifest_file.write("""
515        <manifest>
516          <project name="platform/project1" path="system/project1" />
517          <project name="vendor/project1" path="vendor/project1" />
518        </manifest>""")
519      manifest_file.flush()
520
521      # Here's the module_info.json file. It contains modules whose paths are
522      # "prebuilt/project3" and "prebult/project4", which are not found in the
523      # manifest. Normally create_split_manifest doesn't tolerate a path that
524      # doesn't correspond to a manifest project. However, this test verifies
525      # that you can use these modules if you tell create_split_manifest about
526      # the installed prebuilts via a parameter.
527
528      module_info_file.write("""{
529        "droid": { "class": ["EXECUTABLES"], "path": ["system/project1"], "dependencies": [] },
530        "target_a": { "class": ["EXECUTABLES"], "path": ["system/project1"], "dependencies": ["target_b", "target_c"] },
531        "target_b": { "class": ["SHARED_LIBRARIES"], "path": ["prebuilt/project3"], "dependencies": [] },
532        "target_c": { "class": ["SHARED_LIBRARIES"], "path": ["prebuilt/project4"], "dependencies": [] }
533      }""")
534      module_info_file.flush()
535
536      # droid needs inputs from project1
537      ninja_inputs_droid = b"""
538      system/project1/file1
539      """
540
541      # target_a needs inputs from prebuilt/project3 and prebuilt/project4
542      ninja_inputs_target_a = b"""
543      prebuilt/project3/file2
544      prebuilt/project4/file3
545      """
546
547      # target_b needs inputs from prebuilt/project3
548      ninja_inputs_target_b = b"""
549      prebuilt/project3/file4
550      """
551
552      # target_c needs inputs from prebuilt/project4
553      ninja_inputs_target_c = b"""
554      prebuilt/project4/file5
555      """
556
557      product_makefile = 'vendor/project1/product.mk'
558      os.makedirs(os.path.dirname(product_makefile))
559      os.mknod(product_makefile)
560      kati_stamp_dump = product_makefile.encode()
561
562      mock_check_output.side_effect = [
563          ninja_inputs_droid,
564          kati_stamp_dump,
565          ninja_inputs_target_a,
566          ninja_inputs_target_b,
567          ninja_inputs_target_c,
568      ]
569
570      debug_file = os.path.join(temp_dir, 'debug.json')
571
572      manifest_split.create_split_manifest(
573          targets=['droid'],
574          manifest_file=manifest_file.name,
575          split_manifest_file=split_manifest_file.name,
576          config_files=[],
577          repo_list_file=repo_list_file.name,
578          ninja_build_file='build-target.ninja',
579          ninja_binary='ninja',
580          module_info_file=module_info_file.name,
581          kati_stamp_file='unused kati stamp',
582          overlays=['unused overlay'],
583
584          # This is a key part of the test. Passing these two "projects" as
585          # prebuilts allows create_split_manifest to recognize them as
586          # projects even though they are not in the manifest.
587
588          installed_prebuilts=['prebuilt/project3', 'prebuilt/project4'],
589
590          debug_file = debug_file)
591
592      split_manifest = ET.parse(split_manifest_file.name)
593
594      split_manifest_projects = [
595          child.attrib['name']
596          for child in split_manifest.getroot().findall('project')
597      ]
598
599      # Note that the installed prebuilts do not appear in the final split
600      # manfiest output because they were not in the manifest to begin with.
601
602      self.assertEqual(
603          split_manifest_projects,
604          [
605              # From droid
606              'platform/project1',
607              # Inclusion from the Kati makefile stamp
608              'vendor/project1',
609          ])
610
611      with open(debug_file) as debug_fp:
612        debug_data = json.load(debug_fp)
613
614        # Dependency for droid, but no other adjacent modules
615        self.assertTrue(debug_data['platform/project1']['direct_input'])
616        self.assertFalse(debug_data['platform/project1']['adjacent_input'])
617        self.assertFalse(debug_data['platform/project1']['deps_input'])
618
619        # Included due to the Kati makefile stamp
620        self.assertEqual(debug_data['vendor/project1']['kati_makefiles'][0],
621                         product_makefile)
622
623
624if __name__ == '__main__':
625  unittest.main()
626