diff --git a/CHANGELOG.md b/CHANGELOG.md index c3c2665..e592624 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ and this project adheres to actions will **NOT** be called if the actions do not raise any exception. [#25](https://github.com/grisp/grisp_tools/pull/25) - Add a firmware command to generate GRiSP 2 binary firmwares: [#26](https://github.com/grisp/grisp_tools/pull/26) +- Add a pack command to generate a GRiSP 2 software update package: [#28](https://github.com/grisp/grisp_tools/pull/28) ### Changed diff --git a/rebar.config b/rebar.config index 93fc67a..f45ea0e 100644 --- a/rebar.config +++ b/rebar.config @@ -3,5 +3,6 @@ {mapz, "~> 2.2"}, bbmustache, hackney, - edifa + edifa, + grisp_update_packager ]}. diff --git a/rebar.lock b/rebar.lock index 4d64314..a0cb1a2 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1,41 +1,53 @@ {"1.2.0", [{<<"bbmustache">>,{pkg,<<"bbmustache">>,<<"1.12.2">>},0}, - {<<"certifi">>,{pkg,<<"certifi">>,<<"2.9.0">>},1}, + {<<"certifi">>,{pkg,<<"certifi">>,<<"2.12.0">>},1}, {<<"edifa">>,{pkg,<<"edifa">>,<<"1.0.0">>},0}, {<<"erlexec">>,{pkg,<<"erlexec">>,<<"2.0.7">>},1}, - {<<"hackney">>,{pkg,<<"hackney">>,<<"1.18.1">>},0}, + {<<"grisp_update_packager">>,{pkg,<<"grisp_update_packager">>,<<"1.0.0">>},0}, + {<<"hackney">>,{pkg,<<"hackney">>,<<"1.20.1">>},0}, {<<"idna">>,{pkg,<<"idna">>,<<"6.1.1">>},1}, - {<<"mapz">>,{pkg,<<"mapz">>,<<"2.2.0">>},0}, + {<<"mapz">>,{pkg,<<"mapz">>,<<"2.4.0">>},0}, {<<"metrics">>,{pkg,<<"metrics">>,<<"1.0.1">>},1}, - {<<"mimerl">>,{pkg,<<"mimerl">>,<<"1.2.0">>},1}, - {<<"parse_trans">>,{pkg,<<"parse_trans">>,<<"3.3.1">>},1}, - {<<"ssl_verify_fun">>,{pkg,<<"ssl_verify_fun">>,<<"1.1.6">>},1}, - {<<"unicode_util_compat">>,{pkg,<<"unicode_util_compat">>,<<"0.7.0">>},1}]}. + {<<"mimerl">>,{pkg,<<"mimerl">>,<<"1.3.0">>},1}, + {<<"parse_trans">>,{pkg,<<"parse_trans">>,<<"3.4.1">>},1}, + {<<"quickrand">>,{pkg,<<"quickrand">>,<<"2.0.7">>},2}, + {<<"ssl_verify_fun">>,{pkg,<<"ssl_verify_fun">>,<<"1.1.7">>},1}, + {<<"termseal">>,{pkg,<<"termseal">>,<<"0.1.1">>},1}, + {<<"unicode_util_compat">>,{pkg,<<"unicode_util_compat">>,<<"0.7.0">>},1}, + {<<"uuid">>,{pkg,<<"uuid_erl">>,<<"2.0.7">>},1}]}. [ {pkg_hash,[ {<<"bbmustache">>, <<"0CABDCE0DB9FE6D3318131174B9F2B351328A4C0AFBEB3E6E99BB0E02E9B621D">>}, - {<<"certifi">>, <<"6F2A475689DD47F19FB74334859D460A2DC4E3252A3324BD2111B8F0429E7E21">>}, + {<<"certifi">>, <<"2D1CCA2EC95F59643862AF91F001478C9863C2AC9CB6E2F89780BFD8DE987329">>}, {<<"edifa">>, <<"0F1A01A0C79B7135F334B3FCEEB624F0574C5ED3E4554B06C8664AADA6A339C8">>}, {<<"erlexec">>, <<"76D0BC7487929741B5BB9F74DA2AF5DAF1492134733CF9A05C7AAA278B6934C5">>}, - {<<"hackney">>, <<"F48BF88F521F2A229FC7BAE88CF4F85ADC9CD9BCF23B5DC8EB6A1788C662C4F6">>}, + {<<"grisp_update_packager">>, <<"0532CCD0955398FAC4E1DE90FE85DB941CA609A2F4E066CFFE01ECE41DCCE119">>}, + {<<"hackney">>, <<"8D97AEC62DDDDD757D128BFD1DF6C5861093419F8F7A4223823537BAD5D064E2">>}, {<<"idna">>, <<"8A63070E9F7D0C62EB9D9FCB360A7DE382448200FBBD1B106CC96D3D8099DF8D">>}, - {<<"mapz">>, <<"81EE5AD249BBC9427DAA6C3EE5166F88A448C5B0B2F5BBEE0495266308AFF2BA">>}, + {<<"mapz">>, <<"77A8E38B69BAB16C5D3EBD44E6C619F8AF1F1598B0CAAE301D266605A0865756">>}, {<<"metrics">>, <<"25F094DEA2CDA98213CECC3AEFF09E940299D950904393B2A29D191C346A8486">>}, - {<<"mimerl">>, <<"67E2D3F571088D5CFD3E550C383094B47159F3EEE8FFA08E64106CDF5E981BE3">>}, - {<<"parse_trans">>, <<"16328AB840CC09919BD10DAB29E431DA3AF9E9E7E7E6F0089DD5A2D2820011D8">>}, - {<<"ssl_verify_fun">>, <<"CF344F5692C82D2CD7554F5EC8FD961548D4FD09E7D22F5B62482E5AEAEBD4B0">>}, - {<<"unicode_util_compat">>, <<"BC84380C9AB48177092F43AC89E4DFA2C6D62B40B8BD132B1059ECC7232F9A78">>}]}, + {<<"mimerl">>, <<"D0CD9FC04B9061F82490F6581E0128379830E78535E017F7780F37FEA7545726">>}, + {<<"parse_trans">>, <<"6E6AA8167CB44CC8F39441D05193BE6E6F4E7C2946CB2759F015F8C56B76E5FF">>}, + {<<"quickrand">>, <<"D2BD76676A446E6A058D678444B7FDA1387B813710D1AF6D6E29BB92186C8820">>}, + {<<"ssl_verify_fun">>, <<"354C321CF377240C7B8716899E182CE4890C5938111A1296ADD3EC74CF1715DF">>}, + {<<"termseal">>, <<"C9D93D4FF638EE99F9377D3438FC7AD132D2901EBBAF10C54F8DEA1D7E24D61C">>}, + {<<"unicode_util_compat">>, <<"BC84380C9AB48177092F43AC89E4DFA2C6D62B40B8BD132B1059ECC7232F9A78">>}, + {<<"uuid">>, <<"B2078D2CC814F53AFA52D36C91E08962C7E7373585C623F4C0EA6DFB04B2AF94">>}]}, {pkg_hash_ext,[ {<<"bbmustache">>, <<"688B33A4D5CC2D51F575ADF0B3683FC40A38314A2F150906EDCFC77F5B577B3B">>}, - {<<"certifi">>, <<"266DA46BDB06D6C6D35FDE799BCB28D36D985D424AD7C08B5BB48F5B5CDD4641">>}, + {<<"certifi">>, <<"EE68D85DF22E554040CDB4BE100F33873AC6051387BAF6A8F6CE82272340FF1C">>}, {<<"edifa">>, <<"A1E010561E7D236A24C668D95626BE2BFE082ED0331CE1E6798BE0CD43F59A7B">>}, {<<"erlexec">>, <<"AF2DD940BB8E32F5AA40A65CB455DCAA18F5334FD3507E9BFD14A021E9630897">>}, - {<<"hackney">>, <<"A4ECDAFF44297E9B5894AE499E9A070EA1888C84AFDD1FD9B7B2BC384950128E">>}, + {<<"grisp_update_packager">>, <<"47BFDF6FADBED4B8342205A812198CF913E0223A98A775CAAE5D2FB5D5CF751C">>}, + {<<"hackney">>, <<"FE9094E5F1A2A2C0A7D10918FEE36BFEC0EC2A979994CFF8CFE8058CD9AF38E3">>}, {<<"idna">>, <<"92376EB7894412ED19AC475E4A86F7B413C1B9FBB5BD16DCCD57934157944CEA">>}, - {<<"mapz">>, <<"861A878A86AB7E5897A9B57B3CA0B188FB53CE2935B153872C61F6641E709AA8">>}, + {<<"mapz">>, <<"4B68DF5CF0522E0D6545DF7B681BC052865CDB78405AD4CC9C55FE45EE7B25BE">>}, {<<"metrics">>, <<"69B09ADDDC4F74A40716AE54D140F93BEB0FB8978D8636EADED0C31B6F099F16">>}, - {<<"mimerl">>, <<"F278585650AA581986264638EBF698F8BB19DF297F66AD91B18910DFC6E19323">>}, - {<<"parse_trans">>, <<"07CD9577885F56362D414E8C4C4E6BDF10D43A8767ABB92D24CBE8B24C54888B">>}, - {<<"ssl_verify_fun">>, <<"BDB0D2471F453C88FF3908E7686F86F9BE327D065CC1EC16FA4540197EA04680">>}, - {<<"unicode_util_compat">>, <<"25EEE6D67DF61960CF6A794239566599B09E17E668D3700247BC498638152521">>}]} + {<<"mimerl">>, <<"A1E15A50D1887217DE95F0B9B0793E32853F7C258A5CD227650889B38839FE9D">>}, + {<<"parse_trans">>, <<"620A406CE75DADA827B82E453C19CF06776BE266F5A67CFF34E1EF2CBB60E49A">>}, + {<<"quickrand">>, <<"B8ACBF89A224BC217C3070CA8BEBC6EB236DBE7F9767993B274084EA044D35F0">>}, + {<<"ssl_verify_fun">>, <<"FE4C190E8F37401D30167C8C405EDA19469F34577987C76DDE613E838BBC67F8">>}, + {<<"termseal">>, <<"466280936214AF1894FC431642E83341B7D13580A3F3485820A2D300C5CAEB49">>}, + {<<"unicode_util_compat">>, <<"25EEE6D67DF61960CF6A794239566599B09E17E668D3700247BC498638152521">>}, + {<<"uuid">>, <<"4E4C5CA3461DC47C5E157ED42AA3981A053B7A186792AF972A27B14A9489324E">>}]} ]. diff --git a/src/grisp_tools.erl b/src/grisp_tools.erl index b38b0b3..946e242 100644 --- a/src/grisp_tools.erl +++ b/src/grisp_tools.erl @@ -9,6 +9,7 @@ -export([report/1]). -export([configure/1]). -export([firmware/1]). +-export([pack/1]). %--- API ----------------------------------------------------------------------- @@ -33,3 +34,5 @@ report(Opts) -> grisp_tools_report:run(Opts). configure(Opts) -> grisp_tools_configure:run(Opts). firmware(Opts) -> grisp_tools_firmware:run(Opts). + +pack(Opts) -> grisp_tools_pack:run(Opts). diff --git a/src/grisp_tools.hrl b/src/grisp_tools.hrl new file mode 100644 index 0000000..f7f6221 --- /dev/null +++ b/src/grisp_tools.hrl @@ -0,0 +1,21 @@ +-ifndef(GRISP_TOOLS_HRL). +-define(GRISP_TOOLS_HRL, true). + +-define(MiB, * 1024 * 1024). +-define(DOCKER_TOOLCHAIN_ROOT, "/grisp2-rtems-toolchain/rtems/5"). +-define(GRISP2_BOOTLOADER_FILENAMES, [ + "barebox-phytec-phycore-imx6ull-emmc-512mb.img", + "barebox-phytec-phycore-imx6ul-emmc-512mb.img" +]). +-define(GRISP2_RESERVED_SIZE, (4?MiB)). +-define(GRISP2_SYSTEM_SIZE, (256?MiB)). +-define(GRISP2_IMAGE_SIZE, (?GRISP2_RESERVED_SIZE + 2 * ?GRISP2_SYSTEM_SIZE)). +-define(GRISP2_PARTITIONS, [ + #{role => system, type => fat32, size => ?GRISP2_SYSTEM_SIZE, start => ?GRISP2_RESERVED_SIZE}, + #{role => system, type => fat32, size => ?GRISP2_SYSTEM_SIZE} +]). +-define(GRISP2_FAT_TYPE, 32). +-define(GRISP2_FAT_CLUSTER_SIZE, 4). + + +-endif. % GRISP_TOOLS_HRL \ No newline at end of file diff --git a/src/grisp_tools_firmware.erl b/src/grisp_tools_firmware.erl index 0fc8414..fa1098c 100644 --- a/src/grisp_tools_firmware.erl +++ b/src/grisp_tools_firmware.erl @@ -1,5 +1,6 @@ -module(grisp_tools_firmware). +-include("grisp_tools.hrl"). -include_lib("kernel/include/file.hrl"). % API @@ -10,26 +11,6 @@ -import(grisp_tools_util, [shell/3]). -%--- MACROS -------------------------------------------------------------------- - --define(MiB, * 1024 * 1024). --define(DOCKER_TOOLCHAIN_ROOT, "/grisp2-rtems-toolchain/rtems/5"). --define(GRISP2_BOOTLOADER_FILENAMES, [ - "foobar.img", - "barebox-phytec-phycore-imx6ull-emmc-512mb.img", - "barebox-phytec-phycore-imx6ul-emmc-512mb.img" -]). --define(GRISP2_RESERVED_SIZE, (4?MiB)). --define(GRISP2_SYSTEM_SIZE, (256?MiB)). --define(GRISP2_IMAGE_SIZE, (?GRISP2_RESERVED_SIZE + 2 * ?GRISP2_SYSTEM_SIZE)). --define(GRISP2_PARTITIONS, [ - #{type => fat32, size => ?GRISP2_SYSTEM_SIZE, - start => ?GRISP2_RESERVED_SIZE}, - #{type => fat32, size => ?GRISP2_SYSTEM_SIZE} -]). --define(GRISP2_FAT_TYPE, 32). --define(GRISP2_FAT_CLUSTER_SIZE, 4). - %--- API ----------------------------------------------------------------------- run(State) -> @@ -363,7 +344,7 @@ docker_check_image(State, ImageName) -> docker_export(State, ImageName, InPath, OutDir) -> ok = grisp_tools_util:ensure_dir(OutDir), - Command = ["docker run --volume ", OutDir, ":", OutDir, + Command = ["docker run --rm --volume ", OutDir, ":", OutDir, " ", ImageName, " sh -c \"cd ", OutDir, " && cp -rf '", InPath, "' .\""], case shell(State, Command, [return_on_error]) of diff --git a/src/grisp_tools_pack.erl b/src/grisp_tools_pack.erl new file mode 100644 index 0000000..bae7b02 --- /dev/null +++ b/src/grisp_tools_pack.erl @@ -0,0 +1,176 @@ +-module(grisp_tools_pack). + +-include("grisp_tools.hrl"). +-include_lib("kernel/include/file.hrl"). + +% API +-export([run/1]). + +-import(grisp_tools_util, [event/2]). + + +%--- API ----------------------------------------------------------------------- + +run(State) -> + grisp_tools_util:weave(State, [ + fun grisp_tools_step:config/1, + {pack, [ + fun prepare/1, + fun package/1, + fun cleanup/1 + ]} + ]). + +%--- Tasks --------------------------------------------------------------------- + +prepare(State) -> + Force = maps:get(force, State, false), + State2 = State#{force => Force}, + grisp_tools_util:weave(State2, [ + fun validate_temp_dir/1, + fun validate_system/1, + fun validate_bootloader/1, + fun validate_package/1 + ]). + +validate_temp_dir(State) -> + case maps:find(temp_dir, State) of + {ok, TempDir} when TempDir =/= undefined -> + case filelib:is_dir(TempDir) of + true -> State; + false -> event(State, [{error, directory_not_found, TempDir}]) + end; + _ -> + {Output, State2} = shell(State, "mktemp -d", []), + TempDir = string:trim(Output), + case filelib:is_dir(TempDir) of + true -> + State2#{temp_dir => TempDir, + cleanup_temp_dir => true}; + false -> + event(State2, [{error, directory_not_found, TempDir}]) + end + end. + +validate_system(State) -> + case maps:find(system, State) of + {ok, SysPath} when SysPath =/= undefined -> + case filelib:is_file(SysPath) of + true -> State; + false -> + event(State, [{error, system_not_found, SysPath}]) + end; + _ -> + event(State, [{error, missing_parameter, system}]) + end. + +validate_bootloader(State = #{bootloader := BootPath}) + when BootPath =/= undefined -> + case filelib:is_file(BootPath) of + true -> State; + false -> + event(State, [{error, bootloader_not_found, BootPath}]) + end; +validate_bootloader(State) -> + State#{bootloader => undefined}. + +validate_package(State) -> + case maps:find(package, State) of + {ok, PackageFile} when PackageFile =/= undefined -> + prepare_output_file(State, PackageFile); + _ -> + event(State, [{error, missing_parameter, package}]) + end. + +package(State) -> + grisp_tools_util:weave(State, [ + fun expand_bootloader/1, + fun expand_system/1, + fun build_package/1, + fun cleanup/1 + ], [ + fun cleanup/1 + ]). + +expand_bootloader(State = #{bootloader := BootPath}) + when BootPath =/= undefined -> + {ExpPath, State2} = maybe_expand(State, BootPath), + State2#{bootloader => ExpPath}; +expand_bootloader(State) -> + State. + +expand_system(State = #{system := SysPath}) + when SysPath =/= undefined -> + {ExpPath, State2} = maybe_expand(State, SysPath), + State2#{system => ExpPath}; +expand_system(State) -> + State. + +build_package(State = #{package := PackageFile}) -> + PackagerOpts1 = maps:with([name, version, block_size, + key_file, system, bootloader], State), + PackagerOpts2 = PackagerOpts1#{ + tarball => true, + mbr => ?GRISP2_PARTITIONS + }, + case grisp_update_packager:package(PackageFile, PackagerOpts2) of + ok -> event(State, [{done, PackageFile}]); + {error, Reason} -> event(State, [{error, Reason}]) + end. + +cleanup(State) -> + cleanup_temp_dir(State). + +cleanup_temp_dir(State = #{temp_dir := TempDir, cleanup_temp_dir := true}) -> + {_, State2} = shell(State, "rm -rf '~s'", [TempDir]), + State2#{cleanup_temp_dir => false}; +cleanup_temp_dir(State) -> + State. + + +%--- Internal ------------------------------------------------------------------ + +shell(State, Fmt, Args) -> + Cmd = binary_to_list(iolist_to_binary(io_lib:format(Fmt, Args))), + {{ok, Output}, State2} = grisp_tools_util:shell(State, Cmd), + {Output, State2}. + +is_compressed(Path) -> + Ext = <<".gz">>, + case binary:matches(Path, Ext) of + [{Pos, _Length}] when Pos + byte_size(Ext) =:= byte_size(Path) -> + NoExt = binary:part(Path, 0, byte_size(Path) - byte_size(Ext)), + {true, filename:basename(NoExt)}; + _ -> + {false, filename:basename(Path)} + end. + +maybe_expand(State = #{temp_dir := TempDir}, Path) -> + case is_compressed(Path) of + {false, _} -> {Path, State}; + {true, ExpName} -> + CompName = filename:basename(Path), + State2 = event(State, [{expanding, CompName}]), + TempCompPath = filename:join([TempDir, CompName]), + TempExpPath = filename:join([TempDir, ExpName]), + {_, State2} = shell(State, "cp '~s' '~s'", [Path, TempDir]), + {_, State3} = shell(State2, "gunzip '~s'", [TempCompPath]), + {TempExpPath, State3} + end. + +prepare_output_file(State, Filepath) -> + Force = maps:get(force, State, false), + case file:read_file_info(Filepath) of + {ok, #file_info{}} when Force =:= false -> + event(State, [{error, file_exists, Filepath}]); + {ok, #file_info{type = regular}} when Force =:= true-> + case file:delete(Filepath) of + ok -> State; + {error, _Reason} -> + event(State, [{error, file_access, Filepath}]) + end; + {ok, #file_info{type = regular}} -> + event(State, [{error, not_a_file, Filepath}]); + {error, enoent} -> + State + end.