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
7from six import StringIO
8import os
9import six
10import sys
11import textwrap
12import unittest
13
14from mozunit import (
15    main,
16    MockedOpen,
17)
18
19from mozbuild.configure.options import (
20    InvalidOptionError,
21    NegativeOptionValue,
22    PositiveOptionValue,
23)
24from mozbuild.configure import (
25    ConfigureError,
26    ConfigureSandbox,
27)
28from mozbuild.util import exec_, memoized_property, ReadOnlyNamespace
29
30import mozpack.path as mozpath
31
32test_data_path = mozpath.abspath(mozpath.dirname(__file__))
33test_data_path = mozpath.join(test_data_path, "data")
34
35
36class TestConfigure(unittest.TestCase):
37    def get_config(
38        self, options=[], env={}, configure="moz.configure", prog="/bin/configure"
39    ):
40        config = {}
41        out = StringIO()
42        sandbox = ConfigureSandbox(config, env, [prog] + options, out, out)
43
44        sandbox.run(mozpath.join(test_data_path, configure))
45
46        if "--help" in options:
47            return six.ensure_text(out.getvalue()), config
48        self.assertEquals("", out.getvalue())
49        return config
50
51    def moz_configure(self, source):
52        return MockedOpen(
53            {os.path.join(test_data_path, "moz.configure"): textwrap.dedent(source)}
54        )
55
56    def test_defaults(self):
57        config = self.get_config()
58        self.maxDiff = None
59        self.assertEquals(
60            {
61                "CHOICES": NegativeOptionValue(),
62                "DEFAULTED": PositiveOptionValue(("not-simple",)),
63                "IS_GCC": NegativeOptionValue(),
64                "REMAINDER": (
65                    PositiveOptionValue(),
66                    NegativeOptionValue(),
67                    NegativeOptionValue(),
68                    NegativeOptionValue(),
69                ),
70                "SIMPLE": NegativeOptionValue(),
71                "VALUES": NegativeOptionValue(),
72                "VALUES2": NegativeOptionValue(),
73                "VALUES3": NegativeOptionValue(),
74                "WITH_ENV": NegativeOptionValue(),
75            },
76            config,
77        )
78
79    def test_help(self):
80        help, config = self.get_config(["--help"], prog="configure")
81
82        self.assertEquals({}, config)
83        self.maxDiff = None
84        self.assertEquals(
85            "Usage: configure [options]\n"
86            "\n"
87            "Options: [defaults in brackets after descriptions]\n"
88            "  Help options:\n"
89            "    --help                    print this message\n"
90            "\n"
91            "  Options from python/mozbuild/mozbuild/test/configure/data/included.configure:\n"
92            "    --enable-imports-in-template\n                              Imports in template\n"
93            "\n"
94            "  Options from python/mozbuild/mozbuild/test/configure/data/moz.configure:\n"
95            "    --enable-include          Include\n"
96            "    --enable-simple           Enable simple\n"
97            "    --enable-values           Enable values\n"
98            "    --enable-with-env         Enable with env\n"
99            "    --indirect-option         Indirectly defined option\n"
100            "    --option                  Option\n"
101            "    --returned-choices        Choices\n"
102            "    --with-imports            Imports\n"
103            "    --with-returned-default   Returned default [not-simple]\n"
104            "    --with-stuff              Build with stuff\n"
105            "    --without-thing           Build without thing\n"
106            "\n"
107            "\n"
108            "Environment variables:\n"
109            "  Options from python/mozbuild/mozbuild/test/configure/data/moz.configure:\n"
110            "    CC                        C Compiler\n"
111            "\n",
112            help.replace("\\", "/"),
113        )
114
115    def test_unknown(self):
116        with self.assertRaises(InvalidOptionError):
117            self.get_config(["--unknown"])
118
119    def test_simple(self):
120        for config in (
121            self.get_config(),
122            self.get_config(["--disable-simple"]),
123            # Last option wins.
124            self.get_config(["--enable-simple", "--disable-simple"]),
125        ):
126            self.assertNotIn("ENABLED_SIMPLE", config)
127            self.assertIn("SIMPLE", config)
128            self.assertEquals(NegativeOptionValue(), config["SIMPLE"])
129
130        for config in (
131            self.get_config(["--enable-simple"]),
132            self.get_config(["--disable-simple", "--enable-simple"]),
133        ):
134            self.assertIn("ENABLED_SIMPLE", config)
135            self.assertIn("SIMPLE", config)
136            self.assertEquals(PositiveOptionValue(), config["SIMPLE"])
137            self.assertIs(config["SIMPLE"], config["ENABLED_SIMPLE"])
138
139        # --enable-simple doesn't take values.
140        with self.assertRaises(InvalidOptionError):
141            self.get_config(["--enable-simple=value"])
142
143    def test_with_env(self):
144        for config in (
145            self.get_config(),
146            self.get_config(["--disable-with-env"]),
147            self.get_config(["--enable-with-env", "--disable-with-env"]),
148            self.get_config(env={"MOZ_WITH_ENV": ""}),
149            # Options win over environment
150            self.get_config(["--disable-with-env"], env={"MOZ_WITH_ENV": "1"}),
151        ):
152            self.assertIn("WITH_ENV", config)
153            self.assertEquals(NegativeOptionValue(), config["WITH_ENV"])
154
155        for config in (
156            self.get_config(["--enable-with-env"]),
157            self.get_config(["--disable-with-env", "--enable-with-env"]),
158            self.get_config(env={"MOZ_WITH_ENV": "1"}),
159            self.get_config(["--enable-with-env"], env={"MOZ_WITH_ENV": ""}),
160        ):
161            self.assertIn("WITH_ENV", config)
162            self.assertEquals(PositiveOptionValue(), config["WITH_ENV"])
163
164        with self.assertRaises(InvalidOptionError):
165            self.get_config(["--enable-with-env=value"])
166
167        with self.assertRaises(InvalidOptionError):
168            self.get_config(env={"MOZ_WITH_ENV": "value"})
169
170    def test_values(self, name="VALUES"):
171        for config in (
172            self.get_config(),
173            self.get_config(["--disable-values"]),
174            self.get_config(["--enable-values", "--disable-values"]),
175        ):
176            self.assertIn(name, config)
177            self.assertEquals(NegativeOptionValue(), config[name])
178
179        for config in (
180            self.get_config(["--enable-values"]),
181            self.get_config(["--disable-values", "--enable-values"]),
182        ):
183            self.assertIn(name, config)
184            self.assertEquals(PositiveOptionValue(), config[name])
185
186        config = self.get_config(["--enable-values=foo"])
187        self.assertIn(name, config)
188        self.assertEquals(PositiveOptionValue(("foo",)), config[name])
189
190        config = self.get_config(["--enable-values=foo,bar"])
191        self.assertIn(name, config)
192        self.assertTrue(config[name])
193        self.assertEquals(PositiveOptionValue(("foo", "bar")), config[name])
194
195    def test_values2(self):
196        self.test_values("VALUES2")
197
198    def test_values3(self):
199        self.test_values("VALUES3")
200
201    def test_returned_default(self):
202        config = self.get_config(["--enable-simple"])
203        self.assertIn("DEFAULTED", config)
204        self.assertEquals(PositiveOptionValue(("simple",)), config["DEFAULTED"])
205
206        config = self.get_config(["--disable-simple"])
207        self.assertIn("DEFAULTED", config)
208        self.assertEquals(PositiveOptionValue(("not-simple",)), config["DEFAULTED"])
209
210    def test_returned_choices(self):
211        for val in ("a", "b", "c"):
212            config = self.get_config(
213                ["--enable-values=alpha", "--returned-choices=%s" % val]
214            )
215            self.assertIn("CHOICES", config)
216            self.assertEquals(PositiveOptionValue((val,)), config["CHOICES"])
217
218        for val in ("0", "1", "2"):
219            config = self.get_config(
220                ["--enable-values=numeric", "--returned-choices=%s" % val]
221            )
222            self.assertIn("CHOICES", config)
223            self.assertEquals(PositiveOptionValue((val,)), config["CHOICES"])
224
225        with self.assertRaises(InvalidOptionError):
226            self.get_config(["--enable-values=numeric", "--returned-choices=a"])
227
228        with self.assertRaises(InvalidOptionError):
229            self.get_config(["--enable-values=alpha", "--returned-choices=0"])
230
231    def test_included(self):
232        config = self.get_config(env={"CC": "gcc"})
233        self.assertIn("IS_GCC", config)
234        self.assertEquals(config["IS_GCC"], True)
235
236        config = self.get_config(["--enable-include=extra.configure", "--extra"])
237        self.assertIn("EXTRA", config)
238        self.assertEquals(PositiveOptionValue(), config["EXTRA"])
239
240        with self.assertRaises(InvalidOptionError):
241            self.get_config(["--extra"])
242
243    def test_template(self):
244        config = self.get_config(env={"CC": "gcc"})
245        self.assertIn("CFLAGS", config)
246        self.assertEquals(config["CFLAGS"], ["-Werror=foobar"])
247
248        config = self.get_config(env={"CC": "clang"})
249        self.assertNotIn("CFLAGS", config)
250
251    def test_imports(self):
252        config = {}
253        out = StringIO()
254        sandbox = ConfigureSandbox(config, {}, ["configure"], out, out)
255
256        with self.assertRaises(ImportError):
257            exec_(
258                textwrap.dedent(
259                    """
260                @template
261                def foo():
262                    import sys
263                foo()"""
264                ),
265                sandbox,
266            )
267
268        exec_(
269            textwrap.dedent(
270                """
271            @template
272            @imports('sys')
273            def foo():
274                return sys"""
275            ),
276            sandbox,
277        )
278
279        self.assertIs(sandbox["foo"](), sys)
280
281        exec_(
282            textwrap.dedent(
283                """
284            @template
285            @imports(_from='os', _import='path')
286            def foo():
287                return path"""
288            ),
289            sandbox,
290        )
291
292        self.assertIs(sandbox["foo"](), os.path)
293
294        exec_(
295            textwrap.dedent(
296                """
297            @template
298            @imports(_from='os', _import='path', _as='os_path')
299            def foo():
300                return os_path"""
301            ),
302            sandbox,
303        )
304
305        self.assertIs(sandbox["foo"](), os.path)
306
307        exec_(
308            textwrap.dedent(
309                """
310            @template
311            @imports('__builtin__')
312            def foo():
313                return __builtin__"""
314            ),
315            sandbox,
316        )
317
318        self.assertIs(sandbox["foo"](), six.moves.builtins)
319
320        exec_(
321            textwrap.dedent(
322                """
323            @template
324            @imports(_from='__builtin__', _import='open')
325            def foo():
326                return open('%s')"""
327                % os.devnull
328            ),
329            sandbox,
330        )
331
332        f = sandbox["foo"]()
333        self.assertEquals(f.name, os.devnull)
334        f.close()
335
336        # This unlocks the sandbox
337        exec_(
338            textwrap.dedent(
339                """
340            @template
341            @imports(_import='__builtin__', _as='__builtins__')
342            def foo():
343                import sys
344                return sys"""
345            ),
346            sandbox,
347        )
348
349        self.assertIs(sandbox["foo"](), sys)
350
351        exec_(
352            textwrap.dedent(
353                """
354            @template
355            @imports('__sandbox__')
356            def foo():
357                return __sandbox__"""
358            ),
359            sandbox,
360        )
361
362        self.assertIs(sandbox["foo"](), sandbox)
363
364        exec_(
365            textwrap.dedent(
366                """
367            @template
368            @imports(_import='__sandbox__', _as='s')
369            def foo():
370                return s"""
371            ),
372            sandbox,
373        )
374
375        self.assertIs(sandbox["foo"](), sandbox)
376
377        # Nothing leaked from the function being executed
378        self.assertEquals(list(sandbox), ["__builtins__", "foo"])
379        self.assertEquals(sandbox["__builtins__"], ConfigureSandbox.BUILTINS)
380
381        exec_(
382            textwrap.dedent(
383                """
384            @template
385            @imports('sys')
386            def foo():
387                @depends(when=True)
388                def bar():
389                    return sys
390                return bar
391            bar = foo()"""
392            ),
393            sandbox,
394        )
395
396        with self.assertRaises(NameError) as e:
397            sandbox._depends[sandbox["bar"]].result()
398
399        self.assertIn("name 'sys' is not defined", str(e.exception))
400
401    def test_apply_imports(self):
402        imports = []
403
404        class CountApplyImportsSandbox(ConfigureSandbox):
405            def _apply_imports(self, *args, **kwargs):
406                imports.append((args, kwargs))
407                super(CountApplyImportsSandbox, self)._apply_imports(*args, **kwargs)
408
409        config = {}
410        out = StringIO()
411        sandbox = CountApplyImportsSandbox(config, {}, ["configure"], out, out)
412
413        exec_(
414            textwrap.dedent(
415                """
416            @template
417            @imports('sys')
418            def foo():
419                return sys
420            foo()
421            foo()"""
422            ),
423            sandbox,
424        )
425
426        self.assertEquals(len(imports), 1)
427
428    def test_import_wrapping(self):
429        bar = object()
430        foo = ReadOnlyNamespace(bar=bar)
431
432        class BasicWrappingSandbox(ConfigureSandbox):
433            @memoized_property
434            def _wrapped_foo(self):
435                return foo
436
437        config = {}
438        out = StringIO()
439        sandbox = BasicWrappingSandbox(config, {}, ["configure"], out, out)
440
441        exec_(
442            textwrap.dedent(
443                """
444            @template
445            @imports('foo')
446            def toplevel():
447                return foo
448            @template
449            @imports('foo.bar')
450            def bar():
451                return foo.bar
452            @template
453            @imports('foo.bar')
454            def bar_upper():
455                return foo
456            @template
457            @imports(_from='foo', _import='bar')
458            def from_import():
459                return bar
460            @template
461            @imports(_from='foo', _import='bar', _as='custom_name')
462            def from_import_as():
463                return custom_name
464            @template
465            @imports(_import='foo', _as='custom_name')
466            def import_as():
467                return custom_name
468            """
469            ),
470            sandbox,
471        )
472        self.assertIs(sandbox["toplevel"](), foo)
473        self.assertIs(sandbox["bar"](), bar)
474        self.assertIs(sandbox["bar_upper"](), foo)
475        self.assertIs(sandbox["from_import"](), bar)
476        self.assertIs(sandbox["from_import_as"](), bar)
477        self.assertIs(sandbox["import_as"](), foo)
478
479    def test_os_path(self):
480        config = self.get_config(["--with-imports=%s" % __file__])
481        self.assertIn("HAS_ABSPATH", config)
482        self.assertEquals(config["HAS_ABSPATH"], True)
483        self.assertIn("HAS_GETATIME", config)
484        self.assertEquals(config["HAS_GETATIME"], True)
485        self.assertIn("HAS_GETATIME2", config)
486        self.assertEquals(config["HAS_GETATIME2"], False)
487
488    def test_template_call(self):
489        config = self.get_config(env={"CC": "gcc"})
490        self.assertIn("TEMPLATE_VALUE", config)
491        self.assertEquals(config["TEMPLATE_VALUE"], 42)
492        self.assertIn("TEMPLATE_VALUE_2", config)
493        self.assertEquals(config["TEMPLATE_VALUE_2"], 21)
494
495    def test_template_imports(self):
496        config = self.get_config(["--enable-imports-in-template"])
497        self.assertIn("PLATFORM", config)
498        self.assertEquals(config["PLATFORM"], sys.platform)
499
500    def test_decorators(self):
501        config = {}
502        out = StringIO()
503        sandbox = ConfigureSandbox(config, {}, ["configure"], out, out)
504
505        sandbox.include_file(mozpath.join(test_data_path, "decorators.configure"))
506
507        self.assertNotIn("FOO", sandbox)
508        self.assertNotIn("BAR", sandbox)
509        self.assertNotIn("QUX", sandbox)
510
511    def test_set_config(self):
512        def get_config(*args):
513            return self.get_config(*args, configure="set_config.configure")
514
515        help, config = get_config(["--help"])
516        self.assertEquals(config, {})
517
518        config = get_config(["--set-foo"])
519        self.assertIn("FOO", config)
520        self.assertEquals(config["FOO"], True)
521
522        config = get_config(["--set-bar"])
523        self.assertNotIn("FOO", config)
524        self.assertIn("BAR", config)
525        self.assertEquals(config["BAR"], True)
526
527        config = get_config(["--set-value=qux"])
528        self.assertIn("VALUE", config)
529        self.assertEquals(config["VALUE"], "qux")
530
531        config = get_config(["--set-name=hoge"])
532        self.assertIn("hoge", config)
533        self.assertEquals(config["hoge"], True)
534
535        config = get_config([])
536        self.assertEquals(config, {"BAR": False})
537
538        with self.assertRaises(ConfigureError):
539            # Both --set-foo and --set-name=FOO are going to try to
540            # set_config('FOO'...)
541            get_config(["--set-foo", "--set-name=FOO"])
542
543    def test_set_config_when(self):
544        with self.moz_configure(
545            """
546            option('--with-qux', help='qux')
547            set_config('FOO', 'foo', when=True)
548            set_config('BAR', 'bar', when=False)
549            set_config('QUX', 'qux', when='--with-qux')
550        """
551        ):
552            config = self.get_config()
553            self.assertEquals(
554                config,
555                {
556                    "FOO": "foo",
557                },
558            )
559            config = self.get_config(["--with-qux"])
560            self.assertEquals(
561                config,
562                {
563                    "FOO": "foo",
564                    "QUX": "qux",
565                },
566            )
567
568    def test_set_define(self):
569        def get_config(*args):
570            return self.get_config(*args, configure="set_define.configure")
571
572        help, config = get_config(["--help"])
573        self.assertEquals(config, {"DEFINES": {}})
574
575        config = get_config(["--set-foo"])
576        self.assertIn("FOO", config["DEFINES"])
577        self.assertEquals(config["DEFINES"]["FOO"], True)
578
579        config = get_config(["--set-bar"])
580        self.assertNotIn("FOO", config["DEFINES"])
581        self.assertIn("BAR", config["DEFINES"])
582        self.assertEquals(config["DEFINES"]["BAR"], True)
583
584        config = get_config(["--set-value=qux"])
585        self.assertIn("VALUE", config["DEFINES"])
586        self.assertEquals(config["DEFINES"]["VALUE"], "qux")
587
588        config = get_config(["--set-name=hoge"])
589        self.assertIn("hoge", config["DEFINES"])
590        self.assertEquals(config["DEFINES"]["hoge"], True)
591
592        config = get_config([])
593        self.assertEquals(config["DEFINES"], {"BAR": False})
594
595        with self.assertRaises(ConfigureError):
596            # Both --set-foo and --set-name=FOO are going to try to
597            # set_define('FOO'...)
598            get_config(["--set-foo", "--set-name=FOO"])
599
600    def test_set_define_when(self):
601        with self.moz_configure(
602            """
603            option('--with-qux', help='qux')
604            set_define('FOO', 'foo', when=True)
605            set_define('BAR', 'bar', when=False)
606            set_define('QUX', 'qux', when='--with-qux')
607        """
608        ):
609            config = self.get_config()
610            self.assertEquals(
611                config["DEFINES"],
612                {
613                    "FOO": "foo",
614                },
615            )
616            config = self.get_config(["--with-qux"])
617            self.assertEquals(
618                config["DEFINES"],
619                {
620                    "FOO": "foo",
621                    "QUX": "qux",
622                },
623            )
624
625    def test_imply_option_simple(self):
626        def get_config(*args):
627            return self.get_config(*args, configure="imply_option/simple.configure")
628
629        help, config = get_config(["--help"])
630        self.assertEquals(config, {})
631
632        config = get_config([])
633        self.assertEquals(config, {})
634
635        config = get_config(["--enable-foo"])
636        self.assertIn("BAR", config)
637        self.assertEquals(config["BAR"], PositiveOptionValue())
638
639        with self.assertRaises(InvalidOptionError) as e:
640            get_config(["--enable-foo", "--disable-bar"])
641
642        self.assertEquals(
643            str(e.exception),
644            "'--enable-bar' implied by '--enable-foo' conflicts with "
645            "'--disable-bar' from the command-line",
646        )
647
648    def test_imply_option_negative(self):
649        def get_config(*args):
650            return self.get_config(*args, configure="imply_option/negative.configure")
651
652        help, config = get_config(["--help"])
653        self.assertEquals(config, {})
654
655        config = get_config([])
656        self.assertEquals(config, {})
657
658        config = get_config(["--enable-foo"])
659        self.assertIn("BAR", config)
660        self.assertEquals(config["BAR"], NegativeOptionValue())
661
662        with self.assertRaises(InvalidOptionError) as e:
663            get_config(["--enable-foo", "--enable-bar"])
664
665        self.assertEquals(
666            str(e.exception),
667            "'--disable-bar' implied by '--enable-foo' conflicts with "
668            "'--enable-bar' from the command-line",
669        )
670
671        config = get_config(["--disable-hoge"])
672        self.assertIn("BAR", config)
673        self.assertEquals(config["BAR"], NegativeOptionValue())
674
675        with self.assertRaises(InvalidOptionError) as e:
676            get_config(["--disable-hoge", "--enable-bar"])
677
678        self.assertEquals(
679            str(e.exception),
680            "'--disable-bar' implied by '--disable-hoge' conflicts with "
681            "'--enable-bar' from the command-line",
682        )
683
684    def test_imply_option_values(self):
685        def get_config(*args):
686            return self.get_config(*args, configure="imply_option/values.configure")
687
688        help, config = get_config(["--help"])
689        self.assertEquals(config, {})
690
691        config = get_config([])
692        self.assertEquals(config, {})
693
694        config = get_config(["--enable-foo=a"])
695        self.assertIn("BAR", config)
696        self.assertEquals(config["BAR"], PositiveOptionValue(("a",)))
697
698        config = get_config(["--enable-foo=a,b"])
699        self.assertIn("BAR", config)
700        self.assertEquals(config["BAR"], PositiveOptionValue(("a", "b")))
701
702        with self.assertRaises(InvalidOptionError) as e:
703            get_config(["--enable-foo=a,b", "--disable-bar"])
704
705        self.assertEquals(
706            str(e.exception),
707            "'--enable-bar=a,b' implied by '--enable-foo' conflicts with "
708            "'--disable-bar' from the command-line",
709        )
710
711    def test_imply_option_infer(self):
712        def get_config(*args):
713            return self.get_config(*args, configure="imply_option/infer.configure")
714
715        help, config = get_config(["--help"])
716        self.assertEquals(config, {})
717
718        config = get_config([])
719        self.assertEquals(config, {})
720
721        with self.assertRaises(InvalidOptionError) as e:
722            get_config(["--enable-foo", "--disable-bar"])
723
724        self.assertEquals(
725            str(e.exception),
726            "'--enable-bar' implied by '--enable-foo' conflicts with "
727            "'--disable-bar' from the command-line",
728        )
729
730        with self.assertRaises(ConfigureError) as e:
731            self.get_config([], configure="imply_option/infer_ko.configure")
732
733        self.assertEquals(
734            str(e.exception),
735            "Cannot infer what implies '--enable-bar'. Please add a `reason` "
736            "to the `imply_option` call.",
737        )
738
739    def test_imply_option_immediate_value(self):
740        def get_config(*args):
741            return self.get_config(*args, configure="imply_option/imm.configure")
742
743        help, config = get_config(["--help"])
744        self.assertEquals(config, {})
745
746        config = get_config([])
747        self.assertEquals(config, {})
748
749        config_path = mozpath.abspath(
750            mozpath.join(test_data_path, "imply_option", "imm.configure")
751        )
752
753        with self.assertRaisesRegexp(
754            InvalidOptionError,
755            "--enable-foo' implied by 'imply_option at %s:7' conflicts "
756            "with '--disable-foo' from the command-line" % config_path,
757        ):
758            get_config(["--disable-foo"])
759
760        with self.assertRaisesRegexp(
761            InvalidOptionError,
762            "--enable-bar=foo,bar' implied by 'imply_option at %s:18' "
763            "conflicts with '--enable-bar=a,b,c' from the command-line" % config_path,
764        ):
765            get_config(["--enable-bar=a,b,c"])
766
767        with self.assertRaisesRegexp(
768            InvalidOptionError,
769            "--enable-baz=BAZ' implied by 'imply_option at %s:29' "
770            "conflicts with '--enable-baz=QUUX' from the command-line" % config_path,
771        ):
772            get_config(["--enable-baz=QUUX"])
773
774    def test_imply_option_failures(self):
775        with self.assertRaises(ConfigureError) as e:
776            with self.moz_configure(
777                """
778                imply_option('--with-foo', ('a',), 'bar')
779            """
780            ):
781                self.get_config()
782
783        self.assertEquals(
784            str(e.exception),
785            "`--with-foo`, emitted from `%s` line 2, is unknown."
786            % mozpath.join(test_data_path, "moz.configure"),
787        )
788
789        with self.assertRaises(TypeError) as e:
790            with self.moz_configure(
791                """
792                imply_option('--with-foo', 42, 'bar')
793
794                option('--with-foo', help='foo')
795                @depends('--with-foo')
796                def foo(value):
797                    return value
798            """
799            ):
800                self.get_config()
801
802        self.assertEquals(str(e.exception), "Unexpected type: 'int'")
803
804    def test_imply_option_when(self):
805        with self.moz_configure(
806            """
807            option('--with-foo', help='foo')
808            imply_option('--with-qux', True, when='--with-foo')
809            option('--with-qux', help='qux')
810            set_config('QUX', depends('--with-qux')(lambda x: x))
811        """
812        ):
813            config = self.get_config()
814            self.assertEquals(
815                config,
816                {
817                    "QUX": NegativeOptionValue(),
818                },
819            )
820
821            config = self.get_config(["--with-foo"])
822            self.assertEquals(
823                config,
824                {
825                    "QUX": PositiveOptionValue(),
826                },
827            )
828
829    def test_imply_option_dependency_loop(self):
830        with self.moz_configure(
831            """
832            option('--without-foo', help='foo')
833
834            @depends('--with-foo')
835            def qux_default(foo):
836                return bool(foo)
837
838            option('--with-qux', default=qux_default, help='qux')
839
840            imply_option('--with-foo', depends('--with-qux')(lambda x: x or None))
841
842            set_config('FOO', depends('--with-foo')(lambda x: x))
843            set_config('QUX', depends('--with-qux')(lambda x: x))
844        """
845        ):
846            config = self.get_config()
847            self.assertEquals(
848                config,
849                {
850                    "FOO": PositiveOptionValue(),
851                    "QUX": PositiveOptionValue(),
852                },
853            )
854
855            config = self.get_config(["--without-foo"])
856            self.assertEquals(
857                config,
858                {
859                    "FOO": NegativeOptionValue(),
860                    "QUX": NegativeOptionValue(),
861                },
862            )
863
864            config = self.get_config(["--with-qux"])
865            self.assertEquals(
866                config,
867                {
868                    "FOO": PositiveOptionValue(),
869                    "QUX": PositiveOptionValue(),
870                },
871            )
872
873            with self.assertRaises(InvalidOptionError) as e:
874                config = self.get_config(["--without-foo", "--with-qux"])
875
876            self.assertEquals(
877                str(e.exception),
878                "'--with-foo' implied by '--with-qux' conflicts "
879                "with '--without-foo' from the command-line",
880            )
881
882            config = self.get_config(["--without-qux"])
883            self.assertEquals(
884                config,
885                {
886                    "FOO": PositiveOptionValue(),
887                    "QUX": NegativeOptionValue(),
888                },
889            )
890
891        with self.moz_configure(
892            """
893            option('--with-foo', help='foo')
894
895            @depends('--with-foo')
896            def qux_default(foo):
897                return bool(foo)
898
899            option('--with-qux', default=qux_default, help='qux')
900
901            imply_option('--with-foo', depends('--with-qux')(lambda x: x or None))
902
903            set_config('FOO', depends('--with-foo')(lambda x: x))
904            set_config('QUX', depends('--with-qux')(lambda x: x))
905        """
906        ):
907            config = self.get_config()
908            self.assertEquals(
909                config,
910                {
911                    "FOO": NegativeOptionValue(),
912                    "QUX": NegativeOptionValue(),
913                },
914            )
915
916            config = self.get_config(["--with-foo"])
917            self.assertEquals(
918                config,
919                {
920                    "FOO": PositiveOptionValue(),
921                    "QUX": PositiveOptionValue(),
922                },
923            )
924
925            with self.assertRaises(InvalidOptionError) as e:
926                config = self.get_config(["--with-qux"])
927
928            self.assertEquals(
929                str(e.exception),
930                "'--with-foo' implied by '--with-qux' conflicts "
931                "with '--without-foo' from the default",
932            )
933
934            with self.assertRaises(InvalidOptionError) as e:
935                config = self.get_config(["--without-foo", "--with-qux"])
936
937            self.assertEquals(
938                str(e.exception),
939                "'--with-foo' implied by '--with-qux' conflicts "
940                "with '--without-foo' from the command-line",
941            )
942
943            config = self.get_config(["--without-qux"])
944            self.assertEquals(
945                config,
946                {
947                    "FOO": NegativeOptionValue(),
948                    "QUX": NegativeOptionValue(),
949                },
950            )
951
952        config_path = mozpath.abspath(mozpath.join(test_data_path, "moz.configure"))
953
954        # Same test as above, but using `when` in the `imply_option`.
955        with self.moz_configure(
956            """
957            option('--with-foo', help='foo')
958
959            @depends('--with-foo')
960            def qux_default(foo):
961                return bool(foo)
962
963            option('--with-qux', default=qux_default, help='qux')
964
965            imply_option('--with-foo', True, when='--with-qux')
966
967            set_config('FOO', depends('--with-foo')(lambda x: x))
968            set_config('QUX', depends('--with-qux')(lambda x: x))
969        """
970        ):
971            config = self.get_config()
972            self.assertEquals(
973                config,
974                {
975                    "FOO": NegativeOptionValue(),
976                    "QUX": NegativeOptionValue(),
977                },
978            )
979
980            config = self.get_config(["--with-foo"])
981            self.assertEquals(
982                config,
983                {
984                    "FOO": PositiveOptionValue(),
985                    "QUX": PositiveOptionValue(),
986                },
987            )
988
989            with self.assertRaises(InvalidOptionError) as e:
990                config = self.get_config(["--with-qux"])
991
992            self.assertEquals(
993                str(e.exception),
994                "'--with-foo' implied by 'imply_option at %s:10' conflicts "
995                "with '--without-foo' from the default" % config_path,
996            )
997
998            with self.assertRaises(InvalidOptionError) as e:
999                config = self.get_config(["--without-foo", "--with-qux"])
1000
1001            self.assertEquals(
1002                str(e.exception),
1003                "'--with-foo' implied by 'imply_option at %s:10' conflicts "
1004                "with '--without-foo' from the command-line" % config_path,
1005            )
1006
1007            config = self.get_config(["--without-qux"])
1008            self.assertEquals(
1009                config,
1010                {
1011                    "FOO": NegativeOptionValue(),
1012                    "QUX": NegativeOptionValue(),
1013                },
1014            )
1015
1016    def test_imply_option_recursion(self):
1017        config_path = mozpath.abspath(mozpath.join(test_data_path, "moz.configure"))
1018
1019        message = (
1020            "'--without-foo' appears somewhere in the direct or indirect dependencies "
1021            "when resolving imply_option at %s:8" % config_path
1022        )
1023
1024        with self.moz_configure(
1025            """
1026            option('--without-foo', help='foo')
1027
1028            imply_option('--with-qux', depends('--with-foo')(lambda x: x or None))
1029
1030            option('--with-qux', help='qux')
1031
1032            imply_option('--with-foo', depends('--with-qux')(lambda x: x or None))
1033
1034            set_config('FOO', depends('--with-foo')(lambda x: x))
1035            set_config('QUX', depends('--with-qux')(lambda x: x))
1036        """
1037        ):
1038            # Note: no error is detected when the depends function in the
1039            # imply_options resolve to None, which disables the imply_option.
1040
1041            with self.assertRaises(ConfigureError) as e:
1042                self.get_config()
1043
1044            self.assertEquals(str(e.exception), message)
1045
1046            with self.assertRaises(ConfigureError) as e:
1047                self.get_config(["--with-qux"])
1048
1049            self.assertEquals(str(e.exception), message)
1050
1051            with self.assertRaises(ConfigureError) as e:
1052                self.get_config(["--without-foo", "--with-qux"])
1053
1054            self.assertEquals(str(e.exception), message)
1055
1056    def test_option_failures(self):
1057        with self.assertRaises(ConfigureError) as e:
1058            with self.moz_configure('option("--with-foo", help="foo")'):
1059                self.get_config()
1060
1061        self.assertEquals(
1062            str(e.exception),
1063            "Option `--with-foo` is not handled ; reference it with a @depends",
1064        )
1065
1066        with self.assertRaises(ConfigureError) as e:
1067            with self.moz_configure(
1068                """
1069                option("--with-foo", help="foo")
1070                option("--with-foo", help="foo")
1071            """
1072            ):
1073                self.get_config()
1074
1075        self.assertEquals(str(e.exception), "Option `--with-foo` already defined")
1076
1077        with self.assertRaises(ConfigureError) as e:
1078            with self.moz_configure(
1079                """
1080                option(env="MOZ_FOO", help="foo")
1081                option(env="MOZ_FOO", help="foo")
1082            """
1083            ):
1084                self.get_config()
1085
1086        self.assertEquals(str(e.exception), "Option `MOZ_FOO` already defined")
1087
1088        with self.assertRaises(ConfigureError) as e:
1089            with self.moz_configure(
1090                """
1091                option('--with-foo', env="MOZ_FOO", help="foo")
1092                option(env="MOZ_FOO", help="foo")
1093            """
1094            ):
1095                self.get_config()
1096
1097        self.assertEquals(str(e.exception), "Option `MOZ_FOO` already defined")
1098
1099        with self.assertRaises(ConfigureError) as e:
1100            with self.moz_configure(
1101                """
1102                option(env="MOZ_FOO", help="foo")
1103                option('--with-foo', env="MOZ_FOO", help="foo")
1104            """
1105            ):
1106                self.get_config()
1107
1108        self.assertEquals(str(e.exception), "Option `MOZ_FOO` already defined")
1109
1110        with self.assertRaises(ConfigureError) as e:
1111            with self.moz_configure(
1112                """
1113                option('--with-foo', env="MOZ_FOO", help="foo")
1114                option('--with-foo', help="foo")
1115            """
1116            ):
1117                self.get_config()
1118
1119        self.assertEquals(str(e.exception), "Option `--with-foo` already defined")
1120
1121    def test_option_when(self):
1122        with self.moz_configure(
1123            """
1124            option('--with-foo', help='foo', when=True)
1125            option('--with-bar', help='bar', when=False)
1126            option('--with-qux', env="QUX", help='qux', when='--with-foo')
1127
1128            set_config('FOO', depends('--with-foo', when=True)(lambda x: x))
1129            set_config('BAR', depends('--with-bar', when=False)(lambda x: x))
1130            set_config('QUX', depends('--with-qux', when='--with-foo')(lambda x: x))
1131        """
1132        ):
1133            config = self.get_config()
1134            self.assertEquals(
1135                config,
1136                {
1137                    "FOO": NegativeOptionValue(),
1138                },
1139            )
1140
1141            config = self.get_config(["--with-foo"])
1142            self.assertEquals(
1143                config,
1144                {
1145                    "FOO": PositiveOptionValue(),
1146                    "QUX": NegativeOptionValue(),
1147                },
1148            )
1149
1150            config = self.get_config(["--with-foo", "--with-qux"])
1151            self.assertEquals(
1152                config,
1153                {
1154                    "FOO": PositiveOptionValue(),
1155                    "QUX": PositiveOptionValue(),
1156                },
1157            )
1158
1159            with self.assertRaises(InvalidOptionError) as e:
1160                self.get_config(["--with-bar"])
1161
1162            self.assertEquals(
1163                str(e.exception), "--with-bar is not available in this configuration"
1164            )
1165
1166            with self.assertRaises(InvalidOptionError) as e:
1167                self.get_config(["--with-qux"])
1168
1169            self.assertEquals(
1170                str(e.exception), "--with-qux is not available in this configuration"
1171            )
1172
1173            with self.assertRaises(InvalidOptionError) as e:
1174                self.get_config(["QUX=1"])
1175
1176            self.assertEquals(
1177                str(e.exception), "QUX is not available in this configuration"
1178            )
1179
1180            config = self.get_config(env={"QUX": "1"})
1181            self.assertEquals(
1182                config,
1183                {
1184                    "FOO": NegativeOptionValue(),
1185                },
1186            )
1187
1188            help, config = self.get_config(["--help"])
1189            self.assertEquals(
1190                help.replace("\\", "/"),
1191                textwrap.dedent(
1192                    """\
1193                Usage: configure [options]
1194
1195                Options: [defaults in brackets after descriptions]
1196                  Help options:
1197                    --help                    print this message
1198
1199                  Options from python/mozbuild/mozbuild/test/configure/data/moz.configure:
1200                    --with-foo                foo
1201
1202
1203                Environment variables:
1204            """
1205                ),
1206            )
1207
1208            help, config = self.get_config(["--help", "--with-foo"])
1209            self.assertEquals(
1210                help.replace("\\", "/"),
1211                textwrap.dedent(
1212                    """\
1213                Usage: configure [options]
1214
1215                Options: [defaults in brackets after descriptions]
1216                  Help options:
1217                    --help                    print this message
1218
1219                  Options from python/mozbuild/mozbuild/test/configure/data/moz.configure:
1220                    --with-foo                foo
1221                    --with-qux                qux
1222
1223
1224                Environment variables:
1225            """
1226                ),
1227            )
1228
1229        with self.moz_configure(
1230            """
1231            option('--with-foo', help='foo', when=True)
1232            set_config('FOO', depends('--with-foo')(lambda x: x))
1233        """
1234        ):
1235            with self.assertRaises(ConfigureError) as e:
1236                self.get_config()
1237
1238            self.assertEquals(
1239                str(e.exception),
1240                "@depends function needs the same `when` as " "options it depends on",
1241            )
1242
1243        with self.moz_configure(
1244            """
1245            @depends(when=True)
1246            def always():
1247                return True
1248            @depends(when=True)
1249            def always2():
1250                return True
1251            option('--with-foo', help='foo', when=always)
1252            set_config('FOO', depends('--with-foo', when=always2)(lambda x: x))
1253        """
1254        ):
1255            with self.assertRaises(ConfigureError) as e:
1256                self.get_config()
1257
1258            self.assertEquals(
1259                str(e.exception),
1260                "@depends function needs the same `when` as " "options it depends on",
1261            )
1262
1263        with self.moz_configure(
1264            """
1265            @depends(when=True)
1266            def always():
1267                return True
1268            @depends(when=True)
1269            def always2():
1270                return True
1271            with only_when(always2):
1272                option('--with-foo', help='foo', when=always)
1273                # include() triggers resolution of its dependencies, and their
1274                # side effects.
1275                include(depends('--with-foo', when=always)(lambda x: x))
1276                # The sandbox should figure that the `when` here is
1277                # appropriate. Bad behavior in CombinedDependsFunction.__eq__
1278                # made this fail in the past.
1279                set_config('FOO', depends('--with-foo', when=always)(lambda x: x))
1280        """
1281        ):
1282            self.get_config()
1283
1284        with self.moz_configure(
1285            """
1286            option('--with-foo', help='foo')
1287            option('--without-bar', help='bar', when='--with-foo')
1288            option('--with-qux', help='qux', when='--with-bar')
1289            set_config('QUX', True, when='--with-qux')
1290        """
1291        ):
1292            # These are valid:
1293            self.get_config(["--with-foo"])
1294            self.get_config(["--with-foo", "--with-bar"])
1295            self.get_config(["--with-foo", "--without-bar"])
1296            self.get_config(["--with-foo", "--with-bar", "--with-qux"])
1297            self.get_config(["--with-foo", "--with-bar", "--without-qux"])
1298            with self.assertRaises(InvalidOptionError) as e:
1299                self.get_config(["--with-bar"])
1300            with self.assertRaises(InvalidOptionError) as e:
1301                self.get_config(["--without-bar"])
1302            with self.assertRaises(InvalidOptionError) as e:
1303                self.get_config(["--with-qux"])
1304            with self.assertRaises(InvalidOptionError) as e:
1305                self.get_config(["--without-qux"])
1306            with self.assertRaises(InvalidOptionError) as e:
1307                self.get_config(["--with-foo", "--without-bar", "--with-qux"])
1308            with self.assertRaises(InvalidOptionError) as e:
1309                self.get_config(["--with-foo", "--without-bar", "--without-qux"])
1310
1311    def test_include_failures(self):
1312        with self.assertRaises(ConfigureError) as e:
1313            with self.moz_configure('include("../foo.configure")'):
1314                self.get_config()
1315
1316        self.assertEquals(
1317            str(e.exception),
1318            "Cannot include `%s` because it is not in a subdirectory of `%s`"
1319            % (
1320                mozpath.normpath(mozpath.join(test_data_path, "..", "foo.configure")),
1321                mozpath.normsep(test_data_path),
1322            ),
1323        )
1324
1325        with self.assertRaises(ConfigureError) as e:
1326            with self.moz_configure(
1327                """
1328                include('extra.configure')
1329                include('extra.configure')
1330            """
1331            ):
1332                self.get_config()
1333
1334        self.assertEquals(
1335            str(e.exception),
1336            "Cannot include `%s` because it was included already."
1337            % mozpath.normpath(mozpath.join(test_data_path, "extra.configure")),
1338        )
1339
1340        with self.assertRaises(TypeError) as e:
1341            with self.moz_configure(
1342                """
1343                include(42)
1344            """
1345            ):
1346                self.get_config()
1347
1348        self.assertEquals(str(e.exception), "Unexpected type: 'int'")
1349
1350    def test_include_when(self):
1351        with MockedOpen(
1352            {
1353                os.path.join(test_data_path, "moz.configure"): textwrap.dedent(
1354                    """
1355                option('--with-foo', help='foo')
1356
1357                include('always.configure', when=True)
1358                include('never.configure', when=False)
1359                include('foo.configure', when='--with-foo')
1360
1361                set_config('FOO', foo)
1362                set_config('BAR', bar)
1363                set_config('QUX', qux)
1364            """
1365                ),
1366                os.path.join(test_data_path, "always.configure"): textwrap.dedent(
1367                    """
1368                option('--with-bar', help='bar')
1369                @depends('--with-bar')
1370                def bar(x):
1371                    if x:
1372                        return 'bar'
1373            """
1374                ),
1375                os.path.join(test_data_path, "never.configure"): textwrap.dedent(
1376                    """
1377                option('--with-qux', help='qux')
1378                @depends('--with-qux')
1379                def qux(x):
1380                    if x:
1381                        return 'qux'
1382            """
1383                ),
1384                os.path.join(test_data_path, "foo.configure"): textwrap.dedent(
1385                    """
1386                option('--with-foo-really', help='really foo')
1387                @depends('--with-foo-really')
1388                def foo(x):
1389                    if x:
1390                        return 'foo'
1391
1392                include('foo2.configure', when='--with-foo-really')
1393            """
1394                ),
1395                os.path.join(test_data_path, "foo2.configure"): textwrap.dedent(
1396                    """
1397                set_config('FOO2', True)
1398            """
1399                ),
1400            }
1401        ):
1402            config = self.get_config()
1403            self.assertEquals(config, {})
1404
1405            config = self.get_config(["--with-foo"])
1406            self.assertEquals(config, {})
1407
1408            config = self.get_config(["--with-bar"])
1409            self.assertEquals(
1410                config,
1411                {
1412                    "BAR": "bar",
1413                },
1414            )
1415
1416            with self.assertRaises(InvalidOptionError) as e:
1417                self.get_config(["--with-qux"])
1418
1419            self.assertEquals(
1420                str(e.exception), "--with-qux is not available in this configuration"
1421            )
1422
1423            config = self.get_config(["--with-foo", "--with-foo-really"])
1424            self.assertEquals(
1425                config,
1426                {
1427                    "FOO": "foo",
1428                    "FOO2": True,
1429                },
1430            )
1431
1432    def test_sandbox_failures(self):
1433        with self.assertRaises(KeyError) as e:
1434            with self.moz_configure(
1435                """
1436                include = 42
1437            """
1438            ):
1439                self.get_config()
1440
1441        self.assertIn("Cannot reassign builtins", str(e.exception))
1442
1443        with self.assertRaises(KeyError) as e:
1444            with self.moz_configure(
1445                """
1446                foo = 42
1447            """
1448            ):
1449                self.get_config()
1450
1451        self.assertIn(
1452            "Cannot assign `foo` because it is neither a @depends nor a " "@template",
1453            str(e.exception),
1454        )
1455
1456    def test_depends_failures(self):
1457        with self.assertRaises(ConfigureError) as e:
1458            with self.moz_configure(
1459                """
1460                @depends()
1461                def foo():
1462                    return
1463            """
1464            ):
1465                self.get_config()
1466
1467        self.assertEquals(str(e.exception), "@depends needs at least one argument")
1468
1469        with self.assertRaises(ConfigureError) as e:
1470            with self.moz_configure(
1471                """
1472                @depends('--with-foo')
1473                def foo(value):
1474                    return value
1475            """
1476            ):
1477                self.get_config()
1478
1479        self.assertEquals(
1480            str(e.exception),
1481            "'--with-foo' is not a known option. Maybe it's " "declared too late?",
1482        )
1483
1484        with self.assertRaises(ConfigureError) as e:
1485            with self.moz_configure(
1486                """
1487                @depends('--with-foo=42')
1488                def foo(value):
1489                    return value
1490            """
1491            ):
1492                self.get_config()
1493
1494        self.assertEquals(str(e.exception), "Option must not contain an '='")
1495
1496        with self.assertRaises(TypeError) as e:
1497            with self.moz_configure(
1498                """
1499                @depends(42)
1500                def foo(value):
1501                    return value
1502            """
1503            ):
1504                self.get_config()
1505
1506        self.assertEquals(
1507            str(e.exception),
1508            "Cannot use object of type 'int' as argument " "to @depends",
1509        )
1510
1511        with self.assertRaises(ConfigureError) as e:
1512            with self.moz_configure(
1513                """
1514                @depends('--help')
1515                def foo(value):
1516                    yield
1517            """
1518            ):
1519                self.get_config()
1520
1521        self.assertEquals(
1522            str(e.exception), "Cannot decorate generator functions with @depends"
1523        )
1524
1525        with self.assertRaises(TypeError) as e:
1526            with self.moz_configure(
1527                """
1528                depends('--help')(42)
1529            """
1530            ):
1531                self.get_config()
1532
1533        self.assertEquals(str(e.exception), "Unexpected type: 'int'")
1534
1535        with self.assertRaises(ConfigureError) as e:
1536            with self.moz_configure(
1537                """
1538                option('--foo', help='foo')
1539                @depends('--foo')
1540                def foo(value):
1541                    return value
1542
1543                foo()
1544            """
1545            ):
1546                self.get_config()
1547
1548        self.assertEquals(str(e.exception), "The `foo` function may not be called")
1549
1550        with self.assertRaises(TypeError) as e:
1551            with self.moz_configure(
1552                """
1553                @depends('--help', foo=42)
1554                def foo(_):
1555                    return
1556            """
1557            ):
1558                self.get_config()
1559
1560        self.assertEquals(
1561            str(e.exception), "depends_impl() got an unexpected keyword argument 'foo'"
1562        )
1563
1564    def test_depends_when(self):
1565        with self.moz_configure(
1566            """
1567            @depends(when=True)
1568            def foo():
1569                return 'foo'
1570
1571            set_config('FOO', foo)
1572
1573            @depends(when=False)
1574            def bar():
1575                return 'bar'
1576
1577            set_config('BAR', bar)
1578
1579            option('--with-qux', help='qux')
1580            @depends(when='--with-qux')
1581            def qux():
1582                return 'qux'
1583
1584            set_config('QUX', qux)
1585        """
1586        ):
1587            config = self.get_config()
1588            self.assertEquals(
1589                config,
1590                {
1591                    "FOO": "foo",
1592                },
1593            )
1594
1595            config = self.get_config(["--with-qux"])
1596            self.assertEquals(
1597                config,
1598                {
1599                    "FOO": "foo",
1600                    "QUX": "qux",
1601                },
1602            )
1603
1604    def test_imports_failures(self):
1605        with self.assertRaises(ConfigureError) as e:
1606            with self.moz_configure(
1607                """
1608                @imports('os')
1609                @template
1610                def foo(value):
1611                    return value
1612            """
1613            ):
1614                self.get_config()
1615
1616        self.assertEquals(str(e.exception), "@imports must appear after @template")
1617
1618        with self.assertRaises(ConfigureError) as e:
1619            with self.moz_configure(
1620                """
1621                option('--foo', help='foo')
1622                @imports('os')
1623                @depends('--foo')
1624                def foo(value):
1625                    return value
1626            """
1627            ):
1628                self.get_config()
1629
1630        self.assertEquals(str(e.exception), "@imports must appear after @depends")
1631
1632        for import_ in (
1633            "42",
1634            "_from=42, _import='os'",
1635            "_from='os', _import='path', _as=42",
1636        ):
1637            with self.assertRaises(TypeError) as e:
1638                with self.moz_configure(
1639                    """
1640                    @imports(%s)
1641                    @template
1642                    def foo(value):
1643                        return value
1644                """
1645                    % import_
1646                ):
1647                    self.get_config()
1648
1649            self.assertEquals(str(e.exception), "Unexpected type: 'int'")
1650
1651        with self.assertRaises(TypeError) as e:
1652            with self.moz_configure(
1653                """
1654                @imports('os', 42)
1655                @template
1656                def foo(value):
1657                    return value
1658            """
1659            ):
1660                self.get_config()
1661
1662        self.assertEquals(str(e.exception), "Unexpected type: 'int'")
1663
1664        with self.assertRaises(ValueError) as e:
1665            with self.moz_configure(
1666                """
1667                @imports('os*')
1668                def foo(value):
1669                    return value
1670            """
1671            ):
1672                self.get_config()
1673
1674        self.assertEquals(str(e.exception), "Invalid argument to @imports: 'os*'")
1675
1676    def test_only_when(self):
1677        moz_configure = """
1678            option('--enable-when', help='when')
1679            @depends('--enable-when', '--help')
1680            def when(value, _):
1681                return bool(value)
1682
1683            with only_when(when):
1684                option('--foo', nargs='*', help='foo')
1685                @depends('--foo')
1686                def foo(value):
1687                    return value
1688
1689                set_config('FOO', foo)
1690                set_define('FOO', foo)
1691
1692            # It is possible to depend on a function defined in a only_when
1693            # block. It then resolves to `None`.
1694            set_config('BAR', depends(foo)(lambda x: x))
1695            set_define('BAR', depends(foo)(lambda x: x))
1696        """
1697
1698        with self.moz_configure(moz_configure):
1699            config = self.get_config()
1700            self.assertEqual(
1701                config,
1702                {
1703                    "DEFINES": {},
1704                },
1705            )
1706
1707            config = self.get_config(["--enable-when"])
1708            self.assertEqual(
1709                config,
1710                {
1711                    "BAR": NegativeOptionValue(),
1712                    "FOO": NegativeOptionValue(),
1713                    "DEFINES": {
1714                        "BAR": NegativeOptionValue(),
1715                        "FOO": NegativeOptionValue(),
1716                    },
1717                },
1718            )
1719
1720            config = self.get_config(["--enable-when", "--foo=bar"])
1721            self.assertEqual(
1722                config,
1723                {
1724                    "BAR": PositiveOptionValue(["bar"]),
1725                    "FOO": PositiveOptionValue(["bar"]),
1726                    "DEFINES": {
1727                        "BAR": PositiveOptionValue(["bar"]),
1728                        "FOO": PositiveOptionValue(["bar"]),
1729                    },
1730                },
1731            )
1732
1733            # The --foo option doesn't exist when --enable-when is not given.
1734            with self.assertRaises(InvalidOptionError) as e:
1735                self.get_config(["--foo"])
1736
1737            self.assertEquals(
1738                str(e.exception), "--foo is not available in this configuration"
1739            )
1740
1741        # Cannot depend on an option defined in a only_when block, because we
1742        # don't know what OptionValue would make sense.
1743        with self.moz_configure(
1744            moz_configure
1745            + """
1746            set_config('QUX', depends('--foo')(lambda x: x))
1747        """
1748        ):
1749            with self.assertRaises(ConfigureError) as e:
1750                self.get_config()
1751
1752            self.assertEquals(
1753                str(e.exception),
1754                "@depends function needs the same `when` as " "options it depends on",
1755            )
1756
1757        with self.moz_configure(
1758            moz_configure
1759            + """
1760            set_config('QUX', depends('--foo', when=when)(lambda x: x))
1761        """
1762        ):
1763            self.get_config(["--enable-when"])
1764
1765        # Using imply_option for an option defined in a only_when block fails
1766        # similarly if the imply_option happens outside the block.
1767        with self.moz_configure(
1768            """
1769            imply_option('--foo', True)
1770        """
1771            + moz_configure
1772        ):
1773            with self.assertRaises(InvalidOptionError) as e:
1774                self.get_config()
1775
1776            self.assertEquals(
1777                str(e.exception), "--foo is not available in this configuration"
1778            )
1779
1780        # And similarly doesn't fail when the condition is true.
1781        with self.moz_configure(
1782            """
1783            imply_option('--foo', True)
1784        """
1785            + moz_configure
1786        ):
1787            self.get_config(["--enable-when"])
1788
1789    def test_depends_binary_ops(self):
1790        with self.moz_configure(
1791            """
1792            option('--foo', nargs=1, help='foo')
1793            @depends('--foo')
1794            def foo(value):
1795                return value or 0
1796
1797            option('--bar', nargs=1, help='bar')
1798            @depends('--bar')
1799            def bar(value):
1800                return value or ''
1801
1802            option('--baz', nargs=1, help='baz')
1803            @depends('--baz')
1804            def baz(value):
1805                return value
1806
1807            set_config('FOOorBAR', foo | bar)
1808            set_config('FOOorBARorBAZ', foo | bar | baz)
1809            set_config('FOOandBAR', foo & bar)
1810            set_config('FOOandBARandBAZ', foo & bar & baz)
1811        """
1812        ):
1813            for foo_opt, foo_value in (
1814                ("", 0),
1815                ("--foo=foo", PositiveOptionValue(("foo",))),
1816            ):
1817                for bar_opt, bar_value in (
1818                    ("", ""),
1819                    ("--bar=bar", PositiveOptionValue(("bar",))),
1820                ):
1821                    for baz_opt, baz_value in (
1822                        ("", NegativeOptionValue()),
1823                        ("--baz=baz", PositiveOptionValue(("baz",))),
1824                    ):
1825                        config = self.get_config(
1826                            [x for x in (foo_opt, bar_opt, baz_opt) if x]
1827                        )
1828                        self.assertEqual(
1829                            config,
1830                            {
1831                                "FOOorBAR": foo_value or bar_value,
1832                                "FOOorBARorBAZ": foo_value or bar_value or baz_value,
1833                                "FOOandBAR": foo_value and bar_value,
1834                                "FOOandBARandBAZ": foo_value
1835                                and bar_value
1836                                and baz_value,
1837                            },
1838                        )
1839
1840    def test_depends_getattr(self):
1841        with self.moz_configure(
1842            """
1843            @imports(_from='mozbuild.util', _import='ReadOnlyNamespace')
1844            def namespace(**kwargs):
1845                return ReadOnlyNamespace(**kwargs)
1846
1847            option('--foo', nargs=1, help='foo')
1848            @depends('--foo')
1849            def foo(value):
1850                return value
1851
1852            option('--bar', nargs=1, help='bar')
1853            @depends('--bar')
1854            def bar(value):
1855                return value or None
1856
1857            @depends(foo, bar)
1858            def foobar(foo, bar):
1859                return namespace(foo=foo, bar=bar)
1860
1861            set_config('FOO', foobar.foo)
1862            set_config('BAR', foobar.bar)
1863            set_config('BAZ', foobar.baz)
1864        """
1865        ):
1866            config = self.get_config()
1867            self.assertEqual(
1868                config,
1869                {
1870                    "FOO": NegativeOptionValue(),
1871                },
1872            )
1873
1874            config = self.get_config(["--foo=foo"])
1875            self.assertEqual(
1876                config,
1877                {
1878                    "FOO": PositiveOptionValue(("foo",)),
1879                },
1880            )
1881
1882            config = self.get_config(["--bar=bar"])
1883            self.assertEqual(
1884                config,
1885                {
1886                    "FOO": NegativeOptionValue(),
1887                    "BAR": PositiveOptionValue(("bar",)),
1888                },
1889            )
1890
1891            config = self.get_config(["--foo=foo", "--bar=bar"])
1892            self.assertEqual(
1893                config,
1894                {
1895                    "FOO": PositiveOptionValue(("foo",)),
1896                    "BAR": PositiveOptionValue(("bar",)),
1897                },
1898            )
1899
1900
1901if __name__ == "__main__":
1902    main()
1903