1#!/usr/bin/env escript 2%% -*- erlang -*- 3 4%% 5%% %CopyrightBegin% 6%% 7%% Copyright Ericsson AB 2014. All Rights Reserved. 8%% 9%% Licensed under the Apache License, Version 2.0 (the "License"); 10%% you may not use this file except in compliance with the License. 11%% You may obtain a copy of the License at 12%% 13%% http://www.apache.org/licenses/LICENSE-2.0 14%% 15%% Unless required by applicable law or agreed to in writing, software 16%% distributed under the License is distributed on an "AS IS" BASIS, 17%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18%% See the License for the specific language governing permissions and 19%% limitations under the License. 20%% 21%% %CopyrightEnd% 22%% 23 24%%%------------------------------------------------------------------- 25%%% @author Rickard Green <rickard@erlang.org> 26%%% @copyright (C) 2014, Rickard Green 27%%% @doc 28%%% Verify runtime dependencies when patching OTP applications. 29%%% @end 30%%% Created : 4 Mar 2014 by Rickard Green <rickard@erlang.org> 31%%%------------------------------------------------------------------- 32 33-mode(compile). 34 35-export([main/1]). 36 37main(Args) -> 38 {Force, Release, SourceDir, TargetDir, AppList} = parse_args(Args, 39 false, 40 [], 41 [], 42 [], 43 []), 44 SourceAppInfo = read_source_app_info(AppList, SourceDir), 45 AppVsnsTab0 = current_target_app_vsns(TargetDir, Release), 46 AppVsnsTab1 = add_source_app_vsns(SourceAppInfo, AppVsnsTab0), 47 case verify_runtime_deps(SourceAppInfo, AppVsnsTab1, true) of 48 true -> 49 ok; 50 false -> 51 case Force of 52 true -> 53 warn("Your OTP development system was updated with " 54 "unfulfilled runtime dependencies. The system " 55 "may not be working as expected.", []); 56 false -> 57 err("Unfulfilled runtime dependencies. " 58 "See warnings above.~n", []) 59 end 60 end, 61 halt(0). 62 63parse_args(["-force" | Args], _, Release, SourceDir, TargetDir, Apps) -> 64 parse_args(Args, true, Release, SourceDir, TargetDir, Apps); 65parse_args(["-release", Release | Args], Force, _, SourceDir, TargetDir, Apps) -> 66 parse_args(Args, Force, Release, SourceDir, TargetDir, Apps); 67parse_args(["-source", SourceDir | Args], Force, Release, _, TargetDir, Apps) -> 68 parse_args(Args, Force, Release, SourceDir, TargetDir, Apps); 69parse_args(["-target", TargetDir | Args], Force, Release, SourceDir, _, Apps) -> 70 parse_args(Args, Force, Release, SourceDir, TargetDir, Apps); 71parse_args([App | Args], Force, Release, SourceDir, TargetDir, OldApps) -> 72 parse_args(Args, Force, Release, SourceDir, TargetDir, [App | OldApps]); 73parse_args([], _, [], _, _, _) -> 74 err("Missing release~n", []); 75parse_args([], _, _, [], _, _) -> 76 err("Missing source directory~n", []); 77parse_args([], _, _, _, [], _) -> 78 err("Missing target directory~n", []); 79parse_args([], _, _, _, _, []) -> 80 err("Missing applications~n"); 81parse_args([], Force, Release, SourceDir, TargetDir, Apps) -> 82 {Force, Release, SourceDir, TargetDir, Apps}. 83 84 85%warn(Format) -> 86% warn(Format, []). 87 88warn(Format, Args) -> 89 io:format(standard_error, "WARNING: " ++ Format, Args). 90 91err(Format) -> 92 err(Format, []). 93 94err(Format, Args) -> 95 io:format(standard_error, "ERROR: " ++ Format, Args), 96 halt(1). 97 98read_file(FileName) -> 99 case file:read_file(FileName) of 100 {ok, Content} -> 101 binary_to_list(Content); 102 {error, Error} -> 103 err("Failed to read ~s: ~p~n", [FileName, Error]) 104 end. 105 106consult_file(FileName) -> 107 case file:consult(FileName) of 108 {ok, Terms} -> 109 Terms; 110 {error, Error} -> 111 err("Failed to consult ~s: ~p~n", [FileName, Error]) 112 end. 113 114current_target_app_vsns(TargetDir, Release) -> 115 IAV = read_file(filename:join([TargetDir, "releases", Release, 116 "installed_application_versions"])), 117 DirList = string:tokens(IAV, "\n\r\t "), 118 LibDir = filename:join(TargetDir, "lib"), 119 make_app_vsns_tab(DirList, LibDir, gb_trees:empty()). 120 121make_app_vsns_tab([], _LibDir, GBT) -> 122 GBT; 123make_app_vsns_tab([AppVer | AppVsns], LibDir, GBT0) -> 124 GBT1 = try 125 case file:read_file_info(filename:join(LibDir, AppVer)) of 126 {ok, _FInfo} -> 127 [App, Vsn] = string:tokens(AppVer, "-"), 128 add_app_vsn(App, Vsn, GBT0); 129 _ -> 130 GBT0 131 end 132 catch 133 _:_ -> 134 warn("Unexpected directory: ~p~n", 135 [filename:join(LibDir, AppVer)]), 136 GBT0 137 end, 138 make_app_vsns_tab(AppVsns, LibDir, GBT1). 139 140add_app_vsn(App, VsnList, GBT) when is_atom(App) -> 141 Vsn = parse_vsn(VsnList), 142 case gb_trees:lookup(App, GBT) of 143 none -> 144 gb_trees:insert(App, [Vsn], GBT); 145 {value, Vsns} -> 146 gb_trees:update(App, [Vsn | Vsns], GBT) 147 end; 148add_app_vsn(AppStr, VsnList, GBT) -> 149 add_app_vsn(list_to_atom(AppStr), VsnList, GBT). 150 151add_source_app_vsns([], AppVsnsTab) -> 152 AppVsnsTab; 153add_source_app_vsns([{App, Vsn, _IReqs} | AI], AppVsnsTab) -> 154 add_source_app_vsns(AI, add_app_vsn(App, Vsn, AppVsnsTab)). 155 156read_source_app_info([], _SourceDir) -> 157 []; 158read_source_app_info([App | Apps], SourceDir) -> 159 AppFile = case App of 160 "erts" -> 161 filename:join([SourceDir, "erts", "preloaded", "ebin", 162 "erts.app"]); 163 _ -> 164 filename:join([SourceDir, "lib", App, "ebin", 165 App ++ ".app"]) 166 end, 167 AppAtom = list_to_atom(App), 168 case consult_file(AppFile) of 169 [{application, AppAtom, InfoList}] -> 170 Vsn = case lists:keyfind(vsn, 1, InfoList) of 171 {vsn, V} -> 172 V; 173 _ -> 174 err("Missing vsn in ~p~n", AppFile) 175 end, 176 AI = case lists:keyfind(runtime_dependencies, 1, InfoList) of 177 {runtime_dependencies, IReqs} -> 178 case parse_inst_reqs(IReqs) of 179 error -> 180 err("Failed to parse runtime_dependencies in ~p~n", 181 [AppFile]); 182 ParsedIReqs -> 183 {AppAtom, Vsn, ParsedIReqs} 184 end; 185 _ -> 186 {AppAtom, Vsn, []} 187 end, 188 [AI | read_source_app_info(Apps, SourceDir)]; 189 _ -> 190 err("Failed to parse ~p~n", [AppFile]) 191 end. 192 193parse_vsn(VsnStr) -> 194 list_to_tuple(lists:map(fun (IL) -> 195 list_to_integer(IL) 196 end, string:tokens(VsnStr, "."))). 197 198parse_inst_reqs(InstReqs) -> 199 try 200 parse_inst_reqs_aux(InstReqs) 201 catch 202 _ : _ -> 203 error 204 end. 205 206parse_inst_reqs_aux([]) -> 207 []; 208parse_inst_reqs_aux([IR | IRs]) -> 209 [App, VsnStr] = string:tokens(IR, "-"), 210 [{list_to_atom(App), parse_vsn(VsnStr)} | parse_inst_reqs_aux(IRs)]. 211 212make_app_vsn_str({App, VsnTup}) -> 213 make_app_vsn_str(tuple_to_list(VsnTup), [atom_to_list(App), $-]). 214 215make_app_vsn_str([I], Acc) -> 216 lists:flatten([Acc, integer_to_list(I)]); 217make_app_vsn_str([I | Is], Acc) -> 218 make_app_vsn_str(Is, [Acc, integer_to_list(I), $.]). 219 220missing_min_req(App, AppVsn, IReq) -> 221 warn("Unfulfilled runtime dependency for application ~p-~s: ~s~n", 222 [App, AppVsn, make_app_vsn_str(IReq)]). 223 224verify_runtime_deps([], _AppVsnsTab, Res) -> 225 Res; 226verify_runtime_deps([{App, Vsn, IReqs} | SAIs], AppVsnsTab, Res0) -> 227 Res = lists:foldl( 228 fun ({IRApp, IRMinVsn} = InstReq, AccRes) -> 229 case gb_trees:lookup(IRApp, AppVsnsTab) of 230 none -> 231 missing_min_req(App, Vsn, InstReq), 232 false; 233 {value, AppVsns} -> 234 try 235 lists:foreach( 236 fun (AppVsn) -> 237 case meets_min_req(AppVsn, IRMinVsn) of 238 true -> 239 throw(true); 240 false -> 241 false 242 end 243 end, 244 AppVsns), 245 missing_min_req(App, Vsn, InstReq), 246 false 247 catch 248 throw : true -> 249 AccRes 250 end 251 end 252 end, 253 Res0, 254 IReqs), 255 verify_runtime_deps(SAIs, AppVsnsTab, Res). 256 257meets_min_req(Vsn, Vsn) -> 258 true; 259meets_min_req({X}, VsnReq) -> 260 meets_min_req({X, 0, 0}, VsnReq); 261meets_min_req({X, Y}, VsnReq) -> 262 meets_min_req({X, Y, 0}, VsnReq); 263meets_min_req(Vsn, {X}) -> 264 meets_min_req(Vsn, {X, 0, 0}); 265meets_min_req(Vsn, {X, Y}) -> 266 meets_min_req(Vsn, {X, Y, 0}); 267meets_min_req({X, _Y, _Z}, {XReq, _YReq, _ZReq}) when X > XReq -> 268 true; 269meets_min_req({X, Y, _Z}, {X, YReq, _ZReq}) when Y > YReq -> 270 true; 271meets_min_req({X, Y, Z}, {X, Y, ZReq}) when Z > ZReq -> 272 true; 273meets_min_req({_X, _Y, _Z}, {_XReq, _YReq, _ZReq}) -> 274 false; 275meets_min_req(Vsn, VsnReq) -> 276 gp_meets_min_req(mk_gp_vsn_list(Vsn), mk_gp_vsn_list(VsnReq)). 277 278gp_meets_min_req([X, Y, Z | _Vs], [X, Y, Z]) -> 279 true; 280gp_meets_min_req([X, Y, Z | _Vs], [XReq, YReq, ZReq]) -> 281 meets_min_req({X, Y, Z}, {XReq, YReq, ZReq}); 282gp_meets_min_req([X, Y, Z | Vs], [X, Y, Z | VReqs]) -> 283 gp_meets_min_req_tail(Vs, VReqs); 284gp_meets_min_req(_Vsn, _VReq) -> 285 %% Versions on different version branches, i.e., the minimum 286 %% required functionality is not included in Vsn. 287 false. 288 289gp_meets_min_req_tail([V | Vs], [V | VReqs]) -> 290 gp_meets_min_req_tail(Vs, VReqs); 291gp_meets_min_req_tail([], []) -> 292 true; 293gp_meets_min_req_tail([_V | _Vs], []) -> 294 true; 295gp_meets_min_req_tail([V | _Vs], [VReq]) when V > VReq -> 296 true; 297gp_meets_min_req_tail(_Vs, _VReqs) -> 298 %% Versions on different version branches, i.e., the minimum 299 %% required functionality is not included in Vsn. 300 false. 301 302mk_gp_vsn_list(Vsn) -> 303 [X, Y, Z | Tail] = tuple_to_list(Vsn), 304 [X, Y, Z | remove_trailing_zeroes(Tail)]. 305 306remove_trailing_zeroes([]) -> 307 []; 308remove_trailing_zeroes([0 | Vs]) -> 309 case remove_trailing_zeroes(Vs) of 310 [] -> []; 311 NewVs -> [0 | NewVs] 312 end; 313remove_trailing_zeroes([V | Vs]) -> 314 [V | remove_trailing_zeroes(Vs)]. 315