licenses_cpp: Started writing the licenses file (#170539)

This writes the licenses and associates them with packages. It has
simple deduplication. This also adds linux ci tests. Writing the file
adds about 7 seconds, now we are at 37s. Too much is getting written now
though so that should come down once the output is corrected.

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
This commit is contained in:
gaaclarke 2025-06-13 11:44:50 -07:00 committed by GitHub
parent 5994e6174f
commit 3bddd07fa6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 332 additions and 47 deletions

View File

@ -268,6 +268,40 @@
}
]
},
{
"cas_archive": false,
"drone_dimensions": [
"device_type=none",
"os=Linux"
],
"gclient_variables": {
"use_rbe": true
},
"gn": [
"--target-dir",
"ci/host_release_licenses",
"--runtime-mode",
"release",
"--no-lto",
"--rbe",
"--no-goma"
],
"name": "ci/host_release_licenses",
"description": "Runs the license_cpp script.",
"ninja": {
"config": "ci/host_release_licenses",
"targets": [
"flutter/tools/licenses_cpp",
"flutter/tools/licenses_cpp:licenses_cpp_testrunner"
]
},
"tests": [
{
"name": "Run license_cpp unittests",
"script": "out/ci/host_release_licenses/licenses_cpp_testrunner"
}
]
},
{
"cas_archive": false,
"drone_dimensions": [

View File

@ -4,6 +4,8 @@ source_set("licenses") {
"src/catalog.h",
"src/comments.cc",
"src/comments.h",
"src/comments_util.cc",
"src/comments_util.h",
"src/data.cc",
"src/data.h",
"src/filter.cc",

View File

@ -349,9 +349,9 @@ struct yy_trans_info {
flex_int32_t yy_verify;
flex_int32_t yy_nxt;
};
static const flex_int16_t yy_accept[27] = {0, 0, 0, 0, 0, 0, 0, 9, 7,
7, 1, 7, 4, 4, 6, 6, 0, 1,
0, 1, 2, 3, 0, 5, 0, 5, 0};
static const flex_int16_t yy_accept[28] = {0, 0, 0, 0, 0, 0, 0, 9, 7, 7,
1, 7, 4, 4, 6, 6, 0, 1, 0, 1,
2, 3, 0, 5, 0, 2, 5, 0};
static const YY_CHAR yy_ec[256] = {
0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
@ -371,23 +371,23 @@ static const YY_CHAR yy_ec[256] = {
static const YY_CHAR yy_meta[7] = {0, 1, 1, 2, 1, 1, 3};
static const flex_int16_t yy_base[34] = {
0, 0, 0, 24, 12, 12, 7, 8, 34, 5, 0, 7, 34, 0, 34, 12, 0,
0, 0, 0, 34, 34, 0, 0, 0, 0, 34, 18, 21, 24, 27, 2, 30, 0};
static const flex_int16_t yy_base[36] = {
0, 0, 0, 27, 24, 14, 12, 10, 34, 5, 0, 7, 34, 0, 34, 12, 0, 0,
0, 0, 0, 34, 0, 0, 0, 34, 0, 34, 18, 21, 24, 27, 5, 3, 30, 0};
static const flex_int16_t yy_def[34] = {
0, 27, 27, 28, 28, 29, 29, 26, 26, 26, 30, 26, 26, 31, 26, 26, 9,
30, 11, 30, 26, 26, 15, 32, 33, 32, 0, 26, 26, 26, 26, 26, 26, 26};
static const flex_int16_t yy_def[36] = {
0, 28, 28, 29, 29, 30, 30, 27, 27, 27, 31, 27, 27, 32, 27, 27, 9, 31,
11, 31, 33, 27, 15, 34, 35, 27, 34, 0, 27, 27, 27, 27, 27, 27, 27, 27};
static const flex_int16_t yy_nxt[41] = {
0, 26, 9, 23, 10, 21, 11, 16, 26, 17, 15, 18, 20, 17,
22, 15, 23, 13, 24, 8, 8, 8, 12, 12, 12, 14, 14, 14,
19, 13, 19, 25, 26, 25, 7, 26, 26, 26, 26, 26, 26};
0, 27, 9, 23, 10, 25, 11, 16, 21, 17, 27, 18, 20, 17,
22, 15, 23, 15, 24, 8, 8, 8, 12, 12, 12, 14, 14, 14,
19, 13, 19, 26, 13, 26, 7, 27, 27, 27, 27, 27, 27};
static const flex_int16_t yy_chk[41] = {
0, 0, 2, 33, 2, 31, 2, 9, 7, 9, 6, 9, 11, 11,
15, 5, 15, 4, 15, 27, 27, 27, 28, 28, 28, 29, 29, 29,
30, 3, 30, 32, 0, 32, 26, 26, 26, 26, 26, 26, 26};
0, 0, 2, 35, 2, 33, 2, 9, 32, 9, 7, 9, 11, 11,
15, 6, 15, 5, 15, 28, 28, 28, 29, 29, 29, 30, 30, 30,
31, 4, 31, 34, 3, 34, 27, 27, 27, 27, 27, 27, 27};
/* The intent behind this definition is that it'll catch
* any uses of REJECT which flex missed.
@ -409,6 +409,7 @@ static const flex_int16_t yy_chk[41] = {
#line 20 "comments.l"
#include "flutter/tools/licenses_cpp/src/comments.h"
#include "flutter/tools/licenses_cpp/src/comments_util.h"
#pragma clang diagnostic ignored "-Wsign-compare"
#pragma clang diagnostic ignored "-Wunused-function"
#pragma clang diagnostic ignored "-Wunused-function"
@ -420,9 +421,9 @@ struct LexerContext {
std::function<void(std::string_view)> callback;
std::string buffer;
};
#line 461 "comments.cc"
#line 462 "comments.cc"
#line 463 "comments.cc"
#line 464 "comments.cc"
#define INITIAL 0
#define C_COMMENT 1
@ -678,9 +679,9 @@ YY_DECL {
}
{
#line 39 "comments.l"
#line 40 "comments.l"
#line 728 "comments.cc"
#line 729 "comments.cc"
while (/*CONSTCOND*/ 1) /* loops until end-of-file is reached */
{
@ -705,7 +706,7 @@ YY_DECL {
}
while (yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state) {
yy_current_state = (int)yy_def[yy_current_state];
if (yy_current_state >= 27)
if (yy_current_state >= 28)
yy_c = yy_meta[yy_c];
}
yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c];
@ -734,18 +735,18 @@ YY_DECL {
case 1:
YY_RULE_SETUP
#line 40 "comments.l"
#line 41 "comments.l"
{
BEGIN(BLOCK);
yyextra->buffer.append(yytext, yyleng);
CommentsUtil::AddTrimLine(&yyextra->buffer, yytext, yyleng);
}
YY_BREAK
case 2:
/* rule 2 can match eol */
YY_RULE_SETUP
#line 44 "comments.l"
#line 45 "comments.l"
{
BEGIN(C_COMMENT);
yyextra->buffer.append(yytext, yyleng);
}
YY_BREAK
@ -754,7 +755,6 @@ YY_DECL {
#line 50 "comments.l"
{
BEGIN(INITIAL);
yyextra->buffer.append(yytext, yyleng);
yyextra->callback(yyextra->buffer);
yyextra->buffer.clear();
}
@ -762,7 +762,7 @@ YY_DECL {
case 4:
/* rule 4 can match eol */
YY_RULE_SETUP
#line 56 "comments.l"
#line 55 "comments.l"
{
yyextra->buffer.append(yytext, yyleng);
}
@ -771,9 +771,10 @@ YY_DECL {
case 5:
/* rule 5 can match eol */
YY_RULE_SETUP
#line 62 "comments.l"
#line 61 "comments.l"
{
yyextra->buffer.append(yytext, yyleng);
yyextra->buffer.append("\n", 1);
CommentsUtil::AddTrimLine(&yyextra->buffer, yytext + 1, yyleng - 1);
}
YY_BREAK
case 6:
@ -799,7 +800,7 @@ YY_DECL {
#line 73 "comments.l"
ECHO;
YY_BREAK
#line 853 "comments.cc"
#line 854 "comments.cc"
case YY_STATE_EOF(INITIAL):
case YY_STATE_EOF(C_COMMENT):
case YY_STATE_EOF(BLOCK):
@ -1079,7 +1080,7 @@ static yy_state_type yy_get_previous_state(yyscan_t yyscanner) {
}
while (yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state) {
yy_current_state = (int)yy_def[yy_current_state];
if (yy_current_state >= 27)
if (yy_current_state >= 28)
yy_c = yy_meta[yy_c];
}
yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c];
@ -1107,11 +1108,11 @@ static yy_state_type yy_try_NUL_trans(yy_state_type yy_current_state,
}
while (yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state) {
yy_current_state = (int)yy_def[yy_current_state];
if (yy_current_state >= 27)
if (yy_current_state >= 28)
yy_c = yy_meta[yy_c];
}
yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c];
yy_is_jam = (yy_current_state == 26);
yy_is_jam = (yy_current_state == 27);
(void)yyg;
return yy_is_jam ? 0 : yy_current_state;

View File

@ -18,6 +18,7 @@
%{
#include "flutter/tools/licenses_cpp/src/comments.h"
#include "flutter/tools/licenses_cpp/src/comments_util.h"
#pragma clang diagnostic ignored "-Wsign-compare"
#pragma clang diagnostic ignored "-Wunused-function"
#pragma clang diagnostic ignored "-Wunused-function"
@ -39,17 +40,15 @@ COMMENT_START (\/\/|#)
%%
^[ \t]*{COMMENT_START}[^\n]* {
BEGIN(BLOCK);
yyextra->buffer.append(yytext, yyleng);
CommentsUtil::AddTrimLine(&yyextra->buffer, yytext, yyleng);
}
^[ \t]*\/\* {
^[ \t]*\/\*[\n]? {
BEGIN(C_COMMENT);
yyextra->buffer.append(yytext, yyleng);
}
<C_COMMENT>{
\*\/ {
BEGIN(INITIAL);
yyextra->buffer.append(yytext, yyleng);
yyextra->callback(yyextra->buffer);
yyextra->buffer.clear();
}
@ -60,7 +59,8 @@ COMMENT_START (\/\/|#)
<BLOCK>{
\n[ \t]*{COMMENT_START}[^\n]* {
yyextra->buffer.append(yytext, yyleng);
yyextra->buffer.append("\n", 1);
CommentsUtil::AddTrimLine(&yyextra->buffer, yytext + 1, yyleng - 1);
}
\n|. {
BEGIN(INITIAL);

View File

@ -17,7 +17,7 @@ TEST(CommentsTest, Simple) {
});
ASSERT_EQ(comments.size(), 1u);
EXPECT_EQ(comments[0], "// Hello");
EXPECT_EQ(comments[0], "Hello");
}
TEST(CommentsTest, Nothing) {
@ -47,7 +47,7 @@ dfdd
});
ASSERT_EQ(comments.size(), 1u);
EXPECT_EQ(comments[0], "/*\nhello world\n*/");
EXPECT_EQ(comments[0], "hello world\n");
}
TEST(CommentsTest, MultilineCpp) {
@ -64,5 +64,5 @@ daa
});
ASSERT_EQ(comments.size(), 1u);
EXPECT_EQ(comments[0], "// hello\n// world");
EXPECT_EQ(comments[0], "hello\nworld");
}

View File

@ -0,0 +1,15 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "flutter/tools/licenses_cpp/src/comments_util.h"
#include "flutter/third_party/re2/re2/re2.h"
void CommentsUtil::AddTrimLine(std::string* buffer,
const char* text,
size_t length) {
RE2 regex(R"regex(^(?:\s*//\s?)(.*))regex");
re2::StringPiece captured_content;
RE2::FullMatch(re2::StringPiece(text), regex, &captured_content);
buffer->append(captured_content);
}

View File

@ -0,0 +1,16 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef FLUTTER_TOOLS_LICENSES_CPP_SRC_COMMENTS_UTIL_H_
#define FLUTTER_TOOLS_LICENSES_CPP_SRC_COMMENTS_UTIL_H_
#include <string>
/// @brief Helper functions for the generated comments lexer.
class CommentsUtil {
public:
static void AddTrimLine(std::string* buffer, const char* text, size_t length);
};
#endif // FLUTTER_TOOLS_LICENSES_CPP_SRC_COMMENTS_UTIL_H_

View File

@ -6,8 +6,11 @@
#include <unistd.h>
#include <filesystem>
#include <iostream>
#include <vector>
#include "flutter/third_party/abseil-cpp/absl/container/btree_map.h"
#include "flutter/third_party/abseil-cpp/absl/container/flat_hash_set.h"
#include "flutter/third_party/abseil-cpp/absl/log/log.h"
#include "flutter/third_party/abseil-cpp/absl/status/statusor.h"
#include "flutter/third_party/re2/re2/re2.h"
@ -92,9 +95,53 @@ void PrintProgress(size_t idx, size_t count) {
bool IsStdoutTerminal() {
return isatty(STDOUT_FILENO);
}
class LicensesWriter {
public:
explicit LicensesWriter(std::ostream& licenses) : licenses_(licenses) {}
void Write(const absl::flat_hash_set<std::string>& packages,
std::string_view license) {
std::vector<std::string_view> sorted;
sorted.reserve(packages.size());
sorted.insert(sorted.end(), packages.begin(), packages.end());
std::sort(sorted.begin(), sorted.end());
if (!first_write_) {
for (int i = 0; i < 80; ++i) {
licenses_.put('-');
}
licenses_.put('\n');
}
first_write_ = false;
for (std::string_view package : sorted) {
licenses_ << package << "\n";
}
licenses_ << "\n" << license << "\n";
}
private:
std::ostream& licenses_;
bool first_write_ = true;
};
std::string GetPackageName(const fs::path& full_path) {
std::optional<std::string> result;
bool after_third_party = false;
for (const fs::path& component : full_path) {
if (after_third_party) {
result = component;
after_third_party = false;
} else if (component.string() == "third_party") {
after_third_party = true;
}
}
return result.has_value() ? result.value() : "engine";
}
} // namespace
std::vector<absl::Status> LicenseChecker::Run(std::string_view working_dir,
std::ostream& licenses,
const Data& data) {
std::vector<absl::Status> errors;
std::vector<fs::path> git_repos = GetGitRepos(working_dir);
@ -117,6 +164,8 @@ std::vector<absl::Status> LicenseChecker::Run(std::string_view working_dir,
errors.push_back(git_files.status());
return errors;
}
LicensesWriter writer(licenses);
absl::btree_map<std::string, absl::flat_hash_set<std::string>> license_map;
for (const std::string& git_file : git_files.value()) {
bool did_find_copyright = false;
fs::path full_path = git_repo / git_file;
@ -125,6 +174,9 @@ std::vector<absl::Status> LicenseChecker::Run(std::string_view working_dir,
// Ignore file.
continue;
}
std::string package = GetPackageName(full_path);
VLOG(1) << full_path.string();
absl::StatusOr<MMapFile> file = MMapFile::Make(full_path.string());
if (!file.ok()) {
@ -144,6 +196,16 @@ std::vector<absl::Status> LicenseChecker::Run(std::string_view working_dir,
if (RE2::PartialMatch(comment, pattern, &match)) {
did_find_copyright = true;
VLOG(1) << comment;
auto package_emplace_result = license_map.try_emplace(
comment, absl::flat_hash_set<std::string>());
absl::flat_hash_set<std::string>& comment_set =
package_emplace_result.first->second;
if (comment_set.find(package) != comment_set.end()) {
// License is already seen.
return;
}
comment_set.emplace(std::string(package));
}
});
if (!did_find_copyright) {
@ -151,6 +213,9 @@ std::vector<absl::Status> LicenseChecker::Run(std::string_view working_dir,
absl::NotFoundError("Expected copyright in " + full_path.string()));
}
}
for (const auto& comment_entry : license_map) {
writer.Write(comment_entry.second, comment_entry.first);
}
}
if (IsStdoutTerminal()) {
PrintProgress(count++, git_repos.size());
@ -161,6 +226,7 @@ std::vector<absl::Status> LicenseChecker::Run(std::string_view working_dir,
}
int LicenseChecker::Run(std::string_view working_dir,
std::ostream& licenses,
std::string_view data_dir) {
absl::StatusOr<Data> data = Data::Open(data_dir);
if (!data.ok()) {
@ -168,7 +234,7 @@ int LicenseChecker::Run(std::string_view working_dir,
<< std::endl;
return 1;
}
std::vector<absl::Status> errors = Run(working_dir, data.value());
std::vector<absl::Status> errors = Run(working_dir, licenses, data.value());
for (const absl::Status& status : errors) {
std::cerr << status << std::endl;
}

View File

@ -5,6 +5,7 @@
#ifndef FLUTTER_TOOLS_LICENSES_CPP_SRC_LICENSE_CHECKER_H_
#define FLUTTER_TOOLS_LICENSES_CPP_SRC_LICENSE_CHECKER_H_
#include <iosfwd>
#include <string_view>
#include <vector>
#include "flutter/third_party/abseil-cpp/absl/status/status.h"
@ -14,8 +15,11 @@ class LicenseChecker {
public:
static const char* kHeaderLicenseRegex;
static std::vector<absl::Status> Run(std::string_view working_dir,
std::ostream& licenses,
const Data& data);
static int Run(std::string_view working_dir, std::string_view data_dir);
static int Run(std::string_view working_dir,
std::ostream& licenses,
std::string_view data_dir);
};
#endif // FLUTTER_TOOLS_LICENSES_CPP_SRC_LICENSE_CHECKER_H_

View File

@ -56,6 +56,15 @@ void main() {
}
)header";
const char* kCHeader = R"header(
/*
C Copyright Test
*/
void main() {
}
)header";
const char* kLicense = R"lic(
Test License
v2.0
@ -117,12 +126,13 @@ TEST_F(LicenseCheckerTest, SimplePass) {
ASSERT_EQ(std::system("git add LICENSE"), 0);
ASSERT_EQ(std::system("git commit -m \"test\""), 0);
std::stringstream ss;
std::vector<absl::Status> errors =
LicenseChecker::Run(temp_path->string(), *data);
LicenseChecker::Run(temp_path->string(), ss, *data);
EXPECT_EQ(errors.size(), 0u);
}
TEST_F(LicenseCheckerTest, SimpleFailure) {
TEST_F(LicenseCheckerTest, SimpleMissingLicense) {
absl::StatusOr<fs::path> temp_path = MakeTempDir();
ASSERT_TRUE(temp_path.ok());
@ -135,11 +145,134 @@ TEST_F(LicenseCheckerTest, SimpleFailure) {
ASSERT_EQ(std::system("git add main.cc"), 0);
ASSERT_EQ(std::system("git commit -m \"test\""), 0);
std::stringstream ss;
std::vector<absl::Status> errors =
LicenseChecker::Run(temp_path->string(), *data);
LicenseChecker::Run(temp_path->string(), ss, *data);
EXPECT_EQ(errors.size(), 2u);
EXPECT_TRUE(
FindError(errors, absl::StatusCode::kNotFound, "Expected LICENSE at"));
EXPECT_TRUE(FindError(errors, absl::StatusCode::kNotFound,
"Expected copyright in.*main.cc"));
}
TEST_F(LicenseCheckerTest, SimpleWritesLicensesFile) {
absl::StatusOr<fs::path> temp_path = MakeTempDir();
ASSERT_TRUE(temp_path.ok());
absl::StatusOr<Data> data = MakeTestData();
ASSERT_TRUE(data.ok());
fs::current_path(*temp_path);
ASSERT_EQ(std::system("git init"), 0);
ASSERT_TRUE(WriteFile(kHeader, *temp_path / "main.cc").ok());
ASSERT_TRUE(WriteFile(kLicense, *temp_path / "LICENSE").ok());
ASSERT_EQ(std::system("git add main.cc"), 0);
ASSERT_EQ(std::system("git add LICENSE"), 0);
ASSERT_EQ(std::system("git commit -m \"test\""), 0);
std::stringstream ss;
std::vector<absl::Status> errors =
LicenseChecker::Run(temp_path->string(), ss, *data);
EXPECT_EQ(errors.size(), 0u);
EXPECT_EQ(ss.str(), R"output(engine
Copyright Test
)output");
}
TEST_F(LicenseCheckerTest, SimpleWritesTwoLicensesFiles) {
absl::StatusOr<fs::path> temp_path = MakeTempDir();
ASSERT_TRUE(temp_path.ok());
absl::StatusOr<Data> data = MakeTestData();
ASSERT_TRUE(data.ok());
fs::current_path(*temp_path);
ASSERT_EQ(std::system("git init"), 0);
ASSERT_TRUE(WriteFile(kHeader, *temp_path / "main.cc").ok());
ASSERT_TRUE(WriteFile(kCHeader, *temp_path / "cmain.cc").ok());
ASSERT_TRUE(WriteFile(kLicense, *temp_path / "LICENSE").ok());
ASSERT_EQ(std::system("git add main.cc"), 0);
ASSERT_EQ(std::system("git add cmain.cc"), 0);
ASSERT_EQ(std::system("git add LICENSE"), 0);
ASSERT_EQ(std::system("git commit -m \"test\""), 0);
std::stringstream ss;
std::vector<absl::Status> errors =
LicenseChecker::Run(temp_path->string(), ss, *data);
EXPECT_EQ(errors.size(), 0u);
EXPECT_EQ(ss.str(), R"output(engine
C Copyright Test
--------------------------------------------------------------------------------
engine
Copyright Test
)output");
}
TEST_F(LicenseCheckerTest, SimpleWritesDuplicateLicensesFiles) {
absl::StatusOr<fs::path> temp_path = MakeTempDir();
ASSERT_TRUE(temp_path.ok());
absl::StatusOr<Data> data = MakeTestData();
ASSERT_TRUE(data.ok());
fs::current_path(*temp_path);
ASSERT_EQ(std::system("git init"), 0);
ASSERT_TRUE(WriteFile(kHeader, *temp_path / "a.cc").ok());
ASSERT_TRUE(WriteFile(kHeader, *temp_path / "b.cc").ok());
ASSERT_TRUE(WriteFile(kLicense, *temp_path / "LICENSE").ok());
ASSERT_EQ(std::system("git add a.cc"), 0);
ASSERT_EQ(std::system("git add b.cc"), 0);
ASSERT_EQ(std::system("git add LICENSE"), 0);
ASSERT_EQ(std::system("git commit -m \"test\""), 0);
std::stringstream ss;
std::vector<absl::Status> errors =
LicenseChecker::Run(temp_path->string(), ss, *data);
EXPECT_EQ(errors.size(), 0u);
EXPECT_EQ(ss.str(), R"output(engine
Copyright Test
)output");
}
TEST_F(LicenseCheckerTest, MultiplePackages) {
absl::StatusOr<fs::path> temp_path = MakeTempDir();
ASSERT_TRUE(temp_path.ok());
absl::StatusOr<Data> data = MakeTestData();
ASSERT_TRUE(data.ok());
fs::current_path(*temp_path);
ASSERT_EQ(std::system("mkdir -p third_party/foobar"), 0);
ASSERT_EQ(std::system("git init"), 0);
ASSERT_TRUE(WriteFile(kHeader, *temp_path / "a.cc").ok());
ASSERT_TRUE(
WriteFile(kHeader, *temp_path / "third_party" / "foobar" / "b.cc").ok());
ASSERT_TRUE(WriteFile(kLicense, *temp_path / "LICENSE").ok());
ASSERT_TRUE(
WriteFile(kLicense, *temp_path / "third_party" / "foobar" / "LICENSE")
.ok());
ASSERT_EQ(std::system("git add a.cc"), 0);
ASSERT_EQ(std::system("git add third_party/foobar/b.cc"), 0);
ASSERT_EQ(std::system("git add third_party/foobar/LICENSE"), 0);
ASSERT_EQ(std::system("git add LICENSE"), 0);
ASSERT_EQ(std::system("git commit -m \"test\""), 0);
std::stringstream ss;
std::vector<absl::Status> errors =
LicenseChecker::Run(temp_path->string(), ss, *data);
EXPECT_EQ(errors.size(), 0u);
EXPECT_EQ(ss.str(), R"output(engine
foobar
Copyright Test
)output");
}

View File

@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <fstream>
#include "flutter/third_party/abseil-cpp/absl/flags/flag.h"
#include "flutter/third_party/abseil-cpp/absl/flags/parse.h"
#include "flutter/third_party/abseil-cpp/absl/flags/usage.h"
@ -17,6 +19,10 @@ ABSL_FLAG(std::optional<std::string>,
data_dir,
std::nullopt,
"[REQUIRED] The directory with the licenses.");
ABSL_FLAG(std::optional<std::string>,
licenses_path,
std::nullopt,
"[REQUIRED] The path to write the licenses collection to.");
ABSL_FLAG(int, v, 0, "Set the verbosity of logs.");
int main(int argc, char** argv) {
@ -29,8 +35,16 @@ int main(int argc, char** argv) {
std::optional<std::string> working_dir = absl::GetFlag(FLAGS_working_dir);
std::optional<std::string> data_dir = absl::GetFlag(FLAGS_working_dir);
if (working_dir.has_value() && data_dir.has_value()) {
return LicenseChecker::Run(working_dir.value(), data_dir.value());
std::optional<std::string> licenses_path = absl::GetFlag(FLAGS_licenses_path);
if (working_dir.has_value() && data_dir.has_value() &&
licenses_path.has_value()) {
std::ofstream licenses;
licenses.open(licenses_path.value());
if (licenses.bad()) {
std::cerr << "Unable to write to '" << licenses_path.value() << "'.";
return 1;
}
return LicenseChecker::Run(working_dir.value(), licenses, data_dir.value());
}
if (!working_dir.has_value()) {