mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
License cpp jun16 (#170716)
The big additions here are: 1) Checking against the catalog of known licenses 1) Parsing of the known catalogs from disk 1) Nicer error messages With this PR we are getting pretty close to feature parity. The big changes left are updating the contents of of the data directory so we match what we want. Execution across the directory is still around 35s. Known extra work: 1) Propagate the `data` directory 1) Make sure the auditable output is looking as we want it 1) Make sure to treat the root directory different where we want to enforce the presence of the file copyright 1) Remove hardcoded name "engine" for the root directory (that means shifting all the integration tests to run in a `engine` directory. 1) Maybe cleanup the file format for the known licenses? ## 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:
parent
9c3a133e4f
commit
556b20e7c3
@ -0,0 +1,31 @@
|
||||
google
|
||||
^Copyright \(c\) \d+ Google Inc
|
||||
Copyright \(c\) \d+ Google Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
\* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
\* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
\* Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES \(INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION\) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
\(INCLUDING NEGLIGENCE OR OTHERWISE\) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@ -0,0 +1,29 @@
|
||||
google commented
|
||||
^// Copyright \(c\) \d+ Google Inc
|
||||
// Copyright \(c\) \d+ Google Inc. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// \* Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// \* Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// \* Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES \(INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION\) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// \(INCLUDING NEGLIGENCE OR OTHERWISE\) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@ -4,28 +4,77 @@
|
||||
|
||||
#include "flutter/tools/licenses_cpp/src/catalog.h"
|
||||
|
||||
absl::StatusOr<Catalog> Catalog::Open(std::string_view data_dir) {
|
||||
return absl::UnimplementedError("");
|
||||
}
|
||||
#include <fstream>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
absl::StatusOr<Catalog> Catalog::Open(std::string_view data_dir) {
|
||||
fs::path data_dir_path(data_dir);
|
||||
if (!fs::exists(data_dir_path)) {
|
||||
return absl::InvalidArgumentError(
|
||||
absl::StrCat("Data directory doesn't exist ", data_dir));
|
||||
}
|
||||
fs::path licenses_path = data_dir_path / "licenses";
|
||||
if (!fs::exists(licenses_path)) {
|
||||
return absl::InvalidArgumentError(absl::StrCat(
|
||||
"Licenses directory doesn't exist ", licenses_path.string()));
|
||||
}
|
||||
|
||||
absl::StatusOr<Catalog> Catalog::Make(
|
||||
const std::vector<std::vector<std::string_view>>& entries) {
|
||||
RE2::Set selector(RE2::Options(), RE2::Anchor::UNANCHORED);
|
||||
std::vector<std::unique_ptr<RE2>> matchers;
|
||||
std::vector<std::string> names;
|
||||
|
||||
for (const std::vector<std::string_view>& entry : entries) {
|
||||
if (entry.size() != 3) {
|
||||
return absl::InvalidArgumentError("Entry doesn't have 3 items");
|
||||
for (const fs::path& file : fs::directory_iterator(licenses_path)) {
|
||||
std::ifstream infile(file.string());
|
||||
if (!infile.good()) {
|
||||
return absl::InvalidArgumentError("Unable to open file " + file.string());
|
||||
}
|
||||
|
||||
absl::StatusOr<Entry> entry = ParseEntry(infile);
|
||||
if (!entry.ok()) {
|
||||
return absl::InvalidArgumentError(
|
||||
absl::StrCat("Unable to parse data entry at ", file.string(), " : ",
|
||||
entry.status()));
|
||||
}
|
||||
|
||||
std::string err;
|
||||
names.push_back(std::string(entry[0]));
|
||||
int idx = selector.Add(entry[1], &err);
|
||||
selector.Add(entry->unique, &err);
|
||||
if (!err.empty()) {
|
||||
return absl::InvalidArgumentError(absl::StrCat(
|
||||
"Unable to add unique key from ", file.string(), " : ", err));
|
||||
}
|
||||
names.emplace_back(std::move(entry->name));
|
||||
|
||||
auto matcher_re2 = std::make_unique<RE2>(entry->matcher);
|
||||
if (!matcher_re2) {
|
||||
return absl::InvalidArgumentError("Unable to make matcher.");
|
||||
}
|
||||
|
||||
matchers.emplace_back(std::move(matcher_re2));
|
||||
}
|
||||
|
||||
bool did_compile = selector.Compile();
|
||||
if (!did_compile) {
|
||||
return absl::UnknownError("Unable to compile selector.");
|
||||
}
|
||||
|
||||
return Catalog(std::move(selector), std::move(matchers), std::move(names));
|
||||
}
|
||||
|
||||
absl::StatusOr<Catalog> Catalog::Make(const std::vector<Entry>& entries) {
|
||||
RE2::Set selector(RE2::Options(), RE2::Anchor::UNANCHORED);
|
||||
std::vector<std::unique_ptr<RE2>> matchers;
|
||||
std::vector<std::string> names;
|
||||
|
||||
for (const Entry& entry : entries) {
|
||||
std::string err;
|
||||
names.push_back(std::string(entry.name));
|
||||
int idx = selector.Add(entry.unique, &err);
|
||||
if (idx < 0) {
|
||||
return absl::InvalidArgumentError(
|
||||
absl::StrCat("Unable to add set entry: ", entry[1], " ", err));
|
||||
absl::StrCat("Unable to add set entry: ", entry.unique, " ", err));
|
||||
}
|
||||
matchers.push_back(std::make_unique<RE2>(entry[2]));
|
||||
matchers.push_back(std::make_unique<RE2>(entry.matcher));
|
||||
}
|
||||
|
||||
bool did_compile = selector.Compile();
|
||||
@ -42,7 +91,8 @@ Catalog::Catalog(RE2::Set selector,
|
||||
matchers_(std::move(matchers)),
|
||||
names_(std::move(names)) {}
|
||||
|
||||
absl::StatusOr<std::string> Catalog::FindMatch(std::string_view query) {
|
||||
absl::StatusOr<Catalog::Match> Catalog::FindMatch(
|
||||
std::string_view query) const {
|
||||
std::vector<int> selector_results;
|
||||
if (!selector_.Match(query, &selector_results)) {
|
||||
return absl::NotFoundError("Selector didn't match.");
|
||||
@ -56,10 +106,39 @@ absl::StatusOr<std::string> Catalog::FindMatch(std::string_view query) {
|
||||
return absl::InvalidArgumentError(ss.str());
|
||||
}
|
||||
|
||||
std::string_view match_text;
|
||||
RE2* matcher = matchers_[selector_results[0]].get();
|
||||
if (selector_results.size() == 1 &&
|
||||
RE2::FullMatch(query, *matchers_[selector_results[0]])) {
|
||||
return names_[selector_results[0]];
|
||||
matcher->Match(query, 0, query.length(), RE2::Anchor::UNANCHORED,
|
||||
&match_text,
|
||||
/*nsubmatch=*/1)) {
|
||||
return Match{.matcher = names_[selector_results[0]],
|
||||
.matched_text = match_text};
|
||||
} else {
|
||||
return absl::NotFoundError("Selection didn't match.");
|
||||
return absl::NotFoundError(absl::StrCat(
|
||||
"Selected matcher (", names_[selector_results[0]], ") didn't match."));
|
||||
}
|
||||
}
|
||||
|
||||
absl::StatusOr<Catalog::Entry> Catalog::ParseEntry(std::istream& is) {
|
||||
if (!is.good()) {
|
||||
return absl::InvalidArgumentError("Bad stream.");
|
||||
}
|
||||
std::string name;
|
||||
std::getline(is, name);
|
||||
if (is.eof()) {
|
||||
return absl::InvalidArgumentError("Bad stream.");
|
||||
}
|
||||
std::string unique;
|
||||
std::getline(is, unique);
|
||||
if (is.eof()) {
|
||||
return absl::InvalidArgumentError("Bad stream.");
|
||||
}
|
||||
|
||||
std::string matcher_text((std::istreambuf_iterator<char>(is)),
|
||||
std::istreambuf_iterator<char>());
|
||||
|
||||
return Catalog::Entry{.name = std::move(name),
|
||||
.unique = std::move(unique),
|
||||
.matcher = std::move(matcher_text)};
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
#include "flutter/third_party/re2/re2/re2.h"
|
||||
#include "flutter/third_party/re2/re2/set.h"
|
||||
|
||||
#include <iosfwd>
|
||||
#include <optional>
|
||||
|
||||
/// A storage of licenses that can be matched against.
|
||||
@ -19,20 +20,32 @@
|
||||
/// that. This approach was chosen to minimize the size of the RE2::Set.
|
||||
class Catalog {
|
||||
public:
|
||||
/// VisibleForTesting
|
||||
struct Entry {
|
||||
std::string name;
|
||||
std::string unique;
|
||||
std::string matcher;
|
||||
};
|
||||
|
||||
struct Match {
|
||||
std::string_view matcher;
|
||||
std::string_view matched_text;
|
||||
};
|
||||
|
||||
static absl::StatusOr<Catalog> Open(std::string_view data_dir);
|
||||
|
||||
/// Make a Catalog for testing.
|
||||
/// The format is [[<name>, <unique regex>, <full regex>]*] where the unique
|
||||
/// regex should only match one license.
|
||||
static absl::StatusOr<Catalog> Make(
|
||||
const std::vector<std::vector<std::string_view>>& entries);
|
||||
static absl::StatusOr<Catalog> Make(const std::vector<Entry>& entries);
|
||||
|
||||
/// @brief Tries to identify a match for the `query` across the `Catalog`.
|
||||
/// @param query The text that will be matched against. @return
|
||||
/// absl::StatusCode::kNotFound when a match can't be found.
|
||||
/// @param query The text that will be matched against.
|
||||
/// @return absl::StatusCode::kNotFound when a match can't be found.
|
||||
/// absl::StatusCode::kInvalidArgument if more than one match comes up from
|
||||
/// the selector.
|
||||
absl::StatusOr<std::string> FindMatch(std::string_view query);
|
||||
absl::StatusOr<Match> FindMatch(std::string_view query) const;
|
||||
|
||||
/// VisibleForTesting
|
||||
static absl::StatusOr<Entry> ParseEntry(std::istream& is);
|
||||
|
||||
private:
|
||||
explicit Catalog(RE2::Set selector,
|
||||
|
||||
@ -4,20 +4,85 @@
|
||||
#include "flutter/tools/licenses_cpp/src/catalog.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
static const char* kEntry = R"entry(google
|
||||
Copyright \(c\) \d+ Google Inc
|
||||
Copyright \(c\) \d+ Google Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
\* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
\* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
\* Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES \(INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION\) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
\(INCLUDING NEGLIGENCE OR OTHERWISE\) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
)entry";
|
||||
|
||||
static const char* kSkiaLicense =
|
||||
R"entry(Copyright (c) 2011 Google Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
* Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
)entry";
|
||||
|
||||
TEST(CatalogTest, Simple) {
|
||||
absl::StatusOr<Catalog> catalog =
|
||||
Catalog::Make({{"foobar", ".*foo.*", ".*foo.*"}});
|
||||
ASSERT_TRUE(catalog.ok());
|
||||
absl::StatusOr<std::string> match = catalog->FindMatch("foo");
|
||||
absl::StatusOr<Catalog::Match> match = catalog->FindMatch("foo");
|
||||
ASSERT_TRUE(match.ok());
|
||||
ASSERT_EQ(*match, "foobar");
|
||||
ASSERT_EQ(match->matcher, "foobar");
|
||||
}
|
||||
|
||||
TEST(CatalogTest, MultipleMatch) {
|
||||
absl::StatusOr<Catalog> catalog =
|
||||
Catalog::Make({{"foobar", ".*foo.*", ""}, {"oo", ".*oo.*", ""}});
|
||||
ASSERT_TRUE(catalog.ok()) << catalog.status();
|
||||
absl::StatusOr<std::string> has_match = catalog->FindMatch("foo");
|
||||
absl::StatusOr<Catalog::Match> has_match = catalog->FindMatch("foo");
|
||||
ASSERT_FALSE(has_match.ok());
|
||||
ASSERT_TRUE(RE2::PartialMatch(has_match.status().message(),
|
||||
"Multiple unique matches found"))
|
||||
@ -30,7 +95,7 @@ TEST(CatalogTest, NoSelectorMatch) {
|
||||
absl::StatusOr<Catalog> catalog =
|
||||
Catalog::Make({{"foobar", ".*bar.*", ".*foo.*"}});
|
||||
ASSERT_TRUE(catalog.ok());
|
||||
absl::StatusOr<std::string> match = catalog->FindMatch("foo");
|
||||
absl::StatusOr<Catalog::Match> match = catalog->FindMatch("foo");
|
||||
ASSERT_FALSE(match.ok());
|
||||
ASSERT_EQ(match.status().code(), absl::StatusCode::kNotFound);
|
||||
}
|
||||
@ -39,7 +104,37 @@ TEST(CatalogTest, NoSelectionMatch) {
|
||||
absl::StatusOr<Catalog> catalog =
|
||||
Catalog::Make({{"foobar", ".*foo.*", ".*bar.*"}});
|
||||
ASSERT_TRUE(catalog.ok());
|
||||
absl::StatusOr<std::string> match = catalog->FindMatch("foo");
|
||||
absl::StatusOr<Catalog::Match> match = catalog->FindMatch("foo");
|
||||
ASSERT_FALSE(match.ok());
|
||||
ASSERT_EQ(match.status().code(), absl::StatusCode::kNotFound);
|
||||
}
|
||||
|
||||
TEST(CatalogTest, SimpleParseEntry) {
|
||||
std::stringstream ss;
|
||||
ss << "foobar\n";
|
||||
ss << "unique\n";
|
||||
ss << R"match(Multiline
|
||||
matcher
|
||||
.*)match";
|
||||
|
||||
absl::StatusOr<Catalog::Entry> entry = Catalog::ParseEntry(ss);
|
||||
EXPECT_TRUE(entry.ok()) << entry.status();
|
||||
if (entry.ok()) {
|
||||
EXPECT_EQ(entry->name, "foobar");
|
||||
EXPECT_EQ(entry->unique, "unique");
|
||||
EXPECT_EQ(entry->matcher, R"match(Multiline
|
||||
matcher
|
||||
.*)match");
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CatalogTest, SkiaLicense) {
|
||||
std::stringstream ss;
|
||||
ss << kEntry;
|
||||
absl::StatusOr<Catalog::Entry> entry = Catalog::ParseEntry(ss);
|
||||
ASSERT_TRUE(entry.ok()) << entry.status();
|
||||
absl::StatusOr<Catalog> catalog = Catalog::Make({*entry});
|
||||
ASSERT_TRUE(catalog.ok());
|
||||
absl::StatusOr<Catalog::Match> match = catalog->FindMatch(kSkiaLicense);
|
||||
EXPECT_TRUE(match.ok()) << match.status();
|
||||
}
|
||||
|
||||
@ -26,6 +26,14 @@ absl::StatusOr<Data> Data::Open(std::string_view data_dir) {
|
||||
exclude_path.string() + ": " +
|
||||
include_filter.status().ToString());
|
||||
}
|
||||
absl::StatusOr<Catalog> catalog = Catalog::Open(data_dir);
|
||||
if (!catalog.ok()) {
|
||||
return absl::InvalidArgumentError("Can't open catalog at " +
|
||||
exclude_path.string() + ": " +
|
||||
catalog.status().ToString());
|
||||
}
|
||||
|
||||
return Data{.include_filter = std::move(*include_filter),
|
||||
.exclude_filter = std::move(*exclude_filter)};
|
||||
.exclude_filter = std::move(*exclude_filter),
|
||||
.catalog = std::move(*catalog)};
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
#define FLUTTER_TOOLS_LICENSES_CPP_SRC_DATA_H_
|
||||
|
||||
#include "flutter/third_party/abseil-cpp/absl/status/statusor.h"
|
||||
#include "flutter/tools/licenses_cpp/src/catalog.h"
|
||||
#include "flutter/tools/licenses_cpp/src/filter.h"
|
||||
|
||||
/// In memory representation of the contents of the data directory
|
||||
@ -15,6 +16,7 @@ struct Data {
|
||||
static absl::StatusOr<Data> Open(std::string_view data_dir);
|
||||
Filter include_filter;
|
||||
Filter exclude_filter;
|
||||
Catalog catalog;
|
||||
};
|
||||
|
||||
#endif // FLUTTER_TOOLS_LICENSES_CPP_SRC_DATA_H_
|
||||
|
||||
@ -152,17 +152,6 @@ Package GetPackage(const fs::path& working_dir, const fs::path& full_path) {
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string ReadFile(const fs::path& path) {
|
||||
std::ifstream stream(path);
|
||||
assert(stream.good());
|
||||
std::string license((std::istreambuf_iterator<char>(stream)),
|
||||
std::istreambuf_iterator<char>());
|
||||
if (license[license.size() - 1] == '\n') {
|
||||
license.pop_back();
|
||||
}
|
||||
return license;
|
||||
}
|
||||
|
||||
class LicenseMap {
|
||||
public:
|
||||
void Add(std::string_view package, std::string_view license) {
|
||||
@ -176,13 +165,6 @@ class LicenseMap {
|
||||
}
|
||||
}
|
||||
|
||||
void AddFile(std::string_view package, const fs::path& path) {
|
||||
if (!license_files_.contains(path.string())) {
|
||||
Add(package, ReadFile(path));
|
||||
license_files_.insert(path);
|
||||
}
|
||||
}
|
||||
|
||||
void Write(std::ostream& licenses) {
|
||||
LicensesWriter writer(licenses);
|
||||
for (const auto& comment_entry : map_) {
|
||||
@ -194,6 +176,40 @@ class LicenseMap {
|
||||
absl::btree_map<std::string, absl::flat_hash_set<std::string>> map_;
|
||||
absl::flat_hash_set<std::string> license_files_;
|
||||
};
|
||||
|
||||
/// Checks the a license against known licenses and potentially adds it to the
|
||||
/// license map.
|
||||
/// @param path Path of the license file to check.
|
||||
/// @param package Package the license file belongs to.
|
||||
/// @param data The Data catalog of known licenses.
|
||||
/// @param license_map The LicenseMap tracking seen licenses.
|
||||
/// @return OkStatus if the license is known and successfully written to the
|
||||
/// catalog.
|
||||
absl::Status MatchLicenseFile(const fs::path& path,
|
||||
const Package& package,
|
||||
const Data& data,
|
||||
LicenseMap* license_map) {
|
||||
if (!package.license_file.has_value()) {
|
||||
return absl::InvalidArgumentError("No license file.");
|
||||
}
|
||||
absl::StatusOr<MMapFile> license = MMapFile::Make(path.string());
|
||||
if (!license.ok()) {
|
||||
return license.status();
|
||||
} else {
|
||||
absl::StatusOr<Catalog::Match> match = data.catalog.FindMatch(
|
||||
std::string_view(license->GetData(), license->GetSize()));
|
||||
|
||||
if (match.ok()) {
|
||||
license_map->Add(package.name, match->matched_text);
|
||||
} else {
|
||||
return absl::NotFoundError(absl::StrCat("Unknown license in ",
|
||||
package.license_file->string(),
|
||||
" : ", match.status().message()));
|
||||
}
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::vector<absl::Status> LicenseChecker::Run(std::string_view working_dir,
|
||||
@ -207,6 +223,7 @@ std::vector<absl::Status> LicenseChecker::Run(std::string_view working_dir,
|
||||
|
||||
size_t count = 0;
|
||||
LicenseMap license_map;
|
||||
absl::flat_hash_set<fs::path> seen_license_files;
|
||||
for (const fs::path& git_repo : git_repos) {
|
||||
if (IsStdoutTerminal()) {
|
||||
PrintProgress(count++, git_repos.size());
|
||||
@ -228,7 +245,15 @@ std::vector<absl::Status> LicenseChecker::Run(std::string_view working_dir,
|
||||
|
||||
Package package = GetPackage(working_dir_path, full_path);
|
||||
if (package.license_file.has_value()) {
|
||||
license_map.AddFile(package.name, package.license_file.value());
|
||||
auto [_, is_new_item] =
|
||||
seen_license_files.insert(package.license_file.value());
|
||||
if (is_new_item) {
|
||||
absl::Status match_status = MatchLicenseFile(
|
||||
package.license_file.value(), package, data, &license_map);
|
||||
if (!match_status.ok()) {
|
||||
errors.emplace_back(std::move(match_status));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VLOG(1) << full_path.string();
|
||||
@ -243,18 +268,26 @@ std::vector<absl::Status> LicenseChecker::Run(std::string_view working_dir,
|
||||
return errors;
|
||||
}
|
||||
}
|
||||
IterateComments(file->GetData(), file->GetSize(),
|
||||
[&](std::string_view comment) {
|
||||
VLOG(2) << comment;
|
||||
re2::StringPiece match;
|
||||
if (RE2::PartialMatch(comment, pattern, &match)) {
|
||||
did_find_copyright = true;
|
||||
VLOG(1) << comment;
|
||||
if (!package.license_file.has_value()) {
|
||||
license_map.Add(package.name, comment);
|
||||
}
|
||||
}
|
||||
});
|
||||
IterateComments(
|
||||
file->GetData(), file->GetSize(), [&](std::string_view comment) {
|
||||
VLOG(2) << comment;
|
||||
re2::StringPiece match;
|
||||
if (RE2::PartialMatch(comment, pattern, &match)) {
|
||||
did_find_copyright = true;
|
||||
VLOG(1) << comment;
|
||||
if (!package.license_file.has_value()) {
|
||||
absl::StatusOr<Catalog::Match> match =
|
||||
data.catalog.FindMatch(comment);
|
||||
if (match.ok()) {
|
||||
license_map.Add(package.name, match->matched_text);
|
||||
} else {
|
||||
errors.emplace_back(absl::NotFoundError(
|
||||
absl::StrCat("Unknown license in ", full_path.string(),
|
||||
" : ", match.status().message())));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!did_find_copyright && !package.license_file.has_value()) {
|
||||
errors.push_back(
|
||||
absl::NotFoundError("Expected copyright in " + full_path.string()));
|
||||
|
||||
@ -56,6 +56,13 @@ void main() {
|
||||
}
|
||||
)header";
|
||||
|
||||
const char* kUnknownHeader = R"header(
|
||||
// Unknown Copyright
|
||||
|
||||
void main() {
|
||||
}
|
||||
)header";
|
||||
|
||||
const char* kCHeader = R"header(
|
||||
/*
|
||||
C Copyright Test
|
||||
@ -69,6 +76,11 @@ const char* kLicense = R"lic(Test License
|
||||
v2.0
|
||||
)lic";
|
||||
|
||||
const char* kUnknownLicense = R"lic(Unknown License
|
||||
2025
|
||||
v2.0
|
||||
)lic";
|
||||
|
||||
absl::StatusOr<Data> MakeTestData() {
|
||||
std::stringstream include;
|
||||
include << ".*\\.cc" << std::endl;
|
||||
@ -82,19 +94,29 @@ absl::StatusOr<Data> MakeTestData() {
|
||||
if (!exclude_filter.ok()) {
|
||||
return exclude_filter.status();
|
||||
}
|
||||
|
||||
absl::StatusOr<Catalog> catalog =
|
||||
Catalog::Make({{"test", "Test License", R"lic(Test License
|
||||
v\d\.\d)lic"},
|
||||
{"header", "Copyright Test", "(?:C )?Copyright Test"}});
|
||||
if (!catalog.ok()) {
|
||||
return catalog.status();
|
||||
}
|
||||
|
||||
return Data{
|
||||
.include_filter = std::move(*include_filter),
|
||||
.exclude_filter = std::move(*exclude_filter),
|
||||
.catalog = std::move(catalog.value()),
|
||||
};
|
||||
}
|
||||
|
||||
absl::Status WriteFile(const char* data, const fs::path& path) {
|
||||
absl::Status WriteFile(std::string_view data, const fs::path& path) {
|
||||
std::ofstream of;
|
||||
of.open(path.string(), std::ios::binary);
|
||||
if (!of.good()) {
|
||||
return absl::InternalError("can't open file");
|
||||
}
|
||||
of.write(data, std::strlen(data));
|
||||
of.write(data.data(), data.length());
|
||||
of.close();
|
||||
return absl::OkStatus();
|
||||
}
|
||||
@ -152,7 +174,56 @@ TEST_F(LicenseCheckerTest, SimplePass) {
|
||||
std::stringstream ss;
|
||||
std::vector<absl::Status> errors =
|
||||
LicenseChecker::Run(temp_path->string(), ss, *data);
|
||||
EXPECT_EQ(errors.size(), 0u);
|
||||
EXPECT_EQ(errors.size(), 0u) << errors[0];
|
||||
}
|
||||
|
||||
TEST_F(LicenseCheckerTest, UnknownFileLicense) {
|
||||
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_TRUE(WriteFile(kUnknownHeader, *temp_path / "main.cc").ok());
|
||||
Repo repo;
|
||||
repo.Add(*temp_path / "main.cc");
|
||||
ASSERT_TRUE(repo.Commit().ok());
|
||||
|
||||
std::stringstream ss;
|
||||
std::vector<absl::Status> errors =
|
||||
LicenseChecker::Run(temp_path->string(), ss, *data);
|
||||
EXPECT_EQ(errors.size(), 1u);
|
||||
EXPECT_TRUE(FindError(errors, absl::StatusCode::kNotFound,
|
||||
"Unknown license in.*main.cc"))
|
||||
<< errors[0];
|
||||
}
|
||||
|
||||
TEST_F(LicenseCheckerTest, UnknownLicense) {
|
||||
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_TRUE(WriteFile(kHeader, *temp_path / "main.cc").ok());
|
||||
// Make sure the error is only reported once.
|
||||
ASSERT_TRUE(WriteFile(kHeader, *temp_path / "foo.cc").ok());
|
||||
ASSERT_TRUE(WriteFile(kUnknownLicense, *temp_path / "LICENSE").ok());
|
||||
Repo repo;
|
||||
repo.Add(*temp_path / "main.cc");
|
||||
repo.Add(*temp_path / "foo.cc");
|
||||
repo.Add(*temp_path / "LICENSE");
|
||||
ASSERT_TRUE(repo.Commit().ok());
|
||||
|
||||
std::stringstream ss;
|
||||
std::vector<absl::Status> errors =
|
||||
LicenseChecker::Run(temp_path->string(), ss, *data);
|
||||
EXPECT_EQ(errors.size(), 1u);
|
||||
EXPECT_TRUE(FindError(errors, absl::StatusCode::kNotFound,
|
||||
"Unknown license in.*LICENSE"))
|
||||
<< errors[0];
|
||||
}
|
||||
|
||||
TEST_F(LicenseCheckerTest, SimpleMissingFileLicense) {
|
||||
@ -192,7 +263,7 @@ TEST_F(LicenseCheckerTest, SimpleWritesFileLicensesFile) {
|
||||
std::stringstream ss;
|
||||
std::vector<absl::Status> errors =
|
||||
LicenseChecker::Run(temp_path->string(), ss, *data);
|
||||
EXPECT_EQ(errors.size(), 0u);
|
||||
EXPECT_EQ(errors.size(), 0u) << errors[0];
|
||||
|
||||
EXPECT_EQ(ss.str(), R"output(engine
|
||||
|
||||
@ -223,7 +294,6 @@ TEST_F(LicenseCheckerTest, SimpleWritesTwoFileLicensesFiles) {
|
||||
EXPECT_EQ(ss.str(), R"output(engine
|
||||
|
||||
C Copyright Test
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
engine
|
||||
|
||||
@ -313,7 +383,7 @@ v2.0
|
||||
)output");
|
||||
}
|
||||
|
||||
TEST_F(LicenseCheckerTest, ThirdyPartyDirectoryLicense) {
|
||||
TEST_F(LicenseCheckerTest, ThirdPartyDirectoryLicense) {
|
||||
absl::StatusOr<fs::path> temp_path = MakeTempDir();
|
||||
ASSERT_TRUE(temp_path.ok());
|
||||
|
||||
@ -348,3 +418,66 @@ Test License
|
||||
v2.0
|
||||
)output");
|
||||
}
|
||||
|
||||
TEST_F(LicenseCheckerTest, OnlyPrintMatch) {
|
||||
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_TRUE(WriteFile(kHeader, *temp_path / "main.cc").ok());
|
||||
ASSERT_TRUE(WriteFile(absl::StrCat(kLicense, "\n----------------------\n"),
|
||||
*temp_path / "LICENSE")
|
||||
.ok());
|
||||
Repo repo;
|
||||
repo.Add(*temp_path / "main.cc");
|
||||
repo.Add(*temp_path / "LICENSE");
|
||||
ASSERT_TRUE(repo.Commit().ok());
|
||||
|
||||
std::stringstream ss;
|
||||
std::vector<absl::Status> errors =
|
||||
LicenseChecker::Run(temp_path->string(), ss, *data);
|
||||
EXPECT_EQ(errors.size(), 0u) << errors[0];
|
||||
|
||||
EXPECT_EQ(ss.str(), R"output(engine
|
||||
|
||||
Test License
|
||||
v2.0
|
||||
)output");
|
||||
}
|
||||
|
||||
TEST_F(LicenseCheckerTest, OnlyPrintMatchHeader) {
|
||||
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_TRUE(WriteFile(R"header(
|
||||
// Extra text.
|
||||
// Copyright Test
|
||||
//
|
||||
// Extra text.
|
||||
|
||||
void main() {
|
||||
}
|
||||
)header",
|
||||
*temp_path / "main.cc")
|
||||
.ok());
|
||||
Repo repo;
|
||||
repo.Add(*temp_path / "main.cc");
|
||||
ASSERT_TRUE(repo.Commit().ok());
|
||||
|
||||
std::stringstream ss;
|
||||
std::vector<absl::Status> errors =
|
||||
LicenseChecker::Run(temp_path->string(), ss, *data);
|
||||
EXPECT_EQ(errors.size(), 0u) << errors[0];
|
||||
|
||||
EXPECT_EQ(ss.str(), R"output(engine
|
||||
|
||||
Copyright Test
|
||||
)output");
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user