1# This Source Code Form is subject to the terms of the Mozilla Public
2# License, v. 2.0. If a copy of the MPL was not distributed with this
3# file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5from __future__ import absolute_import, print_function, unicode_literals
6
7import io
8import os
9import six.moves.cPickle as pickle
10import six
11import unittest
12
13from mozpack.manifests import InstallManifest
14from mozunit import main
15
16from mozbuild.backend.recursivemake import RecursiveMakeBackend, RecursiveMakeTraversal
17from mozbuild.backend.test_manifest import TestManifestBackend
18from mozbuild.frontend.emitter import TreeMetadataEmitter
19from mozbuild.frontend.reader import BuildReader
20
21from mozbuild.test.backend.common import BackendTester
22
23import mozpack.path as mozpath
24
25
26class TestRecursiveMakeTraversal(unittest.TestCase):
27    def test_traversal(self):
28        traversal = RecursiveMakeTraversal()
29        traversal.add("", dirs=["A", "B", "C"])
30        traversal.add("", dirs=["D"])
31        traversal.add("A")
32        traversal.add("B", dirs=["E", "F"])
33        traversal.add("C", dirs=["G", "H"])
34        traversal.add("D", dirs=["I", "K"])
35        traversal.add("D", dirs=["J", "L"])
36        traversal.add("E")
37        traversal.add("F")
38        traversal.add("G")
39        traversal.add("H")
40        traversal.add("I", dirs=["M", "N"])
41        traversal.add("J", dirs=["O", "P"])
42        traversal.add("K", dirs=["Q", "R"])
43        traversal.add("L", dirs=["S"])
44        traversal.add("M")
45        traversal.add("N", dirs=["T"])
46        traversal.add("O")
47        traversal.add("P", dirs=["U"])
48        traversal.add("Q")
49        traversal.add("R", dirs=["V"])
50        traversal.add("S", dirs=["W"])
51        traversal.add("T")
52        traversal.add("U")
53        traversal.add("V")
54        traversal.add("W", dirs=["X"])
55        traversal.add("X")
56
57        parallels = set(("G", "H", "I", "J", "O", "P", "Q", "R", "U"))
58
59        def filter(current, subdirs):
60            return (
61                current,
62                [d for d in subdirs.dirs if d in parallels],
63                [d for d in subdirs.dirs if d not in parallels],
64            )
65
66        start, deps = traversal.compute_dependencies(filter)
67        self.assertEqual(start, ("X",))
68        self.maxDiff = None
69        self.assertEqual(
70            deps,
71            {
72                "A": ("",),
73                "B": ("A",),
74                "C": ("F",),
75                "D": ("G", "H"),
76                "E": ("B",),
77                "F": ("E",),
78                "G": ("C",),
79                "H": ("C",),
80                "I": ("D",),
81                "J": ("D",),
82                "K": ("T", "O", "U"),
83                "L": ("Q", "V"),
84                "M": ("I",),
85                "N": ("M",),
86                "O": ("J",),
87                "P": ("J",),
88                "Q": ("K",),
89                "R": ("K",),
90                "S": ("L",),
91                "T": ("N",),
92                "U": ("P",),
93                "V": ("R",),
94                "W": ("S",),
95                "X": ("W",),
96            },
97        )
98
99        self.assertEqual(
100            list(traversal.traverse("", filter)),
101            [
102                "",
103                "A",
104                "B",
105                "E",
106                "F",
107                "C",
108                "G",
109                "H",
110                "D",
111                "I",
112                "M",
113                "N",
114                "T",
115                "J",
116                "O",
117                "P",
118                "U",
119                "K",
120                "Q",
121                "R",
122                "V",
123                "L",
124                "S",
125                "W",
126                "X",
127            ],
128        )
129
130        self.assertEqual(list(traversal.traverse("C", filter)), ["C", "G", "H"])
131
132    def test_traversal_2(self):
133        traversal = RecursiveMakeTraversal()
134        traversal.add("", dirs=["A", "B", "C"])
135        traversal.add("A")
136        traversal.add("B", dirs=["D", "E", "F"])
137        traversal.add("C", dirs=["G", "H", "I"])
138        traversal.add("D")
139        traversal.add("E")
140        traversal.add("F")
141        traversal.add("G")
142        traversal.add("H")
143        traversal.add("I")
144
145        start, deps = traversal.compute_dependencies()
146        self.assertEqual(start, ("I",))
147        self.assertEqual(
148            deps,
149            {
150                "A": ("",),
151                "B": ("A",),
152                "C": ("F",),
153                "D": ("B",),
154                "E": ("D",),
155                "F": ("E",),
156                "G": ("C",),
157                "H": ("G",),
158                "I": ("H",),
159            },
160        )
161
162    def test_traversal_filter(self):
163        traversal = RecursiveMakeTraversal()
164        traversal.add("", dirs=["A", "B", "C"])
165        traversal.add("A")
166        traversal.add("B", dirs=["D", "E", "F"])
167        traversal.add("C", dirs=["G", "H", "I"])
168        traversal.add("D")
169        traversal.add("E")
170        traversal.add("F")
171        traversal.add("G")
172        traversal.add("H")
173        traversal.add("I")
174
175        def filter(current, subdirs):
176            if current == "B":
177                current = None
178            return current, [], subdirs.dirs
179
180        start, deps = traversal.compute_dependencies(filter)
181        self.assertEqual(start, ("I",))
182        self.assertEqual(
183            deps,
184            {
185                "A": ("",),
186                "C": ("F",),
187                "D": ("A",),
188                "E": ("D",),
189                "F": ("E",),
190                "G": ("C",),
191                "H": ("G",),
192                "I": ("H",),
193            },
194        )
195
196    def test_traversal_parallel(self):
197        traversal = RecursiveMakeTraversal()
198        traversal.add("", dirs=["A", "B", "C"])
199        traversal.add("A")
200        traversal.add("B", dirs=["D", "E", "F"])
201        traversal.add("C", dirs=["G", "H", "I"])
202        traversal.add("D")
203        traversal.add("E")
204        traversal.add("F")
205        traversal.add("G")
206        traversal.add("H")
207        traversal.add("I")
208        traversal.add("J")
209
210        def filter(current, subdirs):
211            return current, subdirs.dirs, []
212
213        start, deps = traversal.compute_dependencies(filter)
214        self.assertEqual(start, ("A", "D", "E", "F", "G", "H", "I", "J"))
215        self.assertEqual(
216            deps,
217            {
218                "A": ("",),
219                "B": ("",),
220                "C": ("",),
221                "D": ("B",),
222                "E": ("B",),
223                "F": ("B",),
224                "G": ("C",),
225                "H": ("C",),
226                "I": ("C",),
227                "J": ("",),
228            },
229        )
230
231
232class TestRecursiveMakeBackend(BackendTester):
233    def test_basic(self):
234        """Ensure the RecursiveMakeBackend works without error."""
235        env = self._consume("stub0", RecursiveMakeBackend)
236        self.assertTrue(
237            os.path.exists(mozpath.join(env.topobjdir, "backend.RecursiveMakeBackend"))
238        )
239        self.assertTrue(
240            os.path.exists(
241                mozpath.join(env.topobjdir, "backend.RecursiveMakeBackend.in")
242            )
243        )
244
245    def test_output_files(self):
246        """Ensure proper files are generated."""
247        env = self._consume("stub0", RecursiveMakeBackend)
248
249        expected = ["", "dir1", "dir2"]
250
251        for d in expected:
252            out_makefile = mozpath.join(env.topobjdir, d, "Makefile")
253            out_backend = mozpath.join(env.topobjdir, d, "backend.mk")
254
255            self.assertTrue(os.path.exists(out_makefile))
256            self.assertTrue(os.path.exists(out_backend))
257
258    def test_makefile_conversion(self):
259        """Ensure Makefile.in is converted properly."""
260        env = self._consume("stub0", RecursiveMakeBackend)
261
262        p = mozpath.join(env.topobjdir, "Makefile")
263
264        lines = [
265            l.strip() for l in open(p, "rt").readlines()[1:] if not l.startswith("#")
266        ]
267        self.assertEqual(
268            lines,
269            [
270                "DEPTH := .",
271                "topobjdir := %s" % env.topobjdir,
272                "topsrcdir := %s" % env.topsrcdir,
273                "srcdir := %s" % env.topsrcdir,
274                "srcdir_rel := %s" % mozpath.relpath(env.topsrcdir, env.topobjdir),
275                "relativesrcdir := .",
276                "include $(DEPTH)/config/autoconf.mk",
277                "",
278                "FOO := foo",
279                "",
280                "include $(topsrcdir)/config/recurse.mk",
281            ],
282        )
283
284    def test_missing_makefile_in(self):
285        """Ensure missing Makefile.in results in Makefile creation."""
286        env = self._consume("stub0", RecursiveMakeBackend)
287
288        p = mozpath.join(env.topobjdir, "dir2", "Makefile")
289        self.assertTrue(os.path.exists(p))
290
291        lines = [l.strip() for l in open(p, "rt").readlines()]
292        self.assertEqual(len(lines), 10)
293
294        self.assertTrue(lines[0].startswith("# THIS FILE WAS AUTOMATICALLY"))
295
296    def test_backend_mk(self):
297        """Ensure backend.mk file is written out properly."""
298        env = self._consume("stub0", RecursiveMakeBackend)
299
300        p = mozpath.join(env.topobjdir, "backend.mk")
301
302        lines = [l.strip() for l in open(p, "rt").readlines()[2:]]
303        self.assertEqual(lines, ["DIRS := dir1 dir2"])
304
305        # Make env.substs writable to add ENABLE_TESTS
306        env.substs = dict(env.substs)
307        env.substs["ENABLE_TESTS"] = "1"
308        self._consume("stub0", RecursiveMakeBackend, env=env)
309        p = mozpath.join(env.topobjdir, "backend.mk")
310
311        lines = [l.strip() for l in open(p, "rt").readlines()[2:]]
312        self.assertEqual(lines, ["DIRS := dir1 dir2 dir3"])
313
314    def test_mtime_no_change(self):
315        """Ensure mtime is not updated if file content does not change."""
316
317        env = self._consume("stub0", RecursiveMakeBackend)
318
319        makefile_path = mozpath.join(env.topobjdir, "Makefile")
320        backend_path = mozpath.join(env.topobjdir, "backend.mk")
321        makefile_mtime = os.path.getmtime(makefile_path)
322        backend_mtime = os.path.getmtime(backend_path)
323
324        reader = BuildReader(env)
325        emitter = TreeMetadataEmitter(env)
326        backend = RecursiveMakeBackend(env)
327        backend.consume(emitter.emit(reader.read_topsrcdir()))
328
329        self.assertEqual(os.path.getmtime(makefile_path), makefile_mtime)
330        self.assertEqual(os.path.getmtime(backend_path), backend_mtime)
331
332    def test_substitute_config_files(self):
333        """Ensure substituted config files are produced."""
334        env = self._consume("substitute_config_files", RecursiveMakeBackend)
335
336        p = mozpath.join(env.topobjdir, "foo")
337        self.assertTrue(os.path.exists(p))
338        lines = [l.strip() for l in open(p, "rt").readlines()]
339        self.assertEqual(lines, ["TEST = foo"])
340
341    def test_install_substitute_config_files(self):
342        """Ensure we recurse into the dirs that install substituted config files."""
343        env = self._consume("install_substitute_config_files", RecursiveMakeBackend)
344
345        root_deps_path = mozpath.join(env.topobjdir, "root-deps.mk")
346        lines = [l.strip() for l in open(root_deps_path, "rt").readlines()]
347
348        # Make sure we actually recurse into the sub directory during export to
349        # install the subst file.
350        self.assertTrue(any(l == "recurse_export: sub/export" for l in lines))
351
352    def test_variable_passthru(self):
353        """Ensure variable passthru is written out correctly."""
354        env = self._consume("variable_passthru", RecursiveMakeBackend)
355
356        backend_path = mozpath.join(env.topobjdir, "backend.mk")
357        lines = [l.strip() for l in open(backend_path, "rt").readlines()[2:]]
358
359        expected = {
360            "RCFILE": ["RCFILE := $(srcdir)/foo.rc"],
361            "RCINCLUDE": ["RCINCLUDE := $(srcdir)/bar.rc"],
362            "WIN32_EXE_LDFLAGS": ["WIN32_EXE_LDFLAGS += -subsystem:console"],
363        }
364
365        for var, val in expected.items():
366            # print("test_variable_passthru[%s]" % (var))
367            found = [str for str in lines if str.startswith(var)]
368            self.assertEqual(found, val)
369
370    def test_sources(self):
371        """Ensure SOURCES, HOST_SOURCES and WASM_SOURCES are handled properly."""
372        env = self._consume("sources", RecursiveMakeBackend)
373
374        backend_path = mozpath.join(env.topobjdir, "backend.mk")
375        lines = [l.strip() for l in open(backend_path, "rt").readlines()[2:]]
376
377        expected = {
378            "ASFILES": ["ASFILES += $(srcdir)/bar.s", "ASFILES += $(srcdir)/foo.asm"],
379            "CMMSRCS": ["CMMSRCS += $(srcdir)/bar.mm", "CMMSRCS += $(srcdir)/foo.mm"],
380            "CSRCS": ["CSRCS += $(srcdir)/bar.c", "CSRCS += $(srcdir)/foo.c"],
381            "HOST_CPPSRCS": [
382                "HOST_CPPSRCS += $(srcdir)/bar.cpp",
383                "HOST_CPPSRCS += $(srcdir)/foo.cpp",
384            ],
385            "HOST_CSRCS": [
386                "HOST_CSRCS += $(srcdir)/bar.c",
387                "HOST_CSRCS += $(srcdir)/foo.c",
388            ],
389            "SSRCS": ["SSRCS += $(srcdir)/baz.S", "SSRCS += $(srcdir)/foo.S"],
390            "WASM_CSRCS": ["WASM_CSRCS += $(srcdir)/bar.c"],
391            "WASM_CPPSRCS": ["WASM_CPPSRCS += $(srcdir)/bar.cpp"],
392        }
393
394        for var, val in expected.items():
395            found = [str for str in lines if str.startswith(var)]
396            self.assertEqual(found, val)
397
398    def test_exports(self):
399        """Ensure EXPORTS is handled properly."""
400        env = self._consume("exports", RecursiveMakeBackend)
401
402        # EXPORTS files should appear in the dist_include install manifest.
403        m = InstallManifest(
404            path=mozpath.join(
405                env.topobjdir, "_build_manifests", "install", "dist_include"
406            )
407        )
408        self.assertEqual(len(m), 7)
409        self.assertIn("foo.h", m)
410        self.assertIn("mozilla/mozilla1.h", m)
411        self.assertIn("mozilla/dom/dom2.h", m)
412
413    def test_generated_files(self):
414        """Ensure GENERATED_FILES is handled properly."""
415        env = self._consume("generated-files", RecursiveMakeBackend)
416
417        backend_path = mozpath.join(env.topobjdir, "backend.mk")
418        lines = [l.strip() for l in open(backend_path, "rt").readlines()[2:]]
419
420        expected = [
421            "include $(topsrcdir)/config/AB_rCD.mk",
422            "PRE_COMPILE_TARGETS += $(MDDEPDIR)/bar.c.stub",
423            "bar.c: $(MDDEPDIR)/bar.c.stub ;",
424            "EXTRA_MDDEPEND_FILES += $(MDDEPDIR)/bar.c.pp",
425            "$(MDDEPDIR)/bar.c.stub: %s/generate-bar.py" % env.topsrcdir,
426            "$(REPORT_BUILD)",
427            "$(call py_action,file_generate,%s/generate-bar.py baz bar.c $(MDDEPDIR)/bar.c.pp $(MDDEPDIR)/bar.c.stub)"  # noqa
428            % env.topsrcdir,
429            "@$(TOUCH) $@",
430            "",
431            "EXPORT_TARGETS += $(MDDEPDIR)/foo.h.stub",
432            "foo.h: $(MDDEPDIR)/foo.h.stub ;",
433            "EXTRA_MDDEPEND_FILES += $(MDDEPDIR)/foo.h.pp",
434            "$(MDDEPDIR)/foo.h.stub: %s/generate-foo.py $(srcdir)/foo-data"
435            % (env.topsrcdir),
436            "$(REPORT_BUILD)",
437            "$(call py_action,file_generate,%s/generate-foo.py main foo.h $(MDDEPDIR)/foo.h.pp $(MDDEPDIR)/foo.h.stub $(srcdir)/foo-data)"  # noqa
438            % (env.topsrcdir),
439            "@$(TOUCH) $@",
440            "",
441        ]
442
443        self.maxDiff = None
444        self.assertEqual(lines, expected)
445
446    def test_generated_files_force(self):
447        """Ensure GENERATED_FILES with .force is handled properly."""
448        env = self._consume("generated-files-force", RecursiveMakeBackend)
449
450        backend_path = mozpath.join(env.topobjdir, "backend.mk")
451        lines = [l.strip() for l in open(backend_path, "rt").readlines()[2:]]
452
453        expected = [
454            "include $(topsrcdir)/config/AB_rCD.mk",
455            "PRE_COMPILE_TARGETS += $(MDDEPDIR)/bar.c.stub",
456            "bar.c: $(MDDEPDIR)/bar.c.stub ;",
457            "EXTRA_MDDEPEND_FILES += $(MDDEPDIR)/bar.c.pp",
458            "$(MDDEPDIR)/bar.c.stub: %s/generate-bar.py FORCE" % env.topsrcdir,
459            "$(REPORT_BUILD)",
460            "$(call py_action,file_generate,%s/generate-bar.py baz bar.c $(MDDEPDIR)/bar.c.pp $(MDDEPDIR)/bar.c.stub)"  # noqa
461            % env.topsrcdir,
462            "@$(TOUCH) $@",
463            "",
464            "PRE_COMPILE_TARGETS += $(MDDEPDIR)/foo.c.stub",
465            "foo.c: $(MDDEPDIR)/foo.c.stub ;",
466            "EXTRA_MDDEPEND_FILES += $(MDDEPDIR)/foo.c.pp",
467            "$(MDDEPDIR)/foo.c.stub: %s/generate-foo.py $(srcdir)/foo-data"
468            % (env.topsrcdir),
469            "$(REPORT_BUILD)",
470            "$(call py_action,file_generate,%s/generate-foo.py main foo.c $(MDDEPDIR)/foo.c.pp $(MDDEPDIR)/foo.c.stub $(srcdir)/foo-data)"  # noqa
471            % (env.topsrcdir),
472            "@$(TOUCH) $@",
473            "",
474        ]
475
476        self.maxDiff = None
477        self.assertEqual(lines, expected)
478
479    def test_localized_generated_files(self):
480        """Ensure LOCALIZED_GENERATED_FILES is handled properly."""
481        env = self._consume("localized-generated-files", RecursiveMakeBackend)
482
483        backend_path = mozpath.join(env.topobjdir, "backend.mk")
484        lines = [l.strip() for l in open(backend_path, "rt").readlines()[2:]]
485
486        expected = [
487            "include $(topsrcdir)/config/AB_rCD.mk",
488            "MISC_TARGETS += $(MDDEPDIR)/foo.xyz.stub",
489            "foo.xyz: $(MDDEPDIR)/foo.xyz.stub ;",
490            "EXTRA_MDDEPEND_FILES += $(MDDEPDIR)/foo.xyz.pp",
491            "$(MDDEPDIR)/foo.xyz.stub: %s/generate-foo.py $(call MERGE_FILE,localized-input) $(srcdir)/non-localized-input $(if $(IS_LANGUAGE_REPACK),FORCE)"  # noqa
492            % env.topsrcdir,
493            "$(REPORT_BUILD)",
494            "$(call py_action,file_generate,--locale=$(AB_CD) %s/generate-foo.py main foo.xyz $(MDDEPDIR)/foo.xyz.pp $(MDDEPDIR)/foo.xyz.stub $(call MERGE_FILE,localized-input) $(srcdir)/non-localized-input)"  # noqa
495            % env.topsrcdir,
496            "@$(TOUCH) $@",
497            "",
498            "LOCALIZED_FILES_0_FILES += foo.xyz",
499            "LOCALIZED_FILES_0_DEST = $(FINAL_TARGET)/",
500            "LOCALIZED_FILES_0_TARGET := misc",
501            "INSTALL_TARGETS += LOCALIZED_FILES_0",
502        ]
503
504        self.maxDiff = None
505        self.assertEqual(lines, expected)
506
507    def test_localized_generated_files_force(self):
508        """Ensure LOCALIZED_GENERATED_FILES with .force is handled properly."""
509        env = self._consume("localized-generated-files-force", RecursiveMakeBackend)
510
511        backend_path = mozpath.join(env.topobjdir, "backend.mk")
512        lines = [l.strip() for l in open(backend_path, "rt").readlines()[2:]]
513
514        expected = [
515            "include $(topsrcdir)/config/AB_rCD.mk",
516            "MISC_TARGETS += $(MDDEPDIR)/foo.xyz.stub",
517            "foo.xyz: $(MDDEPDIR)/foo.xyz.stub ;",
518            "EXTRA_MDDEPEND_FILES += $(MDDEPDIR)/foo.xyz.pp",
519            "$(MDDEPDIR)/foo.xyz.stub: %s/generate-foo.py $(call MERGE_FILE,localized-input) $(srcdir)/non-localized-input $(if $(IS_LANGUAGE_REPACK),FORCE)"  # noqa
520            % env.topsrcdir,
521            "$(REPORT_BUILD)",
522            "$(call py_action,file_generate,--locale=$(AB_CD) %s/generate-foo.py main foo.xyz $(MDDEPDIR)/foo.xyz.pp $(MDDEPDIR)/foo.xyz.stub $(call MERGE_FILE,localized-input) $(srcdir)/non-localized-input)"  # noqa
523            % env.topsrcdir,
524            "@$(TOUCH) $@",
525            "",
526            "MISC_TARGETS += $(MDDEPDIR)/abc.xyz.stub",
527            "abc.xyz: $(MDDEPDIR)/abc.xyz.stub ;",
528            "EXTRA_MDDEPEND_FILES += $(MDDEPDIR)/abc.xyz.pp",
529            "$(MDDEPDIR)/abc.xyz.stub: %s/generate-foo.py $(call MERGE_FILE,localized-input) $(srcdir)/non-localized-input FORCE"  # noqa
530            % env.topsrcdir,
531            "$(REPORT_BUILD)",
532            "$(call py_action,file_generate,--locale=$(AB_CD) %s/generate-foo.py main abc.xyz $(MDDEPDIR)/abc.xyz.pp $(MDDEPDIR)/abc.xyz.stub $(call MERGE_FILE,localized-input) $(srcdir)/non-localized-input)"  # noqa
533            % env.topsrcdir,
534            "@$(TOUCH) $@",
535            "",
536        ]
537
538        self.maxDiff = None
539        self.assertEqual(lines, expected)
540
541    def test_localized_generated_files_AB_CD(self):
542        """Ensure LOCALIZED_GENERATED_FILES is handled properly
543        when {AB_CD} and {AB_rCD} are used."""
544        env = self._consume("localized-generated-files-AB_CD", RecursiveMakeBackend)
545
546        backend_path = mozpath.join(env.topobjdir, "backend.mk")
547        lines = [l.strip() for l in open(backend_path, "rt").readlines()[2:]]
548
549        expected = [
550            "include $(topsrcdir)/config/AB_rCD.mk",
551            "MISC_TARGETS += $(MDDEPDIR)/foo$(AB_CD).xyz.stub",
552            "foo$(AB_CD).xyz: $(MDDEPDIR)/foo$(AB_CD).xyz.stub ;",
553            "EXTRA_MDDEPEND_FILES += $(MDDEPDIR)/foo$(AB_CD).xyz.pp",
554            "$(MDDEPDIR)/foo$(AB_CD).xyz.stub: %s/generate-foo.py $(call MERGE_FILE,localized-input) $(srcdir)/non-localized-input $(if $(IS_LANGUAGE_REPACK),FORCE)"  # noqa
555            % env.topsrcdir,
556            "$(REPORT_BUILD)",
557            "$(call py_action,file_generate,--locale=$(AB_CD) %s/generate-foo.py main foo$(AB_CD).xyz $(MDDEPDIR)/foo$(AB_CD).xyz.pp $(MDDEPDIR)/foo$(AB_CD).xyz.stub $(call MERGE_FILE,localized-input) $(srcdir)/non-localized-input)"  # noqa
558            % env.topsrcdir,
559            "@$(TOUCH) $@",
560            "",
561            "bar$(AB_rCD).xyz: $(MDDEPDIR)/bar$(AB_rCD).xyz.stub ;",
562            "EXTRA_MDDEPEND_FILES += $(MDDEPDIR)/bar$(AB_rCD).xyz.pp",
563            "$(MDDEPDIR)/bar$(AB_rCD).xyz.stub: %s/generate-foo.py $(call MERGE_RELATIVE_FILE,localized-input,inner/locales) $(srcdir)/non-localized-input $(if $(IS_LANGUAGE_REPACK),FORCE)"  # noqa
564            % env.topsrcdir,
565            "$(REPORT_BUILD)",
566            "$(call py_action,file_generate,--locale=$(AB_CD) %s/generate-foo.py main bar$(AB_rCD).xyz $(MDDEPDIR)/bar$(AB_rCD).xyz.pp $(MDDEPDIR)/bar$(AB_rCD).xyz.stub $(call MERGE_RELATIVE_FILE,localized-input,inner/locales) $(srcdir)/non-localized-input)"  # noqa
567            % env.topsrcdir,
568            "@$(TOUCH) $@",
569            "",
570            "zot$(AB_rCD).xyz: $(MDDEPDIR)/zot$(AB_rCD).xyz.stub ;",
571            "EXTRA_MDDEPEND_FILES += $(MDDEPDIR)/zot$(AB_rCD).xyz.pp",
572            "$(MDDEPDIR)/zot$(AB_rCD).xyz.stub: %s/generate-foo.py $(call MERGE_RELATIVE_FILE,localized-input,locales) $(srcdir)/non-localized-input $(if $(IS_LANGUAGE_REPACK),FORCE)"  # noqa
573            % env.topsrcdir,
574            "$(REPORT_BUILD)",
575            "$(call py_action,file_generate,--locale=$(AB_CD) %s/generate-foo.py main zot$(AB_rCD).xyz $(MDDEPDIR)/zot$(AB_rCD).xyz.pp $(MDDEPDIR)/zot$(AB_rCD).xyz.stub $(call MERGE_RELATIVE_FILE,localized-input,locales) $(srcdir)/non-localized-input)"  # noqa
576            % env.topsrcdir,
577            "@$(TOUCH) $@",
578            "",
579        ]
580
581        self.maxDiff = None
582        self.assertEqual(lines, expected)
583
584    def test_exports_generated(self):
585        """Ensure EXPORTS that are listed in GENERATED_FILES
586        are handled properly."""
587        env = self._consume("exports-generated", RecursiveMakeBackend)
588
589        # EXPORTS files should appear in the dist_include install manifest.
590        m = InstallManifest(
591            path=mozpath.join(
592                env.topobjdir, "_build_manifests", "install", "dist_include"
593            )
594        )
595        self.assertEqual(len(m), 8)
596        self.assertIn("foo.h", m)
597        self.assertIn("mozilla/mozilla1.h", m)
598        self.assertIn("mozilla/dom/dom1.h", m)
599        self.assertIn("gfx/gfx.h", m)
600        self.assertIn("bar.h", m)
601        self.assertIn("mozilla/mozilla2.h", m)
602        self.assertIn("mozilla/dom/dom2.h", m)
603        self.assertIn("mozilla/dom/dom3.h", m)
604        # EXPORTS files that are also GENERATED_FILES should be handled as
605        # INSTALL_TARGETS.
606        backend_path = mozpath.join(env.topobjdir, "backend.mk")
607        lines = [l.strip() for l in open(backend_path, "rt").readlines()[2:]]
608        expected = [
609            "include $(topsrcdir)/config/AB_rCD.mk",
610            "dist_include_FILES += bar.h",
611            "dist_include_DEST := $(DEPTH)/dist/include/",
612            "dist_include_TARGET := export",
613            "INSTALL_TARGETS += dist_include",
614            "dist_include_mozilla_FILES += mozilla2.h",
615            "dist_include_mozilla_DEST := $(DEPTH)/dist/include/mozilla",
616            "dist_include_mozilla_TARGET := export",
617            "INSTALL_TARGETS += dist_include_mozilla",
618            "dist_include_mozilla_dom_FILES += dom2.h",
619            "dist_include_mozilla_dom_FILES += dom3.h",
620            "dist_include_mozilla_dom_DEST := $(DEPTH)/dist/include/mozilla/dom",
621            "dist_include_mozilla_dom_TARGET := export",
622            "INSTALL_TARGETS += dist_include_mozilla_dom",
623        ]
624        self.maxDiff = None
625        self.assertEqual(lines, expected)
626
627    def test_resources(self):
628        """Ensure RESOURCE_FILES is handled properly."""
629        env = self._consume("resources", RecursiveMakeBackend)
630
631        # RESOURCE_FILES should appear in the dist_bin install manifest.
632        m = InstallManifest(
633            path=os.path.join(env.topobjdir, "_build_manifests", "install", "dist_bin")
634        )
635        self.assertEqual(len(m), 10)
636        self.assertIn("res/foo.res", m)
637        self.assertIn("res/fonts/font1.ttf", m)
638        self.assertIn("res/fonts/desktop/desktop2.ttf", m)
639
640        self.assertIn("res/bar.res.in", m)
641        self.assertIn("res/tests/test.manifest", m)
642        self.assertIn("res/tests/extra.manifest", m)
643
644    def test_test_manifests_files_written(self):
645        """Ensure test manifests get turned into files."""
646        env = self._consume("test-manifests-written", RecursiveMakeBackend)
647
648        tests_dir = mozpath.join(env.topobjdir, "_tests")
649        m_master = mozpath.join(
650            tests_dir, "testing", "mochitest", "tests", "mochitest.ini"
651        )
652        x_master = mozpath.join(tests_dir, "xpcshell", "xpcshell.ini")
653        self.assertTrue(os.path.exists(m_master))
654        self.assertTrue(os.path.exists(x_master))
655
656        lines = [l.strip() for l in open(x_master, "rt").readlines()]
657        self.assertEqual(
658            lines,
659            [
660                "# THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT MODIFY BY HAND.",
661                "",
662                "[include:dir1/xpcshell.ini]",
663                "[include:xpcshell.ini]",
664            ],
665        )
666
667    def test_test_manifest_pattern_matches_recorded(self):
668        """Pattern matches in test manifests' support-files should be recorded."""
669        env = self._consume("test-manifests-written", RecursiveMakeBackend)
670        m = InstallManifest(
671            path=mozpath.join(
672                env.topobjdir, "_build_manifests", "install", "_test_files"
673            )
674        )
675
676        # This is not the most robust test in the world, but it gets the job
677        # done.
678        entries = [e for e in m._dests.keys() if "**" in e]
679        self.assertEqual(len(entries), 1)
680        self.assertIn("support/**", entries[0])
681
682    def test_test_manifest_deffered_installs_written(self):
683        """Shared support files are written to their own data file by the backend."""
684        env = self._consume("test-manifest-shared-support", RecursiveMakeBackend)
685
686        # First, read the generated for ini manifest contents.
687        test_files_manifest = mozpath.join(
688            env.topobjdir, "_build_manifests", "install", "_test_files"
689        )
690        m = InstallManifest(path=test_files_manifest)
691
692        # Then, synthesize one from the test-installs.pkl file. This should
693        # allow us to re-create a subset of the above.
694        env = self._consume("test-manifest-shared-support", TestManifestBackend)
695        test_installs_path = mozpath.join(env.topobjdir, "test-installs.pkl")
696
697        with open(test_installs_path, "rb") as fh:
698            test_installs = pickle.load(fh)
699
700        self.assertEqual(
701            set(test_installs.keys()),
702            set(["child/test_sub.js", "child/data/**", "child/another-file.sjs"]),
703        )
704        for key in test_installs.keys():
705            self.assertIn(key, test_installs)
706
707        synthesized_manifest = InstallManifest()
708        for item, installs in test_installs.items():
709            for install_info in installs:
710                if len(install_info) == 3:
711                    synthesized_manifest.add_pattern_link(*install_info)
712                if len(install_info) == 2:
713                    synthesized_manifest.add_link(*install_info)
714
715        self.assertEqual(len(synthesized_manifest), 3)
716        for item, info in synthesized_manifest._dests.items():
717            self.assertIn(item, m)
718            self.assertEqual(info, m._dests[item])
719
720    def test_xpidl_generation(self):
721        """Ensure xpidl files and directories are written out."""
722        env = self._consume("xpidl", RecursiveMakeBackend)
723
724        # Install manifests should contain entries.
725        install_dir = mozpath.join(env.topobjdir, "_build_manifests", "install")
726        self.assertTrue(os.path.isfile(mozpath.join(install_dir, "xpidl")))
727
728        m = InstallManifest(path=mozpath.join(install_dir, "xpidl"))
729        self.assertIn(".deps/my_module.pp", m)
730
731        m = InstallManifest(path=mozpath.join(install_dir, "xpidl"))
732        self.assertIn("my_module.xpt", m)
733
734        m = InstallManifest(path=mozpath.join(install_dir, "dist_include"))
735        self.assertIn("foo.h", m)
736
737        p = mozpath.join(env.topobjdir, "config/makefiles/xpidl")
738        self.assertTrue(os.path.isdir(p))
739
740        self.assertTrue(os.path.isfile(mozpath.join(p, "Makefile")))
741
742    def test_test_support_files_tracked(self):
743        env = self._consume("test-support-binaries-tracked", RecursiveMakeBackend)
744        m = InstallManifest(
745            path=mozpath.join(env.topobjdir, "_build_manifests", "install", "_tests")
746        )
747        self.assertEqual(len(m), 4)
748        self.assertIn("xpcshell/tests/mozbuildtest/test-library.dll", m)
749        self.assertIn("xpcshell/tests/mozbuildtest/test-one.exe", m)
750        self.assertIn("xpcshell/tests/mozbuildtest/test-two.exe", m)
751        self.assertIn("xpcshell/tests/mozbuildtest/host-test-library.dll", m)
752
753    def test_old_install_manifest_deleted(self):
754        # Simulate an install manifest from a previous backend version. Ensure
755        # it is deleted.
756        env = self._get_environment("stub0")
757        purge_dir = mozpath.join(env.topobjdir, "_build_manifests", "install")
758        manifest_path = mozpath.join(purge_dir, "old_manifest")
759        os.makedirs(purge_dir)
760        m = InstallManifest()
761        m.write(path=manifest_path)
762        with open(
763            mozpath.join(env.topobjdir, "backend.RecursiveMakeBackend"), "w"
764        ) as f:
765            f.write("%s\n" % manifest_path)
766
767        self.assertTrue(os.path.exists(manifest_path))
768        self._consume("stub0", RecursiveMakeBackend, env)
769        self.assertFalse(os.path.exists(manifest_path))
770
771    def test_install_manifests_written(self):
772        env, objs = self._emit("stub0")
773        backend = RecursiveMakeBackend(env)
774
775        m = InstallManifest()
776        backend._install_manifests["testing"] = m
777        m.add_link(__file__, "self")
778        backend.consume(objs)
779
780        man_dir = mozpath.join(env.topobjdir, "_build_manifests", "install")
781        self.assertTrue(os.path.isdir(man_dir))
782
783        expected = ["testing"]
784        for e in expected:
785            full = mozpath.join(man_dir, e)
786            self.assertTrue(os.path.exists(full))
787
788            m2 = InstallManifest(path=full)
789            self.assertEqual(m, m2)
790
791    def test_ipdl_sources(self):
792        """Test that PREPROCESSED_IPDL_SOURCES and IPDL_SOURCES are written to
793        ipdlsrcs.mk correctly."""
794        env = self._get_environment("ipdl_sources")
795
796        # Make substs writable so we can set the value of IPDL_ROOT to reflect
797        # the correct objdir.
798        env.substs = dict(env.substs)
799        env.substs["IPDL_ROOT"] = env.topobjdir
800
801        self._consume("ipdl_sources", RecursiveMakeBackend, env)
802
803        manifest_path = mozpath.join(env.topobjdir, "ipdlsrcs.mk")
804        lines = [l.strip() for l in open(manifest_path, "rt").readlines()]
805
806        # Handle Windows paths correctly
807        topsrcdir = env.topsrcdir.replace(os.sep, "/")
808
809        expected = [
810            "ALL_IPDLSRCS := bar1.ipdl foo1.ipdl %s/bar/bar.ipdl %s/bar/bar2.ipdlh %s/foo/foo.ipdl %s/foo/foo2.ipdlh"  # noqa
811            % tuple([topsrcdir] * 4),
812            "CPPSRCS := UnifiedProtocols0.cpp",
813            "IPDLDIRS := %s %s/bar %s/foo" % (env.topobjdir, topsrcdir, topsrcdir),
814        ]
815
816        found = [
817            str
818            for str in lines
819            if str.startswith(("ALL_IPDLSRCS", "CPPSRCS", "IPDLDIRS"))
820        ]
821        self.assertEqual(found, expected)
822
823    def test_defines(self):
824        """Test that DEFINES are written to backend.mk correctly."""
825        env = self._consume("defines", RecursiveMakeBackend)
826
827        backend_path = mozpath.join(env.topobjdir, "backend.mk")
828        lines = [l.strip() for l in open(backend_path, "rt").readlines()[2:]]
829
830        var = "DEFINES"
831        defines = [val for val in lines if val.startswith(var)]
832
833        expected = ["DEFINES += -DFOO '-DBAZ=\"ab'\\''cd\"' -UQUX -DBAR=7 -DVALUE=xyz"]
834        self.assertEqual(defines, expected)
835
836    def test_local_includes(self):
837        """Test that LOCAL_INCLUDES are written to backend.mk correctly."""
838        env = self._consume("local_includes", RecursiveMakeBackend)
839
840        backend_path = mozpath.join(env.topobjdir, "backend.mk")
841        lines = [l.strip() for l in open(backend_path, "rt").readlines()[2:]]
842
843        expected = [
844            "LOCAL_INCLUDES += -I$(srcdir)/bar/baz",
845            "LOCAL_INCLUDES += -I$(srcdir)/foo",
846        ]
847
848        found = [str for str in lines if str.startswith("LOCAL_INCLUDES")]
849        self.assertEqual(found, expected)
850
851    def test_generated_includes(self):
852        """Test that GENERATED_INCLUDES are written to backend.mk correctly."""
853        env = self._consume("generated_includes", RecursiveMakeBackend)
854
855        backend_path = mozpath.join(env.topobjdir, "backend.mk")
856        lines = [l.strip() for l in open(backend_path, "rt").readlines()[2:]]
857
858        expected = [
859            "LOCAL_INCLUDES += -I$(CURDIR)/bar/baz",
860            "LOCAL_INCLUDES += -I$(CURDIR)/foo",
861        ]
862
863        found = [str for str in lines if str.startswith("LOCAL_INCLUDES")]
864        self.assertEqual(found, expected)
865
866    def test_rust_library(self):
867        """Test that a Rust library is written to backend.mk correctly."""
868        env = self._consume("rust-library", RecursiveMakeBackend)
869
870        backend_path = mozpath.join(env.topobjdir, "backend.mk")
871        lines = [
872            l.strip()
873            for l in open(backend_path, "rt").readlines()[2:]
874            # Strip out computed flags, they're a PITA to test.
875            if not l.startswith("COMPUTED_")
876        ]
877
878        expected = [
879            "RUST_LIBRARY_FILE := %s/x86_64-unknown-linux-gnu/release/libtest_library.a"
880            % env.topobjdir,  # noqa
881            "CARGO_FILE := $(srcdir)/Cargo.toml",
882            "CARGO_TARGET_DIR := %s" % env.topobjdir,
883        ]
884
885        self.assertEqual(lines, expected)
886
887    def test_host_rust_library(self):
888        """Test that a Rust library is written to backend.mk correctly."""
889        env = self._consume("host-rust-library", RecursiveMakeBackend)
890
891        backend_path = mozpath.join(env.topobjdir, "backend.mk")
892        lines = [
893            l.strip()
894            for l in open(backend_path, "rt").readlines()[2:]
895            # Strip out computed flags, they're a PITA to test.
896            if not l.startswith("COMPUTED_")
897        ]
898
899        expected = [
900            "HOST_RUST_LIBRARY_FILE := %s/x86_64-unknown-linux-gnu/release/libhostrusttool.a"
901            % env.topobjdir,  # noqa
902            "CARGO_FILE := $(srcdir)/Cargo.toml",
903            "CARGO_TARGET_DIR := %s" % env.topobjdir,
904        ]
905
906        self.assertEqual(lines, expected)
907
908    def test_host_rust_library_with_features(self):
909        """Test that a host Rust library with features is written to backend.mk correctly."""
910        env = self._consume("host-rust-library-features", RecursiveMakeBackend)
911
912        backend_path = mozpath.join(env.topobjdir, "backend.mk")
913        lines = [
914            l.strip()
915            for l in open(backend_path, "rt").readlines()[2:]
916            # Strip out computed flags, they're a PITA to test.
917            if not l.startswith("COMPUTED_")
918        ]
919
920        expected = [
921            "HOST_RUST_LIBRARY_FILE := %s/x86_64-unknown-linux-gnu/release/libhostrusttool.a"
922            % env.topobjdir,  # noqa
923            "CARGO_FILE := $(srcdir)/Cargo.toml",
924            "CARGO_TARGET_DIR := %s" % env.topobjdir,
925            "HOST_RUST_LIBRARY_FEATURES := musthave cantlivewithout",
926        ]
927
928        self.assertEqual(lines, expected)
929
930    def test_rust_library_with_features(self):
931        """Test that a Rust library with features is written to backend.mk correctly."""
932        env = self._consume("rust-library-features", RecursiveMakeBackend)
933
934        backend_path = mozpath.join(env.topobjdir, "backend.mk")
935        lines = [
936            l.strip()
937            for l in open(backend_path, "rt").readlines()[2:]
938            # Strip out computed flags, they're a PITA to test.
939            if not l.startswith("COMPUTED_")
940        ]
941
942        expected = [
943            "RUST_LIBRARY_FILE := %s/x86_64-unknown-linux-gnu/release/libfeature_library.a"
944            % env.topobjdir,  # noqa
945            "CARGO_FILE := $(srcdir)/Cargo.toml",
946            "CARGO_TARGET_DIR := %s" % env.topobjdir,
947            "RUST_LIBRARY_FEATURES := musthave cantlivewithout",
948        ]
949
950        self.assertEqual(lines, expected)
951
952    def test_rust_programs(self):
953        """Test that {HOST_,}RUST_PROGRAMS are written to backend.mk correctly."""
954        env = self._consume("rust-programs", RecursiveMakeBackend)
955
956        backend_path = mozpath.join(env.topobjdir, "code/backend.mk")
957        lines = [
958            l.strip()
959            for l in open(backend_path, "rt").readlines()[2:]
960            # Strip out computed flags, they're a PITA to test.
961            if not l.startswith("COMPUTED_")
962        ]
963
964        expected = [
965            "CARGO_FILE := %s/code/Cargo.toml" % env.topsrcdir,
966            "CARGO_TARGET_DIR := .",
967            "RUST_PROGRAMS += i686-pc-windows-msvc/release/target.exe",
968            "RUST_CARGO_PROGRAMS += target",
969            "HOST_RUST_PROGRAMS += i686-pc-windows-msvc/release/host.exe",
970            "HOST_RUST_CARGO_PROGRAMS += host",
971        ]
972
973        self.assertEqual(lines, expected)
974
975        root_deps_path = mozpath.join(env.topobjdir, "root-deps.mk")
976        lines = [l.strip() for l in open(root_deps_path, "rt").readlines()]
977
978        self.assertTrue(
979            any(l == "recurse_compile: code/host code/target" for l in lines)
980        )
981
982    def test_final_target(self):
983        """Test that FINAL_TARGET is written to backend.mk correctly."""
984        env = self._consume("final_target", RecursiveMakeBackend)
985
986        final_target_rule = "FINAL_TARGET = $(if $(XPI_NAME),$(DIST)/xpi-stage/$(XPI_NAME),$(DIST)/bin)$(DIST_SUBDIR:%=/%)"  # noqa
987        expected = dict()
988        expected[env.topobjdir] = []
989        expected[mozpath.join(env.topobjdir, "both")] = [
990            "XPI_NAME = mycrazyxpi",
991            "DIST_SUBDIR = asubdir",
992            final_target_rule,
993        ]
994        expected[mozpath.join(env.topobjdir, "dist-subdir")] = [
995            "DIST_SUBDIR = asubdir",
996            final_target_rule,
997        ]
998        expected[mozpath.join(env.topobjdir, "xpi-name")] = [
999            "XPI_NAME = mycrazyxpi",
1000            final_target_rule,
1001        ]
1002        expected[mozpath.join(env.topobjdir, "final-target")] = [
1003            "FINAL_TARGET = $(DEPTH)/random-final-target"
1004        ]
1005        for key, expected_rules in six.iteritems(expected):
1006            backend_path = mozpath.join(key, "backend.mk")
1007            lines = [l.strip() for l in open(backend_path, "rt").readlines()[2:]]
1008            found = [
1009                str
1010                for str in lines
1011                if str.startswith("FINAL_TARGET")
1012                or str.startswith("XPI_NAME")
1013                or str.startswith("DIST_SUBDIR")
1014            ]
1015            self.assertEqual(found, expected_rules)
1016
1017    def test_final_target_pp_files(self):
1018        """Test that FINAL_TARGET_PP_FILES is written to backend.mk correctly."""
1019        env = self._consume("dist-files", RecursiveMakeBackend)
1020
1021        backend_path = mozpath.join(env.topobjdir, "backend.mk")
1022        lines = [l.strip() for l in open(backend_path, "rt").readlines()[2:]]
1023
1024        expected = [
1025            "DIST_FILES_0 += $(srcdir)/install.rdf",
1026            "DIST_FILES_0 += $(srcdir)/main.js",
1027            "DIST_FILES_0_PATH := $(DEPTH)/dist/bin/",
1028            "DIST_FILES_0_TARGET := misc",
1029            "PP_TARGETS += DIST_FILES_0",
1030        ]
1031
1032        found = [str for str in lines if "DIST_FILES" in str]
1033        self.assertEqual(found, expected)
1034
1035    def test_localized_files(self):
1036        """Test that LOCALIZED_FILES is written to backend.mk correctly."""
1037        env = self._consume("localized-files", RecursiveMakeBackend)
1038
1039        backend_path = mozpath.join(env.topobjdir, "backend.mk")
1040        lines = [l.strip() for l in open(backend_path, "rt").readlines()[2:]]
1041
1042        expected = [
1043            "LOCALIZED_FILES_0_FILES += $(wildcard $(LOCALE_SRCDIR)/abc/*.abc)",
1044            "LOCALIZED_FILES_0_FILES += $(call MERGE_FILE,bar.ini)",
1045            "LOCALIZED_FILES_0_FILES += $(call MERGE_FILE,foo.js)",
1046            "LOCALIZED_FILES_0_DEST = $(FINAL_TARGET)/",
1047            "LOCALIZED_FILES_0_TARGET := misc",
1048            "INSTALL_TARGETS += LOCALIZED_FILES_0",
1049        ]
1050
1051        found = [str for str in lines if "LOCALIZED_FILES" in str]
1052        self.assertEqual(found, expected)
1053
1054    def test_localized_pp_files(self):
1055        """Test that LOCALIZED_PP_FILES is written to backend.mk correctly."""
1056        env = self._consume("localized-pp-files", RecursiveMakeBackend)
1057
1058        backend_path = mozpath.join(env.topobjdir, "backend.mk")
1059        lines = [l.strip() for l in open(backend_path, "rt").readlines()[2:]]
1060
1061        expected = [
1062            "LOCALIZED_PP_FILES_0 += $(call MERGE_FILE,bar.ini)",
1063            "LOCALIZED_PP_FILES_0 += $(call MERGE_FILE,foo.js)",
1064            "LOCALIZED_PP_FILES_0_PATH = $(FINAL_TARGET)/",
1065            "LOCALIZED_PP_FILES_0_TARGET := misc",
1066            "LOCALIZED_PP_FILES_0_FLAGS := --silence-missing-directive-warnings",
1067            "PP_TARGETS += LOCALIZED_PP_FILES_0",
1068        ]
1069
1070        found = [str for str in lines if "LOCALIZED_PP_FILES" in str]
1071        self.assertEqual(found, expected)
1072
1073    def test_config(self):
1074        """Test that CONFIGURE_SUBST_FILES are properly handled."""
1075        env = self._consume("test_config", RecursiveMakeBackend)
1076
1077        self.assertEqual(
1078            open(os.path.join(env.topobjdir, "file"), "r").readlines(),
1079            ["#ifdef foo\n", "bar baz\n", "@bar@\n"],
1080        )
1081
1082    def test_prog_lib_c_only(self):
1083        """Test that C-only binary artifacts are marked as such."""
1084        env = self._consume("prog-lib-c-only", RecursiveMakeBackend)
1085
1086        # PROGRAM C-onlyness.
1087        with open(os.path.join(env.topobjdir, "c-program", "backend.mk"), "r") as fh:
1088            lines = fh.readlines()
1089            lines = [line.rstrip() for line in lines]
1090
1091            self.assertIn("PROG_IS_C_ONLY_c_test_program := 1", lines)
1092
1093        with open(os.path.join(env.topobjdir, "cxx-program", "backend.mk"), "r") as fh:
1094            lines = fh.readlines()
1095            lines = [line.rstrip() for line in lines]
1096
1097            # Test for only the absence of the variable, not the precise
1098            # form of the variable assignment.
1099            for line in lines:
1100                self.assertNotIn("PROG_IS_C_ONLY_cxx_test_program", line)
1101
1102        # SIMPLE_PROGRAMS C-onlyness.
1103        with open(
1104            os.path.join(env.topobjdir, "c-simple-programs", "backend.mk"), "r"
1105        ) as fh:
1106            lines = fh.readlines()
1107            lines = [line.rstrip() for line in lines]
1108
1109            self.assertIn("PROG_IS_C_ONLY_c_simple_program := 1", lines)
1110
1111        with open(
1112            os.path.join(env.topobjdir, "cxx-simple-programs", "backend.mk"), "r"
1113        ) as fh:
1114            lines = fh.readlines()
1115            lines = [line.rstrip() for line in lines]
1116
1117            for line in lines:
1118                self.assertNotIn("PROG_IS_C_ONLY_cxx_simple_program", line)
1119
1120        # Libraries C-onlyness.
1121        with open(os.path.join(env.topobjdir, "c-library", "backend.mk"), "r") as fh:
1122            lines = fh.readlines()
1123            lines = [line.rstrip() for line in lines]
1124
1125            self.assertIn("LIB_IS_C_ONLY := 1", lines)
1126
1127        with open(os.path.join(env.topobjdir, "cxx-library", "backend.mk"), "r") as fh:
1128            lines = fh.readlines()
1129            lines = [line.rstrip() for line in lines]
1130
1131            for line in lines:
1132                self.assertNotIn("LIB_IS_C_ONLY", line)
1133
1134    def test_linkage(self):
1135        env = self._consume("linkage", RecursiveMakeBackend)
1136        expected_linkage = {
1137            "prog": {
1138                "SHARED_LIBS": ["qux/qux.so", "../shared/baz.so"],
1139                "STATIC_LIBS": ["../real/foo.a"],
1140                "OS_LIBS": ["-lfoo", "-lbaz", "-lbar"],
1141            },
1142            "shared": {
1143                "OS_LIBS": ["-lfoo"],
1144                "SHARED_LIBS": ["../prog/qux/qux.so"],
1145                "STATIC_LIBS": [],
1146            },
1147            "static": {
1148                "STATIC_LIBS": ["../real/foo.a"],
1149                "OS_LIBS": ["-lbar"],
1150                "SHARED_LIBS": ["../prog/qux/qux.so"],
1151            },
1152            "real": {
1153                "STATIC_LIBS": [],
1154                "SHARED_LIBS": ["../prog/qux/qux.so"],
1155                "OS_LIBS": ["-lbaz"],
1156            },
1157        }
1158        actual_linkage = {}
1159        for name in expected_linkage.keys():
1160            with open(os.path.join(env.topobjdir, name, "backend.mk"), "r") as fh:
1161                actual_linkage[name] = [line.rstrip() for line in fh.readlines()]
1162        for name in expected_linkage:
1163            for var in expected_linkage[name]:
1164                for val in expected_linkage[name][var]:
1165                    val = os.path.normpath(val)
1166                    line = "%s += %s" % (var, val)
1167                    self.assertIn(line, actual_linkage[name])
1168                    actual_linkage[name].remove(line)
1169                for line in actual_linkage[name]:
1170                    self.assertNotIn("%s +=" % var, line)
1171
1172    def test_list_files(self):
1173        env = self._consume("linkage", RecursiveMakeBackend)
1174        expected_list_files = {
1175            "prog/MyProgram_exe.list": [
1176                "../static/bar/bar1.o",
1177                "../static/bar/bar2.o",
1178                "../static/bar/bar_helper/bar_helper1.o",
1179            ],
1180            "shared/baz_so.list": ["baz/baz1.o"],
1181        }
1182        actual_list_files = {}
1183        for name in expected_list_files.keys():
1184            with open(os.path.join(env.topobjdir, name), "r") as fh:
1185                actual_list_files[name] = [line.rstrip() for line in fh.readlines()]
1186        for name in expected_list_files:
1187            self.assertEqual(
1188                actual_list_files[name],
1189                [os.path.normpath(f) for f in expected_list_files[name]],
1190            )
1191
1192        # We don't produce a list file for a shared library composed only of
1193        # object files in its directory, but instead list them in a variable.
1194        with open(os.path.join(env.topobjdir, "prog", "qux", "backend.mk"), "r") as fh:
1195            lines = [line.rstrip() for line in fh.readlines()]
1196
1197        self.assertIn("qux.so_OBJS := qux1.o", lines)
1198
1199    def test_jar_manifests(self):
1200        env = self._consume("jar-manifests", RecursiveMakeBackend)
1201
1202        with open(os.path.join(env.topobjdir, "backend.mk"), "r") as fh:
1203            lines = fh.readlines()
1204
1205        lines = [line.rstrip() for line in lines]
1206
1207        self.assertIn("JAR_MANIFEST := %s/jar.mn" % env.topsrcdir, lines)
1208
1209    def test_test_manifests_duplicate_support_files(self):
1210        """Ensure duplicate support-files in test manifests work."""
1211        env = self._consume(
1212            "test-manifests-duplicate-support-files", RecursiveMakeBackend
1213        )
1214
1215        p = os.path.join(env.topobjdir, "_build_manifests", "install", "_test_files")
1216        m = InstallManifest(p)
1217        self.assertIn("testing/mochitest/tests/support-file.txt", m)
1218
1219    def test_install_manifests_package_tests(self):
1220        """Ensure test suites honor package_tests=False."""
1221        env = self._consume("test-manifests-package-tests", RecursiveMakeBackend)
1222
1223        man_dir = mozpath.join(env.topobjdir, "_build_manifests", "install")
1224        self.assertTrue(os.path.isdir(man_dir))
1225
1226        full = mozpath.join(man_dir, "_test_files")
1227        self.assertTrue(os.path.exists(full))
1228
1229        m = InstallManifest(path=full)
1230
1231        # Only mochitest.js should be in the install manifest.
1232        self.assertTrue("testing/mochitest/tests/mochitest.js" in m)
1233
1234        # The path is odd here because we do not normalize at test manifest
1235        # processing time.  This is a fragile test because there's currently no
1236        # way to iterate the manifest.
1237        self.assertFalse("instrumentation/./not_packaged.java" in m)
1238
1239    def test_program_paths(self):
1240        """PROGRAMs with various moz.build settings that change the destination should produce
1241        the expected paths in backend.mk."""
1242        env = self._consume("program-paths", RecursiveMakeBackend)
1243
1244        expected = [
1245            ("dist-bin", "$(DEPTH)/dist/bin/dist-bin.prog"),
1246            ("dist-subdir", "$(DEPTH)/dist/bin/foo/dist-subdir.prog"),
1247            ("final-target", "$(DEPTH)/final/target/final-target.prog"),
1248            ("not-installed", "not-installed.prog"),
1249        ]
1250        prefix = "PROGRAM = "
1251        for (subdir, expected_program) in expected:
1252            with io.open(os.path.join(env.topobjdir, subdir, "backend.mk"), "r") as fh:
1253                lines = fh.readlines()
1254                program = [
1255                    line.rstrip().split(prefix, 1)[1]
1256                    for line in lines
1257                    if line.startswith(prefix)
1258                ][0]
1259                self.assertEqual(program, expected_program)
1260
1261
1262if __name__ == "__main__":
1263    main()
1264