Skip to main content
Glama
dependency_finalizer.erl4.36 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(dependency_finalizer). -author("loscher@meta.com"). -export([main/1]). -type dep_files_data() :: #{file:filename() => #{string() := file:filename()}}. -spec main([string()]) -> ok | no_return(). main([Source, InFile]) -> do(Source, InFile, stdout); main([Source, InFile, OutFile]) -> do(Source, InFile, {file, OutFile}); main(_) -> usage(), erlang:halt(1). -spec usage() -> ok. usage() -> io:format("~s.escript <source> dependency_spec.term [out.json]", [?MODULE]). -spec do(file:filename(), file:filename(), {file, file:filename()} | stdout) -> ok. do(Source, InFile, OutSpec) -> case read_file(InFile) of {ok, DepFiles} -> FlatDepFiles = lists:foldl(fun maps:merge/2, #{}, maps:values(DepFiles)), Dependencies = build_dep_info(Source, FlatDepFiles), OutData = json:encode(Dependencies), case OutSpec of {file, File} -> ok = write_file(File, OutData); stdout -> io:format("~s~n", [OutData]) end; Err -> io:format(standard_error, "error, could no parse file correctly: ~p~n", [Err]), erlang:halt(1) end. -spec read_file(file:filename()) -> {ok, dep_files_data()} | {error, term()}. read_file(File) -> case file:read_file(File, [raw]) of {ok, Data} -> {ok, json:decode(Data)}; Err -> Err end. -spec build_dep_info(file:filename(), dep_files_data()) -> list(map()). build_dep_info(Source, DepFiles) -> Key = list_to_binary(filename:basename(Source, ".erl") ++ ".beam"), collect_dependencies([Key], DepFiles, sets:new([{version, 2}]), []). collect_dependencies([], _, _, Acc) -> Acc; collect_dependencies([Key | Rest], DepFiles, Visited, Acc) -> case DepFiles of #{Key := DepFile} -> {ok, Dependencies} = read_file(DepFile), {NextKeys, NextVisited, NextAcc} = collect_dependencies_for_key(Dependencies, Key, Rest, Visited, Acc), collect_dependencies( NextKeys, DepFiles, NextVisited, NextAcc ); _ -> %% We cannot find key in DepFiles, which means it's from OTP or missing %% We don't add anything to the dependencies and let the compiler fail for a proper error message. collect_dependencies(Rest, DepFiles, Visited, Acc) end. collect_dependencies_for_key([], _CurrentKey, KeysAcc, VisitedAcc, DepAcc) -> {KeysAcc, VisitedAcc, DepAcc}; collect_dependencies_for_key([#{<<"file">> := File} = Dep | Deps], CurrentKey, KeysAcc, VisitedAcc, DepAcc) -> NextKey = key(File), case sets:is_element(NextKey, VisitedAcc) of true -> collect_dependencies_for_key(Deps, CurrentKey, KeysAcc, VisitedAcc, DepAcc); false -> collect_dependencies_for_key(Deps, CurrentKey, [NextKey | KeysAcc], sets:add_element(CurrentKey, VisitedAcc), [Dep | DepAcc]) end. -spec key(string()) -> string(). key(FileName) -> case filename:extension(FileName) of ".erl" -> filename:basename(FileName, ".erl") ++ ".beam"; _ -> FileName end. -spec write_file(file:filename(), iolist()) -> string(). write_file(File, Data) -> case % We write in raw mode because this is a standalone escript, so we don't % need advanced file server features, and we gain performance by avoiding % calling through the file server file:open(File, [write, binary, raw]) of {ok, Handle} -> try % We use file:pwrite instead of file:write_file to work around % the latter needlessly flattening iolists (as returned by % json:encode/1, etc.) to a binary file:pwrite(Handle, 0, Data) after file:close(Handle) end, ok; {error, _} = Error -> Error 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