Skip to content

Commit

Permalink
Bug 1739067: Scope Mach virtualenv to be checkout-specific r=perftest…
Browse files Browse the repository at this point in the history
…-reviewers,ahal,sparky

Build and run the Mach virtualenv from a `state_dir` that is
"specific-to-topsrcdir".

As part of this, move `get_state_dir()` to `mach` so that it's usable
before `sys.path` entries are fully set up.

Differential Revision: https://phabricator.services.mozilla.com/D130383
  • Loading branch information
Mitchell Hentges committed Nov 29, 2021
1 parent 64f488c commit 6571032
Show file tree
Hide file tree
Showing 22 changed files with 100 additions and 92 deletions.
32 changes: 16 additions & 16 deletions build/mach_initialize.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,18 +167,6 @@ class MetaPathFinder(object):


def _activate_python_environment(topsrcdir, state_dir):
# We need the "mach" module to access the logic to activate the top-level
# Mach site. Since that depends on "packaging" (and, transitively,
# "pyparsing"), we add those to the path too.
sys.path[0:0] = [
os.path.join(topsrcdir, module)
for module in (
os.path.join("python", "mach"),
os.path.join("third_party", "python", "packaging"),
os.path.join("third_party", "python", "pyparsing"),
)
]

from mach.site import MachSiteManager

