summaryrefslogtreecommitdiffstats
path: root/python/mach/mach/util.py
blob: b6bf1727fa942ced38fdaa8ec67d9abd7e8f3215 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

import hashlib
import os
import sys
from pathlib import Path, PurePosixPath
from typing import Optional, Union


class UserError(Exception):
    """Represents an error caused by something the user did wrong rather than
    an internal `mach` failure. Exceptions that are subclasses of this class
    will not be reported as failures to Sentry.
    """


def setenv(key, value):
    """Compatibility shim to ensure the proper string type is used with
    os.environ for the version of Python being used.
    """
    from six import text_type

    encoding = "mbcs" if sys.platform == "win32" else "utf-8"

    if sys.version_info[0] == 2:
        if isinstance(key, text_type):
            key = key.encode(encoding)
        if isinstance(value, text_type):
            value = value.encode(encoding)
    else:
        if isinstance(key, bytes):
            key = key.decode(encoding)
        if isinstance(value, bytes):
            value = value.decode(encoding)

    os.environ[key] = value


def get_state_dir(
    specific_to_topsrcdir=False, topsrcdir: Optional[Union[str, Path]] = None
):
    """Obtain path to a directory to hold state.

    Args:
        specific_to_topsrcdir (bool): If True, return a state dir specific to the current
            srcdir instead of the global state dir (default: False)

    Returns:
        A path to the state dir (str)
    """
    state_dir = Path(os.environ.get("MOZBUILD_STATE_PATH", Path.home() / ".mozbuild"))
    if not specific_to_topsrcdir:
        return str(state_dir)

    if not topsrcdir:
        # Only import MozbuildObject if topsrcdir isn't provided. This is to cover
        # the Mach initialization stage, where "mozbuild" isn't in the import scope.
        from mozbuild.base import MozbuildObject

        topsrcdir = Path(
            MozbuildObject.from_environment(cwd=str(Path(__file__).parent)).topsrcdir
        )

    # Ensure that the topsrcdir is a consistent string before hashing it.
    topsrcdir = Path(topsrcdir).resolve()

    # Shortening to 12 characters makes these directories a bit more manageable
    # in a terminal and is more than good enough for this purpose.
    srcdir_hash = hashlib.sha256(str(topsrcdir).encode("utf-8")).hexdigest()[:12]

    state_dir = state_dir / "srcdirs" / f"{topsrcdir.name}-{srcdir_hash}"

    if not state_dir.is_dir():
        # We create the srcdir here rather than 'mach_initialize.py' so direct
        # consumers of this function don't create the directory inconsistently.
        print(f"Creating local state directory: {state_dir}")
        state_dir.mkdir(mode=0o770, parents=True)
        # Save the topsrcdir that this state dir corresponds to so we can clean
        # it up in the event its srcdir was deleted.
        with (state_dir / "topsrcdir.txt").open(mode="w") as fh:
            fh.write(str(topsrcdir))

    return str(state_dir)


def get_virtualenv_base_dir(topsrcdir):
    return os.path.join(
        get_state_dir(specific_to_topsrcdir=True, topsrcdir=topsrcdir),
        "_virtualenvs",
    )


def win_to_msys_path(path: Path):
    """Convert a windows-style path to msys-style."""
    drive, path = os.path.splitdrive(path)
    path = "/".join(path.split("\\"))
    if drive:
        if path[0] == "/":
            path = path[1:]
        path = f"/{drive[:-1]}/{path}"
    return PurePosixPath(path)


def to_optional_path(path: Optional[Path]):
    if path:
        return Path(path)
    else:
        return None


def to_optional_str(path: Optional[Path]):
    if path:
        return str(path)
    else:
        return None


def strtobool(value: str):
    # Reimplementation of distutils.util.strtobool
    # https://docs.python.org/3.9/distutils/apiref.html#distutils.util.strtobool
    true_vals = ("y", "yes", "t", "true", "on", "1")
    false_vals = ("n", "no", "f", "false", "off", "0")

    value = value.lower()
    if value in true_vals:
        return 1
    if value in false_vals:
        return 0

    raise ValueError(f'Expected one of: {", ".join(true_vals + false_vals)}')