Skip to main content
Glama
vsgo.py12.6 kB
#!/usr/bin/env fbpython # Copyright (c) Meta Platforms, Inc. and affiliates. # # This source code is licensed under both the MIT license found in the # LICENSE-MIT file in the root directory of this source tree and the Apache # License, Version 2.0 found in the LICENSE-APACHE file in the root directory # of this source tree. import json import logging import os import shutil import subprocess import sys MODE_CONFIGS_DIR = "mode_configs" def is_explicit_target(target): return "..." not in target and not target.endswith(":") def is_target_alias(target): return all(symbol not in target for symbol in [":", "/", "..."]) def get_mode_hashes( sample_target, explicit_targets, mode_files, extra_buck_options, debug ): # Resolve sample_target to full target label to match the BXL script output. sample_target = subprocess.check_output( ["buck2", "targets", sample_target], text=True, ).strip() mode_hashes = {} default_mode_file = mode_files[0] for mode_file in mode_files: bxl_cmds = [ "buck2", ] if mode_file != default_mode_file: bxl_cmds += ["--isolation-dir", "vsgo-" + mode_file.split("/")[-1]] bxl_cmds += ( [ "bxl", "@" + mode_file, "prelude//ide_integrations/visual_studio/get_mode_hashes.bxl:main", ] + extra_buck_options + ["--", "--target", sample_target] + explicit_targets ) if debug: print("Running bxl command:", " ".join(bxl_cmds)) try: output = json.loads( subprocess.check_output( bxl_cmds, text=True, stderr=subprocess.PIPE, ) ) # Change the key 'sample_target' to 'default' in the output JSON if sample_target not in output: raise Exception( "BXL script did not return sample_target in the output JSON" ) output["default"] = output.pop(sample_target) except subprocess.CalledProcessError as e: print("\nstdout:\n" + e.stdout, file=sys.stderr) print("\nstderr:\n" + e.stderr, file=sys.stderr) raise mode_hashes[mode_file] = output return mode_hashes # Replace option prefix "--" with "\-\-" to avoid BXL mistakes them as arguments to BXL. def _escape_arg(arg): if arg.startswith("--"): return r"\-\-" + arg[2:] if arg.startswith("-"): return r"\-" + arg[1:] return arg def gen_mode_configs(bxl_path, mode_files, fbsource, debug): mode_config_paths = [] default_mode_file = mode_files[0] for mode_file in mode_files: bxl_cmds = [ "buck2", ] if mode_file != default_mode_file: bxl_cmds += ["--isolation-dir", "vsgo-" + mode_file.split("/")[-1]] bxl_cmds += [ "bxl", "@" + mode_file, os.path.dirname(bxl_path) + "/gen_mode_configs.bxl:main", "--", "--mode_name", mode_file, "--fbsource", str(fbsource).lower(), ] if debug: print("Running BXL command:", " ".join(bxl_cmds)) try: bxl_process = subprocess.run( bxl_cmds, text=True, capture_output=True, check=True ) except subprocess.CalledProcessError as e: print("\nstdout:\n" + e.stdout, file=sys.stderr) print("\nstderr:\n" + e.stderr, file=sys.stderr) raise mode_config_paths.append(bxl_process.stdout.strip()) return mode_config_paths def main( targets, mode_files, extra_bxl_options, extra_buck_options, generated_folder, recursive_target_types, target_exclude_patterns, target_include_patterns, solution_name, startup_target, fbsource, bxl_path, sample_target, debug, ): mode_configs = gen_mode_configs(bxl_path, mode_files, fbsource, debug) if len(mode_files) > 1: explicit_targets = [target for target in targets if is_explicit_target(target)] mode_hashes = get_mode_hashes( sample_target, explicit_targets, mode_files, extra_bxl_options, debug, ) else: mode_hashes = {} if debug: print("mode_hashes:", mode_hashes) default_mode_file = mode_files[0] bxl_cmds = ( [ "buck2", "bxl", "@" + default_mode_file, ] + extra_bxl_options + [ bxl_path + ":main", "--", ] + ["--target"] + targets + ["--mode_files"] + mode_files ) if extra_buck_options: # Pass extra buck options verbatim so that run/debug invokes buck using the same options beside target and mode file. bxl_cmds += ["--extra_buck_options"] + [ _escape_arg(o) for o in extra_buck_options ] bxl_cmds += ["--mode_hashes", json.dumps(mode_hashes, separators=(",", ":"))] if recursive_target_types: bxl_cmds += ["--recursive_target_types"] + recursive_target_types if target_exclude_patterns: bxl_cmds += ["--target_exclude_pattern"] + target_exclude_patterns if target_include_patterns: bxl_cmds += ["--target_include_pattern"] + target_include_patterns if solution_name: bxl_cmds += ["--solution_name", solution_name] if startup_target: bxl_cmds += ["--startup_target", startup_target] bxl_cmds += ["--output", "json"] bxl_cmds += ["--fbsource", str(fbsource).lower()] if debug: bxl_cmds += ["--log_level", "0"] print("Running BXL command:", " ".join(bxl_cmds)) # Capture both stdout and stderr to "tee" to both console and remote error logs. try: bxl_process = subprocess.run( bxl_cmds, text=True, capture_output=True, check=True ) except subprocess.CalledProcessError as e: print("\nstdout:\n" + e.stdout, file=sys.stderr) print("\nstderr:\n" + e.stderr, file=sys.stderr) raise # Show potential warnings to user. print(bxl_process.stderr, file=sys.stderr) bxl_output = json.loads(bxl_process.stdout) sln_path = bxl_output["sln_path"] parts = sln_path.split(os.sep) gen_root_index = parts.index("main.bxl") + 1 gen_root = os.sep.join(parts[: gen_root_index + 1]) os.makedirs(os.path.join(gen_root, MODE_CONFIGS_DIR), exist_ok=True) for file_path in mode_configs: file_name = os.path.basename(file_path) destination_file_path = os.path.join(gen_root, MODE_CONFIGS_DIR, file_name) shutil.copy(file_path, destination_file_path) logging.warning( "Copying generated project and solution files to final destination ..." ) shutil.copytree(gen_root, generated_folder, dirs_exist_ok=True) sln_relative_to_gen_root = os.sep.join(parts[gen_root_index + 1 :]) sln_path = os.path.normcase( os.path.join(generated_folder, sln_relative_to_gen_root) ) dest_sln_path = os.path.join(generated_folder, os.path.basename(sln_path)) shutil.move(sln_path, dest_sln_path) return bxl_output | {"sln_path": dest_sln_path} DEFAULT_RECURSIVE_TARGET_TYPES = [ "cxx_binary", "cxx_library", "cxx_test", "alias", "command_alias", ] if __name__ == "__main__": import argparse parser = argparse.ArgumentParser( prog="vsgo", description="Visual Studio project generator for buck targets", ) parser.add_argument( "targets", help="""List of buck targets, aliases and/or patterns. Dependencies and transitive dependencies are automatically pulled in. Individual specified targets are preferred over target patterns as the latter will usually pull in unused targets which could potentially slow down project generation and project loading significantly. Examples: 1. Single fully-specified target: //arvr/projects/pcsdk:OVRServer (Only fully-specified targets are guaranteed to be able to link to the debugger) 2. target alias: ovrserver 3. '...' target pattern (CAUTION: Using '...' in targets is not properly supported and may lead to incorrect behavior. Please consider using explicit targets instead. It also might bring in massive targets with recursive dependencies and result in very slow solution generating and loading): //arvr/projects/mixedreality/... 4. Combinations of above: ovrserver //arvr/projects/pcsdk:OVRServer """, nargs="*", type=str, default=[], ) parser.add_argument( "--target", nargs="+", help="alias for positional argument targets. Preferred over positional arguments", default=[], ) parser.add_argument( "--mode_files", nargs="+", help="list of mode files (without leading `@`), which will be generated as different configurations", default=[], ) parser.add_argument( "--extra_bxl_options", nargs="+", help="extra options when running BXL scripts during project generation", default=[], ) parser.add_argument( "--extra_buck_options", nargs="+", help="extra options when running buck from generated project settings. Note '-' within option value needs to be escaped, e.g., `vsgo //third-party/semver:basic_example --extra_buck_options '\\-\\-out' 'C:\\open\\temp-out' '\\-\\-local-only'`", default=[], ) parser.add_argument( "--generated_folder", action="store", help="output directory of generated projects and solutions", default="generated_projects", ) parser.add_argument( "--recursive_target_types", nargs="+", help="the types of targets that will be recovered when target patterns ('...') are expanded and transitive dependencies are queried. Default to " + str(DEFAULT_RECURSIVE_TARGET_TYPES), default=DEFAULT_RECURSIVE_TARGET_TYPES, ) parser.add_argument( "--target_include_patterns", nargs="+", help="include target(s) only if it matches buck target pattern", default=[], ) parser.add_argument( "--target_exclude_patterns", nargs="+", help="exclude target(s) if it matches buck target pattern", default=[], ) parser.add_argument( "--solution_name", action="store", help="name of the generated solution", ) parser.add_argument( "--startup_target", action="store", help="buck target to be set as the default startup project", ) parser.add_argument( "--fbsource", action="store_true", help="whether to turn on fbsource specific behaviors", default=False, ) parser.add_argument( "--bxl_path", action="store", help="path of BXL script", default="prelude//ide_integrations/visual_studio/main.bxl", ) parser.add_argument( "--sample_target", action="store", help="sample target to build in order to get configuration hash values", ) parser.add_argument( "--debug", action="store_true", help="verbose output", default=False, ) args = parser.parse_args() args.targets.extend(args.target) if not args.targets: parser.error( "Required argument targets not found! Specify targets using either preferred `--target` option or as positional arguments." ) if not args.mode_files: parser.error("Required argument mode_files not found!") bxl_output = main( targets=args.targets, mode_files=args.mode_files, extra_bxl_options=args.extra_bxl_options, extra_buck_options=args.extra_buck_options, generated_folder=args.generated_folder, recursive_target_types=args.recursive_target_types, target_exclude_patterns=args.target_exclude_patterns, target_include_patterns=args.target_include_patterns, solution_name=args.solution_name, startup_target=args.startup_target, fbsource=args.fbsource, bxl_path=args.bxl_path, sample_target=args.sample_target, debug=args.debug, ) print("Solution generated at " + bxl_output["sln_path"])

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/systeminit/si'

If you have feedback or need assistance with the MCP directory API, please join our Discord server