File 0300-gh-Add-SBOM-generation.patch of Package erlang
From a8d83001c586295db7730f9f080521b67c2e49d0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Lukas=20Backstr=C3=B6m?= <lukas@erlang.org>
Date: Mon, 3 Feb 2025 14:24:07 +0100
Subject: [PATCH 2/2] gh: Add SBOM generation
Co-authored-by: Kiko Fernandez-Reyes <kiko@erlang.org>
---
.github/scripts/otp-compliance.es | 726 +++++++++++++++++++++++++
.github/workflows/main.yaml | 121 ++++-
.ort.yml | 848 ++++++++++++++++++++++++++++++
.ort/config/config.yml | 47 ++
4 files changed, 1741 insertions(+), 1 deletion(-)
create mode 100755 .github/scripts/otp-compliance.es
create mode 100644 .ort.yml
create mode 100644 .ort/config/config.yml
diff --git a/.github/scripts/otp-compliance.es b/.github/scripts/otp-compliance.es
new file mode 100755
index 0000000000..a4493a6d71
--- /dev/null
+++ b/.github/scripts/otp-compliance.es
@@ -0,0 +1,726 @@
+#!/usr/bin/env escript
+%% -*- erlang -*-
+
+%% SPDX-License-Identifier: Apache-2.0
+%% SPDX-FileCopyrightText: 2024 Erlang/OTP and its contributors
+
+-define(default_classified_result, "scan-result-classified.json").
+-define(default_scan_result, "scan-result.json").
+-define(diff_classified_result, "scan-result-diff.json").
+-define(license_ref_name, "LicenseRef-NOASSERTION").
+-define(license_ref_copyright, "Erlang/OTP contributors").
+
+
+%%
+%% Commands
+%%
+%% sbom
+%%
+%% otp-info: given an oss-review-toolkit (ORT) scan result and a
+%% source SBOM, it populates the fields that ORT can't
+%% in Unmanaged projects.
+%%
+%% fix-licenses: fixes licenses from NOASSERTION to Apache-2.0
+%% for files that existed since OTP-18.
+%%
+%% compliance useful for CI/CD compliance checks.
+%%
+%% detect: given a scan-result from ORT, it detects files without license
+%% and writes them into disk.
+%%
+%% check: given a recent scan-result from ORT (possibly from PR), and an
+%% existing file with known files without licenses (from prev. commit),
+%% calculate if new files without licenses have been added to the repo.
+%%
+%% explore
+%%
+%% classify: takes as input a scan of ort and returns a json file containing
+%% as keys the licenses and as values the files under those licenses.
+%%
+%% diff: performs a diff of existing classification file against
+%% other classification files. this is useful to guarantee that
+%% files that had license X had not unexpectedly been reported differently.
+%%
+
+%%
+%% USE OF COMMANDS
+%%
+%% The commands `classify` and `diff` are useful for exploring the licenses.
+%% ORT does not report in an easy way which files have been attached to which licenses,
+%% unless one generates a report. At the time, we cannot generate an SBOM,
+%% so we are in the dark.
+%%
+%% The commands `detect` and `check` can be used in CI/CD to
+%% prevent entering new files with unknown license. In the normal case,
+%% the `detect` command only needs to be issued once in the repo.
+%% Once we keep track of this file, the command is not needed anymore,
+%% as the list of files with no license should not grow, and only
+%% the `check` command should be executed in the CI/CD.
+%%
+%%
+
+main(Args) ->
+ argparse:run(Args, cli(), #{progname => otp_compliance}).
+
+cli() ->
+ #{ commands =>
+ #{"sbom" =>
+ #{ help => """
+ Contains useful commands to fix an ORT generated source SBOM.
+
+ """,
+ commands =>
+ #{"otp-info" =>
+ #{ help =>
+ """
+ Adds information missing in ORT's Erlang/OTP source SBOM
+ - Add homepage
+ - Fixes license of `*.beam` files
+ - Fixes project name
+
+ """,
+ arguments => [ sbom_option(),
+ input_option() ],
+ handler => fun sbom_otp/1}}},
+ "explore" =>
+ #{ help => """
+ Explore license data.
+ Useful to figure out the mapping files-to-licenses.
+
+ """,
+ commands =>
+ #{"classify-license" =>
+ #{ help =>
+ """
+ Classify files by their license group.
+ - Input file expects a scan-result from ORT.
+ - Output file shows mapping between licenses and files.
+ The output file can be fed to the `explore diff` command.
+
+ """,
+ arguments => [ input_option(?default_scan_result),
+ output_option(?default_classified_result),
+ apply_excludes(),
+ apply_curations() ],
+ handler => fun classify_license/1},
+ "classify-license-copyright" =>
+ #{ help =>
+ """
+ Pair files with their copyright and license.
+ Depends on a `scan-result.json` and the output of the `classify-license`.
+
+ """,
+ arguments => [ input_option(?default_scan_result),
+ base_file(?default_classified_result),
+ output_option() ],
+ handler => fun classify_path_license_copyright/1},
+
+ "reuse-gen-toml" =>
+ #{ help =>
+ """
+ Generates a TOML file based on the 'classify-license-copyright' results.
+ Files with no license are given LicenseRef-UnknownLicense.txt.
+
+ """,
+ arguments => [input_option()],
+ handler => fun reuse_gen_toml/1},
+ "diff" =>
+ #{ help =>
+ """
+ Compare against previous license results.
+ - Input file should be the output of the `classify` command for input and base files.
+ - Output returns a summary of additions and deletions per license.
+
+ """,
+ arguments => [ input_option(?default_classified_result),
+ base_file(),
+ output_option(?diff_classified_result) ],
+ handler => fun diff/1}
+ }
+ },
+ "compliance" =>
+ #{ help => """
+ Commands to enforce compliance policy towards unlicensed files.
+
+ """,
+ commands =>
+ #{"detect" =>
+ #{ help =>
+ """
+ Detects unlicensed files.
+ - Input file expects a scan-result from ORT.
+ - Output file is a list of files without license.
+ The output file can be fed to the `compliance check` command.
+
+ """,
+ arguments => [ input_option(?default_scan_result),
+ output_option(),
+ apply_excludes() ],
+ handler => fun detect_no_license/1},
+ "check" =>
+ #{ help =>
+ """
+ Checks that no new unlicensed files have been added.
+ - Input file expects scan-result from ORT.
+ - Base file expects output file from `no_license` command.
+
+ """,
+ arguments => [ input_option(?default_scan_result),
+ base_file(),
+ apply_excludes(),
+ output_option() ],
+ handler => fun check_no_license/1}}}}}.
+
+%%
+%% Options
+%%
+input_option() ->
+ #{name => input_file,
+ type => binary,
+ long => "-input-file"}.
+
+
+input_option(Default) ->
+ (input_option())#{default => Default}.
+
+sbom_option() ->
+ #{name => sbom_file,
+ type => binary,
+ default => "bom.spdx.json",
+ long => "-sbom-file"}.
+
+output_option(Default) ->
+ #{name => output_file,
+ type => binary,
+ default => Default,
+ long => "-output-file"}.
+
+output_option() ->
+ #{name => output_file,
+ type => binary,
+ required => true,
+ long => "-output-file"}.
+
+apply_excludes() ->
+ #{name => exclude,
+ type => boolean,
+ short => $e,
+ default => true,
+ long => "-apply-excludes"}.
+
+apply_curations() ->
+ #{name => curations,
+ type => boolean,
+ short => $c,
+ default => true,
+ long => "-apply-curations"}.
+
+base_file() ->
+ #{name => base_file,
+ type => binary,
+ long => "-base-file"}.
+base_file(DefaultFile) ->
+ #{name => base_file,
+ type => binary,
+ default => DefaultFile,
+ long => "-base-file"}.
+
+
+%%
+%% Commands
+%%
+
+sbom_otp(#{sbom_file := SbomFile}=Input) ->
+ Sbom = decode(SbomFile),
+
+ Licenses = path_to_license(Input),
+ Copyrights = path_to_copyright(Input, Licenses),
+ Fixes = sbom_fixes(Licenses, Copyrights),
+ Spdx = execute_sbom_fixes(Sbom, Fixes),
+ file:write_file(SbomFile, json:encode(Spdx)).
+
+execute_sbom_fixes(Sbom, Fixes) ->
+ lists:foldl(fun ({Fun, Data}, Acc) -> Fun(Data, Acc) end, Sbom, Fixes).
+
+sbom_fixes(Licenses, Copyrights) ->
+ [{fun fix_name/2, ~"Erlang/OTP"},
+ {fun fix_creators_tooling/2, ~"Tool: otp_compliance"},
+ {fun fix_supplier/2, ~"Organization: Ericsson AB"},
+ {fun fix_download_location/2, ~"https://github.com/erlang/otp/releases"},
+ {fun fix_beam_licenses/2, {Licenses, Copyrights}} ].
+
+
+fix_name(Name, Sbom) ->
+ Sbom#{ ~"name" := Name}.
+
+fix_creators_tooling(Tool, #{ ~"creationInfo" := #{~"creators" := [ORT | _]}=Creators}=Sbom) ->
+ SHA = list_to_binary(string:trim(".sha." ++ os:cmd("git rev-parse HEAD"))),
+ Sbom#{~"creationInfo" := Creators#{ ~"creators" := [ORT, <<Tool/binary, SHA/binary>>]}}.
+
+fix_supplier(Name, #{~"packages" := [ Packages ] }=Sbom) ->
+ Sbom#{~"packages" := [maps:update_with(~"supplier", fun(_) -> Name end, Name, Packages)]}.
+
+
+fix_download_location(Url, #{~"packages" := [ Packages ] }=Sbom) ->
+ Packages1 = Packages#{~"downloadLocation" := Url },
+ Sbom#{~"packages" := [ Packages1 ]}.
+
+%% re-populate licenses to .beam files from their .erl files
+%% e.g., the lists.beam file should have the same license as lists.erl
+fix_beam_licenses(LicensesAndCopyrights,
+ #{ ~"packages" := [Package],
+ ~"files" := Files}=Sbom) ->
+ Package1 = Package#{ ~"homepage" := ~"https://www.erlang.org",
+ ~"licenseConcluded" := ~"Apache-2.0"},
+ Files1= lists:map(
+ fun (SPDX) ->
+ %% Adds license and copyright from .erl or .hrl file to its .beam equivalent
+ case SPDX of
+ #{~"fileName" := <<"lib/stdlib/uc_spec/", _Filename/binary>>,
+ ~"licenseInfoInFiles" := [License]} when License =/= ~"NONE", License =/= ~"NOASSERTION"->
+ files_have_no_license(SPDX#{~"licenseConcluded" := License});
+
+ #{~"fileName" := ~"bootstrap/lib/stdlib/ebin/erl_parse.beam"} ->
+ %% beam file auto-generated from grammar file
+ files_have_no_license(fix_beam_spdx_license(~"lib/stdlib/src/erl_parse.yrl", LicensesAndCopyrights, SPDX));
+
+ #{~"fileName" := ~"bootstrap/lib/stdlib/ebin/unicode_util.beam"} ->
+ %% follows from otp/lib/stdlib/uc_spec/README-UPDATE.txt
+ files_have_no_license(SPDX#{~"licenseConcluded" := ~"Unicode-3.0 AND Apache-2.0"});
+
+ #{~"fileName" := <<"erts/emulator/internal_doc/",Filename/binary>>} ->
+ case binary:split(Filename, ~".md") of
+ [_File, _Ext] ->
+ SPDX#{~"licenseConcluded" := ~"Apache-2.0"};
+ _ ->
+ SPDX
+ end;
+
+ #{~"fileName" := Filename} ->
+ case bootstrap_mappings(Filename) of
+ {error, not_beam_file} ->
+ fix_spdx_license(SPDX);
+ {Path, Filename1} ->
+ case binary:split(Filename1, ~".beam") of
+ [File, _] ->
+ files_have_no_license(fix_beam_spdx_license(Path, File, LicensesAndCopyrights, SPDX));
+ _ ->
+ SPDX
+ end
+ end
+ end
+ end, Files),
+ Sbom#{ ~"files" := Files1, ~"packages" := [Package1]}.
+
+bootstrap_mappings(<<"bootstrap/lib/compiler/ebin/", Filename/binary>>) -> {~"lib/compiler/src/", Filename};
+bootstrap_mappings(<<"bootstrap/lib/kernel/ebin/",Filename/binary>>) -> {<<"lib/kernel/src/">>, Filename};
+bootstrap_mappings(<<"bootstrap/lib/kernel/include/",Filename/binary>>) -> {<<"lib/kernel/include/">>, Filename};
+bootstrap_mappings(<<"bootstrap/lib/stdlib/ebin/",Filename/binary>>) -> {<<"lib/stdlib/src/">>, Filename};
+bootstrap_mappings(<<"erts/preloaded/ebin/",Filename/binary>>) -> {<<"erts/preloaded/src/">>, Filename};
+bootstrap_mappings(_Other) ->
+ {error, not_beam_file}.
+
+%% fixes spdx license of beam files
+fix_beam_spdx_license(Path, {Licenses, Copyrights}, SPDX) ->
+ License = maps:get(Path, Licenses, ~"NOASSERTION"),
+ Copyright = maps:get(Path, Copyrights, ~"NOASSERTION"),
+ fix_spdx_license(SPDX#{ ~"copyrightText" := Copyright, ~"licenseConcluded" := License }).
+
+fix_beam_spdx_license(Path, File, LicensesAndCopyrights, SPDX) when is_binary(Path),
+ is_binary(File) ->
+ Spdx0 = fix_beam_spdx_license(<<Path/binary, File/binary, ".erl">>, LicensesAndCopyrights, SPDX),
+ case maps:get(~"licenseConcluded", Spdx0) of
+ ~"NOASSERTION" ->
+ fix_beam_spdx_license(<<Path/binary, File/binary, ".hrl">>, LicensesAndCopyrights, Spdx0);
+ _ ->
+ Spdx0
+ end.
+
+files_have_no_license(Spdx) ->
+ Spdx#{~"licenseInfoInFiles" := [~"NONE"]}.
+
+none_to_noassertion(~"NONE") ->
+ ~"NOASSERTION";
+none_to_noassertion(X) ->
+ X.
+
+%% TODO: check which license curations have actually licenses in files, and which ones
+%% are added to annotate a file with a license. this latter should not be a curation
+%% in ORT, but in this script.
+%% fixes spdx license of non-beam files
+fix_spdx_license(#{~"licenseInfoInFiles" := [LicenseInFile],
+ ~"licenseConcluded" := License,
+ ~"copyrightText" := C}=SPDX) ->
+ License1 = case License of
+ ~"NONE" -> LicenseInFile;
+ ~"NOASSERTION" -> LicenseInFile;
+ Other -> Other
+ end,
+ SPDX#{ ~"licenseConcluded" := none_to_noassertion(License1),
+ ~"copyrightText" := none_to_noassertion(C)
+ };
+fix_spdx_license(#{~"copyrightText" := C}=SPDX) ->
+ SPDX#{ ~"copyrightText" := none_to_noassertion(C)}.
+
+%% Given an input file, returns a mapping of
+%% #{filepath => license} for each file path towards its license.
+path_to_license(Input) ->
+ ClassifyInput = Input#{ exclude => true,
+ curations => false},
+ ClassifyLicense = group_by_licenses(ClassifyInput),
+ maps:fold(fun (K, Vs, Acc) ->
+ maps:merge(maps:from_keys(Vs, K), Acc)
+ end, #{}, ClassifyLicense).
+
+path_to_copyright(Input, _Licenses) ->
+ ClassifyInput = Input#{ exclude => true},
+ ClassifyCopyright = group_by_copyrights(ClassifyInput),
+ maps:fold(fun (K, Vs, Acc) ->
+ maps:merge(maps:from_keys(Vs, K), Acc)
+ end, #{}, ClassifyCopyright).
+
+classify_license(#{output_file := Output}=Input) ->
+ R = group_by_licenses(Input),
+ ok = file:write_file(Output, json:encode(R)).
+
+classify_path_license_copyright(#{output_file := Output,
+ input_file := Filename,
+ base_file := LicenseFileGroup}) ->
+ Copyrights = classify_copyright_result(Filename),
+ Licenses = expand_license_result(LicenseFileGroup),
+ Files = lists:sort(lists:uniq(maps:keys(Copyrights) ++ maps:keys(Licenses))),
+ X = lists:foldl(fun (Path, Acc) ->
+ Copyright = maps:get(Path, Copyrights, ~"NONE"),
+ License = maps:get(Path, Licenses, ~"NONE"),
+ Acc#{Path => #{ ~"Copyright" => Copyright, ~"License" => License}}
+ end, #{}, Files),
+ ok = file:write_file(Output, json:encode(X)).
+
+expand_license_result(Filename) ->
+ Json = decode(Filename),
+ maps:fold(fun (License, Paths, Acc) ->
+ maps:merge(Acc, maps:from_list([{Path, License} || Path <- Paths]))
+ end, #{}, Json).
+
+classify_copyright_result(Filename) ->
+ Json = decode(Filename),
+ Copyrights = copyrights(scan_results(Json)),
+ lists:foldl(fun (Copyright, Acc) ->
+ #{<<"statement">> := CopyrightSt, <<"location">> := Location} = Copyright,
+ #{<<"path">> := Path, <<"start_line">> := _StartLine, <<"end_line">> := _EndLine} = Location,
+ Acc#{Path => CopyrightSt}
+ end, #{}, Copyrights).
+
+reuse_gen_toml(#{input_file := Input}) ->
+ #{~"files" := Files} = decode(Input),
+ GitIgnore = lists:foldl(fun (Path, Acc) ->
+ add_annotation(Path, ?license_ref_name, ?license_ref_copyright) ++ Acc
+ end, "", gitignore_files()),
+ Result = lists:foldl(fun(#{~"copyrightText" := C, ~"licenseConcluded" := L, ~"fileName" := Path}, Acc) ->
+ {LicenseString, CopyrightString}=
+ case L of
+ ~"NONE" ->
+ {?license_ref_name, "NOASSERTION"};
+ ~"NOASSERTION" ->
+ {?license_ref_name, "NOASSERTION"};
+ _ ->
+ case C of
+ _ when C == ~"NOASSERTION"; C=="NONE" ->
+ {L, ?license_ref_copyright};
+ _ ->
+ {L, C}
+ end
+
+ end,
+ add_annotation(Path, LicenseString, CopyrightString) ++ Acc
+ end, GitIgnore, Files),
+ TOML = "version = 1\n\n" ++ Result,
+ io:format("~ts", [TOML]).
+
+
+add_annotation(Path, License, Copyright) ->
+ LicenseId = io_lib:format("SPDX-License-Identifier = \"~ts\"\n", [License]),
+ CopyrightId = io_lib:format("SPDX-FileCopyrightText = ~p\n", [conversion_from_bin(string:split(Copyright, "\n", all))]),
+ io_lib:format("[[annotations]]\npath = \"~ts\"\n~s~s\n",
+ [Path, LicenseId, CopyrightId]).
+
+conversion_from_bin([]) -> [];
+conversion_from_bin([Bin | Ls]) when is_binary(Bin) ->
+ [erlang:binary_to_list(Bin) | conversion_from_bin(Ls)];
+conversion_from_bin([NotBin | Ls]) when is_list(NotBin) ->
+ [NotBin | conversion_from_bin(Ls)].
+
+
+group_by_licenses(#{input_file := Filename,
+ exclude := ApplyExclude,
+ curations := ApplyCuration}) ->
+ Json = decode(Filename),
+ Excludes = apply_excludes(Json, ApplyExclude),
+ Curations = apply_curations(Json, ApplyCuration),
+
+ Licenses = licenses(scan_results(Json)),
+ lists:foldl(fun (License, Acc) ->
+ group_by_license(Excludes, Curations, License, Acc)
+ end, #{}, Licenses).
+
+group_by_copyrights(#{input_file := Filename,
+ exclude := ApplyExclude}) ->
+ Json = decode(Filename),
+ Excludes = apply_excludes(Json, ApplyExclude),
+ Copyrights = copyrights(scan_results(Json)),
+ lists:foldl(fun (Copyright, Acc) ->
+ group_by_copyright(Excludes, Copyright, Acc)
+ end, #{}, Copyrights).
+
+
+apply_excludes(Json, ApplyExclude) ->
+ onlyif([], ApplyExclude, fun () -> convert_excludes(excludes(Json)) end).
+
+apply_curations(Json, ApplyCuration) ->
+ onlyif([], ApplyCuration, fun () -> curations(Json) end).
+
+diff(#{input_file := InputFile, base_file := BaseFile, output_file := Output}) ->
+ Input = decode(InputFile),
+ Base = decode(BaseFile),
+ KeyList = maps:keys(Input) ++ maps:keys(Base),
+ KeySet = sets:from_list(KeyList),
+ Data = sets:fold(fun(Key, Acc) -> set_difference(Key, Input, Base, Acc) end, #{}, KeySet),
+ file:write_file(Output, json:encode(Data)).
+
+detect_no_license(#{input_file := InputFile,
+ output_file := OutputFile,
+ exclude := ApplyExcludes}) ->
+ Input = decode(InputFile),
+ SortedResult = compute_unlicense_files(Input, ApplyExcludes),
+ file:write_file(OutputFile, json:encode(SortedResult)).
+
+compute_unlicense_files(Input, ApplyExcludes) ->
+ Licenses = licenses(scan_results(Input)),
+
+ PathsWithLicense =
+ lists:foldl(fun (#{<<"location">> := #{<<"path">> := Path}}, Acc) ->
+ sets:add_element(Path, Acc)
+ end, sets:new(), Licenses),
+
+ %% Get all files, incluiding those without license
+ Files = files_from_scanner(Input),
+ AllPaths =
+ lists:foldl(fun (#{<<"path">> := Path}, Acc) ->
+ sets:add_element(Path, Acc)
+ end, sets:new(), Files),
+
+ %% Paths without license
+ PathsWithoutLicense = sets:to_list(sets:subtract(AllPaths, PathsWithLicense)),
+
+ %% Excluded files that should be ignored
+ Excludes = excludes(Input),
+ ExcludeRegex = onlyif([], ApplyExcludes, fun () -> convert_excludes(Excludes) end),
+ Result = lists:foldl(fun(Path, Acc) ->
+ case exclude_path(Path, ExcludeRegex) of
+ true ->
+ Acc;
+ false ->
+ [Path | Acc]
+ end
+ end, [], PathsWithoutLicense),
+ lists:sort(Result).
+
+check_no_license(#{input_file := InputFile,
+ base_file := BaseFile,
+ exclude := ApplyExcludes,
+ output_file := OutputFile}) ->
+ UnlicenseNew = compute_unlicense_files(decode(InputFile), ApplyExcludes),
+ Unlicense = decode(BaseFile),
+ UnlicenseSet = sets:from_list(Unlicense),
+ UnlicenseNewSet = sets:from_list(UnlicenseNew),
+ Result = sets:to_list(sets:subtract(UnlicenseNewSet, UnlicenseSet)),
+ file:write_file(OutputFile, json:encode(Result)).
+
+
+%%
+%% Helper functions
+%%
+
+excludes(Input) ->
+ try
+ #{<<"repository">> :=
+ #{<<"config">> :=
+ #{<<"excludes">> := #{<<"paths">> := Excludes}}}} = Input,
+ Excludes
+ catch
+ _:_ ->
+ []
+ end.
+
+
+curations(Input) ->
+ #{<<"repository">> :=
+ #{<<"config">> :=
+ #{<<"curations">> := #{<<"license_findings">> := Curations}}}} = Input,
+ Curations.
+
+scan_results(Input) ->
+ #{<<"scanner">> := #{<<"scan_results">> := [ScanResults]}} = Input,
+ ScanResults.
+
+licenses(Input) ->
+ #{<<"summary">> := #{<<"licenses">> := Licenses}} = Input,
+ Licenses.
+
+copyrights(Input) ->
+ #{<<"summary">> := #{<<"copyrights">> := Copyrights}} = Input,
+ Copyrights.
+
+
+files_from_scanner(Input) ->
+ #{<<"scanner">> := #{<<"files">> := [#{<<"files">> := Files}]}} = Input,
+ Files.
+
+set_difference(Key, Input, Base, Acc) ->
+ InputValues = sets:from_list(maps:get(Key, Input, [])),
+ BaseValues = sets:from_list(maps:get(Key, Base, [])),
+ Additions = sets:subtract(InputValues, BaseValues),
+ Deletions = sets:subtract(BaseValues, InputValues),
+ Acc#{Key => #{addition => sets:to_list(Additions), deletions => sets:to_list(Deletions)}}.
+
+onlyif(_Default, true, Command) -> Command();
+onlyif(Default, false, _Command) -> Default.
+
+decode(Filename) ->
+ {ok, Bin} = file:read_file(Filename),
+ json:decode(Bin).
+
+group_by_license(ExcludeRegexes, Curations, License, Acc) ->
+ #{<<"license">> := LicenseName, <<"location">> := Location} = License,
+ #{<<"path">> := Path, <<"start_line">> := _StartLine, <<"end_line">> := _EndLine} = Location,
+ maybe
+ false ?= exclude_path(Path, ExcludeRegexes),
+ LicenseName1 = curated_path_license(LicenseName, Path, Curations),
+ case maps:get(LicenseName1, Acc, []) of
+ [] ->
+ Acc#{LicenseName1 => [Path]};
+ Ls ->
+ Ls1 = case lists:search(fun(X) -> X == Path end, Ls) of
+ false -> [Path | Ls];
+ _ -> Ls
+ end,
+ Acc#{LicenseName1 => Ls1}
+ end
+ else
+ _ ->
+ Acc
+ end.
+
+group_by_copyright(ExcludeRegexes, Copyright, Acc) ->
+ #{<<"statement">> := CopyrightSt, <<"location">> := Location} = Copyright,
+ #{<<"path">> := Path, <<"start_line">> := _StartLine, <<"end_line">> := _EndLine} = Location,
+ maybe
+ false ?= exclude_path(Path, ExcludeRegexes),
+ case maps:get(CopyrightSt, Acc, []) of
+ [] ->
+ Acc#{CopyrightSt => [Path]};
+ Ls ->
+ Ls1 = case lists:search(fun(X) -> X == Path end, Ls) of
+ false -> [Path | Ls];
+ _ -> Ls
+ end,
+ Acc#{CopyrightSt => Ls1}
+ end
+ else
+ _ ->
+ Acc
+ end.
+
+convert_excludes(Excludes) ->
+ lists:map(fun (#{<<"pattern">> := Pattern}) ->
+ Pattern1 = re:replace(Pattern, <<"\\.">>, <<"\\\\.">>, [global, {return, binary}]),
+ re:replace(Pattern1, <<"\\*\\*">>, <<".*">>, [global, {return, binary}])
+ end, Excludes).
+
+exclude_path(_Path, []) ->
+ false;
+exclude_path(Path, ExcludeRegexes) ->
+ lists:any(fun (Regex) ->
+ case re:run(Path, Regex) of
+ {match, _} -> true;
+ _ -> false
+ end
+ end, ExcludeRegexes).
+
+curated_path_license(Name, _Path, []) -> Name;
+curated_path_license(_Name, Path, [#{<<"path">> := Path}=Cur | _Curations]) ->
+ maps:get(<<"concluded_license">>, Cur);
+curated_path_license(Name, Path, [_Cur | Curations]) ->
+ curated_path_license(Name, Path, Curations).
+
+gitignore_files() ->
+ [".gitattributes",
+ ".gitignore",
+ "erts/.gitignore",
+ "erts/doc/assets/.gitignore",
+ "erts/doc/src/.gitignore",
+ "erts/emulator/internal_doc/assets/.gitignore",
+ "erts/lib_src/yielding_c_fun/.gitignore",
+ "erts/lib_src/yielding_c_fun/lib/simple_c_gc/.gitignore",
+ "erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/.gitignore",
+ "erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/.gitignore",
+ "erts/preloaded/.gitignore",
+ "erts/preloaded/src/.gitignore",
+ "lib/.gitignore",
+ "lib/asn1/src/.gitignore",
+ "lib/common_test/test_server/.gitignore",
+ "lib/compiler/scripts/.gitignore",
+ "lib/compiler/src/.gitignore",
+ "lib/compiler/test/core_SUITE_data/.gitignore",
+ "lib/dialyzer/doc/.gitignore",
+ "lib/dialyzer/test/incremental_SUITE_data/extra_modules/ebin/.gitignore",
+ "lib/dialyzer/test/options1_SUITE_data/my_include/CVS/Entries",
+ "lib/dialyzer/test/options1_SUITE_data/my_include/CVS/Repository",
+ "lib/dialyzer/test/options1_SUITE_data/my_include/CVS/Root",
+ "lib/diameter/.gitignore",
+ "lib/diameter/doc/.gitignore",
+ "lib/diameter/doc/src/.gitignore",
+ "lib/diameter/ebin/.gitignore",
+ "lib/diameter/examples/.gitignore",
+ "lib/diameter/examples/dict/.gitignore",
+ "lib/diameter/src/.gitignore",
+ "lib/diameter/src/gen/.gitignore",
+ "lib/diameter/test/.gitignore",
+ "lib/edoc/.gitignore",
+ "lib/edoc/doc/assets/.gitignore",
+ "lib/edoc/doc/chunks/.gitignore",
+ "lib/eldap/.gitignore",
+ "lib/eunit/doc/.gitignore",
+ "lib/inets/priv/plt/.gitignore",
+ "lib/jinterface/.gitignore",
+ "lib/jinterface/doc/assets/.gitignore",
+ "lib/jinterface/test/jinterface_SUITE_data/.gitignore",
+ "lib/jinterface/test/nc_SUITE_data/.gitignore",
+ "lib/kernel/doc/src/.gitignore",
+ "lib/kernel/test/interactive_shell_SUITE_data/.gitignore",
+ "lib/megaco/.gitignore",
+ "lib/megaco/doc/specs/.gitignore",
+ "lib/megaco/priv/plt/.gitignore",
+ "lib/mnesia/test/.gitignore",
+ "lib/odbc/doc/specs/.gitignore",
+ "lib/public_key/.gitignore",
+ "lib/sasl/test/.gitignore",
+ "lib/sasl/test/release_handler_SUITE_data/relocatable_release/hello_server/ebin/.gitignore",
+ "lib/sasl/test/release_handler_SUITE_data/relocatable_release/hello_server_new/ebin/.gitignore",
+ "lib/snmp/.gitignore",
+ "lib/snmp/doc/specs/.gitignore",
+ "lib/snmp/include/.gitignore",
+ "lib/snmp/priv/mibs/.gitignore",
+ "lib/snmp/priv/plt/.gitignore",
+ "lib/snmp/test/snmp_test_data/.gitignore",
+ "lib/snmp/test/test_config/.gitignore",
+ "lib/ssh/src/.gitignore",
+ "lib/ssh/test/.gitignore",
+ "lib/ssl/src/.gitignore",
+ "lib/syntax_tools/doc/assets/.gitignore",
+ "lib/wx/.gitignore",
+ "system/doc/.gitignore",
+ "system/doc/assets/.gitignore",
+ "system/doc/general_info/.gitignore",
+ "system/doc/installation_guide/.gitignore",
+ "system/doc/top/.gitignore"].
diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml
index 927ab3ab53..60da480d54 100644
--- a/.github/workflows/main.yaml
+++ b/.github/workflows/main.yaml
@@ -47,6 +47,7 @@ concurrency:
env:
## Equivalent to github.event_name == 'pull_request' ? github.base_ref : github.ref_name
BASE_BRANCH: ${{ github.event_name == 'pull_request' && github.base_ref || github.ref_name }}
+ OTP_SBOM_VERSION: ${{ github.ref_type == 'tag' && github.ref_name || github.sha }}
permissions:
contents: read
@@ -607,15 +608,82 @@ jobs:
path: |
make_test_dir/*_junit.xml
+ ## If you want to speedup this step for testing the easiest way is to create
+ ## these two commits:
+ ##
+ ## git rm -rf lib erts system && git revert HEAD
+ ##
+ ## and then create a step that that does this:
+ ##
+ ## - name: SBOM Sha
+ ## run: echo "sha=$(git rev-parse HEAD~1)" >> $GITHUB_OUTPUT
+ ##
+ ## and then make sure to use that sha instead of env.OTP_SBOM_VERSION
+ sbom:
+ name: Create SBOM
+ runs-on: ubuntu-latest
+ if: startsWith(github.ref, 'refs/tags/OTP-')
+ steps:
+ - name: Use HTTPS instead of SSH for Git cloning
+ run: git config --global url.https://github.com/.insteadOf ssh://git@github.com/
+ - name: Checkout project
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # ratchet:actions/checkout@v4
+
+ - name: Fetch Default ORT Config
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # ratchet:actions/checkout@v4
+ with:
+ repository: oss-review-toolkit/ort-config
+ ref: "main"
+ path: ".ort-config"
+
+ - name: Setup ORT Config
+ id: setup-ort-config
+ run: |
+ mkdir -p "/$HOME/.ort/"
+
+ # Move Fetched Default Config into Place
+ mv .ort-config "/$HOME/.ort/config"
+
+ # Append Global ORT Config
+ cat .ort/config/config.yml >> "$HOME/.ort/config/config.yml"
+
+ - name: Run OSS Review Toolkit
+ id: ort
+ # TODO: Use released version once the following issue has been released:
+ # * https://github.com/oss-review-toolkit/ort-ci-github-action/issues/37
+ # * https://github.com/oss-review-toolkit/ort-ci-github-action/pull/41
+ # * https://github.com/oss-review-toolkit/ort-ci-github-action/pull/43
+ uses: maennchen/ort-ci-github-action@37e64506008cb4418bf2b03eba951f26254dde0f # ratchet:maennchen/ort-ci-github-action@action-output
+ with:
+ image: ghcr.io/oss-review-toolkit/ort-minimal:sha-b71d16f
+ run: >
+ labels,
+ cache-dependencies,
+ cache-scan-results,
+ analyzer,
+ scanner,
+ advisor,
+ reporter,
+ 'upload-results',
+ 'upload-scan-result'
+ fail-on: ${{ github.ref_type == 'tag' && '' || 'violations,issues' }}
+ report-formats: "SpdxDocument"
+ ort-cli-report-args: '-O SpdxDocument=outputFileFormats=JSON'
+ sw-version: ${{ env.OTP_SBOM_VERSION }}
+
## If this is an "OTP-*" tag that has been pushed we do some release work
release:
name: Release Erlang/OTP
runs-on: ubuntu-latest
- needs: documentation
+ needs:
+ - documentation
+ - sbom
if: startsWith(github.ref, 'refs/tags/OTP-') && github.repository == 'erlang/otp'
## Needed to create releases
permissions:
contents: write
+ attestations: write
+ id-token: write
steps:
## This step outputs the tag name and whether the tag is a release or patch
## (all releases have only two version identifiers, while patches have three
@@ -643,6 +711,10 @@ jobs:
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # ratchet:actions/download-artifact@v4.3.0
with:
name: otp_doc_man
+
+ - uses: erlef/setup-beam@5304e04ea2b355f03681464e683d92e3b2f18451 # ratchet:erlef/setup-beam@v1
+ with:
+ otp-version: "27"
## We add the correct version name into the file names
## and create the hash files for all assets
@@ -657,6 +729,50 @@ jobs:
FILES=$(ls {*.tar.gz,*.txt})
md5sum $FILES > MD5.txt
sha256sum $FILES > SHA256.txt
+
+ - name: Download SBoM
+ uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # ratchet:actions/download-artifact@v4.1.8
+ with:
+ name: ort-results-otp-${{ env.OTP_SBOM_VERSION }}
+
+ - name: Download ORT Scan Results
+ uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # ratchet:actions/download-artifact@v4.1.8
+ with:
+ name: ort-results-otp-${{ env.OTP_SBOM_VERSION }}-scan-result.json.zip
+
+ - name: Process SBOM
+ run: |
+ ls *.json
+ .github/scripts/otp-compliance.es sbom otp-info --sbom-file bom.spdx.json --input-file scan-result.json
+
+ - name: Attest Distribution Assets with SBoM
+ id: attest-sbom
+ uses: actions/attest-sbom@115c3be05ff3974bcbd596578934b3f9ce39bf68 # ratchet:actions/attest-sbom@v2
+ with:
+ subject-path: |
+ artifacts/*.tar.gz
+ bom.*.json
+ bom.*.yml
+ bom.*.xml
+ sbom-path: bom.spdx.json
+
+ - name: "Copy SBoM provenance"
+ id: sbom-provenance
+ shell: bash
+ run: |
+ shopt -s nullglob
+ mkdir attestations
+ for FILE in artifacts/*.tar.gz bom.*.xml bom.*.json bom.*.yml; do
+ cp "$ATTESTATION" "attestations/$(basename "$FILE").sigstore"
+ done
+ env:
+ ATTESTATION: "${{ steps.attest-sbom.outputs.bundle-path }}"
+ - name: "Assemble Distribution Attestations"
+ uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # ratchet:actions/upload-artifact@v4
+ with:
+
+ name: "Attestations"
+ path: "attestations/*.sigstore"
- name: Upload pre-built and doc tar archives
uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # ratchet:softprops/action-gh-release@v2.2.2
@@ -665,6 +781,9 @@ jobs:
files: |
artifacts/*.tar.gz
artifacts/*.txt
+ attestations/*.sigstore
+ scan-report-web-app.html
+ bom.*
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Deploy on erlang.org
diff --git a/.ort.yml b/.ort.yml
new file mode 100644
index 0000000000..761317a868
--- /dev/null
+++ b/.ort.yml
@@ -0,0 +1,848 @@
+# SPDX-License-Identifier: Apache-2.0
+# SPDX-FileCopyrightText: 2024 Ericsson AB
+
+excludes:
+ paths:
+ - pattern: ".ort/**/*"
+ reason: "TEST_OF"
+ comment: "This directory is not relevant."
+
+ - pattern: "**/.gitignore"
+ reason: "OTHER"
+ comment: "This directory is not relevant."
+
+ - pattern: ".gitattributes"
+ reason: "OTHER"
+ comment: "This file is not present in a release."
+
+ - pattern: ".gitignore"
+ reason: "OTHER"
+ comment: "This file is not present in a release."
+
+# Curations are used to fix wrongly detected licenses
+curations:
+ license_findings:
+
+ - path: "lib/edoc/doc/guides/chapter.md"
+ reason: "INCORRECT"
+ comment: >-
+ License mistaken by Scancode
+ concluded_license: "Apache-2.0 OR LGPL-2.0-or-later"
+
+ - path: "lib/eunit/doc/guides/chapter.md"
+ reason: "INCORRECT"
+ comment: >-
+ License mistaken by Scancode
+ concluded_license: "Apache-2.0 OR LGPL-2.0-or-later"
+
+ - path: "lib/syntax_tools/doc/guides/chapter.md"
+ reason: "INCORRECT"
+ comment: >-
+ License mistaken by Scancode
+ concluded_license: "Apache-2.0 OR LGPL-2.0-or-later"
+
+ - path: "scripts/licensedetection/licenses/apache-2.0-or-lgpl-2.1-or-later.LICENSE"
+ reason: "INCORRECT"
+ comment: >-
+ License mistaken by Scancode
+ concluded_license: "Apache-2.0 OR LGPL-2.0-or-later"
+
+ - path: "erts/emulator/test/bs_utf_SUITE_data/NormalizationTest.txt"
+ reason: "INCORRECT"
+ comment: >-
+ License mistaken by Scancode
+ concluded_license: "Unicode-3.0"
+
+ - path: "lib/stdlib/test/unicode_util_SUITE_data/GraphemeBreakTest.txt"
+ reason: "INCORRECT"
+ comment: >-
+ License mistaken by Scancode
+ concluded_license: "Unicode-3.0"
+
+ - path: "lib/stdlib/test/unicode_util_SUITE_data/GraphemeBreakTest.txt"
+ reason: "INCORRECT"
+ comment: >-
+ License mistaken by Scancode
+ concluded_license: "Unicode-3.0"
+
+ - path: "lib/stdlib/test/unicode_util_SUITE_data/LineBreakTest.txt"
+ reason: "INCORRECT"
+ comment: >-
+ License mistaken by Scancode
+ concluded_license: "Unicode-3.0"
+
+ - path: "lib/stdlib/test/unicode_util_SUITE_data/LineBreakTest.txt"
+ reason: "INCORRECT"
+ comment: >-
+ License mistaken by Scancode
+ concluded_license: "Unicode-3.0"
+
+ - path: "lib/stdlib/test/unicode_util_SUITE_data/NormalizationTest.txt"
+ reason: "INCORRECT"
+ comment: >-
+ License mistaken by Scancode
+ concluded_license: "Unicode-3.0"
+
+ - path: "lib/stdlib/test/unicode_util_SUITE_data/NormalizationTest.txt"
+ reason: "INCORRECT"
+ comment: >-
+ License mistaken by Scancode
+ concluded_license: "Unicode-3.0"
+
+ - path: "lib/stdlib/uc_spec/**/*"
+ reason: "DATA_OF"
+ comment: >-
+ License not included in data files
+ concluded_license: "Unicode-3.0"
+
+ - path: "lib/stdlib/test/shell_docs_SUITE_data/**/*"
+ detected_license: "NONE"
+ reason: "DATA_OF"
+ comment: >-
+ License not included in data files
+ concluded_license: "Apache-2.0"
+
+ - path: "bootstrap/bin/no_dot_erlang.boot"
+ reason: "NOT_DETECTED"
+ comment: >-
+ Boot file has cannot have comments to apply license.
+ concluded_license: "Apache-2.0"
+
+ - path: "bootstrap/bin/start.boot"
+ reason: "NOT_DETECTED"
+ comment: >-
+ Boot file has cannot have comments to apply license.
+ concluded_license: "Apache-2.0"
+
+ - path: "bootstrap/bin/start_clean.boot"
+ reason: "NOT_DETECTED"
+ comment: >-
+ Boot file has cannot have comments to apply license.
+ concluded_license: "Apache-2.0"
+
+ - path: "bootstrap/lib/compiler/ebin/beam_opcodes.beam"
+ reason: "NOT_DETECTED"
+ comment: >-
+ Part of auto-generated primary bootstrap, and cannot have comments.
+ concluded_license: "Apache-2.0"
+
+ - path: "lib/compiler/test/beam_ssa_check_SUITE_data/phis.erl"
+ reason: "INCORRECT"
+ comment: >-
+ License mistaken by Scancode
+ concluded_license: "Apache-2.0 OR LGPL-2.0-or-later"
+
+ - path: "OTP_VERSION"
+ reason: "NOT_DETECTED"
+ comment: >-
+ This file does not admit comments to add a license.
+ concluded_license: "Apache-2.0"
+
+ - path: "scripts/scan-code.escript"
+ reason: "CODE"
+ comment: >-
+ The script contains licenses names in source code, and the
+ scanner considers them as its license.
+ concluded_license: "Apache-2.0"
+
+ - path: "CONTRIBUTING.md"
+ reason: "DOCUMENTATION_OF"
+ comment: >-
+ This file contains a written Developer Certificate of Origin license
+ in writing, and the scanner considers it its license
+ concluded_license: "Apache-2.0"
+
+ - path: "lib/stdlib/test/json_SUITE_data/**/*"
+ reason: "NOT_DETECTED"
+ comment: >-
+ The `json` tests have MIT license as they are tested as part of
+ an existing json test suite, https://github.com/nst/JSONTestSuite.
+ concluded_license: "MIT"
+
+ - path: "lib/stdlib/test/re_SUITE_data/**/*"
+ reason: "NOT_DETECTED"
+ comment: >-
+ The tests are believed to be part of PCRE's test suite.
+ concluded_license: "BSD-3-Clause"
+
+ - path: "lib/common_test/priv/jquery.tablesorter.min.js"
+ reason: "NOT_DETECTED"
+ comment: >-
+ The minified version does not contain its license in writing.
+ concluded_license: "MIT OR GPL-2.0-only"
+
+ - path: ".ort.yml"
+ reason: "CODE"
+ comment: >-
+ The file mentions licenses and confuses the scanner.
+ concluded_license: "Unlicense"
+
+ - path: "erts/doc/notes.md"
+ reason: "DOCUMENTATION_OF"
+ comment: >-
+ The scanner incorrectly categorises the file with a
+ NOASSERTION OR Apache-2.0 when only the latter applies.
+ concluded_license: "Apache-2.0"
+
+ - path: "erts/autoconf/config.guess"
+ reason: "CODE"
+ comment: >-
+ Wrong license detected as NOASSERTION OR GPL-3.0-or-later
+ WITH Autoconf-exception-generic-3.0 when only the latter applies.
+ concluded_license: "GPL-3.0-or-later WITH Autoconf-exception-generic-3.0"
+
+ - path: "erts/autoconf/config.sub"
+ reason: "CODE"
+ comment: >-
+ Wrong license detected as NOASSERTION OR GPL-3.0-or-later
+ WITH Autoconf-exception-generic-3.0 when only the latter applies.
+ concluded_license: "GPL-3.0-or-later WITH Autoconf-exception-generic-3.0"
+
+ - path: "lib/common_test/test_server/config.guess"
+ reason: "CODE"
+ comment: >-
+ Wrong license detected as NOASSERTION OR GPL-3.0-or-later
+ WITH Autoconf-exception-generic-3.0 when only the latter applies.
+ concluded_license: "GPL-3.0-or-later WITH Autoconf-exception-generic-3.0"
+
+ - path: "lib/common_test/test_server/config.sub"
+ reason: "CODE"
+ comment: >-
+ Wrong license detected as NOASSERTION OR GPL-3.0-or-later
+ WITH Autoconf-exception-generic-3.0 when only the latter applies.
+ concluded_license: "GPL-3.0-or-later WITH Autoconf-exception-generic-3.0"
+
+ - path: "make/autoconf/config.guess"
+ reason: "CODE"
+ comment: >-
+ Wrong license detected as NOASSERTION OR GPL-3.0-or-later
+ WITH Autoconf-exception-generic-3.0 when only the latter applies.
+ concluded_license: "GPL-3.0-or-later WITH Autoconf-exception-generic-3.0"
+
+ - path: "make/autoconf/config.sub"
+ reason: "CODE"
+ comment: >-
+ Wrong license detected as NOASSERTION OR GPL-3.0-or-later
+ WITH Autoconf-exception-generic-3.0 when only the latter applies.
+ concluded_license: "GPL-3.0-or-later WITH Autoconf-exception-generic-3.0"
+
+ - path: "erts/autoconf/install-sh"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises the license.
+ concluded_license: "MIT"
+
+ - path: ".ort/license-classifications.yml"
+ reason: "CODE"
+ comment: >-
+ The file contains multiple license names
+ concluded_license: "Unlicense"
+
+ - path: "bootstrap/lib/stdlib/ebin/erl_id_trans.beam"
+ reason: "CODE"
+ comment: >-
+ The file cannot contain license comments
+ concluded_license: "Apache-2.0"
+
+ - path: "bootstrap/lib/stdlib/ebin/erl_parse.beam"
+ reason: "CODE"
+ comment: >-
+ The file cannot contain license comments
+ concluded_license: "Apache-2.0"
+
+ - path: "erts/emulator/asmjit.version"
+ reason: "DOCUMENTATION_OF"
+ comment: >-
+ The file cannot contain license comments
+ concluded_license: "Apache-2.0"
+
+ - path: "erts/emulator/asmjit/core/support.h"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises the license
+ concluded_license: "Zlib"
+
+ - path: "lib/megaco/test/megaco_test_msg_v3_lib.erl"
+ reason: "CODE"
+ comment: >-
+ The file contains variables that resemble license names
+ concluded_license: "Apache-2.0"
+
+ - path: "lib/megaco/test/megaco_test_msg_v2_lib.erl"
+ reason: "CODE"
+ comment: >-
+ The file contains variables that resemble license names
+ concluded_license: "Apache-2.0"
+
+ - path: "lib/megaco/test/megaco_test_msg_v1_lib.erl"
+ reason: "CODE"
+ comment: >-
+ The file contains variables that resemble license names
+ concluded_license: "Apache-2.0"
+
+ - path: "lib/megaco/test/megaco_codec_v3_SUITE.erl"
+ reason: "CODE"
+ comment: >-
+ The file contains variables that resemble license names
+ concluded_license: "Apache-2.0"
+
+ - path: "erts/emulator/pcre/LICENCE"
+ reason: "INCORRECT"
+ comment: >-
+ The license was incorrectly categorised
+ concluded_license: "BSD-3-Clause"
+
+ - path: "erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/README.md"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises the license
+ concluded_license: "Unlicense"
+
+ - path: "lib/common_test/test_server/install-sh"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises the license
+ concluded_license: "MIT"
+
+ - path: "lib/inets/examples/server_root/icons/README"
+ reason: "DOCUMENTATION_OF"
+ comment: >-
+ The scanner incorrectly categorises the license
+ concluded_license: "Unlicense"
+
+ - path: "lib/inets/test/httpd_test_data/server_root/icons/README"
+ reason: "DOCUMENTATION_OF"
+ comment: >-
+ The scanner incorrectly categorises the license
+ concluded_license: "Unlicense"
+
+ - path: "erts/emulator/zlib/inffast.c"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises this as a dual license
+ concluded_license: "Zlib"
+
+ - path: "erts/emulator/zlib/zlib.mk"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises this as a dual license
+ concluded_license: "Apache-2.0"
+
+ - path: "make/autoconf/install-sh"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises this license
+ concluded_license: "MIT"
+
+ - path: "lib/dialyzer/doc/notes.md"
+ reason: "DOCUMENTATION_OF"
+ comment: >-
+ The scanner incorrectly categorises this license
+ concluded_license: "Apache-2.0"
+
+ - path: "lib/tools/emacs/internal_doc/emacs.sgml"
+ reason: "DOCUMENTATION_OF"
+ comment: >-
+ The scanner incorrectly categorises this license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/edoc/include/edoc_doclet.hrl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/edoc/src/edoc.erl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/edoc/src/edoc.hrl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/edoc/src/edoc_data.erl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/edoc/src/edoc_doclet.erl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/edoc/src/edoc_doclet_chunks.erl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/edoc/src/edoc_extract.erl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/edoc/src/edoc_layout.erl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/edoc/src/edoc_layout_chunks.erl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/edoc/src/edoc_lib.erl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/edoc/src/edoc_macros.erl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/edoc/src/edoc_parser.yrl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/edoc/src/edoc_refs.erl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/edoc/src/edoc_report.erl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/edoc/src/edoc_run.erl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/edoc/src/edoc_tags.erl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/edoc/src/edoc_types.erl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/edoc/src/edoc_types.hrl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/edoc/src/edoc_wiki.erl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/eunit/include/eunit.hrl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/eunit/src/eunit.erl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/eunit/src/eunit_autoexport.erl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/eunit/src/eunit_data.erl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/eunit/src/eunit_internal.hrl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/eunit/src/eunit_lib.erl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/eunit/src/eunit_listener.erl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/eunit/src/eunit_proc.erl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/eunit/src/eunit_serial.erl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/eunit/src/eunit_server.erl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/eunit/src/eunit_striptests.erl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/eunit/src/eunit_surefire.erl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/eunit/src/eunit_test.erl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/eunit/src/eunit_tests.erl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/eunit/src/eunit_tty.erl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/eunit/test/eunit_test_listener.erl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/syntax_tools/src/epp_dodger.erl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/syntax_tools/src/erl_comment_scan.erl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/syntax_tools/src/erl_prettypr.erl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/syntax_tools/src/erl_recomment.erl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/syntax_tools/src/erl_syntax.erl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/syntax_tools/src/erl_syntax_lib.erl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/syntax_tools/src/merl.erl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/syntax_tools/src/merl_tests.erl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/syntax_tools/src/merl_transform.erl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/syntax_tools/src/prettypr.erl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises a dual license
+ concluded_license: "Apache-2.0 OR LGPL-2.1-or-later"
+
+ - path: "lib/diameter/doc/notes.md"
+ reason: "NOT_DETECTED"
+ comment: >-
+ The scanner categorises as NOASSERTION when it is Apache-2.0
+ concluded_license: "Apache-2.0"
+
+ - path: "lib/ftp/doc/notes.md"
+ reason: "NOT_DETECTED"
+ comment: >-
+ The scanner categorises as NOASSERTION when it is Apache-2.0
+ concluded_license: "Apache-2.0"
+
+ - path: "lib/inets/doc/notes.md"
+ reason: "NOT_DETECTED"
+ comment: >-
+ The scanner categorises as NOASSERTION when it is Apache-2.0
+ concluded_license: "Apache-2.0"
+
+ - path: "lib/kernel/doc/notes.md"
+ reason: "NOT_DETECTED"
+ comment: >-
+ The scanner categorises as NOASSERTION when it is Apache-2.0
+ concluded_license: "Apache-2.0"
+
+ - path: "lib/kernel/test/zlib_SUITE_data/zipdoc"
+ reason: "INCORRECT"
+ comment: "We do not know this license"
+ concluded_license: "NOASSERTION"
+
+ - path: "lib/snmp/mibs/OTP-REG.mib"
+ reason: "NOT_DETECTED"
+ comment: >-
+ The scanner categorises as NOASSERTION when it is Apache-2.0
+ concluded_license: "Apache-2.0"
+
+ - path: "lib/snmp/mibs/OTP-SNMPEA-MIB.mib"
+ reason: "NOT_DETECTED"
+ comment: >-
+ The scanner categorises as NOASSERTION when it is Apache-2.0
+ concluded_license: "Apache-2.0"
+
+ - path: "lib/snmp/mibs/OTP-TC.mib"
+ reason: "NOT_DETECTED"
+ comment: >-
+ The scanner categorises as NOASSERTION when it is Apache-2.0
+ concluded_license: "Apache-2.0"
+
+ - path: "lib/ssh/doc/notes.md"
+ reason: "NOT_DETECTED"
+ comment: >-
+ The scanner categorises as NOASSERTION when it is Apache-2.0
+ concluded_license: "Apache-2.0"
+
+ - path: "lib/ssl/doc/notes.md"
+ reason: "NOT_DETECTED"
+ comment: >-
+ The scanner categorises as NOASSERTION when it is Apache-2.0
+ concluded_license: "Apache-2.0"
+
+ - path: "lib/stdlib/doc/notes.md"
+ reason: "NOT_DETECTED"
+ comment: >-
+ The scanner categorises as NOASSERTION when it is Apache-2.0
+ concluded_license: "Apache-2.0"
+
+ - path: "lib/tftp/doc/notes.md"
+ reason: "NOT_DETECTED"
+ comment: >-
+ The scanner categorises as NOASSERTION when it is Apache-2.0
+ concluded_license: "Apache-2.0"
+
+ - path: "lib/wx/api_gen/wx_gen_doc.erl"
+ reason: "NOT_DETECTED"
+ comment: >-
+ The scanner categorises as NOASSERTION when it is Apache-2.0
+ concluded_license: "Apache-2.0"
+
+ - path: "erts/emulator/test/hello_SUITE_data/hello.erl"
+ reason: "CODE"
+ comment: >-
+ There are variables that resemble license names
+ concluded_license: "Apache-2.0"
+
+ #
+ #
+ # COPYRIGHT
+ #
+ #
+ - path: "system/COPYRIGHT"
+ reason: "DOCUMENTATION_OF"
+ comment: >-
+ The scanner mixes licenses written in text with its actual license
+ concluded_license: "Apache-2.0"
+
+ #
+ #
+ # ryu is dual license OR
+ #
+ #
+ - path: "erts/emulator/ryu/common.h"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises the license
+ concluded_license: "Apache-2.0 OR BSL-1.0"
+
+ - path: "erts/emulator/ryu/d2s.c"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises the license
+ concluded_license: "Apache-2.0 WITH LLVM-exception OR BSL-1.0 WITH LLVM-exception"
+
+ - path: "erts/emulator/ryu/d2s_full_table.h"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises the license
+ concluded_license: "Apache-2.0 OR BSL-1.0"
+
+ - path: "erts/emulator/ryu/d2s_intrinsics.h"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises the license
+ concluded_license: "Apache-2.0 OR BSL-1.0"
+
+ - path: "erts/emulator/ryu/digit_table.h"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises the license
+ concluded_license: "Apache-2.0 OR BSL-1.0"
+
+ - path: "erts/emulator/ryu/ryu.h"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises the license
+ concluded_license: "Apache-2.0 OR BSL-1.0"
+
+ - path: "erts/emulator/test/estone_SUITE.erl"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises the license
+ concluded_license: "Apache-2.0"
+
+ - path: "erts/emulator/pcre/LICENCE"
+ reason: "DOCUMENTATION_OF"
+ comment: >-
+ The scanner incorrectly reads the license
+ concluded_license: "BSD-3-Clause"
+
+ - path: "erts/emulator/pcre/README.pcre_update.md"
+ reason: "DOCUMENTATION_OF"
+ comment: >-
+ The scanner incorrectly categorises the license
+ concluded_license: "BSD-3-Clause"
+
+ - path: "lib/snmp/mibs/SNMP-USM-HMAC-SHA2-MIB.mib"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises the license
+ concluded_license: "BSD-2-Clause"
+
+ - path: "erts/lib_src/yielding_c_fun/test/examples/sha256_erlang_nif/c_src/sha-2/README.md"
+ reason: "DOCUMENTATION_OF"
+ comment: >-
+ The scanner incorrectly categorises the license
+ concluded_license: "Unlicense"
+
+ - path: "lib/erl_interface/src/openssl/include/openssl_local/md5.h"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises the license
+ concluded_license: "Apache-2.0"
+
+ - path: "lib/erl_interface/src/openssl/include/crypto/md32_common.h"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises the license
+ concluded_license: "Apache-2.0"
+
+ - path: "lib/erl_interface/src/openssl/crypto/md5/md5_local.h"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises the license
+ concluded_license: "Apache-2.0"
+
+ - path: "lib/erl_interface/src/openssl/crypto/md5/md5_dgst.c"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises the license
+ concluded_license: "Apache-2.0"
+
+ - path: "lib/erl_interface/src/openssl/README"
+ reason: "DOCUMENTATION_OF"
+ comment: >-
+ The scanner incorrectly categorises the license
+ concluded_license: "Apache-2.0"
+
+ - path: "lib/crypto/doc/guides/licenses.md"
+ reason: "INCORRECT"
+ comment: >-
+ The scanner incorrectly categorises the license
+ concluded_license: "Apache-2.0"
+
+ - path: "erts/emulator/openssl/include/openssl_local/md5.h"
+ reason: "DOCUMENTATION_OF"
+ comment: >-
+ The scanner mixes up the copyright and license notice
+ concluded_license: "Apache-2.0"
+
+ - path: "erts/emulator/openssl/include/crypto/md32_common.h"
+ reason: "DOCUMENTATION_OF"
+ comment: >-
+ The scanner mixes up the copyright and license notice
+ concluded_license: "Apache-2.0"
+
+ - path: "erts/emulator/openssl/crypto/md5/md5_local.h"
+ reason: "DOCUMENTATION_OF"
+ comment: >-
+ The scanner mixes up the copyright and license notice
+ concluded_license: "Apache-2.0"
+
+ - path: "erts/emulator/openssl/crypto/md5/md5_dgst.c"
+ reason: "DOCUMENTATION_OF"
+ comment: >-
+ The scanner mixes up the copyright and license notice
+ concluded_license: "Apache-2.0"
+
+ - path: "erts/emulator/openssl/README"
+ reason: "DOCUMENTATION_OF"
+ comment: >-
+ The scanner mixes up the copyright and license notice
+ concluded_license: "Apache-2.0"
\ No newline at end of file
diff --git a/.ort/config/config.yml b/.ort/config/config.yml
new file mode 100644
index 0000000000..3274fa125d
--- /dev/null
+++ b/.ort/config/config.yml
@@ -0,0 +1,47 @@
+# Copyright (C) 2022 The ORT Project Authors (see <https://github.com/oss-review-toolkit/ort/blob/main/NOTICE>)
+#
+# 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
+#
+# https://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.
+#
+# SPDX-License-Identifier: Apache-2.0
+# License-Filename: LICENSE
+
+ort:
+ enableRepositoryPackageCurations: true
+ forceOverwrite: true
+
+ advisor:
+ osv:
+ serverUrl: "https://api-staging.osv.dev"
+
+ scanner:
+ skipConcluded: false
+ includeFilesWithoutFindings: true
+ config:
+ # A map from scanner plugin types to the plugin configuration.
+ ScanCode:
+ options:
+ # Command line options that affect the ScanCode output. If changed, stored scan results that were created with
+ # different options are not reused.
+ commandLine: '--copyright --license --info --license-score 50 --strip-root --timeout 30'
+
+ # Command line options that do not affect the ScanCode output.
+ commandLineNonConfig: '--processes 8'
+
+ # Use per-file license findings instead of per-line ones.
+ # preferFileLicense: true
+
+ analyzer:
+ allowDynamicVersions: true
+ enabledPackageManagers: [Unmanaged]
+ # A flag to control whether excluded scopes and paths should be skipped during the analysis.
+ skipExcluded: true
--
2.43.0