Skip to main content
Glama
elp.bxl8.15 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. # Provide information so that ELP is able to load a BUCK project load("@prelude//:paths.bzl", "paths") BuildResults = record( result = field(dict[Label, bxl.BuildResult]), artifacts = field(dict[Label, list[bxl.EnsuredArtifact]]), ) # ------------------ IMPL ------------------ def _add_includes_to_map(includes_map, includes): for inc in includes: includes_map[inc["value"]] = inc["type"] return includes_map def _clean_up_includes(includes): # - Strip to just dir, not file name # - Remove duplicates # Note: Sometimes the buck rule generating the includes has an excludes glob for a directory. # This flattening will potentially expose excluded files in the directory. # But we do it, because otherwise the performance in erlang_service parsing is too poor. # For generated include files we cannot perform this optimisation, instead we list them # individually. include_paths = [paths.dirname(p["value"]) for p in includes if p["type"] == "path"] include_paths = [_as_path(p) for p in _list_dedupe(include_paths)] builds = [b for b in includes if b["type"] == "build"] return include_paths + builds def _get_includes(ctx, includes_target, build_results): includes = [] for inc in includes_target.value(): includes += _get_absolute_path(ctx, inc, build_results) return _clean_up_includes(includes) def _get_absolute_path(ctx, src, build_results, allow_multiple = True) -> list[dict[str, str | bxl.EnsuredArtifact]]: """ Get the absolute path of the thing passed in, which is either an artifact or a target label. """ if isinstance(src, ConfiguredProvidersLabel) and build_results and src in build_results.result: target = src.configured_target() build_result = build_results.result[src] failed_targets = [] for _ in build_result.failures(): failed_targets.append(str(target)) if failed_targets != []: return [_as_error("target {} failed to build due to failures on the following targets [{}]".format(target, ", ".join(failed_targets)))] paths = build_results.artifacts[src] if not allow_multiple and len(paths) != 1: return [_as_error("target {} has multiple outputs: {}".format(target, ", ".join([str(p) for p in paths])))] return [_as_build(path.abs_path()) for path in paths] elif isinstance(src, ConfiguredProvidersLabel): return [_as_target(str(src.raw_target()))] else: return [_as_path(get_path_without_materialization(src, ctx, abs = True))] def _elp_config(ctx): included_targets = ctx.cli_args.included_targets # Load the prelude targets first, so if we are working in the prelude they will be overwritten # by included or deps targets, and processed in ELP included_targets = ctx.configured_targets(included_targets, None) all_targets = ctx.cquery().deps(ctx.configured_targets(included_targets, None)) deps_targets = all_targets - included_targets all = {tgt.label.raw_target(): ("app", tgt.attrs_lazy()) for tgt in ctx.cquery().kind("^(erlang_app|erlang_test)$", included_targets)} all.update({tgt.label.raw_target(): ("dep", tgt.attrs_lazy()) for tgt in ctx.cquery().kind("^erlang_app$", deps_targets)}) build_results = None if ctx.cli_args.build_generated_code: build_results = prebuild(ctx, all) result = {} for label_name, (typ, attrs) in all.items(): includes_map = {} includes = attrs.get("includes") if not includes: includes = [] else: includes = _get_includes(ctx, includes, build_results) includes_map = _add_includes_to_map(includes_map, includes) deps_list = [] apps_list = [] included_apps_list = [] deps = attrs.get("deps") if deps: deps_list = [tgt.raw_target() for tgt in deps.value()] apps = attrs.get("applications") if apps: apps_list = [tgt.raw_target() for tgt in apps.value()] included_apps = attrs.get("included_applications") if included_apps: included_apps_list = [tgt.raw_target() for tgt in included_apps.value()] srcs_attr = attrs.get("srcs") srcs = [] if srcs_attr: for src in srcs_attr.value(): srcs += _get_absolute_path(ctx, src, build_results) suite = attrs.get("suite") if not suite: suite = None elif suite.value() == None: suite = None else: [suite_info] = _get_absolute_path(ctx, suite.value(), build_results, allow_multiple = False) if suite_info["type"] == "path" or suite_info["type"] == "build": suite = suite_info["value"] else: suite = None includes = [t for t in includes_map.keys() if includes_map[t] == "path" or includes_map[t] == "build"] srcs = _construct_output(srcs) result[label_name] = dict( name = attrs.get("name"), app_name = attrs.get("app_name"), suite = suite, srcs = srcs, includes = includes, labels = attrs.get("labels"), deps = deps_list, apps = apps_list, included_apps = included_apps_list, origin = typ, ) ctx.output.print_json(result) def prebuild(ctx, all): labels = [] for _label_name, (_typ, attrs) in all.items(): target_labels = attrs.get("labels") if target_labels: if "elp_skip_generation" in target_labels.value(): continue includes = attrs.get("includes") if includes: for includes in includes.value(): if isinstance(includes, ConfiguredProvidersLabel): labels.append(includes) srcs = attrs.get("srcs") if srcs: for src in srcs.value(): if isinstance(src, ConfiguredProvidersLabel): labels.append(src) suite = attrs.get("suite") if suite: suite = suite.value() if isinstance(suite, ConfiguredProvidersLabel): labels.append(suite) build_results = ctx.build(labels) artifacts = ctx.output.ensure_multiple(build_results) return BuildResults(result = build_results, artifacts = artifacts) def _construct_output(items: list[dict[str, str | bxl.EnsuredArtifact]]) -> list[str | bxl.EnsuredArtifact]: # we completely ignore targets, since we don't have support for generated files in ELP paths = _list_dedupe([p.get("value") for p in items if p.get("type") in ["path", "build"]]) return paths def _list_dedupe(xs): return {x: True for x in xs}.keys() def _as_path(src): return {"type": "path", "value": src} def _as_target(src): return {"type": "target", "value": src} def _as_build(artifact): return {"type": "build", "value": artifact} def _as_error(error): warning("ERROR: {}".format(error)) return {"type": "error", "value": error} # ------------------ INTERFACE ------------------ elp_config = bxl_main( impl = _elp_config, cli_args = { "build_generated_code": cli_args.option(cli_args.bool(), default = False, doc = "Enable builds for generated sources"), "deps_targets": cli_args.option(cli_args.list(cli_args.string()), doc = "Target to include deps from, if specified. See corresponding field in .elp.toml"), "included_targets": cli_args.list(cli_args.string(), doc = "Targets to include in the query. See corresponding field in .elp.toml"), }, ) # Run with `buck2 bxl prelude//erlang/elp.bxl:elp_config` # e.g. # buck2 bxl prelude//erlang/elp.bxl:elp_config -- --included_targets cell//...

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