blob: d1f2a3aa26cb01e45fd410f9a3b6ccd53accf84e [file] [log] [blame]
#!/usr/bin/env python3
# Copyright 2023 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Verifies the output of the license collection gathering aspect."""
import argparse
import dataclasses
import sys
import shutil
from fuchsia.tools.licenses.common_types import *
from typing import List
@dataclasses.dataclass(frozen=True)
class UnlicensedTargetInfo:
"""Container for a Unlicensed Target information"""
label: str
build_file_path: str
rule_kind: str
unlicensed_resources: List[str]
rule_attr_names: List[str]
def from_json_dict(input: DictReader) -> "UnlicensedTargetInfo":
return UnlicensedTargetInfo(
label=input.get("label"),
build_file_path=input.get("build_file_path"),
rule_kind=input.get("rule_kind"),
unlicensed_resources=input.get_string_list("unlicensed_resources"),
rule_attr_names=input.get_string_list("rule_attr_names"),
)
def summary_for_error(self) -> str:
if self.unlicensed_resources:
resources_str = "\n ".join(
trim_long_str_list(
[f"resource={r}" for r in self.unlicensed_resources],
max_len=5,
)
)
return f"{self.label} in {self.build_file_path} (rule={self.rule_kind} attrs={','.join(self.rule_attr_names)})\n {resources_str}"
else:
return f"{self.label} in {self.build_file_path} (rule={self.rule_kind})"
def example_for_fix(self, root_target) -> str:
target_local_name = self.label
if ":" in target_local_name:
target_local_name = target_local_name.split(":")[-1]
elif "/" in target_local_name:
target_local_name = target_local_name.split("/")[-1]
return f"""
For example, to add license information to {self.label},
edit the file {self.build_file_path} as follows:
First, declare a Bazel license target:
```
load("@rules_license//rules:license.bzl", "license")
license(
name = "license",
package_name = "see note 2",
license_text = "see note 3",
)
```
Notes:
1. You may skip this step if a `license` target is already defined.
2. Skip these steps if the dependency is on `@legacy_ninja_build_outputs//`
targets. See "other options" instead.
3. `package_name` is the externally visible name of the licensed package.
E.g. "grpc", "Fuchsia", "absl".
4. `license_text` file can be either a plain file
(E.g. common names are `LICENSE`, `NOTICE.txt`),
or a JSON spdx file (must be `.spdx.json`).
If you don't know what is the license file for the target, consult
https://fuchsia.dev/fuchsia-src/contribute/governance/policy/osrb-process
5. You may optionally also add `package_url` to specify the publicly hosted
location of the package.
Next, associate {self.label} with the license:
You may associated ALL targets in {self.build_file_path}
using the `package` declaration:
```
package(
...
default_applicable_licenses = [":license"],
)
```
Or, you may associate just `:{target_local_name}`:
```
{self.rule_kind} {{
name = "{target_local_name}"
applicable_licenses = [":license"]
...
}}
```
Note: `{self.rule_kind}` may not be explicitly called in the build file,
but indirectly invoked through a Starlark function. In that case,
make sure the function forwards the `applicable_licenses` attribute
to `{self.rule_kind}`.
Other Options:
1. For targets that depend on `@gn_targets//...` resources, simply use
`applicable_licenses = ["@gn_targets//:all_licenses_spdx_json"]`,
which points to a license file automatically generated by GN for all the
Ninja built outputs.
2. To investigate why '{self.label}' is included in the build graph, run:
```
bazel cquery 'somepath({root_target},{self.label})'
```
3. To systemically remove all targets of kind `{self.rule_kind}` you
may want to change `collection_policy.bzl`.
"""
def main():
parser = argparse.ArgumentParser()
parser.add_argument(
"--licenses_collection_input",
help="A json file containing the serialized output of the license collection gathering aspect.",
required=True,
)
parser.add_argument(
"--verified_licenses_collection_output",
help="A verified copy of the input json file.",
required=True,
)
args = parser.parse_args()
reader = DictReader.create_from_file(args.licenses_collection_input)
unlicensed_targets = [
UnlicensedTargetInfo.from_json_dict(r)
for r in reader.get_readers_list("unlicensed_targets")
]
if unlicensed_targets:
root_target = reader.get("root_target")
target_summaries = "\n ".join(
trim_long_str_list(
[t.summary_for_error() for t in unlicensed_targets], max_len=100
)
)
target_for_example = unlicensed_targets[0]
example = target_for_example.example_for_fix(root_target)
print(
f"""ERROR: Targets are missing required license information!
The following {len(unlicensed_targets)} targets are missing license information:
{target_summaries}
These are third_party or prebuilt targets, or otherwise depend directly on exported
files that are third_party or prebuilt targets. You should either add
license information (via the `applicable_licenses` attribute) or remove
the targets from the build graph.
{example}
"""
)
sys.exit(-1)
shutil.copyfile(
args.licenses_collection_input, args.verified_licenses_collection_output
)
if __name__ == "__main__":
main()