summaryrefslogtreecommitdiffstats
path: root/python/mozbuild/mozbuild/backend/mach_commands.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/mozbuild/mozbuild/backend/mach_commands.py')
-rw-r--r--python/mozbuild/mozbuild/backend/mach_commands.py420
1 files changed, 420 insertions, 0 deletions
diff --git a/python/mozbuild/mozbuild/backend/mach_commands.py b/python/mozbuild/mozbuild/backend/mach_commands.py
new file mode 100644
index 0000000000..1b83ebc826
--- /dev/null
+++ b/python/mozbuild/mozbuild/backend/mach_commands.py
@@ -0,0 +1,420 @@
+# 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 argparse
+import logging
+import os
+import subprocess
+import sys
+
+import mozpack.path as mozpath
+from mach.decorators import Command, CommandArgument
+from mozfile import which
+
+from mozbuild import build_commands
+
+
+@Command(
+ "ide",
+ category="devenv",
+ description="Generate a project and launch an IDE.",
+ virtualenv_name="build",
+)
+@CommandArgument("ide", choices=["eclipse", "visualstudio", "vscode"])
+@CommandArgument(
+ "--no-interactive",
+ default=False,
+ action="store_true",
+ help="Just generate the configuration",
+)
+@CommandArgument("args", nargs=argparse.REMAINDER)
+def run(command_context, ide, no_interactive, args):
+ interactive = not no_interactive
+
+ if ide == "eclipse":
+ backend = "CppEclipse"
+ elif ide == "visualstudio":
+ backend = "VisualStudio"
+ elif ide == "vscode":
+ backend = "Clangd"
+
+ if ide == "eclipse" and not which("eclipse"):
+ command_context.log(
+ logging.ERROR,
+ "ide",
+ {},
+ "Eclipse CDT 8.4 or later must be installed in your PATH.",
+ )
+ command_context.log(
+ logging.ERROR,
+ "ide",
+ {},
+ "Download: http://www.eclipse.org/cdt/downloads.php",
+ )
+ return 1
+
+ if ide == "vscode":
+ rc = build_commands.configure(command_context)
+
+ if rc != 0:
+ return rc
+
+ # First install what we can through install manifests.
+ rc = command_context._run_make(
+ directory=command_context.topobjdir,
+ target="pre-export",
+ line_handler=None,
+ )
+ if rc != 0:
+ return rc
+
+ # Then build the rest of the build dependencies by running the full
+ # export target, because we can't do anything better.
+ for target in ("export", "pre-compile"):
+ rc = command_context._run_make(
+ directory=command_context.topobjdir,
+ target=target,
+ line_handler=None,
+ )
+ if rc != 0:
+ return rc
+ else:
+ # Here we refresh the whole build. 'build export' is sufficient here and is
+ # probably more correct but it's also nice having a single target to get a fully
+ # built and indexed project (gives a easy target to use before go out to lunch).
+ res = command_context._mach_context.commands.dispatch(
+ "build", command_context._mach_context
+ )
+ if res != 0:
+ return 1
+
+ # Generate or refresh the IDE backend.
+ python = command_context.virtualenv_manager.python_path
+ config_status = os.path.join(command_context.topobjdir, "config.status")
+ args = [python, config_status, "--backend=%s" % backend]
+ res = command_context._run_command_in_objdir(
+ args=args, pass_thru=True, ensure_exit_code=False
+ )
+ if res != 0:
+ return 1
+
+ if ide == "eclipse":
+ eclipse_workspace_dir = get_eclipse_workspace_path(command_context)
+ subprocess.check_call(["eclipse", "-data", eclipse_workspace_dir])
+ elif ide == "visualstudio":
+ visual_studio_workspace_dir = get_visualstudio_workspace_path(command_context)
+ subprocess.call(["explorer.exe", visual_studio_workspace_dir])
+ elif ide == "vscode":
+ return setup_vscode(command_context, interactive)
+
+
+def get_eclipse_workspace_path(command_context):
+ from mozbuild.backend.cpp_eclipse import CppEclipseBackend
+
+ return CppEclipseBackend.get_workspace_path(
+ command_context.topsrcdir, command_context.topobjdir
+ )
+
+
+def get_visualstudio_workspace_path(command_context):
+ return os.path.normpath(
+ os.path.join(command_context.topobjdir, "msvc", "mozilla.sln")
+ )
+
+
+def setup_vscode(command_context, interactive):
+ from mozbuild.backend.clangd import find_vscode_cmd
+
+ # Check if platform has VSCode installed
+ if interactive:
+ vscode_cmd = find_vscode_cmd()
+ if vscode_cmd is None:
+ choice = prompt_bool(
+ "VSCode cannot be found, and may not be installed. Proceed?"
+ )
+ if not choice:
+ return 1
+
+ vscode_settings = mozpath.join(
+ command_context.topsrcdir, ".vscode", "settings.json"
+ )
+
+ new_settings = {}
+ artifact_prefix = ""
+ if command_context.config_environment.is_artifact_build:
+ artifact_prefix = (
+ "\nArtifact build configured: Skipping clang and rust setup. "
+ "If you later switch to a full build, please re-run this command."
+ )
+ else:
+ new_settings = setup_clangd_rust_in_vscode(command_context)
+
+ # Add file associations.
+ new_settings = {
+ **new_settings,
+ "files.associations": {
+ "*.jsm": "javascript",
+ "*.sjs": "javascript",
+ },
+ # Note, the top-level editor settings are left as default to allow the
+ # user's defaults (if any) to take effect.
+ "[javascript][javascriptreact][typescript][typescriptreact][json][html]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "editor.formatOnSave": True,
+ },
+ }
+
+ import difflib
+ import json
+
+ # Load the existing .vscode/settings.json file, to check if if needs to
+ # be created or updated.
+ try:
+ with open(vscode_settings) as fh:
+ old_settings_str = fh.read()
+ except FileNotFoundError:
+ print(
+ "Configuration for {} will be created.{}".format(
+ vscode_settings, artifact_prefix
+ )
+ )
+ old_settings_str = None
+
+ if old_settings_str is None:
+ # No old settings exist
+ with open(vscode_settings, "w") as fh:
+ json.dump(new_settings, fh, indent=4)
+ else:
+ # Merge our new settings with the existing settings, and check if we
+ # need to make changes. Only prompt & write out the updated config
+ # file if settings actually changed.
+ try:
+ old_settings = json.loads(old_settings_str)
+ prompt_prefix = ""
+ except ValueError:
+ old_settings = {}
+ prompt_prefix = (
+ "\n**WARNING**: Parsing of existing settings file failed. "
+ "Existing settings will be lost!"
+ )
+
+ # If we've got an old section with the formatting configuration, remove it
+ # so that we effectively "upgrade" the user to include json from the new
+ # settings. The user is presented with the diffs so should spot any issues.
+ if "[javascript][javascriptreact][typescript][typescriptreact]" in old_settings:
+ old_settings.pop(
+ "[javascript][javascriptreact][typescript][typescriptreact]"
+ )
+ if (
+ "[javascript][javascriptreact][typescript][typescriptreact][json]"
+ in old_settings
+ ):
+ old_settings.pop(
+ "[javascript][javascriptreact][typescript][typescriptreact][json]"
+ )
+
+ settings = {**old_settings, **new_settings}
+
+ if old_settings != settings:
+ # Prompt the user with a diff of the changes we're going to make
+ new_settings_str = json.dumps(settings, indent=4)
+ if interactive:
+ print(
+ "\nThe following modifications to {settings} will occur:\n{diff}".format(
+ settings=vscode_settings,
+ diff="".join(
+ difflib.unified_diff(
+ old_settings_str.splitlines(keepends=True),
+ new_settings_str.splitlines(keepends=True),
+ "a/.vscode/settings.json",
+ "b/.vscode/settings.json",
+ n=30,
+ )
+ ),
+ )
+ )
+ choice = prompt_bool(
+ "{}{}\nProceed with modifications to {}?".format(
+ artifact_prefix, prompt_prefix, vscode_settings
+ )
+ )
+ if not choice:
+ return 1
+
+ with open(vscode_settings, "w") as fh:
+ fh.write(new_settings_str)
+
+ if not interactive:
+ return 0
+
+ # Open vscode with new configuration, or ask the user to do so if the
+ # binary was not found.
+ if vscode_cmd is None:
+ print(
+ "Please open VS Code manually and load directory: {}".format(
+ command_context.topsrcdir
+ )
+ )
+ return 0
+
+ rc = subprocess.call(vscode_cmd + [command_context.topsrcdir])
+
+ if rc != 0:
+ command_context.log(
+ logging.ERROR,
+ "ide",
+ {},
+ "Unable to open VS Code. Please open VS Code manually and load "
+ "directory: {}".format(command_context.topsrcdir),
+ )
+ return rc
+
+ return 0
+
+
+def setup_clangd_rust_in_vscode(command_context):
+ clangd_cc_path = mozpath.join(command_context.topobjdir, "clangd")
+
+ # Verify if the required files are present
+ clang_tools_path = mozpath.join(
+ command_context._mach_context.state_dir, "clang-tools"
+ )
+ clang_tidy_bin = mozpath.join(clang_tools_path, "clang-tidy", "bin")
+
+ clangd_path = mozpath.join(
+ clang_tidy_bin,
+ "clangd" + command_context.config_environment.substs.get("BIN_SUFFIX", ""),
+ )
+
+ if not os.path.exists(clangd_path):
+ command_context.log(
+ logging.ERROR,
+ "ide",
+ {},
+ "Unable to locate clangd in {}.".format(clang_tidy_bin),
+ )
+ rc = get_clang_tools(command_context, clang_tools_path)
+
+ if rc != 0:
+ return rc
+
+ import multiprocessing
+
+ from mozbuild.code_analysis.utils import ClangTidyConfig
+
+ clang_tidy_cfg = ClangTidyConfig(command_context.topsrcdir)
+
+ if sys.platform == "win32":
+ cargo_check_command = [sys.executable, "mach"]
+ else:
+ cargo_check_command = ["./mach"]
+
+ cargo_check_command += [
+ "--log-no-times",
+ "cargo",
+ "check",
+ "-j",
+ str(multiprocessing.cpu_count() // 2),
+ "--all-crates",
+ "--message-format-json",
+ ]
+
+ clang_tidy = {}
+ clang_tidy["Checks"] = ",".join(clang_tidy_cfg.checks)
+ clang_tidy.update(clang_tidy_cfg.checks_config)
+
+ # Write .clang-tidy yml
+ import yaml
+
+ with open(".clang-tidy", "w") as file:
+ yaml.dump(clang_tidy, file)
+
+ clangd_cfg = {
+ "CompileFlags": {
+ "CompilationDatabase": clangd_cc_path,
+ }
+ }
+
+ with open(".clangd", "w") as file:
+ yaml.dump(clangd_cfg, file)
+
+ return {
+ "clangd.path": clangd_path,
+ "clangd.arguments": [
+ "-j",
+ str(multiprocessing.cpu_count() // 2),
+ "--limit-results",
+ "0",
+ "--completion-style",
+ "detailed",
+ "--background-index",
+ "--all-scopes-completion",
+ "--log",
+ "info",
+ "--pch-storage",
+ "disk",
+ "--clang-tidy",
+ ],
+ "rust-analyzer.server.extraEnv": {
+ # Point rust-analyzer at the real target directory used by our
+ # build, so it can discover the files created when we run `./mach
+ # cargo check`.
+ "CARGO_TARGET_DIR": command_context.topobjdir,
+ },
+ "rust-analyzer.cargo.buildScripts.overrideCommand": cargo_check_command,
+ "rust-analyzer.check.overrideCommand": cargo_check_command,
+ }
+
+
+def get_clang_tools(command_context, clang_tools_path):
+ import shutil
+
+ if os.path.isdir(clang_tools_path):
+ shutil.rmtree(clang_tools_path)
+
+ # Create base directory where we store clang binary
+ os.mkdir(clang_tools_path)
+
+ from mozbuild.artifact_commands import artifact_toolchain
+
+ job, _ = command_context.platform
+
+ if job is None:
+ command_context.log(
+ logging.ERROR,
+ "ide",
+ {},
+ "The current platform isn't supported. "
+ "Currently only the following platforms are "
+ "supported: win32/win64, linux64 and macosx64.",
+ )
+ return 1
+
+ job += "-clang-tidy"
+
+ # We want to unpack data in the clang-tidy mozbuild folder
+ currentWorkingDir = os.getcwd()
+ os.chdir(clang_tools_path)
+ rc = artifact_toolchain(
+ command_context, verbose=False, from_build=[job], no_unpack=False, retry=0
+ )
+ # Change back the cwd
+ os.chdir(currentWorkingDir)
+
+ return rc
+
+
+def prompt_bool(prompt, limit=5):
+ """Prompts the user with prompt and requires a boolean value."""
+ from distutils.util import strtobool
+
+ for _ in range(limit):
+ try:
+ return strtobool(input(prompt + " [Y/N]\n"))
+ except ValueError:
+ print(
+ "ERROR! Please enter a valid option! Please use any of the following:"
+ " Y, N, True, False, 1, 0"
+ )
+ return False