diff --git a/engine/src/flutter/ci/builders/linux_unopt.json b/engine/src/flutter/ci/builders/linux_unopt.json index cbcbb38b908..b37a4c16073 100644 --- a/engine/src/flutter/ci/builders/linux_unopt.json +++ b/engine/src/flutter/ci/builders/linux_unopt.json @@ -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": [ diff --git a/engine/src/flutter/tools/licenses_cpp/BUILD.gn b/engine/src/flutter/tools/licenses_cpp/BUILD.gn index 057b13af60a..44507c022ac 100644 --- a/engine/src/flutter/tools/licenses_cpp/BUILD.gn +++ b/engine/src/flutter/tools/licenses_cpp/BUILD.gn @@ -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", diff --git a/engine/src/flutter/tools/licenses_cpp/src/comments.cc b/engine/src/flutter/tools/licenses_cpp/src/comments.cc index fc14f459b0b..08c5d5d5e88 100644 --- a/engine/src/flutter/tools/licenses_cpp/src/comments.cc +++ b/engine/src/flutter/tools/licenses_cpp/src/comments.cc @@ -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 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; diff --git a/engine/src/flutter/tools/licenses_cpp/src/comments.l b/engine/src/flutter/tools/licenses_cpp/src/comments.l index 4f47185863a..2fdaedfa500 100644 --- a/engine/src/flutter/tools/licenses_cpp/src/comments.l +++ b/engine/src/flutter/tools/licenses_cpp/src/comments.l @@ -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); } { \*\/ { BEGIN(INITIAL); - yyextra->buffer.append(yytext, yyleng); yyextra->callback(yyextra->buffer); yyextra->buffer.clear(); } @@ -60,7 +59,8 @@ COMMENT_START (\/\/|#) { \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); diff --git a/engine/src/flutter/tools/licenses_cpp/src/comments_unittests.cc b/engine/src/flutter/tools/licenses_cpp/src/comments_unittests.cc index f0e9d5ede14..635f024ff55 100644 --- a/engine/src/flutter/tools/licenses_cpp/src/comments_unittests.cc +++ b/engine/src/flutter/tools/licenses_cpp/src/comments_unittests.cc @@ -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"); } diff --git a/engine/src/flutter/tools/licenses_cpp/src/comments_util.cc b/engine/src/flutter/tools/licenses_cpp/src/comments_util.cc new file mode 100644 index 00000000000..6daa108e6c7 --- /dev/null +++ b/engine/src/flutter/tools/licenses_cpp/src/comments_util.cc @@ -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); +} diff --git a/engine/src/flutter/tools/licenses_cpp/src/comments_util.h b/engine/src/flutter/tools/licenses_cpp/src/comments_util.h new file mode 100644 index 00000000000..d5be49e787b --- /dev/null +++ b/engine/src/flutter/tools/licenses_cpp/src/comments_util.h @@ -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 + +/// @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_ diff --git a/engine/src/flutter/tools/licenses_cpp/src/license_checker.cc b/engine/src/flutter/tools/licenses_cpp/src/license_checker.cc index 8890dde59dc..c5ee1a29348 100644 --- a/engine/src/flutter/tools/licenses_cpp/src/license_checker.cc +++ b/engine/src/flutter/tools/licenses_cpp/src/license_checker.cc @@ -6,8 +6,11 @@ #include #include +#include #include +#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& packages, + std::string_view license) { + std::vector 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 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 LicenseChecker::Run(std::string_view working_dir, + std::ostream& licenses, const Data& data) { std::vector errors; std::vector git_repos = GetGitRepos(working_dir); @@ -117,6 +164,8 @@ std::vector LicenseChecker::Run(std::string_view working_dir, errors.push_back(git_files.status()); return errors; } + LicensesWriter writer(licenses); + absl::btree_map> 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 LicenseChecker::Run(std::string_view working_dir, // Ignore file. continue; } + + std::string package = GetPackageName(full_path); + VLOG(1) << full_path.string(); absl::StatusOr file = MMapFile::Make(full_path.string()); if (!file.ok()) { @@ -144,6 +196,16 @@ std::vector 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()); + absl::flat_hash_set& 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 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 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::Open(data_dir); if (!data.ok()) { @@ -168,7 +234,7 @@ int LicenseChecker::Run(std::string_view working_dir, << std::endl; return 1; } - std::vector errors = Run(working_dir, data.value()); + std::vector errors = Run(working_dir, licenses, data.value()); for (const absl::Status& status : errors) { std::cerr << status << std::endl; } diff --git a/engine/src/flutter/tools/licenses_cpp/src/license_checker.h b/engine/src/flutter/tools/licenses_cpp/src/license_checker.h index 2b70a19568f..617b552703d 100644 --- a/engine/src/flutter/tools/licenses_cpp/src/license_checker.h +++ b/engine/src/flutter/tools/licenses_cpp/src/license_checker.h @@ -5,6 +5,7 @@ #ifndef FLUTTER_TOOLS_LICENSES_CPP_SRC_LICENSE_CHECKER_H_ #define FLUTTER_TOOLS_LICENSES_CPP_SRC_LICENSE_CHECKER_H_ +#include #include #include #include "flutter/third_party/abseil-cpp/absl/status/status.h" @@ -14,8 +15,11 @@ class LicenseChecker { public: static const char* kHeaderLicenseRegex; static std::vector 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_ diff --git a/engine/src/flutter/tools/licenses_cpp/src/license_checker_unittests.cc b/engine/src/flutter/tools/licenses_cpp/src/license_checker_unittests.cc index f307ac9fa74..a64734a3086 100644 --- a/engine/src/flutter/tools/licenses_cpp/src/license_checker_unittests.cc +++ b/engine/src/flutter/tools/licenses_cpp/src/license_checker_unittests.cc @@ -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 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 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 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 temp_path = MakeTempDir(); + ASSERT_TRUE(temp_path.ok()); + + absl::StatusOr 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 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 temp_path = MakeTempDir(); + ASSERT_TRUE(temp_path.ok()); + + absl::StatusOr 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 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 temp_path = MakeTempDir(); + ASSERT_TRUE(temp_path.ok()); + + absl::StatusOr 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 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 temp_path = MakeTempDir(); + ASSERT_TRUE(temp_path.ok()); + + absl::StatusOr 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 errors = + LicenseChecker::Run(temp_path->string(), ss, *data); + EXPECT_EQ(errors.size(), 0u); + + EXPECT_EQ(ss.str(), R"output(engine +foobar + +Copyright Test +)output"); +} diff --git a/engine/src/flutter/tools/licenses_cpp/src/main.cc b/engine/src/flutter/tools/licenses_cpp/src/main.cc index c1ea91239ef..a9661be75d9 100644 --- a/engine/src/flutter/tools/licenses_cpp/src/main.cc +++ b/engine/src/flutter/tools/licenses_cpp/src/main.cc @@ -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 + #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, data_dir, std::nullopt, "[REQUIRED] The directory with the licenses."); +ABSL_FLAG(std::optional, + 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 working_dir = absl::GetFlag(FLAGS_working_dir); std::optional 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 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()) {