Created
August 16, 2013 16:51
-
-
Save maxlapshin/6251523 to your computer and use it in GitHub Desktop.
Prepare debian package with simple erlang script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env escript | |
-mode(compile). | |
-record(fpm, { | |
target, | |
category, | |
url, | |
description = "no description", | |
maintainer, | |
post_install, | |
pre_uninstall, | |
post_uninstall, | |
config_files = [], | |
name, | |
replaces = [], | |
conflicts = [], | |
version, | |
arch, | |
paths = [] | |
}). | |
main(Args) -> | |
State = parse_args(Args, #fpm{}), | |
make_package(State), | |
ok. | |
fpm_error(Format) -> | |
fpm_error(Format, []). | |
fpm_error(Format, Args) -> | |
io:format(Format, Args), | |
halt(1). | |
parse_args(["-s", "dir" | Args], State) -> | |
parse_args(Args, State); | |
parse_args(["-s", Source | _Args], _State) -> | |
fpm_error("-s '~s' is not supported\n", [Source]); | |
parse_args(["--url", URL|Args], #fpm{} = State) -> | |
parse_args(Args, State#fpm{url = URL}); | |
parse_args(["--description", Desc|Args], #fpm{} = State) -> | |
parse_args(Args, State#fpm{description = Desc}); | |
parse_args(["--category", Desc|Args], #fpm{} = State) -> | |
parse_args(Args, State#fpm{category = Desc}); | |
parse_args(["-m", Desc|Args], #fpm{} = State) -> | |
parse_args(Args, State#fpm{maintainer = Desc}); | |
parse_args(["--post-install", V|Args], #fpm{} = State) -> | |
case file:read_file(V) of | |
{ok, Bin} -> parse_args(Args, State#fpm{post_install = Bin}); | |
{error, E} -> fpm_error("Failed to read post-install ~s", [E]) | |
end; | |
parse_args(["--post-uninstall", V|Args], #fpm{} = State) -> | |
case file:read_file(V) of | |
{ok, Bin} -> parse_args(Args, State#fpm{post_uninstall = Bin}); | |
{error, E} -> fpm_error("Failed ot read post-uninstall ~s", E) | |
end; | |
parse_args(["--pre-uninstall", V|Args], #fpm{} = State) -> | |
case file:read_file(V) of | |
{ok, Bin} -> parse_args(Args, State#fpm{pre_uninstall = Bin}); | |
{error, E} -> fpm_error("Failed to read pre-uninstall ~s", [E]) | |
end; | |
parse_args(["--config-files", V|Args], #fpm{config_files = Conf} = State) -> | |
parse_args(Args, State#fpm{config_files = Conf ++ [V]}); | |
parse_args(["-t", "deb"|Args], #fpm{} = State) -> | |
parse_args(Args, State#fpm{target = deb}); | |
parse_args(["-t", Target|_Args], #fpm{} = _State) -> | |
fpm_error("-t '~s' is not supported\n",[Target]); | |
parse_args(["-n", V|Args], #fpm{} = State) -> | |
parse_args(Args, State#fpm{name = V}); | |
parse_args(["--replaces", V|Args], #fpm{replaces = R} = State) -> | |
parse_args(Args, State#fpm{replaces = R ++ [V]}); | |
parse_args(["--conflicts", V|Args], #fpm{conflicts = R} = State) -> | |
parse_args(Args, State#fpm{conflicts = R ++ [V]}); | |
parse_args(["-v", V|Args], #fpm{} = State) -> | |
parse_args(Args, State#fpm{version = V}); | |
parse_args(["-a", V|Args], #fpm{} = State) -> | |
parse_args(Args, State#fpm{arch = V}); | |
parse_args(["--"++Option, _V|Args], #fpm{} = State) -> | |
io:format("unknown option '~s'\n", [Option]), | |
parse_args(Args, State); | |
parse_args(["-"++Option, _V|Args], #fpm{} = State) -> | |
io:format("unknown option '~s'\n", [Option]), | |
parse_args(Args, State); | |
parse_args(Paths, #fpm{} = State) -> | |
State#fpm{paths = Paths}. | |
validate_package(#fpm{name = undefined}) -> | |
fpm_error("name is required\n"); | |
validate_package(#fpm{arch = undefined}) -> | |
fpm_error("arch is required\n"); | |
validate_package(#fpm{version = undefined}) -> | |
fpm_error("version is required\n"); | |
validate_package(_) -> | |
ok. | |
make_package(#fpm{target = deb, name = Name, version = Version, arch = Arch} = State) -> | |
validate_package(State), | |
pack_control(State), | |
pack_data(State), | |
file:write_file("debian-binary", "2.0\n"), | |
Path = Name++"_" ++ Version++ "_" ++ Arch ++ ".deb", | |
file:delete(Path), | |
os:cmd("ar qc "++Path++" debian-binary control.tar.gz data.tar.gz"), | |
file:delete("control.tar.gz"), | |
file:delete("data.tar.gz"), | |
file:delete("debian-binary"), | |
ok. | |
pack_control(#fpm{post_install = Postinst, pre_uninstall = Prerm, post_uninstall = Postrm} = State) -> | |
Files = [{"control", control_content(State)}] ++ | |
possible_file(conffiles, conf_files(State)) ++ | |
possible_file(postinst, Postinst) ++ | |
possible_file(prerm, Prerm) ++ | |
possible_file(postrm, Postrm), | |
file:delete("control.tar.gz"), | |
erl_tar:create("control.tar.gz", Files, [compressed]), | |
ok. | |
possible_file(_, undefined) -> []; | |
possible_file(Name, Content) -> [{atom_to_list(Name),iolist_to_binary(Content)}]. | |
conf_files(#fpm{config_files = Conf}) -> | |
[[C,"\n"] || C <- Conf]. | |
control_content(#fpm{name = Name, version = Version, maintainer = Maintainer, conflicts = Conflicts, | |
arch = Arch, | |
replaces = Replaces, category = Category, url = URL, description = Description}) -> | |
Content = [ | |
header("Package", Name), | |
header("Version", Version), | |
header("Architecture", Arch), | |
header("Maintainer", Maintainer), | |
header("Conflicts", join_list(Conflicts)), | |
header("Replaces", join_list(Replaces)), | |
header("Standards-Version", "3.9.1"), | |
header("Section",Category), | |
header("Priority", "extra"), | |
header("Homepage", URL), | |
header("Description", Description) | |
], | |
iolist_to_binary(Content). | |
header(_, undefined) -> ""; | |
header(Key, Value) -> [Key, ": ", Value, "\n"]. | |
join_list([]) -> undefined; | |
join_list(Items) -> string:join(Items, ", "). | |
pack_data(#fpm{paths = Paths}) -> | |
AllPaths = lookup_files(Paths), | |
file:delete("data.tar.gz"), | |
% {ok, Tar} = erl_tar:open("data.tar.gz", [write,compressed]), | |
% lists:foreach(fun(Path) -> | |
% case filelib:is_dir(Path) of | |
% true -> | |
% end, AllPaths), | |
{Dirs, Files} = lists:partition(fun filelib:is_dir/1, AllPaths), | |
% io:format("dirs: ~p\n",[Dirs]), | |
% io:format("files: ~p\n",[Files]), | |
"" = os:cmd(tar()++" --owner=root --numeric-owner --group 0 --no-recursion -cf data.tar.gz "++string:join(Dirs, " ")), | |
"" = os:cmd(tar()++" --owner=root --numeric-owner --group 0 -rf data.tar.gz "++string:join(Files, " ")), | |
% os:cmd(tar()++" --owner=root --group=root --no-recursion -cf data.tar.gz "++string:join(AllPaths, " ")), | |
ok. | |
lookup_files(Dirs) -> | |
lookup_files(Dirs, sets:new()). | |
lookup_files([], Set) -> | |
lists:usort(sets:to_list(Set)); | |
lookup_files([Dir|Dirs], Set) -> | |
Set1 = filelib:fold_files(Dir, ".*", true, fun(Path, Acc) -> | |
add_recursive_path(Path, Acc) | |
end, Set), | |
lookup_files(Dirs, Set1). | |
add_recursive_path("/", Set) -> | |
Set; | |
add_recursive_path(".", Set) -> | |
Set; | |
add_recursive_path(Path, Set) -> | |
add_recursive_path(filename:dirname(Path), sets:add_element(Path, Set)). | |
tar() -> | |
case os:type() of | |
{unix,darwin} -> "gnutar"; | |
{unix,linux} -> "tar" | |
end. | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment