Skip to content

Commit

Permalink
github: add license compliance checks per PR
Browse files Browse the repository at this point in the history
  • Loading branch information
kikofernandez committed Nov 13, 2024
1 parent 1f800db commit 9b28ef1
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ jobs:
changes: ${{ steps.changes.outputs.changes }}
c-code-changes: ${{ steps.c-code-changes.outputs.changes }}
all: ${{ steps.apps.outputs.all }}
added_files: ${{ steps.cache.outputs.added_files }}
steps:
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # ratchet:actions/[email protected]
- uses: ./.github/actions/build-base-image
Expand Down Expand Up @@ -112,6 +113,8 @@ jobs:
- deleted: '**/*.h'
deleted:
- deleted: '**'
added:
- added: '**'
bootstrap:
- 'bootstrap/**'
configure:
Expand Down Expand Up @@ -467,6 +470,9 @@ jobs:
- name: Run dialyzer
run: docker run -v $PWD/:/github otp '/github/scripts/run-dialyzer'

- name: Run Scancode
run: docker run -v $PWD/:/github otp 'pip install scancode-toolkit==32.2.1 && /github/scripts/scan-code.escript scancode --file-or-dir "${{ needs.pack.outputs.added_files }}" --template-path /github/scripts/scan-code/template.txt'

test:
name: Test Erlang/OTP
runs-on: ubuntu-latest
Expand Down
125 changes: 125 additions & 0 deletions scripts/scan-code.escript
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#!/usr/bin/env escript
%% -*- erlang -*-

%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1996-2024. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%

main(Args) ->
argparse:run(Args, cli(), #{progname => otp_compliance}).

cli() ->
#{ help =>
"""
Run 'scancode' with multiple options
""",
arguments => [ scan_option(),
template_option(),
file_or_dir() ],
handler => fun scancode/1}.
%% #{ commands =>
%% #{"scancode" =>
%% }.

approved() ->
[ "mit", "agpl-3.0", "apache-2.0", "boost-1.0", "llvm-exception",
"lgpl-2.1-plus", "cc0-1.0", "bsd-simplified", "bsd-new", "pcre",
"fsf-free", "autoconf-exception-3.0", "mpl-1.1", "public-domain",
"autoconf-simple-exception", "unicode", "tcl", "gpl-2.0 WITH classpath-exception-2.0",
"zlib", "lgpl-2.0-plus WITH wxwindows-exception-3.1", "lgpl-2.0-plus",
"openssl-ssleay", "cc-by-sa-3.0", "cc-by-4.0", "dco-1.1", "fsf-ap",
"agpl-1.0-plus", "agpl-1.0", "agpl-3.0-plus", "classpath-exception-2.0",
"ietf-trust"].

not_approved() ->
["gpl", "gpl-3.0-plus", "gpl-2.0", "gpl-1.0-plus", "unlicense",
"erlangpl-1.1", "gpl-2.0-plus", "null"].


scan_option() ->
#{name => scan_option,
type => string,
default => "cli",
long => "-scan-option"}.

file_or_dir() ->
#{name => file_or_dir,
type => string,
required => true,
long => "-file-or-dir"}.

template_option() ->
#{name => template_path,
type => string,
default => "scripts/scan-code/template.txt",
long => "-template-path"}.


scancode(#{file_or_dir := FilesOrDirs}=Config) ->
Files = string:split(FilesOrDirs, " ", all),
Results = lists:foldl(fun (File, Errors) ->
case validate(Config, File) of
{error, Err} ->
[Err | Errors];
_ ->
Errors
end
end, [], Files),
case Results of
[] ->
ok;
Errors ->
error(Errors)
end.

validate(#{scan_option := Options,
template_path := TemplatePath}, File) ->
Command = "scancode -" ++ Options ++ " --custom-output - --custom-template " ++ TemplatePath ++ " " ++ File,
Port = open_port({spawn, Command}, [stream, in, eof, hide, exit_status]),
Result = loop(Port, []),
Ls = string:split(string:trim(Result, both), ",", all),

case lists:filter(fun ([]) -> false; (_) -> true end, Ls) of
[] ->
{error, {File, no_license_found}};
Ls1 ->
NotApproved = lists:any(fun (License) -> lists:member(License, not_approved()) end, Ls1),
case NotApproved of
true ->
{error, {File, license_not_approved}};
false ->
InPolicy = lists:all(fun (License) -> lists:member(License, approved()) end, Ls1),
case InPolicy of
false ->
%% this can happen if a license is
%% not in the approve/not_approved list
{error, {File, license_not_approved}};
true ->
ok
end
end
end.

loop(Port, Acc) ->
receive
{Port, {data, Data}} ->
loop(Port, [Data|Acc]);
{Port,{exit_status, ExitStatus}} ->
0 = ExitStatus,
lists:flatten(lists:reverse(Acc))
end.
7 changes: 7 additions & 0 deletions scripts/scan-code/template.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{% if files.license_copyright %}
{%- for location, data in files.license_copyright.items() %}
{%- for row in data %}
{%- if row.what == 'license'%}{{ row.value|escape }},{% endif %}
{%- endfor %}
{%- endfor %}
{% endif %}

0 comments on commit 9b28ef1

Please sign in to comment.