Skip to main content
Glama
erlang_toolchain.bzl12.1 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( ":erlang_info.bzl", "ErlangMultiVersionToolchainInfo", "ErlangOTPBinariesInfo", "ErlangParseTransformInfo", "ErlangToolchainInfo", "Tool", "Tools", ) Toolchain = ErlangToolchainInfo ToolchainUtillInfo = provider( # @unsorted-dict-items fields = { "app_src_script": provider_field(Artifact), "boot_script_builder": provider_field(Artifact), "core_parse_transforms": provider_field(list[Dependency]), "dependency_analyzer": provider_field(Artifact), "dependency_finalizer": provider_field(Artifact), "erlc_trampoline": provider_field(Artifact), "escript_trampoline": provider_field(Artifact), "escript_builder": provider_field(Artifact), "release_variables_builder": provider_field(Artifact), "include_erts": provider_field(Artifact), "utility_modules": provider_field(list[Artifact]), }, ) def select_toolchains(ctx: AnalysisContext) -> dict[str, Toolchain]: """helper returning toolchains""" return ctx.attrs._toolchain[ErlangMultiVersionToolchainInfo].toolchains def get_primary(ctx: AnalysisContext) -> str: return ctx.attrs._toolchain[ErlangMultiVersionToolchainInfo].primary def get_primary_tools(ctx: AnalysisContext) -> Tools: return (get_primary_toolchain(ctx)).otp_binaries def get_primary_toolchain(ctx: AnalysisContext) -> Toolchain: return (select_toolchains(ctx)[get_primary(ctx)]) def _multi_version_toolchain_impl(ctx: AnalysisContext) -> list[Provider]: toolchains = {} for toolchain in ctx.attrs.targets: toolchain_info = toolchain[ErlangToolchainInfo] toolchains[toolchain_info.name] = toolchain_info return [ DefaultInfo(), ErlangMultiVersionToolchainInfo( toolchains = toolchains, primary = ctx.attrs.targets[0][ErlangToolchainInfo].name, ), ] multi_version_toolchain_rule = rule( impl = _multi_version_toolchain_impl, attrs = { "targets": attrs.list(attrs.dep()), }, is_toolchain_rule = True, ) def _config_erlang_toolchain_impl(ctx: AnalysisContext) -> list[Provider]: """ rule for erlang toolchain """ # split the options string to get a list of options erl_opts = ctx.attrs.erl_opts.split() emu_flags = ctx.attrs.emu_flags.split() # get otp binaries binaries_info = ctx.attrs.otp_binaries[ErlangOTPBinariesInfo] erl = cmd_args([binaries_info.erl] + emu_flags) erlc = cmd_args(binaries_info.erlc, hidden = binaries_info.erl) escript = cmd_args(binaries_info.escript, hidden = binaries_info.erl) otp_binaries = Tools( name = ctx.attrs.name, erl = erl, erlc = erlc, escript = escript, _tools_binaries = binaries_info, ) # extract utility artefacts utils = ctx.attrs.toolchain_utilities[ToolchainUtillInfo] core_parse_transforms = _gen_parse_transforms( ctx, otp_binaries.erlc, utils.core_parse_transforms, ) parse_transforms = _gen_parse_transforms( ctx, otp_binaries.erlc, ctx.attrs.parse_transforms, ) intersection = [key for key in parse_transforms if key in core_parse_transforms] if len(intersection): fail("conflicting parse_transform with core parse_transform found: %s" % (repr(intersection),)) utility_modules = _gen_util_beams(ctx, utils.utility_modules, otp_binaries.erlc) app_src_script = _gen_toolchain_script(ctx, utils.app_src_script, otp_binaries) boot_script_builder = _gen_toolchain_script(ctx, utils.boot_script_builder, otp_binaries) dependency_analyzer = _gen_toolchain_script(ctx, utils.dependency_analyzer, otp_binaries) dependency_finalizer = _gen_toolchain_script(ctx, utils.dependency_finalizer, otp_binaries) escript_builder = _gen_toolchain_script(ctx, utils.escript_builder, otp_binaries) release_variables_builder = _gen_toolchain_script(ctx, utils.release_variables_builder, otp_binaries) include_erts = _gen_toolchain_script(ctx, utils.include_erts, otp_binaries) return [ DefaultInfo(), ErlangToolchainInfo( name = ctx.attrs.name, app_src_script = app_src_script, boot_script_builder = boot_script_builder, dependency_analyzer = dependency_analyzer, dependency_finalizer = dependency_finalizer, erl_opts = erl_opts, env = ctx.attrs.env, emu_flags = emu_flags, erlc_trampoline = utils.erlc_trampoline, escript_trampoline = utils.escript_trampoline, escript_builder = escript_builder, otp_binaries = otp_binaries, release_variables_builder = release_variables_builder, include_erts = include_erts, core_parse_transforms = core_parse_transforms, parse_transforms = parse_transforms, parse_transforms_filters = ctx.attrs.parse_transforms_filters, utility_modules = utility_modules, ), ] def _configured_otp_binaries_impl(ctx: AnalysisContext) -> list[Provider]: name = ctx.attrs.name tools = get_primary_tools(ctx) bin_dir = ctx.actions.symlinked_dir( name, { "erl": tools._tools_binaries.erl, "erlc": tools._tools_binaries.erlc, "escript": tools._tools_binaries.escript, }, ) return [ DefaultInfo( default_output = bin_dir, sub_targets = { "erl": [DefaultInfo(default_output = tools._tools_binaries.erl), RunInfo(tools.erl)], "erlc": [DefaultInfo(default_output = tools._tools_binaries.erlc), RunInfo(tools.erlc)], "escript": [DefaultInfo(default_output = tools._tools_binaries.escript), RunInfo(tools.escript)], }, ), ] configured_otp_binaries = rule( impl = _configured_otp_binaries_impl, attrs = { "_toolchain": attrs.dep(), }, ) def _gen_parse_transforms(ctx: AnalysisContext, erlc: Tool, parse_transforms: list[Dependency]) -> dict[str, (Artifact, Artifact)]: transforms = {} for dep in parse_transforms: src = dep[ErlangParseTransformInfo].source extra = dep[ErlangParseTransformInfo].extra_files module_name, _ = paths.split_extension(src.basename) if module_name in transforms: fail("ambiguous global parse_transforms defined: %s", (module_name,)) transforms[module_name] = _gen_parse_transform_beam(ctx, src, extra, erlc) return transforms def _gen_parse_transform_beam( ctx: AnalysisContext, src: Artifact, extra: list[Artifact], erlc: Tool) -> (Artifact, Artifact): name, _ext = paths.split_extension(src.basename) # install resources resource_dir = ctx.actions.symlinked_dir( paths.join(name, "resources"), {infile.basename: infile for infile in extra}, ) # build beam output = ctx.actions.declare_output(name, name + ".beam") _compile_toolchain_module(ctx, src, output.as_output(), erlc) return output, resource_dir default_toolchain_script_args_pre = cmd_args( "+A0", "+S1:1", "+sbtu", "+MMscs", "8", "+MMsco", "false", "-mode", "minimal", "-noinput", "-noshell", "-eval", ) default_toolchain_script_args_post = cmd_args("-s", "erlang", "halt", "--") def _gen_toolchain_script(ctx: AnalysisContext, script: Artifact, tools: Tools) -> Tool: name, _ext = paths.split_extension(script.basename) out = ctx.actions.declare_output(name, name + ".beam") _compile_toolchain_module(ctx, script, out.as_output(), tools.erlc) eval = cmd_args(name, ":main(init:get_plain_arguments())", delimiter = "") return cmd_args( tools.erl, cmd_args(out, parent = 1, prepend = "-pa"), default_toolchain_script_args_pre, eval, default_toolchain_script_args_post, ) config_erlang_toolchain_rule = rule( impl = _config_erlang_toolchain_impl, attrs = { "core_parse_transforms": attrs.list(attrs.dep(), default = ["@prelude//erlang/toolchain:transform_project_root"]), "emu_flags": attrs.string(default = ""), "env": attrs.dict(key = attrs.string(), value = attrs.string(), default = {}), "erl_opts": attrs.string(default = ""), "otp_binaries": attrs.dep(), "parse_transforms": attrs.list(attrs.dep()), "parse_transforms_filters": attrs.dict(key = attrs.string(), value = attrs.list(attrs.string())), "toolchain_utilities": attrs.dep(default = "@prelude//erlang/toolchain:toolchain_utilities"), }, ) def _gen_util_beams( ctx: AnalysisContext, sources: list[Artifact], erlc: Tool) -> Artifact: beams = [] for src in sources: output = ctx.actions.declare_output(paths.join( "__build", paths.replace_extension(src.basename, ".beam"), )) _compile_toolchain_module(ctx, src, output.as_output(), erlc) beams.append(output) beam_dir = ctx.actions.symlinked_dir( "utility_modules", {beam.basename: beam for beam in beams}, ) return beam_dir def _compile_toolchain_module( ctx: AnalysisContext, src: Artifact, out: OutputArtifact, erlc: Tool): # NOTE: since we do NOT define +debug_info, this is hermetic ctx.actions.run( [erlc, "+deterministic", "-o", cmd_args(out, parent = 1), src], category = "erlc", identifier = src.short_path, ) # Parse Transform def erlang_otp_binaries_impl(ctx: AnalysisContext): erl = ctx.attrs.erl erlc = ctx.attrs.erlc escript = ctx.attrs.escript return [ DefaultInfo(), ErlangOTPBinariesInfo( erl = erl, erlc = erlc, escript = escript, ), ] erlang_parse_transform = rule( impl = lambda ctx: [ DefaultInfo(), ErlangParseTransformInfo( source = ctx.attrs.src, extra_files = ctx.attrs.extra_files, ), ], attrs = { "extra_files": attrs.list(attrs.source(), default = []), "src": attrs.source(), }, ) def _toolchain_utils(ctx: AnalysisContext) -> list[Provider]: return [ DefaultInfo(), ToolchainUtillInfo( app_src_script = ctx.attrs.app_src_script, boot_script_builder = ctx.attrs.boot_script_builder, core_parse_transforms = ctx.attrs.core_parse_transforms, dependency_analyzer = ctx.attrs.dependency_analyzer, dependency_finalizer = ctx.attrs.dependency_finalizer, erlc_trampoline = ctx.attrs.erlc_trampoline, escript_trampoline = ctx.attrs.escript_trampoline, escript_builder = ctx.attrs.escript_builder, release_variables_builder = ctx.attrs.release_variables_builder, include_erts = ctx.attrs.include_erts, utility_modules = ctx.attrs.utility_modules, ), ] toolchain_utilities = rule( impl = _toolchain_utils, attrs = { "app_src_script": attrs.source(), "boot_script_builder": attrs.source(), "core_parse_transforms": attrs.list(attrs.dep()), "dependency_analyzer": attrs.source(), "dependency_finalizer": attrs.source(), "erlc_trampoline": attrs.source(), "escript_builder": attrs.source(), "escript_trampoline": attrs.source(), "include_erts": attrs.source(), "release_variables_builder": attrs.source(), "utility_modules": attrs.list(attrs.source()), }, )

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