|
| 1 | +#!/usr/bin/env escript |
| 2 | +%% -*- erlang -*- |
| 3 | + |
| 4 | +%% |
| 5 | +%% %CopyrightBegin% |
| 6 | +%% |
| 7 | +%% Copyright Ericsson AB 1996-2024. 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 | +main(Args) -> |
| 24 | + argparse:run(Args, cli(), #{progname => otp_compliance}). |
| 25 | + |
| 26 | +cli() -> |
| 27 | + #{ help => |
| 28 | + """ |
| 29 | + Run 'scancode' with multiple options |
| 30 | + """, |
| 31 | + arguments => [ scan_option(), |
| 32 | + template_option(), |
| 33 | + prefix_option(), |
| 34 | + file_or_dir() ], |
| 35 | + handler => fun scancode/1}. |
| 36 | + |
| 37 | +approved() -> |
| 38 | + [ "mit", "agpl-3.0", "apache-2.0", "boost-1.0", "llvm-exception", |
| 39 | + "lgpl-2.1-plus", "cc0-1.0", "bsd-simplified", "bsd-new", "pcre", |
| 40 | + "fsf-free", "autoconf-exception-3.0", "mpl-1.1", "public-domain", |
| 41 | + "autoconf-simple-exception", "unicode", "tcl", "gpl-2.0 WITH classpath-exception-2.0", |
| 42 | + "zlib", "lgpl-2.0-plus WITH wxwindows-exception-3.1", "lgpl-2.0-plus", |
| 43 | + "openssl-ssleay", "cc-by-sa-3.0", "cc-by-4.0", "dco-1.1", "fsf-ap", |
| 44 | + "agpl-1.0-plus", "agpl-1.0", "agpl-3.0-plus", "classpath-exception-2.0", |
| 45 | + "ietf-trust"]. |
| 46 | + |
| 47 | +not_approved() -> |
| 48 | + ["gpl", "gpl-3.0-plus", "gpl-2.0", "gpl-1.0-plus", "unlicense", |
| 49 | + "erlangpl-1.1", "gpl-2.0-plus", "null"]. |
| 50 | + |
| 51 | + |
| 52 | +scan_option() -> |
| 53 | + #{name => scan_option, |
| 54 | + type => string, |
| 55 | + default => "cli", |
| 56 | + long => "-scan-option"}. |
| 57 | + |
| 58 | +prefix_option() -> |
| 59 | + #{name => prefix, |
| 60 | + type => string, |
| 61 | + default => "", |
| 62 | + long => "-prefix"}. |
| 63 | + |
| 64 | + |
| 65 | +file_or_dir() -> |
| 66 | + #{name => file_or_dir, |
| 67 | + type => string, |
| 68 | + required => true, |
| 69 | + long => "-file-or-dir"}. |
| 70 | + |
| 71 | +template_option() -> |
| 72 | + #{name => template_path, |
| 73 | + type => string, |
| 74 | + default => "scripts/scan-code/template.txt", |
| 75 | + long => "-template-path"}. |
| 76 | + |
| 77 | + |
| 78 | +scancode(#{ file_or_dir := FilesOrDirs}=Config) -> |
| 79 | + Files = string:split(FilesOrDirs, " ", all), |
| 80 | + Results = lists:foldl(fun (File, Errors) -> |
| 81 | + Command = scancode(Config, File), |
| 82 | + case execute(Command, File) of |
| 83 | + {error, Err} -> |
| 84 | + [Err | Errors]; |
| 85 | + ok -> |
| 86 | + Errors |
| 87 | + end |
| 88 | + end, [], Files), |
| 89 | + case Results of |
| 90 | + [] -> |
| 91 | + ok; |
| 92 | + Errors -> |
| 93 | + error(Errors) |
| 94 | + end. |
| 95 | + |
| 96 | +scancode(#{scan_option := Options, |
| 97 | + prefix := Prefix, |
| 98 | + template_path := TemplatePath}, File) -> |
| 99 | + "scancode -" ++ Options ++ " --custom-output - --custom-template " ++ TemplatePath ++ " " ++ Prefix ++ File. |
| 100 | + |
| 101 | +execute(Command, File) -> |
| 102 | + Port = open_port({spawn, Command}, [stream, in, eof, hide, exit_status]), |
| 103 | + Result = loop(Port, []), |
| 104 | + Ls = string:split(string:trim(Result, both), ",", all), |
| 105 | + |
| 106 | + case lists:filter(fun ([]) -> false; (_) -> true end, Ls) of |
| 107 | + [] -> |
| 108 | + {error, {File, no_license_found}}; |
| 109 | + Ls1 -> |
| 110 | + NotApproved = lists:any(fun (License) -> lists:member(License, not_approved()) end, Ls1), |
| 111 | + case NotApproved of |
| 112 | + true -> |
| 113 | + {error, {File, license_not_approved}}; |
| 114 | + false -> |
| 115 | + InPolicy = lists:all(fun (License) -> lists:member(License, approved()) end, Ls1), |
| 116 | + case InPolicy of |
| 117 | + false -> |
| 118 | + %% this can happen if a license is |
| 119 | + %% not in the approve/not_approved list |
| 120 | + {error, {File, license_not_approved}}; |
| 121 | + true -> |
| 122 | + ok |
| 123 | + end |
| 124 | + end |
| 125 | + end. |
| 126 | + |
| 127 | +loop(Port, Acc) -> |
| 128 | + receive |
| 129 | + {Port, {data, Data}} -> |
| 130 | + loop(Port, [Data|Acc]); |
| 131 | + {Port,{exit_status, _ExitStatus}} -> |
| 132 | + lists:flatten(lists:reverse(Acc)) |
| 133 | + end. |
0 commit comments