mach_environment = MachSiteManager.from_environment(
Expand Down Expand Up @@ -212,13 +200,25 @@ def initialize(topsrcdir):
if os.path.exists(deleted_dir):
shutil.rmtree(deleted_dir, ignore_errors=True)

# We need the "mach" module to access the logic to parse virtualenv
# requirements. Since that depends on "packaging" (and, transitively,
# "pyparsing"), we add those to the path too.
sys.path[0:0] = [
os.path.join(topsrcdir, module)
for module in (
os.path.join("python", "mach"),
os.path.join("third_party", "python", "packaging"),
os.path.join("third_party", "python", "pyparsing"),
)
]

from mach.util import setenv, get_state_dir

state_dir = _create_state_dir()
_activate_python_environment(topsrcdir, state_dir)
_activate_python_environment(topsrcdir, get_state_dir(True, topsrcdir=topsrcdir))

import mach.base
import mach.main
from mach.util import setenv
from mozboot.util import get_state_dir

# Set a reasonable limit to the number of open files.
#
Expand Down Expand Up @@ -322,7 +322,7 @@ def populate_context(key=None):
return state_dir

if key == "local_state_dir":
return get_state_dir(srcdir=True)
return get_state_dir(specific_to_topsrcdir=True)

if key == "topdir":
return topsrcdir
Expand Down
2 changes: 1 addition & 1 deletion python/l10n/test_fluent_migrations/fmt.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import sys

import hglib
from mozboot.util import get_state_dir
from mach.util import get_state_dir
import mozpack.path as mozpath

from compare_locales.merge import merge_channels
Expand Down
2 changes: 1 addition & 1 deletion python/mach/mach/sentry.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from threading import Thread

import sentry_sdk
from mozboot.util import get_state_dir
from mach.util import get_state_dir
from mach.telemetry import is_telemetry_enabled
from mozversioncontrol import (
get_repository_object,
Expand Down
3 changes: 2 additions & 1 deletion python/mach/mach/telemetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@

from mach.config import ConfigSettings
from mach.telemetry_interface import NoopTelemetry, GleanTelemetry
from mozboot.util import get_state_dir, get_mach_virtualenv_binary
from mach.util import get_state_dir
from mozboot.util import get_mach_virtualenv_binary
from mozbuild.base import MozbuildObject, BuildEnvironmentNotFoundException
from mozbuild.settings import TelemetrySettings
from mozbuild.telemetry import filter_args
Expand Down
48 changes: 46 additions & 2 deletions python/mach/mach/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@

from __future__ import absolute_import, unicode_literals

import hashlib
import os
import sys

from six import text_type


class UserError(Exception):
"""Represents an error caused by something the user did wrong rather than
Expand All @@ -21,6 +20,8 @@ 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:
Expand All @@ -35,3 +36,46 @@ def setenv(key, value):
value = value.decode(encoding)

os.environ[key] = value


def get_state_dir(specific_to_topsrcdir=False, topsrcdir=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 = os.environ.get("MOZBUILD_STATE_PATH", os.path.expanduser("~/.mozbuild"))
if not specific_to_topsrcdir:
return 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 = os.path.abspath(
MozbuildObject.from_environment(cwd=os.path.dirname(__file__)).topsrcdir
)
# 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(topsrcdir.encode("utf-8")).hexdigest()[:12]

state_dir = os.path.join(
state_dir, "srcdirs", "{}-{}".format(os.path.basename(topsrcdir), srcdir_hash)
)

if not os.path.isdir(state_dir):
# We create the srcdir here rather than 'mach_initialize.py' so direct
# consumers of this function don't create the directory inconsistently.
print("Creating local state directory: %s" % state_dir)
os.makedirs(state_dir, mode=0o770)
# Save the topsrcdir that this state dir corresponds to so we can clean
# it up in the event its srcdir was deleted.
with open(os.path.join(state_dir, "topsrcdir.txt"), "w") as fh:
fh.write(topsrcdir)

return state_dir
2 changes: 1 addition & 1 deletion python/mozboot/mozboot/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@ def install_toolchain_artifact(
raise ValueError(
"Need a state directory (e.g. ~/.mozbuild) to download " "artifacts"
)
python_location = get_mach_virtualenv_binary(state_dir=self.state_dir)
python_location = get_mach_virtualenv_binary()
if not os.path.exists(python_location):
raise ValueError("python not found at %s" % python_location)

Expand Down
3 changes: 1 addition & 2 deletions python/mozboot/mozboot/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import time
from distutils.version import LooseVersion
from mozfile import which
from mach.util import UserError
from mach.util import get_state_dir, UserError
from mach.telemetry import initialize_telemetry_setting

from mozboot.base import MODERN_RUST_VERSION
Expand All @@ -31,7 +31,6 @@
from mozboot.windows import WindowsBootstrapper
from mozboot.mozillabuild import MozillaBuildBootstrapper
from mozboot.mozconfig import find_mozconfig, MozconfigBuilder
from mozboot.util import get_state_dir

# Use distro package to retrieve linux platform information
import distro
Expand Down
54 changes: 6 additions & 48 deletions python/mozboot/mozboot/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,16 @@

from __future__ import absolute_import, print_function, unicode_literals

import hashlib
import os
import platform
import subprocess
from subprocess import CalledProcessError

from mach.site import PythonVirtualenv
from mach.util import get_state_dir
from mozfile import which


here = os.path.join(os.path.dirname(__file__))


MINIMUM_RUST_VERSION = "1.53.0"


Expand All @@ -26,53 +23,14 @@ def get_tools_dir(srcdir=False):
return get_state_dir(srcdir)


def get_state_dir(srcdir=False):
"""Obtain path to a directory to hold state.
Args:
srcdir (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 = os.environ.get("MOZBUILD_STATE_PATH", os.path.expanduser("~/.mozbuild"))
if not srcdir:
return state_dir

# This function can be called without the build virutualenv, and in that
# case srcdir is supposed to be False. Import mozbuild here to avoid
# breaking that usage.
from mozbuild.base import MozbuildObject

srcdir = os.path.abspath(MozbuildObject.from_environment(cwd=here).topsrcdir)
# 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(srcdir.encode("utf-8")).hexdigest()[:12]

state_dir = os.path.join(
state_dir, "srcdirs", "{}-{}".format(os.path.basename(srcdir), srcdir_hash)
def get_mach_virtualenv_root():
return os.path.join(
get_state_dir(specific_to_topsrcdir=True), "_virtualenvs", "mach"
)

if not os.path.isdir(state_dir):
# We create the srcdir here rather than 'mach_initialize.py' so direct
# consumers of this function don't create the directory inconsistently.
print("Creating local state directory: %s" % state_dir)
os.makedirs(state_dir, mode=0o770)
# Save the topsrcdir that this state dir corresponds to so we can clean
# it up in the event its srcdir was deleted.
with open(os.path.join(state_dir, "topsrcdir.txt"), "w") as fh:
fh.write(srcdir)

return state_dir


def get_mach_virtualenv_root(state_dir=None):
return os.path.join(state_dir or get_state_dir(), "_virtualenvs", "mach")


def get_mach_virtualenv_binary(state_dir=None):
root = get_mach_virtualenv_root(state_dir=state_dir)
def get_mach_virtualenv_binary():
root = get_mach_virtualenv_root()
return PythonVirtualenv(root).python_path


Expand Down
2 changes: 1 addition & 1 deletion python/mozbuild/mozbuild/repackaging/msix.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

from six.moves import shlex_quote

from mozboot.util import get_state_dir
from mach.util import get_state_dir
from mozbuild.util import ensureParentDir
from mozfile import which
from mozpack.copier import FileCopier
Expand Down
2 changes: 1 addition & 1 deletion python/mozperftest/mozperftest/fzf/fzf.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import json

from mozterm import Terminal
from mozboot.util import get_state_dir
from mach.util import get_state_dir
from distutils.spawn import find_executable


Expand Down
2 changes: 1 addition & 1 deletion python/mozperftest/mozperftest/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,8 @@ def main(argv=sys.argv[1:]):
from mozbuild.mozconfig import MozconfigLoader
from mozbuild.base import MachCommandBase, MozbuildObject
from mozperftest import PerftestArgumentParser
from mozboot.util import get_state_dir
from mach.logging import LoggingManager
from mach.util import get_state_dir

mozconfig = SRC_ROOT / "browser" / "config" / "mozconfig"
if mozconfig.exists():
Expand Down
2 changes: 1 addition & 1 deletion testing/raptor/mach_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

import mozfile
from mach.decorators import Command
from mozboot.util import get_state_dir
from mach.util import get_state_dir
from mozbuild.base import (
MozbuildObject,
BinaryNotFoundException,
Expand Down
2 changes: 1 addition & 1 deletion testing/web-platform/mach_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ def create_parser_fission_regressions():

def create_parser_testpaths():
import argparse
from mozboot.util import get_state_dir
from mach.util import get_state_dir

parser = argparse.ArgumentParser()
parser.add_argument(
Expand Down
2 changes: 1 addition & 1 deletion testing/web-platform/manifestupdate.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

from six.moves import configparser

from mozboot.util import get_state_dir
from mach.util import get_state_dir

from mozlog.structured import commandline

Expand Down
6 changes: 3 additions & 3 deletions tools/lint/python/l10n_lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from datetime import datetime, timedelta
import os

from mozboot import util as mb_util
from mach import util as mach_util
from mozlint import result, pathutils
from mozpack import path as mozpath
import mozversioncontrol.repoupdate
Expand All @@ -23,7 +23,7 @@


def lint(paths, lintconfig, **lintargs):
l10n_base = mb_util.get_state_dir()
l10n_base = mach_util.get_state_dir()
root = lintargs["root"]
exclude = lintconfig.get("exclude")
extensions = lintconfig.get("extensions")
Expand Down Expand Up @@ -81,7 +81,7 @@ def lint(paths, lintconfig, **lintargs):


def gecko_strings_setup(**lint_args):
gs = mozpath.join(mb_util.get_state_dir(), LOCALE)
gs = mozpath.join(mach_util.get_state_dir(), LOCALE)
marker = mozpath.join(gs, ".hg", "l10n_pull_marker")
try:
last_pull = datetime.fromtimestamp(os.stat(marker).st_mtime)
Expand Down
2 changes: 1 addition & 1 deletion tools/tryselect/docs/tasks.rst
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ You can test that everything is working by running these commands:

.. code-block:: shell
$ statedir=`mach python -c "from mozboot.util import get_state_dir; print(get_state_dir(srcdir=True))"`
$ statedir=`mach python -c "from mach.util import get_state_dir; print(get_state_dir(specific_to_topsrcdir=True))"`
$ rm -rf $statedir/cache/taskgraph
$ touch taskcluster/mach_commands.py
# wait a minute for generation to trigger and finish
Expand Down
2 changes: 1 addition & 1 deletion tools/tryselect/mach_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
SettingsProvider,
SubCommand,
)
from mozboot.util import get_state_dir
from mach.util import get_state_dir
from mozbuild.base import BuildEnvironmentNotFoundException
from mozbuild.util import memoize

Expand Down
8 changes: 5 additions & 3 deletions tools/tryselect/push.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import traceback

import six
from mozboot.util import get_state_dir
from mach.util import get_state_dir
from mozbuild.base import MozbuildObject
from mozversioncontrol import get_repository_object, MissingVCSExtension
from .util.manage_estimates import (
Expand Down Expand Up @@ -51,7 +51,7 @@
vcs = get_repository_object(build.topsrcdir)

history_path = os.path.join(
get_state_dir(srcdir=True), "history", "try_task_configs.json"
get_state_dir(specific_to_topsrcdir=True), "history", "try_task_configs.json"
)


Expand Down Expand Up @@ -120,7 +120,9 @@ def display_push_estimates(try_task_config):
if task_labels is None:
return

cache_dir = os.path.join(get_state_dir(srcdir=True), "cache", "taskgraph")
cache_dir = os.path.join(
get_state_dir(specific_to_topsrcdir=True), "cache", "taskgraph"
)

graph_cache = None
dep_cache = None
Expand Down
2 changes: 1 addition & 1 deletion tools/tryselect/selectors/coverage.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import datetime


from mozboot.util import get_state_dir
from mach.util import get_state_dir
from mozbuild.base import MozbuildObject
from mozpack.files import FileFinder
from moztest.resolve import TestResolver
Expand Down
Loading

0 comments on commit 6571032

Please sign in to comment.