Skip to main content
Glama
escript_builder.erl5.66 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. %% @format -module(escript_builder). -author("loscher@fb.com"). -moduledoc """ Build an escript from a given spec file. The spec file format is defined in erlang_escript.bzl usage: escript_builder.escript escript_build_spec.term """. -export([main/1]). -include_lib("kernel/include/file.hrl"). -type escript_artifact_spec() :: #{ ArchivePath :: file:filename_all() => FileSystemPath :: file:filename_all() }. -type escript_load_spec() :: [{ArchivePath :: file:filename(), FileSystemPath :: file:filename()}]. -type escript_archive_spec() :: [{ArchivePath :: file:filename(), binary()}]. -spec main([string()]) -> ok. main([Spec]) -> try {ok, Contents} = file:read_file(Spec, [raw]), Decoded = json:decode(Contents), do(Decoded) catch Type:{abort, Reason} -> io:format(standard_error, "~s: ~s~n", [Type, Reason]), erlang:halt(1) end; main(_) -> usage(). -spec usage() -> ok. usage() -> io:format("escript_builder.escript build_spec.term ~n"). -spec do(map()) -> ok. do(#{ <<"artifacts">> := Artifacts, <<"emu_args">> := EmuArgs0, <<"output">> := EscriptPath }) -> ArchiveSpec = prepare_files(Artifacts), Shebang = "/usr/bin/env escript", Comment = "", EmuArgs1 = [string:trim(Arg) || Arg <- EmuArgs0], FinalEmuArgs = unicode:characters_to_list( [" ", lists:join(" ", EmuArgs1)] ), EscriptSections = [ {shebang, Shebang}, {comment, Comment}, {emu_args, FinalEmuArgs}, {archive, ArchiveSpec, []} ], case escript:create(EscriptPath, EscriptSections) of ok -> ok; {error, EscriptError} -> error(unicode:characters_to_binary(io_lib:format("could not create escript: ~p", [EscriptError]))) end, %% set executable bits (unix only) {ok, #file_info{mode = Mode}} = file:read_file_info(EscriptPath), ok = file:change_mode(EscriptPath, Mode bor 8#00111). -spec prepare_files(escript_artifact_spec()) -> escript_archive_spec(). prepare_files(Artifacts) -> Files = expand_to_files_list(Artifacts), load_parallel(Files). -spec expand_to_files_list(escript_artifact_spec()) -> escript_load_spec(). expand_to_files_list(Artifacts) -> maps:fold( fun(ArchivePathBin, FSPath, AccOuter) -> ArchivePath = binary_to_list(ArchivePathBin), case filelib:is_dir(FSPath) of true -> Files = filelib:wildcard("**", binary_to_list(FSPath)), lists:foldl( fun(FileShortPath, AccInner) -> FileOrDirPath = filename:join(FSPath, FileShortPath), case filelib:is_dir(FileOrDirPath) of true -> AccInner; false -> [ {filename:join(ArchivePath, FileShortPath), FileOrDirPath} | AccInner ] end end, AccOuter, Files ); false -> [{ArchivePath, FSPath} | AccOuter] end end, [], Artifacts ). -spec load_parallel(escript_load_spec()) -> escript_archive_spec(). load_parallel([]) -> []; load_parallel(Files) -> Self = self(), F = fun() -> worker(Self) end, Jobs = min(length(Files), erlang:system_info(schedulers)), Refs = #{element(2,spawn_monitor(F)) => [] || _I <- lists:seq(1, Jobs)}, queue(Files, Refs, maps:size(Refs), []). -spec worker(pid()) -> ok. worker(QueuePid) -> QueuePid ! self(), receive {load, {ArchivePath, FSPath}} -> QueuePid ! {done, FSPath, {ArchivePath, file_contents(FSPath)}}, worker(QueuePid); empty -> ok end. -spec file_contents(file:filename()) -> binary(). file_contents(Filename) -> case file:read_file(Filename, [raw]) of {ok, Bin} -> Bin; Error -> error({read_file, Filename, Error}) end. -spec queue(escript_load_spec(), #{reference() => []}, non_neg_integer(), escript_archive_spec()) -> escript_archive_spec(). queue([], _JobRefs, 0, Acc) -> Acc; queue(Files, JobRefs, NumLeft, Acc) -> receive {done, File, Res} -> io:format("Loaded ~ts~n", [File]), queue(Files, JobRefs, NumLeft, [Res | Acc]); {'DOWN', Mref, _, _Pid, Info} -> case Info of normal when is_map_key(Mref, JobRefs) -> queue(Files, JobRefs, NumLeft-1, Acc); _ -> io:format("ERROR: Compilation failed: ~p", [Info]), erlang:halt(1) end; Worker when is_pid(Worker) -> case Files of [] -> Worker ! empty, queue(Files, JobRefs, NumLeft, Acc); [_|_] -> [NextFile | MoreFiles] = Files, Worker ! {load, NextFile}, queue(MoreFiles,JobRefs, NumLeft, Acc) end end.

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