/* * Copyright (C) 2021 The Android Open Source Project * * 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. */ #pragma once #include #include #include #include #include #include #include #include #include "common/libs/utils/result.h" #include "common/libs/utils/type_name.h" namespace cuttlefish { template class Feature { public: virtual ~Feature() = default; virtual std::string Name() const = 0; static Result TopologicalVisit( const std::unordered_set& features, const std::function(Subclass*)>& callback); private: virtual std::unordered_set Dependencies() const = 0; }; class SetupFeature : public virtual Feature { public: virtual ~SetupFeature(); static Result RunSetup(const std::vector& features); virtual bool Enabled() const = 0; private: virtual Result ResultSetup() = 0; }; template class ReturningSetupFeature : public SetupFeature { public: ReturningSetupFeature() { if constexpr (std::is_void_v) { calculated_ = false; } else { calculated_ = {}; } } template std::enable_if_t, S&> operator*() { CHECK(calculated_.has_value()) << "precondition violation"; return *calculated_; } template std::enable_if_t, const S&> operator*() const { CHECK(calculated_.has_value()) << "precondition violation"; return *calculated_; } template std::enable_if_t, S*> operator->() { CHECK(calculated_.has_value()) << "precondition violation"; return &*calculated_; } template std::enable_if_t, const S*> operator->() const { CHECK(calculated_.has_value()) << "precondition violation"; return &*calculated_; } template std::enable_if_t, S> Move() { CHECK(calculated_.has_value()) << "precondition violation"; return std::move(*calculated_); } private: Result ResultSetup() override final { if constexpr (std::is_void_v) { CF_EXPECT(!calculated_, "precondition violation"); CF_EXPECT(Calculate()); calculated_ = true; } else { CF_EXPECT(!calculated_.has_value(), "precondition violation"); calculated_ = CF_EXPECT(Calculate()); } return {}; } virtual Result Calculate() = 0; std::conditional_t, bool, std::optional> calculated_; }; class FlagFeature : public Feature { public: static Result ProcessFlags(const std::vector& features, std::vector& flags); static bool WriteGflagsHelpXml(const std::vector& features, std::ostream& out); private: // Must be executed in dependency order following Dependencies(). Expected to // mutate the `flags` argument to remove handled flags, and possibly introduce // new flag values (e.g. from a file). virtual Result Process(std::vector& flags) = 0; // TODO(schuffelen): Migrate the xml help to human-readable help output after // the gflags migration is done. // Write an xml fragment that is compatible with gflags' `--helpxml` format. virtual bool WriteGflagsCompatHelpXml(std::ostream& out) const = 0; }; template Result Feature::TopologicalVisit( const std::unordered_set& features, const std::function(Subclass*)>& callback) { enum class Status { UNVISITED, VISITING, VISITED }; std::unordered_map features_status; for (const auto& feature : features) { features_status[feature] = Status::UNVISITED; } std::function(Subclass*)> visit; visit = [&callback, &features_status, &visit](Subclass* feature) -> Result { CF_EXPECT(features_status.count(feature) > 0, "Dependency edge to " << feature->Name() << " but it is not part of the feature " << "graph. This feature is either disabled or not correctly " << "registered."); if (features_status[feature] == Status::VISITED) { return {}; } CF_EXPECT(features_status[feature] != Status::VISITING, "Cycle detected while visiting " << feature->Name()); features_status[feature] = Status::VISITING; for (const auto& dependency : feature->Dependencies()) { CF_EXPECT(dependency != nullptr, "SetupFeature " << feature->Name() << " has a null dependency."); CF_EXPECT(visit(dependency), "Error detected while visiting " << feature->Name()); } features_status[feature] = Status::VISITED; CF_EXPECT(callback(feature), "Callback error on " << feature->Name()); return {}; }; for (const auto& feature : features) { CF_EXPECT(visit(feature)); // `visit` will log the error chain. } return {}; } template std::unordered_set SetupFeatureDeps( const std::tuple& args) { std::unordered_set deps; std::apply( [&deps](auto&&... arg) { ( [&] { using ArgType = std::remove_reference_t; if constexpr (std::is_base_of_v) { deps.insert(static_cast(&arg)); } }(), ...); }, args); return deps; } template class GenericReturningSetupFeature : public ReturningSetupFeature { public: INJECT(GenericReturningSetupFeature(Args... args)) : args_(std::forward_as_tuple(args...)) {} bool Enabled() const override { return true; } std::string Name() const override { static constexpr auto kName = ValueName(); return std::string(kName); } std::unordered_set Dependencies() const override { return SetupFeatureDeps(args_); } private: Result Calculate() override { if constexpr (std::is_void_v) { CF_EXPECT(std::apply(Fn, args_)); return {}; } else { return CF_EXPECT(std::apply(Fn, args_)); } } std::tuple args_; }; template struct GenericSetupImpl; template struct GenericSetupImpl (*)(Args...)> { using Type = GenericReturningSetupFeature; static fruit::Component< fruit::Required...>, Type> Component() { return fruit::createComponent() .template addMultibinding(); } }; template using AutoSetup = GenericSetupImpl; } // namespace cuttlefish