| // Copyright 2024 The Bazel Authors. All rights reserved. |
| // |
| // 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 |
| // |
| // http://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. |
| |
| #include <fstream> |
| #include <iostream> |
| #include <memory> |
| #include <ostream> |
| #include <string> |
| #include <vector> |
| |
| #include "absl/container/flat_hash_map.h" |
| #include "absl/container/flat_hash_set.h" |
| #include "absl/log/die_if_null.h" |
| #include "absl/strings/str_split.h" |
| #include "absl/strings/string_view.h" |
| #include "src/tools/one_version/allowlist.h" |
| #include "src/tools/one_version/duplicate_class_collector.h" |
| #include "src/tools/one_version/one_version.h" |
| #include "src/tools/singlejar/input_jar.h" |
| #include "src/tools/singlejar/token_stream.h" |
| #include "src/tools/singlejar/zip_headers.h" |
| |
| // Scans a classpath and reports one version violations. |
| // |
| // usage: --output <file to touch> |
| // --inputs <jar1,label1 jar2,label2 ... jarN,labelN> |
| int main(int argc, char *argv[]) { |
| std::string output_file; |
| bool succeed_on_found_violations = false; |
| std::string allowlist_file; |
| std::vector<std::string> inputs; |
| ArgTokenStream tokens(argc - 1, argv + 1); |
| while (!tokens.AtEnd()) { |
| if (tokens.MatchAndSet("--output", &output_file) || |
| tokens.MatchAndSet("--succeed_on_found_violations", |
| &succeed_on_found_violations) || |
| tokens.MatchAndSet("--allowlist", &allowlist_file) || |
| tokens.MatchAndSet("--inputs", &inputs)) { |
| } else { |
| std::cerr << "error: bad command line argument " << tokens.token() |
| << std::endl; |
| return 1; |
| } |
| } |
| |
| std::unique_ptr<one_version::Allowlist> allowlist; |
| if (allowlist_file.empty()) { |
| allowlist = std::make_unique<one_version::MapAllowlist>( |
| absl::flat_hash_map<std::string, absl::flat_hash_set<std::string>>()); |
| } else { |
| std::ifstream in(allowlist_file); |
| if (!in) { |
| std::cerr << "error: unable to open allowlist file: " << allowlist_file |
| << std::endl; |
| return 1; |
| } |
| absl::flat_hash_map<std::string, absl::flat_hash_set<std::string>> map; |
| std::string line; |
| while (std::getline(in, line)) { |
| std::vector<std::string> parts = |
| absl::StrSplit(line, absl::MaxSplits(' ', 1)); |
| if (parts.size() != 2) { |
| std::cerr << "error: expected <package> <label>, got: " << line |
| << std::endl; |
| return 1; |
| } |
| map[parts[0]].insert(parts[1]); |
| } |
| allowlist = std::make_unique<one_version::MapAllowlist>(std::move(map)); |
| } |
| one_version::OneVersion one_version(std::move(allowlist)); |
| |
| for (const std::string &input : inputs) { |
| std::vector<std::string> pieces = absl::StrSplit(input, ','); |
| if (pieces.size() != 2) { |
| std::cerr << "error: expected <jar path>,<label>, got: " << input |
| << std::endl; |
| return 1; |
| } |
| std::string jar = pieces[0]; |
| std::string label = pieces[1]; |
| InputJar input_jar; |
| if (!input_jar.Open(jar)) { |
| std::cerr << "error: unable to open: " << jar << std::endl; |
| return 1; |
| } |
| const CDH *dir_entry; |
| const LH *local_header; |
| while ((dir_entry = input_jar.NextEntry(&local_header))) { |
| absl::string_view file_name(ABSL_DIE_IF_NULL(local_header)->file_name(), |
| local_header->file_name_length()); |
| one_version.Add(file_name, dir_entry, |
| one_version::Label(label, jar, /*allowlisted=*/false)); |
| } |
| input_jar.Close(); |
| } |
| |
| std::vector<one_version::Violation> violations = one_version.Report(); |
| |
| // Touch the output file, which exists because blaze actions are required to |
| // have outputs but is always empty. |
| std::ofstream o(output_file.c_str()); |
| if (!violations.empty()) { |
| // TODO(cushon): support other classpaths (runtime, android resources) |
| std::cerr << "Found one definition violations on the runtime classpath:\n" |
| << one_version::DuplicateClassCollector::Report(violations); |
| return succeed_on_found_violations ? 0 : 1; |
| } |
| |
| return 0; |
| } |