Prepare debian package with simple erlang script
#!/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