Skip to main content
Glama
ide.bxl11.4 kB
# 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. load("@prelude//:paths.bzl", "paths") load("@prelude//haskell:library_info.bzl", "HaskellLibraryProvider") load("@prelude//haskell:link_info.bzl", "HaskellLinkInfo") load("@prelude//haskell:toolchain.bzl", "HaskellToolchainInfo") load("@prelude//linking:link_info.bzl", "LinkStyle") # This script computes a solution for loading a Haskell project in VSCode # The solution includes: # - flags (and materializing any artifacts used by the flags) # - source files # - external dependencies whose changes will cause a reload (e.g. TARGETS files) # There are only 3 rule types that we need to support # - haskell_binary # - haskell_library # - haskell_ide (aka "projects") # The input to the script is usually a source file, although it can also be a target # Solving an input involves: # 1. Finding its owner target, if the input is a file # 2. Finding the target's "project", which involves a rdeps search # 3. Computing the project solution (flags, sources and dependencies) # 4. Outputting the solution as JSON _HASKELL_BIN = "prelude//rules.bzl:haskell_binary" _HASKELL_IDE = "prelude//rules.bzl:haskell_ide" _HASKELL_LIB = "prelude//rules.bzl:haskell_library" linkStyle = LinkStyle("static") configuration_modifiers = ["ovr_config//third-party/ghc/constraints:8.8.3"] def _impl_target(ctx): target = ctx.cli_args.target project_universe = ctx.cli_args.project_universe res = _find_project_and_solve(ctx, target, project_universe) solution = _assembleSolution(ctx, linkStyle, res) _print_solution(ctx, solution) def _print_solution(ctx, solution): if not ctx.cli_args.bios: ctx.output.print_json(solution) else: for flag in solution["flags"]: ctx.output.print(flag) for src in solution["sources"]: ctx.output.print(src) def _impl_file(ctx): project_universe = ctx.cli_args.project_universe res = _solution_for_file(ctx, ctx.cli_args.file, project_universe) solution = _assembleSolution(ctx, linkStyle, res) _print_solution(ctx, solution) def _solution_for_file(ctx, file, project_universe): unconfigured_owners = ctx.uquery().owner(file) target_universe = ctx.target_universe(unconfigured_owners).target_set() owners = ctx.cquery().owner(file, target_universe) if not owners or len(owners) == 0: return { "external_dependencies": [], "flags": [], "generated_dependencies": [], "haskell_deps": {}, "import_dirs": [], "owner": "No owner found for " + file, "project": "", "project_type": "", "sources": [], "targets": [], } owner = owners[0] result = _find_project_and_solve(ctx, owner, project_universe) result["owner"] = owner.label.raw_target() return result def _find_project_and_solve(ctx, target, project_universe = []): prefix = target.label.package.split("/", 1)[0] local_universe = _find_project_universe(ctx, target.label.cell, prefix) if local_universe: project_universe.extend(local_universe) project = _find_target_in_universe(ctx, target, dedupe(project_universe)) result = _solution_for_target(ctx, project) result["project"] = project.label.raw_target() result["project_type"] = project.rule_type return result def _find_target_in_universe(ctx, target, project_universe): for p in project_universe: cfg_p = ctx.lazy.configured_target_node( p, modifiers = configuration_modifiers, ).catch().resolve() if not (cfg_p.is_ok()) or cfg_p.unwrap() == None: continue cfg_p = cfg_p.unwrap() members = cfg_p.resolved_attrs_eager(ctx).include_projects for member in members: if target.label.raw_target() == member.label.raw_target(): return cfg_p return target def _find_project_universe(ctx, cell, prefix): return ctx.uquery().eval("kind(haskell_ide, %s//%s/...)" % (cell, prefix)) def _solution_for_target(ctx, target, exclude = {}): result = None if target.rule_type == _HASKELL_LIB: result = _solution_for_haskell_lib(ctx, target, exclude) elif target.rule_type == _HASKELL_BIN: result = _solution_for_haskell_bin(ctx, target, exclude) elif target.rule_type == _HASKELL_IDE: result = _solution_for_haskell_ide(ctx, target) if result == None: return {"error": "Cannot handle rule type " + target.rule_type} return result def _solution_for_haskell_ide(ctx, target): resolved_attrs = target.resolved_attrs_eager(ctx) results = [] deps = {} for dep in resolved_attrs.deps_query: t = ctx.configured_targets( dep.label.raw_target(), modifiers = configuration_modifiers, ) if (t.rule_type == _HASKELL_LIB or t.rule_type == _HASKELL_BIN): deps[dep.label] = t for lib in deps.values(): results.append(_solution_for_target(ctx, lib, deps)) final = merge(results) final["targets"].extend(targetsForTarget(ctx, target)) return final def _solution_for_haskell_bin(ctx, target, exclude): return _solution_for_haskell_lib(ctx, target, exclude) def _solution_for_haskell_lib(ctx, target, exclude): resolved_attrs = target.resolved_attrs_eager(ctx) hli = ctx.analysis(target).providers().get(HaskellLibraryProvider) haskellLibs = {} for dep in resolved_attrs.deps + resolved_attrs.template_deps: if exclude.get(dep.label) == None: providers = ctx.analysis(dep.label).providers() lb = providers.get(HaskellLinkInfo) if lb != None: haskellLibs[dep.label] = lb sources = [] for item in ctx.output.ensure_multiple(resolved_attrs.srcs.values()): sources.append(item.abs_path()) import_dirs = {} root = ctx.root() for key, item in resolved_attrs.srcs.items(): # because BXL won't give you the path of an ensured artifact sp = get_path_without_materialization(item, ctx) (_, ext) = paths.split_extension(sp) diff = sp.removesuffix(paths.replace_extension(key, ext)) import_dirs["%s/%s" % (root, diff)] = () haskell_toolchain = ctx.analysis(resolved_attrs._haskell_toolchain.label) toolchain = haskell_toolchain.providers().get(HaskellToolchainInfo) binutils_path = paths.join(root, toolchain.ghci_binutils_path) cc_path = paths.join(root, toolchain.ghci_cc_path) cxx_path = paths.join(root, toolchain.ghci_cxx_path) cpp_path = paths.join(root, toolchain.ghci_cpp_path) flags = [ "-this-unit-id", "fbcode_fake_unit_id", "-optP-undef", "-optP-traditional-cpp", "-I.", "-no-global-package-db", "-no-user-package-db", "-hide-all-packages", "-pgma%s" % cc_path, "-pgml%s" % cxx_path, "-pgmc%s" % cc_path, "-pgmP%s" % cpp_path, "-opta-B%s" % binutils_path, "-optc-B%s" % binutils_path, ] flags.extend(resolved_attrs.compiler_flags) return { "exclude_packages": {hli.lib.get(linkStyle).name: ()} if hli else {}, "flags": flags, "generated_dependencies": externalSourcesForTarget(ctx, target), "haskell_deps": haskellLibs, "import_dirs": import_dirs.keys(), "sources": sources, "targets": targetsForTarget(ctx, target), } def targetsForTarget(ctx, target): buildfile = ctx.cquery().buildfile(target) root = ctx.root() paths = [] for b in buildfile: paths.append("%s/%s" % (root, str(b).replace("//", "/"))) return paths def externalSourcesForTarget(ctx, target): deps3 = ctx.cquery().deps(target, 3) thrifts = ctx.cquery().attrfilter("labels", "thrift_library=hs2/compile", deps3) paths = [] for thrift in thrifts: paths.extend(thrift.resolved_attrs_lazy(ctx).get("srcs")) return paths def merge(results): seen_flags = set() flags = [] sources = {} haskellDeps = {} import_dirs = {} generated_dependencies = {} targets = {} exclude_packages = {} for result in results: for flag in result["flags"]: if not (flag in seen_flags): flags.append(flag) seen_flags.add(flag) for source in result["sources"]: sources[source] = () for p, v in result["haskell_deps"].items(): haskellDeps[p] = v for i in result["import_dirs"]: import_dirs[i] = () for dep in result["generated_dependencies"]: generated_dependencies[dep] = () for t in result["targets"]: targets[t] = () for t in result["exclude_packages"]: exclude_packages[t] = () return { "exclude_packages": exclude_packages, "flags": flags, "generated_dependencies": generated_dependencies.keys(), "haskell_deps": haskellDeps, "import_dirs": import_dirs.keys(), "sources": sources.keys(), "targets": targets.keys(), } def _assembleSolution(ctx, linkStyle, result): flags = result["flags"] package_dbs = {} for i in result["import_dirs"]: flags.append("-i%s" % i) hlis = {} for provider in result["haskell_deps"].values(): info = provider.info.get(linkStyle) if info != None: for item in info.traverse(): if result["exclude_packages"].get(item.name) == None: hlis[item.name] = item for hli in hlis.values(): flags.append("-package") flags.append(hli.name) ctx.output.ensure_multiple(hli.stub_dirs) ctx.output.ensure_multiple(hli.libs) ctx.output.ensure_multiple(hli.import_dirs.values()) package_dbs[hli.db] = () for pkgdb in ctx.output.ensure_multiple(package_dbs.keys()): flags.append("-package-db") flags.append(pkgdb.abs_path()) external_deps = result["targets"] for s in ctx.output.ensure_multiple(result["generated_dependencies"]): external_deps.append(s.abs_path()) return { "externalDependencies": external_deps, "flags": result["flags"], "owner": result.get("owner"), "owner_type": result.get("owner_type"), "project": result.get("project"), "project_type": result.get("project_type"), # TODO check for duplicate module names in sources "sources": result["sources"], } _common_flags = { "bios": cli_args.bool(False, "Output GHC flags and targets separated by newlines"), "project_universe": cli_args.list(cli_args.target_label("list of haskell_ide targets"), []), } ide_for_target = bxl_main( impl = _impl_target, cli_args = dict(_common_flags.items() + { "target": cli_args.target_label(), }.items()), ) ide_for_file = bxl_main( impl = _impl_file, cli_args = dict(_common_flags.items() + { "file": cli_args.string("File to load in IDE"), }.items()), )

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