Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
persistent_cache.cc
Go to the documentation of this file.
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "flutter/common/graphics/persistent_cache.h"
6
7#include <future>
8#include <memory>
9#include <string>
10#include <string_view>
11#include <utility>
12
13#include "flutter/fml/base32.h"
14#include "flutter/fml/file.h"
15#include "flutter/fml/hex_codec.h"
16#include "flutter/fml/logging.h"
17#include "flutter/fml/make_copyable.h"
18#include "flutter/fml/mapping.h"
19#include "flutter/fml/paths.h"
20#include "flutter/fml/trace_event.h"
21#include "flutter/shell/common/base64.h"
22#include "flutter/shell/version/version.h"
23#include "openssl/sha.h"
24#include "rapidjson/document.h"
26
27namespace flutter {
28
29std::string PersistentCache::cache_base_path_;
30
31std::shared_ptr<AssetManager> PersistentCache::asset_manager_;
32
33std::mutex PersistentCache::instance_mutex_;
34std::unique_ptr<PersistentCache> PersistentCache::gPersistentCache;
35
37 if (key.data() == nullptr || key.size() == 0) {
38 return "";
39 }
40
41 uint8_t sha_digest[SHA_DIGEST_LENGTH];
42 SHA1(static_cast<const uint8_t*>(key.data()), key.size(), sha_digest);
43
44 std::string_view view(reinterpret_cast<const char*>(sha_digest),
45 SHA_DIGEST_LENGTH);
46 return fml::HexEncode(view);
47}
48
50
51std::atomic<bool> PersistentCache::cache_sksl_ = false;
52std::atomic<bool> PersistentCache::strategy_set_ = false;
53
55 if (strategy_set_ && value != cache_sksl_) {
56 FML_LOG(ERROR) << "Cache SkSL can only be set before the "
57 "GrContextOptions::fShaderCacheStrategy is set.";
58 return;
59 }
60 cache_sksl_ = value;
61}
62
64 std::scoped_lock lock(instance_mutex_);
65 if (gPersistentCache == nullptr) {
66 gPersistentCache.reset(new PersistentCache(gIsReadOnly));
67 }
68 return gPersistentCache.get();
69}
70
72 std::scoped_lock lock(instance_mutex_);
73 gPersistentCache.reset(new PersistentCache(gIsReadOnly));
74 strategy_set_ = false;
75}
76
78 cache_base_path_ = std::move(path);
79}
80
82 // Make sure that this is called after the worker task runner setup so all the
83 // file system modifications would happen on that single thread to avoid
84 // racing.
85 FML_CHECK(GetWorkerTaskRunner());
86
87 std::promise<bool> removed;
88 GetWorkerTaskRunner()->PostTask([&removed,
89 cache_directory = cache_directory_]() {
90 if (cache_directory->is_valid()) {
91 // Only remove files but not directories.
92 FML_LOG(INFO) << "Purge persistent cache.";
93 fml::FileVisitor delete_file = [](const fml::UniqueFD& directory,
94 const std::string& filename) {
95 // Do not delete directories. Return true to continue with other files.
96 if (fml::IsDirectory(directory, filename.c_str())) {
97 return true;
98 }
99 return fml::UnlinkFile(directory, filename.c_str());
100 };
101 removed.set_value(VisitFilesRecursively(*cache_directory, delete_file));
102 } else {
103 removed.set_value(false);
104 }
105 });
106 return removed.get_future().get();
107}
108
109namespace {
110
111constexpr char kEngineComponent[] = "flutter_engine";
112
113static void FreeOldCacheDirectory(const fml::UniqueFD& cache_base_dir) {
114 fml::UniqueFD engine_dir =
115 fml::OpenDirectoryReadOnly(cache_base_dir, kEngineComponent);
116 if (!engine_dir.is_valid()) {
117 return;
118 }
119 fml::VisitFiles(engine_dir, [](const fml::UniqueFD& directory,
120 const std::string& filename) {
121 if (filename != GetFlutterEngineVersion()) {
122 auto dir = fml::OpenDirectory(directory, filename.c_str(), false,
124 if (dir.is_valid()) {
125 fml::RemoveDirectoryRecursively(directory, filename.c_str());
126 }
127 }
128 return true;
129 });
130}
131
132static std::shared_ptr<fml::UniqueFD> MakeCacheDirectory(
133 const std::string& global_cache_base_path,
134 bool read_only,
135 bool cache_sksl) {
136 fml::UniqueFD cache_base_dir;
137 if (global_cache_base_path.length()) {
138 cache_base_dir = fml::OpenDirectory(global_cache_base_path.c_str(), false,
140 } else {
141 cache_base_dir = fml::paths::GetCachesDirectory();
142 }
143
144 if (cache_base_dir.is_valid()) {
145 FreeOldCacheDirectory(cache_base_dir);
146 std::vector<std::string> components = {
147 kEngineComponent, GetFlutterEngineVersion(), "skia", GetSkiaVersion()};
148 if (cache_sksl) {
149 components.push_back(PersistentCache::kSkSLSubdirName);
150 }
151 return std::make_shared<fml::UniqueFD>(
152 CreateDirectory(cache_base_dir, components,
155 } else {
156 return std::make_shared<fml::UniqueFD>();
157 }
158}
159} // namespace
160
161sk_sp<SkData> ParseBase32(const std::string& input) {
162 std::pair<bool, std::string> decode_result = fml::Base32Decode(input);
163 if (!decode_result.first) {
164 FML_LOG(ERROR) << "Base32 can't decode: " << input;
165 return nullptr;
166 }
167 const std::string& data_string = decode_result.second;
168 return SkData::MakeWithCopy(data_string.data(), data_string.length());
169}
170
171sk_sp<SkData> ParseBase64(const std::string& input) {
173
174 size_t output_len;
175 error = Base64::Decode(input.c_str(), input.length(), nullptr, &output_len);
176 if (error != Base64::Error::kNone) {
177 FML_LOG(ERROR) << "Base64 decode error: " << (int)error;
178 FML_LOG(ERROR) << "Base64 can't decode: " << input;
179 return nullptr;
180 }
181
183 void* output = data->writable_data();
184 error = Base64::Decode(input.c_str(), input.length(), output, &output_len);
185 if (error != Base64::Error::kNone) {
186 FML_LOG(ERROR) << "Base64 decode error: " << (int)error;
187 FML_LOG(ERROR) << "Base64 can't decode: " << input;
188 return nullptr;
189 }
190
191 return data;
192}
193
194size_t PersistentCache::PrecompileKnownSkSLs(GrDirectContext* context) const {
195 // clang-tidy has trouble reasoning about some of the complicated array and
196 // pointer-arithmetic code in rapidjson.
197 // NOLINTNEXTLINE(clang-analyzer-cplusplus.PlacementNew)
198 auto known_sksls = LoadSkSLs();
199 // A trace must be present even if no precompilations have been completed.
200 FML_TRACE_EVENT("flutter", "PersistentCache::PrecompileKnownSkSLs", "count",
201 known_sksls.size());
202
203 if (context == nullptr) {
204 return 0;
205 }
206
207 size_t precompiled_count = 0;
208 for (const auto& sksl : known_sksls) {
209 TRACE_EVENT0("flutter", "PrecompilingSkSL");
210 if (context->precompileShader(*sksl.key, *sksl.value)) {
211 precompiled_count++;
212 }
213 }
214
215 FML_TRACE_COUNTER("flutter", "PersistentCache::PrecompiledSkSLs",
216 reinterpret_cast<int64_t>(this), // Trace Counter ID
217 "Successful", precompiled_count);
218 return precompiled_count;
219}
220
221std::vector<PersistentCache::SkSLCache> PersistentCache::LoadSkSLs() const {
222 TRACE_EVENT0("flutter", "PersistentCache::LoadSkSLs");
223 std::vector<PersistentCache::SkSLCache> result;
224 fml::FileVisitor visitor = [&result](const fml::UniqueFD& directory,
225 const std::string& filename) {
226 SkSLCache cache = LoadFile(directory, filename, true);
227 if (cache.key != nullptr && cache.value != nullptr) {
228 result.push_back(cache);
229 } else {
230 FML_LOG(ERROR) << "Failed to load: " << filename;
231 }
232 return true;
233 };
234
235 // Only visit sksl_cache_directory_ if this persistent cache is valid.
236 // However, we'd like to continue visit the asset dir even if this persistent
237 // cache is invalid.
238 if (IsValid()) {
239 // In case `rewinddir` doesn't work reliably, load SkSLs from a freshly
240 // opened directory (https://github.com/flutter/flutter/issues/65258).
241 fml::UniqueFD fresh_dir =
242 fml::OpenDirectoryReadOnly(*cache_directory_, kSkSLSubdirName);
243 if (fresh_dir.is_valid()) {
244 fml::VisitFiles(fresh_dir, visitor);
245 }
246 }
247
248 std::unique_ptr<fml::Mapping> mapping = nullptr;
249 if (asset_manager_ != nullptr) {
250 mapping = asset_manager_->GetAsMapping(kAssetFileName);
251 }
252 if (mapping == nullptr) {
253 FML_LOG(INFO) << "No sksl asset found.";
254 } else {
255 FML_LOG(INFO) << "Found sksl asset. Loading SkSLs from it...";
256 rapidjson::Document json_doc;
257 rapidjson::ParseResult parse_result =
258 json_doc.Parse(reinterpret_cast<const char*>(mapping->GetMapping()),
259 mapping->GetSize());
260 if (parse_result.IsError()) {
261 FML_LOG(ERROR) << "Failed to parse json file: " << kAssetFileName;
262 } else {
263 for (auto& item : json_doc["data"].GetObject()) {
264 sk_sp<SkData> key = ParseBase32(item.name.GetString());
265 sk_sp<SkData> sksl = ParseBase64(item.value.GetString());
266 if (key != nullptr && sksl != nullptr) {
267 result.push_back({key, sksl});
268 } else {
269 FML_LOG(ERROR) << "Failed to load: " << item.name.GetString();
270 }
271 }
272 }
273 }
274
275 return result;
276}
277
278PersistentCache::PersistentCache(bool read_only)
279 : is_read_only_(read_only),
280 cache_directory_(MakeCacheDirectory(cache_base_path_, read_only, false)),
281 sksl_cache_directory_(
282 MakeCacheDirectory(cache_base_path_, read_only, true)) {
283 if (!IsValid()) {
284 FML_LOG(WARNING) << "Could not acquire the persistent cache directory. "
285 "Caching of GPU resources on disk is disabled.";
286 }
287}
288
290
291bool PersistentCache::IsValid() const {
292 return cache_directory_ && cache_directory_->is_valid();
293}
294
295PersistentCache::SkSLCache PersistentCache::LoadFile(
296 const fml::UniqueFD& dir,
297 const std::string& file_name,
298 bool need_key) {
299 SkSLCache result;
300 auto file = fml::OpenFileReadOnly(dir, file_name.c_str());
301 if (!file.is_valid()) {
302 return result;
303 }
304 auto mapping = std::make_unique<fml::FileMapping>(file);
305 if (mapping->GetSize() < sizeof(CacheObjectHeader)) {
306 return result;
307 }
308 const CacheObjectHeader* header =
309 reinterpret_cast<const CacheObjectHeader*>(mapping->GetMapping());
310 if (header->signature != CacheObjectHeader::kSignature ||
312 FML_LOG(INFO) << "Persistent cache header is corrupt: " << file_name;
313 return result;
314 }
315 if (mapping->GetSize() < sizeof(CacheObjectHeader) + header->key_size) {
316 FML_LOG(INFO) << "Persistent cache size is corrupt: " << file_name;
317 return result;
318 }
319 if (need_key) {
321 mapping->GetMapping() + sizeof(CacheObjectHeader), header->key_size);
322 }
323 size_t value_offset = sizeof(CacheObjectHeader) + header->key_size;
324 result.value = SkData::MakeWithCopy(mapping->GetMapping() + value_offset,
325 mapping->GetSize() - value_offset);
326 return result;
327}
328
329// |GrContextOptions::PersistentCache|
331 TRACE_EVENT0("flutter", "PersistentCacheLoad");
332 if (!IsValid()) {
333 return nullptr;
334 }
335 auto file_name = SkKeyToFilePath(key);
336 if (file_name.empty()) {
337 return nullptr;
338 }
339 auto result =
340 PersistentCache::LoadFile(*cache_directory_, file_name, false).value;
341 if (result != nullptr) {
342 TRACE_EVENT0("flutter", "PersistentCacheLoadHit");
343 }
344 return result;
345}
346
348 const fml::RefPtr<fml::TaskRunner>& worker,
349 const std::shared_ptr<fml::UniqueFD>& cache_directory,
350 std::string key,
351 std::unique_ptr<fml::Mapping> value) {
352 // The static leak checker gets confused by the use of fml::MakeCopyable.
353 // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
354 auto task = fml::MakeCopyable([cache_directory, //
355 file_name = std::move(key), //
356 mapping = std::move(value) //
357 ]() mutable {
358 TRACE_EVENT0("flutter", "PersistentCacheStore");
359 if (!fml::WriteAtomically(*cache_directory, //
360 file_name.c_str(), //
361 *mapping) //
362 ) {
363 FML_LOG(WARNING) << "Could not write cache contents to persistent store.";
364 }
365 });
366
367 if (!worker) {
368 FML_LOG(WARNING)
369 << "The persistent cache has no available workers. Performing the task "
370 "on the current thread. This slow operation is going to occur on a "
371 "frame workload.";
372 task();
373 } else {
374 worker->PostTask(std::move(task));
375 }
376}
377
378std::unique_ptr<fml::MallocMapping> PersistentCache::BuildCacheObject(
379 const SkData& key,
380 const SkData& data) {
381 size_t total_size = sizeof(CacheObjectHeader) + key.size() + data.size();
382 uint8_t* mapping_buf = reinterpret_cast<uint8_t*>(malloc(total_size));
383 if (!mapping_buf) {
384 return nullptr;
385 }
386 auto mapping = std::make_unique<fml::MallocMapping>(mapping_buf, total_size);
387
389 memcpy(mapping_buf, &header, sizeof(CacheObjectHeader));
390 mapping_buf += sizeof(CacheObjectHeader);
391 memcpy(mapping_buf, key.data(), key.size());
392 mapping_buf += key.size();
393 memcpy(mapping_buf, data.data(), data.size());
394
395 return mapping;
396}
397
398// |GrContextOptions::PersistentCache|
400 stored_new_shaders_ = true;
401
402 if (is_read_only_) {
403 return;
404 }
405
406 if (!IsValid()) {
407 return;
408 }
409
410 auto file_name = SkKeyToFilePath(key);
411
412 if (file_name.empty()) {
413 return;
414 }
415
416 std::unique_ptr<fml::MallocMapping> mapping = BuildCacheObject(key, data);
417 if (!mapping) {
418 return;
419 }
420
421 PersistentCacheStore(GetWorkerTaskRunner(),
422 cache_sksl_ ? sksl_cache_directory_ : cache_directory_,
423 std::move(file_name), std::move(mapping));
424}
425
427 if (is_read_only_ || !IsValid()) {
428 FML_LOG(ERROR) << "Could not dump SKP from read-only or invalid persistent "
429 "cache.";
430 return;
431 }
432
433 std::stringstream name_stream;
435 name_stream << "shader_dump_" << std::to_string(ticks) << ".skp";
436 std::string file_name = name_stream.str();
437 FML_LOG(INFO) << "Dumping " << file_name;
438 auto mapping = std::make_unique<fml::DataMapping>(
439 std::vector<uint8_t>{data.bytes(), data.bytes() + data.size()});
440 PersistentCacheStore(GetWorkerTaskRunner(), cache_directory_,
441 std::move(file_name), std::move(mapping));
442}
443
445 const fml::RefPtr<fml::TaskRunner>& task_runner) {
446 std::scoped_lock lock(worker_task_runners_mutex_);
447 worker_task_runners_.insert(task_runner);
448}
449
451 const fml::RefPtr<fml::TaskRunner>& task_runner) {
452 std::scoped_lock lock(worker_task_runners_mutex_);
453 auto found = worker_task_runners_.find(task_runner);
454 if (found != worker_task_runners_.end()) {
455 worker_task_runners_.erase(found);
456 }
457}
458
459fml::RefPtr<fml::TaskRunner> PersistentCache::GetWorkerTaskRunner() const {
461
462 std::scoped_lock lock(worker_task_runners_mutex_);
463 if (!worker_task_runners_.empty()) {
464 worker = *worker_task_runners_.begin();
465 }
466
467 return worker;
468}
469
470void PersistentCache::SetAssetManager(std::shared_ptr<AssetManager> value) {
471 TRACE_EVENT_INSTANT0("flutter", "PersistentCache::SetAssetManager");
472 asset_manager_ = std::move(value);
473}
474
475std::vector<std::unique_ptr<fml::Mapping>>
477 if (!asset_manager_) {
479 << "PersistentCache::GetSkpsFromAssetManager: Asset manager not set!";
480 return std::vector<std::unique_ptr<fml::Mapping>>();
481 }
482 return asset_manager_->GetAsMappings(".*\\.skp$", "shaders");
483}
484
485} // namespace flutter
static size_t total_size(SkSBlockAllocator< N > &pool)
Type::kYUV Type::kRGBA() int(0.7 *637)
bool precompileShader(const SkData &key, const SkData &data)
static sk_sp< SkData > MakeUninitialized(size_t length)
Definition SkData.cpp:116
static sk_sp< SkData > MakeWithCopy(const void *data, size_t length)
Definition SkData.cpp:111
static void SetAssetManager(std::shared_ptr< AssetManager > value)
void RemoveWorkerTaskRunner(const fml::RefPtr< fml::TaskRunner > &task_runner)
std::vector< std::unique_ptr< fml::Mapping > > GetSkpsFromAssetManager() const
static void SetCacheDirectoryPath(std::string path)
sk_sp< SkData > load(const SkData &key) override
static PersistentCache * GetCacheForProcess()
void DumpSkp(const SkData &data)
static void SetCacheSkSL(bool value)
void AddWorkerTaskRunner(const fml::RefPtr< fml::TaskRunner > &task_runner)
void store(const SkData &key, const SkData &data) override
static std::string SkKeyToFilePath(const SkData &key)
static std::unique_ptr< fml::MallocMapping > BuildCacheObject(const SkData &key, const SkData &data)
virtual void PostTask(const fml::closure &task) override
constexpr int64_t ToNanoseconds() const
Definition time_delta.h:61
TimeDelta ToEpochDelta() const
Definition time_point.h:52
static TimePoint Now()
Definition time_point.cc:49
bool is_valid() const
const uint8_t uint32_t uint32_t GError ** error
uint8_t value
GAsyncResult * result
#define FML_LOG(severity)
Definition logging.h:82
#define FML_CHECK(condition)
Definition logging.h:85
const char * GetSkiaVersion()
Definition version.cc:15
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified vm service A custom Dart VM Service port The default is to pick a randomly available open port disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode disable vm service Disable mDNS Dart VM Service publication Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set endless trace Enable an endless trace buffer The default is a ring buffer This is useful when very old events need to viewed For during application launch Memory usage will continue to grow indefinitely however Start app with an specific route defined on the framework flutter assets Path to the Flutter assets directory enable service port Allow the VM service to fallback to automatic port selection if binding to a specified port fails trace Trace early application lifecycle Automatically switches to an endless trace buffer trace skia Filters out all Skia trace event categories except those that are specified in this comma separated list dump skp on shader Automatically dump the skp that triggers new shader compilations This is useful for writing custom ShaderWarmUp to reduce jank By this is not enabled to reduce the overhead purge persistent cache
Definition switches.h:191
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir path
Definition switches.h:57
const char * GetFlutterEngineVersion()
Definition version.cc:11
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified vm service A custom Dart VM Service port The default is to pick a randomly available open port disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode disable vm service Disable mDNS Dart VM Service publication Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set endless trace Enable an endless trace buffer The default is a ring buffer This is useful when very old events need to viewed For during application launch Memory usage will continue to grow indefinitely however Start app with an specific route defined on the framework flutter assets Path to the Flutter assets directory enable service port Allow the VM service to fallback to automatic port selection if binding to a specified port fails trace Trace early application lifecycle Automatically switches to an endless trace buffer trace skia Filters out all Skia trace event categories except those that are specified in this comma separated list dump skp on shader Automatically dump the skp that triggers new shader compilations This is useful for writing custom ShaderWarmUp to reduce jank By this is not enabled to reduce the overhead purge persistent Remove all existing persistent cache This is mainly for debugging purposes such as reproducing the shader compilation jank trace to file
Definition switches.h:201
sk_sp< SkData > ParseBase32(const std::string &input)
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot data
Definition switches.h:41
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified vm service A custom Dart VM Service port The default is to pick a randomly available open port disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode disable vm service Disable mDNS Dart VM Service publication Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set endless trace Enable an endless trace buffer The default is a ring buffer This is useful when very old events need to viewed For during application launch Memory usage will continue to grow indefinitely however Start app with an specific route defined on the framework flutter assets dir
Definition switches.h:145
sk_sp< SkData > ParseBase64(const std::string &input)
static void PersistentCacheStore(const fml::RefPtr< fml::TaskRunner > &worker, const std::shared_ptr< fml::UniqueFD > &cache_directory, std::string key, std::unique_ptr< fml::Mapping > value)
fml::UniqueFD GetCachesDirectory()
std::string HexEncode(std::string_view input)
Definition hex_codec.cc:14
fml::UniqueFD OpenFileReadOnly(const fml::UniqueFD &base_directory, const char *path)
Definition file.cc:92
bool VisitFiles(const fml::UniqueFD &directory, const FileVisitor &visitor)
bool WriteAtomically(const fml::UniqueFD &base_directory, const char *file_name, const Mapping &mapping)
fml::UniqueFD OpenDirectoryReadOnly(const fml::UniqueFD &base_directory, const char *path)
Definition file.cc:97
std::pair< bool, std::string > Base32Decode(const std::string &input)
Definition base32.cc:55
fml::UniqueFD OpenDirectory(const char *path, bool create_if_necessary, FilePermission permission)
Definition file_posix.cc:97
internal::CopyableLambda< T > MakeCopyable(T lambda)
bool RemoveDirectoryRecursively(const fml::UniqueFD &parent, const char *directory_name)
Definition file.cc:120
FilePermission
Definition file.h:24
std::function< bool(const fml::UniqueFD &directory, const std::string &filename)> FileVisitor
Definition file.h:98
static const char header[]
Definition skpbench.cpp:88
#define ERROR(message)
#define TRACE_EVENT0(category_group, name)
#define FML_TRACE_COUNTER(category_group, name, counter_id, arg1,...)
Definition trace_event.h:85
#define TRACE_EVENT_INSTANT0(category_group, name)
#define FML_TRACE_EVENT(category_group, name,...)
#define CreateDirectory