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 json
8import os
9import shutil
10import sys
11import tempfile
12import unittest
13
14from six import StringIO
15from mozfile.mozfile import NamedTemporaryFile
16
17from mozunit import main
18
19from mach.logging import LoggingManager
20
21from mozbuild.base import (
22    BadEnvironmentException,
23    MachCommandBase,
24    MozbuildObject,
25    PathArgument,
26)
27
28from mozbuild.backend.configenvironment import ConfigEnvironment
29from buildconfig import topsrcdir, topobjdir
30import mozpack.path as mozpath
31
32from mozbuild.test.common import prepare_tmp_topsrcdir
33
34
35curdir = os.path.dirname(__file__)
36log_manager = LoggingManager()
37
38
39class TestMozbuildObject(unittest.TestCase):
40    def setUp(self):
41        self._old_cwd = os.getcwd()
42        self._old_env = dict(os.environ)
43        os.environ.pop("MOZCONFIG", None)
44        os.environ.pop("MOZ_OBJDIR", None)
45
46    def tearDown(self):
47        os.chdir(self._old_cwd)
48        os.environ.clear()
49        os.environ.update(self._old_env)
50
51    def get_base(self, topobjdir=None):
52        return MozbuildObject(topsrcdir, None, log_manager, topobjdir=topobjdir)
53
54    def test_objdir_config_guess(self):
55        base = self.get_base()
56
57        with NamedTemporaryFile(mode="wt") as mozconfig:
58            os.environ["MOZCONFIG"] = mozconfig.name
59
60            self.assertIsNotNone(base.topobjdir)
61            self.assertEqual(len(base.topobjdir.split()), 1)
62            config_guess = base.resolve_config_guess()
63            self.assertTrue(base.topobjdir.endswith(config_guess))
64            self.assertTrue(os.path.isabs(base.topobjdir))
65            self.assertTrue(base.topobjdir.startswith(base.topsrcdir))
66
67    def test_objdir_trailing_slash(self):
68        """Trailing slashes in topobjdir should be removed."""
69        base = self.get_base()
70
71        with NamedTemporaryFile(mode="wt") as mozconfig:
72            mozconfig.write("mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/foo/")
73            mozconfig.flush()
74            os.environ["MOZCONFIG"] = mozconfig.name
75
76            self.assertEqual(base.topobjdir, mozpath.join(base.topsrcdir, "foo"))
77            self.assertTrue(base.topobjdir.endswith("foo"))
78
79    def test_objdir_config_status(self):
80        """Ensure @CONFIG_GUESS@ is handled when loading mozconfig."""
81        base = self.get_base()
82        guess = base.resolve_config_guess()
83
84        # There may be symlinks involved, so we use real paths to ensure
85        # path consistency.
86        d = os.path.realpath(tempfile.mkdtemp())
87        try:
88            mozconfig = os.path.join(d, "mozconfig")
89            with open(mozconfig, "wt") as fh:
90                fh.write("mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/foo/@CONFIG_GUESS@")
91            print("Wrote mozconfig %s" % mozconfig)
92
93            topobjdir = os.path.join(d, "foo", guess)
94            os.makedirs(topobjdir)
95
96            # Create a fake topsrcdir.
97            prepare_tmp_topsrcdir(d)
98
99            mozinfo = os.path.join(topobjdir, "mozinfo.json")
100            with open(mozinfo, "wt") as fh:
101                json.dump(
102                    dict(
103                        topsrcdir=d,
104                        mozconfig=mozconfig,
105                    ),
106                    fh,
107                )
108
109            os.environ["MOZCONFIG"] = mozconfig
110            os.chdir(topobjdir)
111
112            obj = MozbuildObject.from_environment(detect_virtualenv_mozinfo=False)
113
114            self.assertEqual(obj.topobjdir, mozpath.normsep(topobjdir))
115        finally:
116            os.chdir(self._old_cwd)
117            shutil.rmtree(d)
118
119    def test_relative_objdir(self):
120        """Relative defined objdirs are loaded properly."""
121        d = os.path.realpath(tempfile.mkdtemp())
122        try:
123            mozconfig = os.path.join(d, "mozconfig")
124            with open(mozconfig, "wt") as fh:
125                fh.write("mk_add_options MOZ_OBJDIR=./objdir")
126
127            topobjdir = mozpath.join(d, "objdir")
128            os.mkdir(topobjdir)
129
130            mozinfo = os.path.join(topobjdir, "mozinfo.json")
131            with open(mozinfo, "wt") as fh:
132                json.dump(
133                    dict(
134                        topsrcdir=d,
135                        mozconfig=mozconfig,
136                    ),
137                    fh,
138                )
139
140            os.environ["MOZCONFIG"] = mozconfig
141            child = os.path.join(topobjdir, "foo", "bar")
142            os.makedirs(child)
143            os.chdir(child)
144
145            obj = MozbuildObject.from_environment(detect_virtualenv_mozinfo=False)
146
147            self.assertEqual(obj.topobjdir, topobjdir)
148
149        finally:
150            os.chdir(self._old_cwd)
151            shutil.rmtree(d)
152
153    @unittest.skipIf(
154        not hasattr(os, "symlink") or os.name == "nt", "symlinks not available."
155    )
156    def test_symlink_objdir(self):
157        """Objdir that is a symlink is loaded properly."""
158        d = os.path.realpath(tempfile.mkdtemp())
159        try:
160            topobjdir_real = os.path.join(d, "objdir")
161            topobjdir_link = os.path.join(d, "objlink")
162
163            os.mkdir(topobjdir_real)
164            os.symlink(topobjdir_real, topobjdir_link)
165
166            mozconfig = os.path.join(d, "mozconfig")
167            with open(mozconfig, "wt") as fh:
168                fh.write("mk_add_options MOZ_OBJDIR=%s" % topobjdir_link)
169
170            mozinfo = os.path.join(topobjdir_real, "mozinfo.json")
171            with open(mozinfo, "wt") as fh:
172                json.dump(
173                    dict(
174                        topsrcdir=d,
175                        mozconfig=mozconfig,
176                    ),
177                    fh,
178                )
179
180            os.chdir(topobjdir_link)
181            obj = MozbuildObject.from_environment(detect_virtualenv_mozinfo=False)
182            self.assertEqual(obj.topobjdir, topobjdir_real)
183
184            os.chdir(topobjdir_real)
185            obj = MozbuildObject.from_environment(detect_virtualenv_mozinfo=False)
186            self.assertEqual(obj.topobjdir, topobjdir_real)
187
188        finally:
189            os.chdir(self._old_cwd)
190            shutil.rmtree(d)
191
192    def test_mach_command_base_inside_objdir(self):
193        """Ensure a MachCommandBase constructed from inside the objdir works."""
194
195        d = os.path.realpath(tempfile.mkdtemp())
196
197        try:
198            topobjdir = os.path.join(d, "objdir")
199            os.makedirs(topobjdir)
200
201            topsrcdir = os.path.join(d, "srcdir")
202            prepare_tmp_topsrcdir(topsrcdir)
203
204            mozinfo = os.path.join(topobjdir, "mozinfo.json")
205            with open(mozinfo, "wt") as fh:
206                json.dump(
207                    dict(
208                        topsrcdir=topsrcdir,
209                    ),
210                    fh,
211                )
212
213            os.chdir(topobjdir)
214
215            class MockMachContext(object):
216                pass
217
218            context = MockMachContext()
219            context.cwd = topobjdir
220            context.topdir = topsrcdir
221            context.settings = None
222            context.log_manager = None
223            context.detect_virtualenv_mozinfo = False
224
225            o = MachCommandBase(context, None)
226
227            self.assertEqual(o.topobjdir, mozpath.normsep(topobjdir))
228            self.assertEqual(o.topsrcdir, mozpath.normsep(topsrcdir))
229
230        finally:
231            os.chdir(self._old_cwd)
232            shutil.rmtree(d)
233
234    def test_objdir_is_srcdir_rejected(self):
235        """Ensure the srcdir configurations are rejected."""
236        d = os.path.realpath(tempfile.mkdtemp())
237
238        try:
239            # The easiest way to do this is to create a mozinfo.json with data
240            # that will never happen.
241            mozinfo = os.path.join(d, "mozinfo.json")
242            with open(mozinfo, "wt") as fh:
243                json.dump({"topsrcdir": d}, fh)
244
245            os.chdir(d)
246
247            with self.assertRaises(BadEnvironmentException):
248                MozbuildObject.from_environment(detect_virtualenv_mozinfo=False)
249
250        finally:
251            os.chdir(self._old_cwd)
252            shutil.rmtree(d)
253
254    def test_objdir_mismatch(self):
255        """Ensure MachCommandBase throwing on objdir mismatch."""
256        d = os.path.realpath(tempfile.mkdtemp())
257
258        try:
259            real_topobjdir = os.path.join(d, "real-objdir")
260            os.makedirs(real_topobjdir)
261
262            topobjdir = os.path.join(d, "objdir")
263            os.makedirs(topobjdir)
264
265            topsrcdir = os.path.join(d, "srcdir")
266            prepare_tmp_topsrcdir(topsrcdir)
267
268            mozconfig = os.path.join(d, "mozconfig")
269            with open(mozconfig, "wt") as fh:
270                fh.write("mk_add_options MOZ_OBJDIR=%s" % real_topobjdir)
271
272            mozinfo = os.path.join(topobjdir, "mozinfo.json")
273            with open(mozinfo, "wt") as fh:
274                json.dump(
275                    dict(
276                        topsrcdir=topsrcdir,
277                        mozconfig=mozconfig,
278                    ),
279                    fh,
280                )
281
282            os.chdir(topobjdir)
283
284            class MockMachContext(object):
285                pass
286
287            context = MockMachContext()
288            context.cwd = topobjdir
289            context.topdir = topsrcdir
290            context.settings = None
291            context.log_manager = None
292            context.detect_virtualenv_mozinfo = False
293
294            stdout = sys.stdout
295            sys.stdout = StringIO()
296            try:
297                with self.assertRaises(SystemExit):
298                    MachCommandBase(context, None)
299
300                self.assertTrue(
301                    sys.stdout.getvalue().startswith(
302                        "Ambiguous object directory detected."
303                    )
304                )
305            finally:
306                sys.stdout = stdout
307
308        finally:
309            os.chdir(self._old_cwd)
310            shutil.rmtree(d)
311
312    def test_config_environment(self):
313        d = os.path.realpath(tempfile.mkdtemp())
314
315        try:
316            with open(os.path.join(d, "config.status"), "w") as fh:
317                fh.write("# coding=utf-8\n")
318                fh.write("from __future__ import unicode_literals\n")
319                fh.write("topobjdir = '%s'\n" % mozpath.normsep(d))
320                fh.write("topsrcdir = '%s'\n" % topsrcdir)
321                fh.write("mozconfig = None\n")
322                fh.write("defines = { 'FOO': 'foo' }\n")
323                fh.write("substs = { 'QUX': 'qux' }\n")
324                fh.write(
325                    "__all__ = ['topobjdir', 'topsrcdir', 'defines', "
326                    "'substs', 'mozconfig']"
327                )
328
329            base = self.get_base(topobjdir=d)
330
331            ce = base.config_environment
332            self.assertIsInstance(ce, ConfigEnvironment)
333
334            self.assertEqual(base.defines, ce.defines)
335            self.assertEqual(base.substs, ce.substs)
336
337            self.assertEqual(base.defines, {"FOO": "foo"})
338            self.assertEqual(
339                base.substs,
340                {
341                    "ACDEFINES": "-DFOO=foo",
342                    "ALLEMPTYSUBSTS": "",
343                    "ALLSUBSTS": "ACDEFINES = -DFOO=foo\nQUX = qux",
344                    "QUX": "qux",
345                },
346            )
347        finally:
348            shutil.rmtree(d)
349
350    def test_get_binary_path(self):
351        base = self.get_base(topobjdir=topobjdir)
352
353        platform = sys.platform
354
355        # We should ideally use the config.status from the build. Let's install
356        # a fake one.
357        substs = [
358            ("MOZ_APP_NAME", "awesomeapp"),
359            ("MOZ_BUILD_APP", "awesomeapp"),
360        ]
361        if sys.platform.startswith("darwin"):
362            substs.append(("OS_ARCH", "Darwin"))
363            substs.append(("BIN_SUFFIX", ""))
364            substs.append(("MOZ_MACBUNDLE_NAME", "Nightly.app"))
365        elif sys.platform.startswith(("win32", "cygwin")):
366            substs.append(("OS_ARCH", "WINNT"))
367            substs.append(("BIN_SUFFIX", ".exe"))
368        else:
369            substs.append(("OS_ARCH", "something"))
370            substs.append(("BIN_SUFFIX", ""))
371
372        base._config_environment = ConfigEnvironment(
373            base.topsrcdir, base.topobjdir, substs=substs
374        )
375
376        p = base.get_binary_path("xpcshell", False)
377        if platform.startswith("darwin"):
378            self.assertTrue(p.endswith("Contents/MacOS/xpcshell"))
379        elif platform.startswith(("win32", "cygwin")):
380            self.assertTrue(p.endswith("xpcshell.exe"))
381        else:
382            self.assertTrue(p.endswith("dist/bin/xpcshell"))
383
384        p = base.get_binary_path(validate_exists=False)
385        if platform.startswith("darwin"):
386            self.assertTrue(p.endswith("Contents/MacOS/awesomeapp"))
387        elif platform.startswith(("win32", "cygwin")):
388            self.assertTrue(p.endswith("awesomeapp.exe"))
389        else:
390            self.assertTrue(p.endswith("dist/bin/awesomeapp"))
391
392        p = base.get_binary_path(validate_exists=False, where="staged-package")
393        if platform.startswith("darwin"):
394            self.assertTrue(
395                p.endswith("awesomeapp/Nightly.app/Contents/MacOS/awesomeapp")
396            )
397        elif platform.startswith(("win32", "cygwin")):
398            self.assertTrue(p.endswith("awesomeapp\\awesomeapp.exe"))
399        else:
400            self.assertTrue(p.endswith("awesomeapp/awesomeapp"))
401
402        self.assertRaises(Exception, base.get_binary_path, where="somewhere")
403
404        p = base.get_binary_path("foobar", validate_exists=False)
405        if platform.startswith("win32"):
406            self.assertTrue(p.endswith("foobar.exe"))
407        else:
408            self.assertTrue(p.endswith("foobar"))
409
410
411class TestPathArgument(unittest.TestCase):
412    def test_path_argument(self):
413        # Absolute path
414        p = PathArgument("/obj/foo", "/src", "/obj", "/src")
415        self.assertEqual(p.relpath(), "foo")
416        self.assertEqual(p.srcdir_path(), "/src/foo")
417        self.assertEqual(p.objdir_path(), "/obj/foo")
418
419        # Relative path within srcdir
420        p = PathArgument("foo", "/src", "/obj", "/src")
421        self.assertEqual(p.relpath(), "foo")
422        self.assertEqual(p.srcdir_path(), "/src/foo")
423        self.assertEqual(p.objdir_path(), "/obj/foo")
424
425        # Relative path within subdirectory
426        p = PathArgument("bar", "/src", "/obj", "/src/foo")
427        self.assertEqual(p.relpath(), "foo/bar")
428        self.assertEqual(p.srcdir_path(), "/src/foo/bar")
429        self.assertEqual(p.objdir_path(), "/obj/foo/bar")
430
431        # Relative path within objdir
432        p = PathArgument("foo", "/src", "/obj", "/obj")
433        self.assertEqual(p.relpath(), "foo")
434        self.assertEqual(p.srcdir_path(), "/src/foo")
435        self.assertEqual(p.objdir_path(), "/obj/foo")
436
437        # "." path
438        p = PathArgument(".", "/src", "/obj", "/src/foo")
439        self.assertEqual(p.relpath(), "foo")
440        self.assertEqual(p.srcdir_path(), "/src/foo")
441        self.assertEqual(p.objdir_path(), "/obj/foo")
442
443        # Nested src/obj directories
444        p = PathArgument("bar", "/src", "/src/obj", "/src/obj/foo")
445        self.assertEqual(p.relpath(), "foo/bar")
446        self.assertEqual(p.srcdir_path(), "/src/foo/bar")
447        self.assertEqual(p.objdir_path(), "/src/obj/foo/bar")
448
449
450if __name__ == "__main__":
451    main()
452