Repository: facebook/folly Branch: main Commit: 8539216d20b7 Files: 3157 Total size: 23.2 MB Directory structure: gitextract_rc_779ue/ ├── .buckconfig ├── .buckconfig.d/ │ └── common.buckconfig ├── .buckroot ├── .devcontainer/ │ ├── Containerfile │ └── devcontainer.json ├── .github/ │ ├── dependabot.yml │ ├── scripts/ │ │ ├── bad_targets │ │ └── buck_build_and_test.sh │ └── workflows/ │ ├── TagIt.yml │ ├── devcontainer.yml │ ├── getdeps_linux.yml │ ├── getdeps_mac.yml │ ├── getdeps_shared-libs_linux.yml │ ├── getdeps_windows.yml │ └── oss-build-and-test.yml ├── .gitignore ├── .projectid ├── BUCK ├── CMake/ │ ├── FindCython.cmake │ ├── FindFastFloat.cmake │ ├── FindFmt.cmake │ ├── FindLZ4.cmake │ ├── FindLibAIO.cmake │ ├── FindLibDwarf.cmake │ ├── FindLibUring.cmake │ ├── FindLibiberty.cmake │ ├── FindLibsodium.cmake │ ├── FindSnappy.cmake │ ├── FindZstd.cmake │ ├── FollyCompilerMSVC.cmake │ ├── FollyCompilerUnix.cmake │ ├── FollyConfigChecks.cmake │ ├── FollyFunctions.cmake │ ├── GenPkgConfig.cmake │ ├── folly-config.cmake.in │ ├── folly-config.h.cmake │ ├── folly-deps.cmake │ └── libfolly.pc.in ├── CMakeLists.txt ├── CMakeListsForBuck2.txt ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── PACKAGE ├── README.md ├── buck2 ├── build/ │ ├── buck2/ │ │ ├── README.md │ │ └── install_deps/ │ │ ├── BUCK │ │ ├── install_deps.sh │ │ └── repos/ │ │ ├── fedora │ │ ├── homebrew │ │ └── ubuntu │ └── fbcode_builder/ │ ├── .gitignore │ ├── CMake/ │ │ ├── FBBuildOptions.cmake │ │ ├── FBCMakeParseArgs.cmake │ │ ├── FBCompilerSettings.cmake │ │ ├── FBCompilerSettingsMSVC.cmake │ │ ├── FBCompilerSettingsUnix.cmake │ │ ├── FBPythonBinary.cmake │ │ ├── FBPythonTestAddTests.cmake │ │ ├── FBThriftCppLibrary.cmake │ │ ├── FBThriftLibrary.cmake │ │ ├── FBThriftPyLibrary.cmake │ │ ├── FindCares.cmake │ │ ├── FindDoubleConversion.cmake │ │ ├── FindGMock.cmake │ │ ├── FindGflags.cmake │ │ ├── FindGlog.cmake │ │ ├── FindLMDB.cmake │ │ ├── FindLibEvent.cmake │ │ ├── FindLibUnwind.cmake │ │ ├── FindLibiberty.cmake │ │ ├── FindPCRE.cmake │ │ ├── FindPCRE2.cmake │ │ ├── FindRe2.cmake │ │ ├── FindSodium.cmake │ │ ├── FindXxhash.cmake │ │ ├── FindZstd.cmake │ │ ├── Findibverbs.cmake │ │ ├── RustStaticLibrary.cmake │ │ ├── fb_py_test_main.py │ │ ├── fb_py_win_main.c │ │ └── make_fbpy_archive.py │ ├── LICENSE │ ├── README.md │ ├── getdeps/ │ │ ├── __init__.py │ │ ├── builder.py │ │ ├── buildopts.py │ │ ├── cache.py │ │ ├── cargo.py │ │ ├── copytree.py │ │ ├── dyndeps.py │ │ ├── envfuncs.py │ │ ├── errors.py │ │ ├── expr.py │ │ ├── fetcher.py │ │ ├── include_rewriter.py │ │ ├── load.py │ │ ├── manifest.py │ │ ├── platform.py │ │ ├── py_wheel_builder.py │ │ ├── runcmd.py │ │ ├── subcmd.py │ │ └── test/ │ │ ├── expr_test.py │ │ ├── fixtures/ │ │ │ └── duplicate/ │ │ │ ├── foo │ │ │ └── subdir/ │ │ │ └── foo │ │ ├── manifest_test.py │ │ ├── platform_test.py │ │ ├── retry_test.py │ │ ├── scratch_test.py │ │ └── strip_marker_test.py │ ├── getdeps.py │ ├── manifests/ │ │ ├── CLI11 │ │ ├── autoconf │ │ ├── automake │ │ ├── benchmark │ │ ├── blake3 │ │ ├── boost │ │ ├── boost-python │ │ ├── bz2 │ │ ├── c-ares │ │ ├── cabal │ │ ├── cachelib │ │ ├── cinderx-3_14 │ │ ├── cinderx-main │ │ ├── clang │ │ ├── clang19 │ │ ├── cmake │ │ ├── cpptoml │ │ ├── double-conversion │ │ ├── double-conversion-python │ │ ├── eden │ │ ├── edencommon │ │ ├── exprtk │ │ ├── fast_float │ │ ├── fatal │ │ ├── fb303 │ │ ├── fboss │ │ ├── fbthrift │ │ ├── fbthrift-python │ │ ├── fizz │ │ ├── fizz-python │ │ ├── fmt │ │ ├── fmt-python │ │ ├── folly │ │ ├── folly-python │ │ ├── gcc12 │ │ ├── gcc14 │ │ ├── gflags │ │ ├── ghc │ │ ├── git-lfs │ │ ├── glean │ │ ├── glog │ │ ├── googletest │ │ ├── gperf │ │ ├── hexdump │ │ ├── hsthrift │ │ ├── iproute2 │ │ ├── jom │ │ ├── jq │ │ ├── katran │ │ ├── libaio │ │ ├── libaio-python │ │ ├── libbpf │ │ ├── libcurl │ │ ├── libdwarf │ │ ├── libdwarf-python │ │ ├── libelf │ │ ├── libevent │ │ ├── libevent-python │ │ ├── libffi │ │ ├── libgit2 │ │ ├── libgpiod │ │ ├── libiberty │ │ ├── libiberty-python │ │ ├── libibverbs │ │ ├── libmnl │ │ ├── libnl │ │ ├── liboqs │ │ ├── libsai │ │ ├── libsodium │ │ ├── libunwind │ │ ├── libusb │ │ ├── libyaml │ │ ├── llvm │ │ ├── lmdb │ │ ├── lz4 │ │ ├── lz4-python │ │ ├── magic_enum │ │ ├── mcrouter │ │ ├── mononoke │ │ ├── mononoke_integration │ │ ├── moxygen │ │ ├── mvfst │ │ ├── mvfst-python │ │ ├── ncurses │ │ ├── nghttp2 │ │ ├── ninja │ │ ├── nlohmann-json │ │ ├── nmap │ │ ├── numa │ │ ├── openr │ │ ├── openssl │ │ ├── osxfuse │ │ ├── patchelf │ │ ├── pcre2 │ │ ├── perl │ │ ├── pexpect │ │ ├── proxygen │ │ ├── python │ │ ├── python-3_14 │ │ ├── python-click │ │ ├── python-filelock │ │ ├── python-main │ │ ├── python-psutil │ │ ├── python-ptyprocess │ │ ├── python-pyyaml │ │ ├── python-setuptools │ │ ├── python-setuptools-69 │ │ ├── python-six │ │ ├── python-toml │ │ ├── ragel │ │ ├── range-v3 │ │ ├── rdma-core │ │ ├── re2 │ │ ├── rebalancer │ │ ├── ripgrep │ │ ├── rocksdb │ │ ├── rust-shed │ │ ├── sapling │ │ ├── snappy │ │ ├── sparsemap │ │ ├── sqlite3 │ │ ├── systemd │ │ ├── tabulate │ │ ├── tree │ │ ├── wangle │ │ ├── wangle-python │ │ ├── watchman │ │ ├── xxhash │ │ ├── xz │ │ ├── yaml-cpp │ │ ├── zlib │ │ ├── zlib-python │ │ ├── zstd │ │ └── zstd-python │ └── patches/ │ ├── boost_1_83_0.patch │ ├── iproute2_oss.patch │ ├── libiberty_install_pic_lib.patch │ └── zlib_dont_build_more_than_needed.patch ├── build.bat ├── build.sh └── folly/ ├── .clang-format ├── AtomicHashArray-inl.h ├── AtomicHashArray.h ├── AtomicHashMap-inl.h ├── AtomicHashMap.h ├── AtomicIntrusiveLinkedList.h ├── AtomicLinkedList.h ├── AtomicUnorderedMap.h ├── BUCK ├── BUILD_MODE.bzl ├── Benchmark.cpp ├── Benchmark.h ├── BenchmarkUtil.cpp ├── BenchmarkUtil.h ├── Bits.h ├── CMakeLists.txt ├── CODING_GUIDELINES.md ├── CPortability.h ├── CancellationToken-inl.h ├── CancellationToken.cpp ├── CancellationToken.h ├── Chrono.h ├── ClockGettimeWrappers.cpp ├── ClockGettimeWrappers.h ├── ConcurrentBitSet.h ├── ConcurrentLazy.h ├── ConcurrentSkipList-inl.h ├── ConcurrentSkipList.h ├── ConstexprMath.h ├── ConstructorCallbackList.h ├── Conv.cpp ├── Conv.h ├── CppAttributes.h ├── CpuId.h ├── DefaultKeepAliveExecutor.h ├── Demangle.cpp ├── Demangle.h ├── DiscriminatedPtr.h ├── DynamicConverter.h ├── Exception.h ├── ExceptionString.cpp ├── ExceptionString.h ├── ExceptionWrapper-inl.h ├── ExceptionWrapper.cpp ├── ExceptionWrapper.h ├── Executor.cpp ├── Executor.h ├── Expected.h ├── FBString.h ├── FBVector.h ├── File.cpp ├── File.h ├── FileUtil.cpp ├── FileUtil.h ├── Fingerprint.cpp ├── Fingerprint.h ├── FixedString.h ├── FmtUtility.cpp ├── FmtUtility.h ├── FollyMemcpy.cpp ├── FollyMemcpy.h ├── FollyMemset.cpp ├── FollyMemset.h ├── Format-inl.h ├── Format.cpp ├── Format.h ├── FormatArg.h ├── FormatTraits.h ├── Function.h ├── GLog.h ├── GroupVarint.cpp ├── GroupVarint.h ├── Hash.h ├── IPAddress.cpp ├── IPAddress.h ├── IPAddressException.h ├── IPAddressV4.cpp ├── IPAddressV4.h ├── IPAddressV6.cpp ├── IPAddressV6.h ├── Indestructible.h ├── IndexedMemPool.h ├── IntrusiveList.h ├── Lazy.h ├── Likely.h ├── MPMCPipeline.h ├── MPMCQueue.h ├── MacAddress.cpp ├── MacAddress.h ├── MapUtil.h ├── Math.h ├── MaybeManagedPtr.h ├── Memory.h ├── MicroLock.cpp ├── MicroLock.h ├── MicroSpinLock.h ├── MoveWrapper.h ├── ObserverContainer.h ├── OperationCancelled.h ├── Optional.h ├── Overload.h ├── PackedSyncPtr.h ├── Padded.h ├── Poly-inl.h ├── Poly.h ├── PolyException.h ├── Portability.h ├── Preprocessor.h ├── ProducerConsumerQueue.h ├── RWSpinLock.h ├── Random-inl.h ├── Random.cpp ├── Random.h ├── Range.h ├── Replaceable.h ├── ScopeGuard.cpp ├── ScopeGuard.h ├── SharedMutex.cpp ├── SharedMutex.h ├── Singleton-inl.h ├── Singleton.cpp ├── Singleton.h ├── SingletonThreadLocal.cpp ├── SingletonThreadLocal.h ├── SocketAddress.cpp ├── SocketAddress.h ├── SpinLock.h ├── String-inl.h ├── String.cpp ├── String.h ├── Subprocess.cpp ├── Subprocess.h ├── Synchronized.h ├── SynchronizedPtr.h ├── ThreadCachedInt.h ├── ThreadLocal.h ├── TimeoutQueue.cpp ├── TimeoutQueue.h ├── TokenBucket.h ├── Traits.h ├── Try-inl.h ├── Try.cpp ├── Try.h ├── UTF8String.h ├── Unicode.cpp ├── Unicode.h ├── Unit.h ├── Uri-inl.h ├── Uri.cpp ├── Uri.h ├── Utility.h ├── VERSION ├── Varint.h ├── VirtualExecutor.h ├── algorithm/ │ ├── BUCK │ ├── BinaryHeap.h │ ├── CMakeLists.txt │ ├── StableRadixSort.h │ ├── simd/ │ │ ├── BUCK │ │ ├── CMakeLists.txt │ │ ├── Contains.cpp │ │ ├── Contains.h │ │ ├── FindFixed.h │ │ ├── Ignore.h │ │ ├── Movemask.h │ │ ├── detail/ │ │ │ ├── BUCK │ │ │ ├── CMakeLists.txt │ │ │ ├── ContainsImpl.h │ │ │ ├── SimdAnyOf.h │ │ │ ├── SimdForEach.h │ │ │ ├── SimdPlatform.h │ │ │ ├── Traits.h │ │ │ ├── UnrollUtils.h │ │ │ └── test/ │ │ │ ├── BUCK │ │ │ ├── SimdAnyOfTest.cpp │ │ │ ├── SimdForEachTest.cpp │ │ │ ├── TraitsTest.cpp │ │ │ └── UnrollUtilsTest.cpp │ │ ├── find_first_of.h │ │ ├── find_first_of_extra.h │ │ └── test/ │ │ ├── BUCK │ │ ├── ContainsTest.cpp │ │ ├── FindFixedBenchmark.cpp │ │ ├── FindFixedTest.cpp │ │ ├── MovemaskTest.cpp │ │ ├── find_first_of_bench.cpp │ │ └── find_first_of_test.cpp │ └── test/ │ ├── BUCK │ ├── BinaryHeapTest.cpp │ ├── StableRadixSortBenchmark.cpp │ └── StableRadixSortTest.cpp ├── base64.h ├── buck_config/ │ ├── BUCK │ └── constraints/ │ └── BUCK ├── build/ │ ├── bootstrap-osx-homebrew.sh │ └── build-debs-ubuntu-18.04.sh ├── channels/ │ ├── BUCK │ ├── CMakeLists.txt │ ├── Channel-fwd.h │ ├── Channel-inl.h │ ├── Channel.h │ ├── ChannelCallbackHandle.h │ ├── ChannelProcessor-inl.h │ ├── ChannelProcessor.h │ ├── ConsumeChannel-inl.h │ ├── ConsumeChannel.h │ ├── FanoutChannel-inl.h │ ├── FanoutChannel.h │ ├── FanoutSender-inl.h │ ├── FanoutSender.h │ ├── MaxConcurrentRateLimiter.cpp │ ├── MaxConcurrentRateLimiter.h │ ├── Merge-inl.h │ ├── Merge.h │ ├── MergeChannel-inl.h │ ├── MergeChannel.h │ ├── MultiplexChannel-inl.h │ ├── MultiplexChannel.h │ ├── OnClosedException.h │ ├── Producer-inl.h │ ├── Producer.h │ ├── ProxyChannel-inl.h │ ├── ProxyChannel.h │ ├── RateLimiter.h │ ├── Transform-inl.h │ ├── Transform.h │ ├── detail/ │ │ ├── AtomicQueue.h │ │ ├── BUCK │ │ ├── CMakeLists.txt │ │ ├── ChannelBridge.h │ │ ├── IntrusivePtr.h │ │ ├── MultiplexerTraits.h │ │ ├── PointerVariant.h │ │ ├── Utility.h │ │ └── test/ │ │ ├── AtomicQueueTest.cpp │ │ ├── BUCK │ │ └── PointerVariantTest.cpp │ └── test/ │ ├── BUCK │ ├── ChannelProcessorTest.cpp │ ├── ChannelTest.cpp │ ├── ChannelTestUtil.h │ ├── FanoutChannelTest.cpp │ ├── FanoutSenderTest.cpp │ ├── MaxConcurrentRateLimiterTest.cpp │ ├── MergeChannelTest.cpp │ ├── MergeTest.cpp │ ├── MultiplexChannelTest.cpp │ ├── ProducerTest.cpp │ ├── ProxyChannelTest.cpp │ └── TransformTest.cpp ├── chrono/ │ ├── BUCK │ ├── CMakeLists.txt │ ├── Clock.h │ ├── Conv.h │ ├── Hardware.h │ └── test/ │ ├── BUCK │ ├── ClockTest.cpp │ └── ConvTest.cpp ├── cli/ │ ├── Args.cpp │ ├── Args.h │ ├── BUCK │ ├── CMakeLists.txt │ ├── NestedCommandLineApp.cpp │ ├── NestedCommandLineApp.h │ ├── ProgramOptions.cpp │ ├── ProgramOptions.h │ └── test/ │ ├── ArgsTest.cpp │ ├── BUCK │ ├── NestedCommandLineAppExample.cpp │ ├── NestedCommandLineAppTest.cpp │ ├── NestedCommandLineAppTestHelper.cpp │ ├── ProgramOptionsTest.cpp │ └── ProgramOptionsTestHelper.cpp ├── codec/ │ ├── BUCK │ ├── CMakeLists.txt │ ├── Uuid.h │ ├── hex.h │ └── test/ │ ├── BUCK │ ├── UuidBenchmark.cpp │ ├── UuidTest.cpp │ └── hex_test.cpp ├── compression/ │ ├── BUCK │ ├── CMakeLists.txt │ ├── Compression.cpp │ ├── Compression.h │ ├── CompressionContextPool.h │ ├── CompressionContextPoolSingletons.cpp │ ├── CompressionContextPoolSingletons.h │ ├── CompressionCoreLocalContextPool.h │ ├── Instructions.h │ ├── QuotientMultiSet-inl.h │ ├── QuotientMultiSet.cpp │ ├── QuotientMultiSet.h │ ├── Select64.cpp │ ├── Select64.h │ ├── Utils.h │ ├── Zlib.cpp │ ├── Zlib.h │ ├── Zstd.cpp │ ├── Zstd.h │ ├── elias_fano/ │ │ ├── BUCK │ │ ├── BitVectorCoding.h │ │ ├── CMakeLists.txt │ │ ├── CodingDetail.h │ │ ├── EliasFanoCoding.h │ │ └── test/ │ │ ├── BUCK │ │ ├── BitVectorCodingTest.cpp │ │ └── EliasFanoCodingTest.cpp │ └── test/ │ ├── BUCK │ ├── CodingTestUtils.cpp │ ├── CodingTestUtils.h │ ├── CompressionContextPoolBenchmark.cpp │ ├── CompressionContextPoolTest.cpp │ ├── CompressionTest.cpp │ ├── InstructionsTest.cpp │ ├── LargeTest.cpp │ ├── QuotientMultiSetBenchmark.cpp │ ├── QuotientMultiSetTest.cpp │ └── Select64Test.cpp ├── concurrency/ │ ├── AtomicSharedPtr.h │ ├── BUCK │ ├── CMakeLists.txt │ ├── CacheLocality.cpp │ ├── CacheLocality.h │ ├── ConcurrentHashMap.h │ ├── CoreCachedSharedPtr.h │ ├── DeadlockDetector.cpp │ ├── DeadlockDetector.h │ ├── DynamicBoundedQueue.h │ ├── PriorityUnboundedQueueSet.h │ ├── ProcessLocalUniqueId.cpp │ ├── ProcessLocalUniqueId.h │ ├── SingletonRelaxedCounter.h │ ├── ThreadCachedSynchronized.h │ ├── UnboundedQueue.h │ ├── container/ │ │ ├── BUCK │ │ ├── CMakeLists.txt │ │ ├── FlatCombiningPriorityQueue.h │ │ ├── LockFreeRingBuffer.h │ │ ├── RelaxedConcurrentPriorityQueue.h │ │ ├── SingleWriterFixedHashMap.h │ │ ├── atomic_grow_array.h │ │ └── test/ │ │ ├── BUCK │ │ ├── FlatCombiningPriorityQueueTest.cpp │ │ ├── LockFreeRingBufferTest.cpp │ │ ├── RelaxedConcurrentPriorityQueueTest.cpp │ │ ├── SingleWriterFixedHashMapTest.cpp │ │ └── atomic_grow_array_test.cpp │ ├── detail/ │ │ ├── AtomicSharedPtr-detail.h │ │ └── ConcurrentHashMap-detail.h │ ├── memory/ │ │ ├── AtomicReadMostlyMainPtr.cpp │ │ ├── AtomicReadMostlyMainPtr.h │ │ ├── BUCK │ │ ├── CMakeLists.txt │ │ ├── PrimaryPtr.h │ │ ├── ReadMostlySharedPtr.h │ │ ├── TLRefCount.h │ │ └── test/ │ │ ├── AtomicReadMostlyMainPtrBenchmark.cpp │ │ ├── AtomicReadMostlyMainPtrTest.cpp │ │ ├── BUCK │ │ ├── PrimaryPtrTest.cpp │ │ ├── ReadMostlySharedPtrBenchmark.cpp │ │ ├── ReadMostlySharedPtrStressTest.cpp │ │ ├── ReadMostlySharedPtrTest.cpp │ │ ├── RefCountBenchmark.cpp │ │ └── RefCountTest.cpp │ └── test/ │ ├── AtomicSharedPtrCounted.h │ ├── AtomicSharedPtrPerformance.cpp │ ├── AtomicSharedPtrTest.cpp │ ├── BUCK │ ├── CacheLocalityBenchmark.cpp │ ├── CacheLocalityInitMain.cpp │ ├── CacheLocalityTest.cpp │ ├── ConcurrentHashMapBench.cpp │ ├── ConcurrentHashMapStressTest.cpp │ ├── ConcurrentHashMapTest.cpp │ ├── CoreCachedSharedPtrTest.cpp │ ├── DynamicBoundedQueueTest.cpp │ ├── PriorityUnboundedQueueSetTest.cpp │ ├── ProcessLocalUniqueIdTest.cpp │ ├── SingletonRelaxedCounterBench.cpp │ ├── SingletonRelaxedCounterTest.cpp │ ├── ThreadCachedSynchronizedBench.cpp │ ├── ThreadCachedSynchronizedTest.cpp │ └── UnboundedQueueTest.cpp ├── container/ │ ├── Access.h │ ├── Array.h │ ├── BUCK │ ├── BitIterator.h │ ├── CMakeLists.txt │ ├── CollectionUtil.h │ ├── Enumerate.h │ ├── EvictingCacheMap.h │ ├── F14.md │ ├── F14Map-fwd.h │ ├── F14Map.h │ ├── F14Set-fwd.h │ ├── F14Set.h │ ├── FBVector.h │ ├── Foreach-inl.h │ ├── Foreach.h │ ├── HeterogeneousAccess-fwd.h │ ├── HeterogeneousAccess.h │ ├── IntrusiveHeap.h │ ├── IntrusiveList.h │ ├── Iterator.h │ ├── MapUtil.h │ ├── Merge.h │ ├── RegexMatchCache.cpp │ ├── RegexMatchCache.h │ ├── Reserve.h │ ├── SparseByteSet.h │ ├── StdBitset.h │ ├── View.h │ ├── WeightedEvictingCacheMap.h │ ├── detail/ │ │ ├── BUCK │ │ ├── BitIteratorDetail.h │ │ ├── BoolWrapper.h │ │ ├── CMakeLists.txt │ │ ├── F14Defaults.h │ │ ├── F14IntrinsicsAvailability.h │ │ ├── F14MapFallback.h │ │ ├── F14Mask.h │ │ ├── F14Policy.h │ │ ├── F14SetFallback.h │ │ ├── F14Table.cpp │ │ ├── F14Table.h │ │ ├── Util.h │ │ ├── tape_detail.h │ │ └── test/ │ │ ├── BUCK │ │ ├── BoolWrapperTest.cpp │ │ └── F14DetailTest.cpp │ ├── heap_vector_types.h │ ├── irange.h │ ├── range_traits.h │ ├── small_vector.h │ ├── sorted_vector_types.h │ ├── span.h │ ├── tape.h │ ├── test/ │ │ ├── .clang-tidy │ │ ├── AccessTest.cpp │ │ ├── ArrayTest.cpp │ │ ├── BUCK │ │ ├── BitIteratorBench.cpp │ │ ├── BitIteratorTest.cpp │ │ ├── CollectionUtilTest.cpp │ │ ├── EnumerateTest.cpp │ │ ├── EvictingCacheMapBench.cpp │ │ ├── EvictingCacheMapTest.cpp │ │ ├── F14AsanSupportTest.cpp │ │ ├── F14FwdTest.cpp │ │ ├── F14InterprocessTest.cpp │ │ ├── F14MapTest.cpp │ │ ├── F14PolicyTest.cpp │ │ ├── F14SetTest.cpp │ │ ├── F14SmallOverheads.cpp │ │ ├── F14TestUtil.h │ │ ├── FBVectorBenchmark.cpp │ │ ├── FBVectorBenchmarks.cpp.h │ │ ├── FBVectorTest.cpp │ │ ├── FBVectorTests.cpp.h │ │ ├── ForeachBenchmark.cpp │ │ ├── ForeachTest.cpp │ │ ├── HashMapsBench.cpp │ │ ├── HeterogeneousAccessTest.cpp │ │ ├── IRangeTest.cpp │ │ ├── IntrusiveHeapTest.cpp │ │ ├── IteratorTest.cpp │ │ ├── MapUtilTest.cpp │ │ ├── MergeTest.cpp │ │ ├── RegexMatchCacheTest.cpp │ │ ├── ReserveTest.cpp │ │ ├── SparseByteSetBenchmark.cpp │ │ ├── SparseByteSetTest.cpp │ │ ├── StdBitsetBenchmark.cpp │ │ ├── StdBitsetTest.cpp │ │ ├── TrackingTypes.h │ │ ├── UtilTest.cpp │ │ ├── WeightedEvictingCacheMapTest.cpp │ │ ├── heap_vector_types_test.cpp │ │ ├── range_traits_test.cpp │ │ ├── small_vector_test.cpp │ │ ├── sorted_vector_test.cpp │ │ ├── span_test.cpp │ │ ├── tape_bench.cpp │ │ ├── tape_test.cpp │ │ └── vector_bool_test.cpp │ └── vector_bool.h ├── coro/ │ ├── Accumulate-inl.h │ ├── Accumulate.h │ ├── AsyncGenerator.h │ ├── AsyncPipe.h │ ├── AsyncScope.h │ ├── AsyncStack.h │ ├── AutoCleanup-fwd.h │ ├── AutoCleanup.h │ ├── BUCK │ ├── BasePromise.h │ ├── Baton.cpp │ ├── Baton.h │ ├── BlockingWait.h │ ├── BoundedQueue.h │ ├── CMakeLists.txt │ ├── Cleanup.h │ ├── Collect-inl.h │ ├── Collect.h │ ├── Concat-inl.h │ ├── Concat.h │ ├── Coroutine.h │ ├── CurrentExecutor.h │ ├── DetachOnCancel.h │ ├── Error.h │ ├── Filter-inl.h │ ├── Filter.h │ ├── FutureUtil.h │ ├── Generator.h │ ├── GmockHelpers.h │ ├── GtestHelpers.h │ ├── Invoke.h │ ├── Merge-inl.h │ ├── Merge.h │ ├── Mutex.cpp │ ├── Mutex.h │ ├── Nothrow.h │ ├── Promise.h │ ├── README.md │ ├── Result.h │ ├── Retry.h │ ├── RustAdaptors.h │ ├── ScopeExit.h │ ├── SerialQueueRunner.cpp │ ├── SerialQueueRunner.h │ ├── SharedLock.h │ ├── SharedMutex.cpp │ ├── SharedMutex.h │ ├── SharedPromise.h │ ├── Sleep-inl.h │ ├── Sleep.h │ ├── SmallUnboundedQueue.h │ ├── Synchronized.h │ ├── Task.h │ ├── TaskWrapper.h │ ├── TimedWait.h │ ├── Timeout-inl.h │ ├── Timeout.h │ ├── Traits.h │ ├── Transform-inl.h │ ├── Transform.h │ ├── UnboundedQueue.h │ ├── ValueOrError.h │ ├── ValueOrFatal.h │ ├── ViaIfAsync.h │ ├── WithAsyncStack.h │ ├── WithCancellation.h │ ├── detail/ │ │ ├── BUCK │ │ ├── Barrier.h │ │ ├── BarrierTask.h │ │ ├── CMakeLists.txt │ │ ├── CurrentAsyncFrame.h │ │ ├── Helpers.h │ │ ├── InlineTask.h │ │ ├── Malloc.cpp │ │ ├── Malloc.h │ │ ├── ManualLifetime.h │ │ ├── PickTaskWrapper.h │ │ ├── Traits.h │ │ └── test/ │ │ ├── BUCK │ │ └── PickTaskWrapperTest.cpp │ ├── safe/ │ │ ├── AsyncClosure-fwd.h │ │ ├── AsyncClosure.h │ │ ├── BUCK │ │ ├── BindCaptures.h │ │ ├── CMakeLists.txt │ │ ├── Captures.h │ │ ├── NowTask.h │ │ ├── SafeTask.h │ │ ├── detail/ │ │ │ ├── AsyncClosure.h │ │ │ ├── BUCK │ │ │ ├── BindAsyncClosure.h │ │ │ ├── CMakeLists.txt │ │ │ ├── DefineMovableDeepConstLrefCopyable.h │ │ │ └── test/ │ │ │ ├── BUCK │ │ │ ├── BindAsyncClosureTest.cpp │ │ │ └── DefineMovableDeepConstLrefCopyableTest.cpp │ │ ├── docs/ │ │ │ ├── AsUnsafe.md │ │ │ └── Captures.md │ │ └── test/ │ │ ├── AsyncClosureTest.cpp │ │ ├── BUCK │ │ ├── CapturesTest.cpp │ │ ├── NowTaskTest.cpp │ │ └── SafeTaskTest.cpp │ ├── scripts/ │ │ ├── BUCK │ │ ├── __init__.py │ │ ├── co_bt.py │ │ └── test/ │ │ ├── BUCK │ │ └── co_bt.py │ └── test/ │ ├── AccumulateTest.cpp │ ├── AsyncGeneratorBenchmark.cpp │ ├── AsyncGeneratorTest.cpp │ ├── AsyncPipeTest.cpp │ ├── AsyncScopeTest.cpp │ ├── AsyncStackTest.cpp │ ├── BUCK │ ├── BatonTest.cpp │ ├── BlockingWaitBenchmark.cpp │ ├── BlockingWaitTest.cpp │ ├── BoundedQueueTest.cpp │ ├── CollectAllBenchmark.cpp │ ├── CollectAllTryBenchmark.cpp │ ├── CollectTest.cpp │ ├── ConcatTest.cpp │ ├── CoroBenchmarkAllocator.cpp │ ├── CoroBenchmarkNRVO.cpp │ ├── CoroTest.cpp │ ├── CoroutineTest.cpp │ ├── CurrentExecutorTest.cpp │ ├── ErrorTest.cpp │ ├── FilterTest.cpp │ ├── FutureUtilTest.cpp │ ├── GeneratorTest.cpp │ ├── GmockHelpersTest.cpp │ ├── GtestHelpersTest.cpp │ ├── InlineTaskTest.cpp │ ├── MergeTest.cpp │ ├── MutexTest.cpp │ ├── PromiseBenchmark.cpp │ ├── PromiseTest.cpp │ ├── RequestContextTest.cpp │ ├── RetryTest.cpp │ ├── RustAdaptorsTest.cpp │ ├── ScopeExitTest.cpp │ ├── SerialQueueRunnerTest.cpp │ ├── SharedMutexBenchmark.cpp │ ├── SharedMutexTest.cpp │ ├── SharedPromiseTest.cpp │ ├── SleepTest.cpp │ ├── SmallUnboundedQueueTest.cpp │ ├── SuspendedStackTest.cpp │ ├── SynchronizedTest.cpp │ ├── TaskBenchmark.cpp │ ├── TaskTest.cpp │ ├── TaskWrapperTest.cpp │ ├── TimeoutTest.cpp │ ├── TraitsTest.cpp │ ├── TransformTest.cpp │ ├── UnboundedQueueTest.cpp │ ├── ValueOrErrorTest.cpp │ ├── ValueOrFatalTest.cpp │ └── WithAsyncStackTest.cpp ├── crypto/ │ ├── BUCK │ ├── Blake2xb.cpp │ ├── Blake2xb.h │ ├── CMakeLists.txt │ ├── LtHash-inl.h │ ├── LtHash.cpp │ ├── LtHash.h │ ├── detail/ │ │ ├── BUCK │ │ ├── CMakeLists.txt │ │ ├── LtHashInternal.h │ │ ├── MathOperation_AVX2.cpp │ │ ├── MathOperation_SSE2.cpp │ │ └── MathOperation_Simple.cpp │ └── test/ │ ├── BUCK │ ├── Blake2xbBenchmark.cpp │ ├── Blake2xbTest.cpp │ ├── LtHashBenchmark.cpp │ └── LtHashTest.cpp ├── debugging/ │ ├── CMakeLists.txt │ ├── exception_tracer/ │ │ ├── BUCK │ │ ├── CMakeLists.txt │ │ ├── Compatibility.h │ │ ├── ExceptionAbi.h │ │ ├── ExceptionCounterLib.cpp │ │ ├── ExceptionCounterLib.h │ │ ├── ExceptionCounterLibStaticRegistration.cpp │ │ ├── ExceptionStackTraceLib.cpp │ │ ├── ExceptionTracer.cpp │ │ ├── ExceptionTracer.h │ │ ├── ExceptionTracerLib.cpp │ │ ├── ExceptionTracerLib.h │ │ ├── README.md │ │ ├── SmartExceptionStackTraceHooks.cpp │ │ ├── SmartExceptionTracer.cpp │ │ ├── SmartExceptionTracer.h │ │ ├── SmartExceptionTracerSingleton.cpp │ │ ├── SmartExceptionTracerSingleton.h │ │ ├── StackTrace.cpp │ │ ├── StackTrace.h │ │ └── test/ │ │ ├── BUCK │ │ ├── ExceptionCounterTest.cpp │ │ ├── ExceptionTracerBenchmark.cpp │ │ ├── ExceptionTracerTest.cpp │ │ ├── ExceptionTracerUncaughtTest.cpp │ │ ├── SmartExceptionTracerBenchmark.cpp │ │ ├── SmartExceptionTracerTest.cpp │ │ └── exception_tracer_uncaught_test.py │ └── symbolizer/ │ ├── BUCK │ ├── CMakeLists.txt │ ├── Dwarf.cpp │ ├── Dwarf.h │ ├── DwarfImpl.cpp │ ├── DwarfImpl.h │ ├── DwarfLineNumberVM.cpp │ ├── DwarfLineNumberVM.h │ ├── DwarfSection.cpp │ ├── DwarfSection.h │ ├── DwarfUtil.cpp │ ├── DwarfUtil.h │ ├── Elf-inl.h │ ├── Elf.cpp │ ├── Elf.h │ ├── ElfCache.cpp │ ├── ElfCache.h │ ├── LineReader.cpp │ ├── LineReader.h │ ├── SignalHandler.cpp │ ├── SignalHandler.h │ ├── StackTrace.cpp │ ├── StackTrace.h │ ├── SymbolizePrinter.cpp │ ├── SymbolizePrinter.h │ ├── SymbolizedFrame.cpp │ ├── SymbolizedFrame.h │ ├── Symbolizer.cpp │ ├── Symbolizer.h │ ├── detail/ │ │ ├── BUCK │ │ ├── CMakeLists.txt │ │ ├── Debug.cpp │ │ └── Debug.h │ ├── test/ │ │ ├── BUCK │ │ ├── Crash.cpp │ │ ├── DwarfBenchmark.cpp │ │ ├── ElfTest.cpp │ │ ├── LineReaderTest.cpp │ │ ├── SignalHandlerTest.cpp │ │ ├── SignalHandlerTest.h │ │ ├── SimpleElf.cpp │ │ ├── SmallSigAltStackCrash.cpp │ │ ├── StackTraceBenchmark.cpp │ │ ├── StackTraceSizeLimitTest.cpp │ │ ├── StackTraceTest.cpp │ │ ├── SymbolizedFrameTest.cpp │ │ ├── SymbolizerTest.cpp │ │ ├── SymbolizerTestUtils-inl.h │ │ ├── SymbolizerTestUtils.cpp │ │ ├── SymbolizerTestUtils.h │ │ ├── compare-addr2line.sh │ │ ├── gnu_debuglink_test.sh │ │ ├── symbolizer_dwp_compability.sh │ │ ├── tool/ │ │ │ ├── BUCK │ │ │ └── segv.cpp │ │ └── validate_folly_symbolizer.bzl │ └── tool/ │ ├── Addr2Line.cpp │ ├── BUCK │ ├── LibSegFault.cpp │ └── libFollySegFault.so.v ├── defs.bzl ├── detail/ │ ├── AsyncTrace.cpp │ ├── AsyncTrace.h │ ├── AtomicHashUtils.h │ ├── AtomicUnorderedMapUtils.h │ ├── Avx2.cpp │ ├── Avx2.h │ ├── BUCK │ ├── BenchmarkAdaptive.cpp │ ├── BenchmarkAdaptive.h │ ├── CMakeLists.txt │ ├── DiscriminatedPtrDetail.h │ ├── FileUtilDetail.cpp │ ├── FileUtilDetail.h │ ├── FileUtilVectorDetail.h │ ├── FingerprintPolynomial.h │ ├── Futex-inl.h │ ├── Futex.cpp │ ├── Futex.h │ ├── GroupVarintDetail.h │ ├── IPAddress.cpp │ ├── IPAddress.h │ ├── IPAddressSource.h │ ├── Iterators.h │ ├── MPMCPipelineDetail.h │ ├── MemoryIdler.cpp │ ├── MemoryIdler.h │ ├── PerfScoped.cpp │ ├── PerfScoped.h │ ├── PolyDetail.h │ ├── RangeCommon.cpp │ ├── RangeCommon.h │ ├── RangeSimd.cpp │ ├── RangeSimd.h │ ├── RangeSse42.cpp │ ├── RangeSse42.h │ ├── SimpleSimdStringUtils.cpp │ ├── SimpleSimdStringUtils.h │ ├── SimpleSimdStringUtilsImpl.h │ ├── Singleton.h │ ├── SlowFingerprint.h │ ├── SocketFastOpen.cpp │ ├── SocketFastOpen.h │ ├── SplitStringSimd.h │ ├── SplitStringSimdImpl.h │ ├── Sse.cpp │ ├── Sse.h │ ├── StaticSingletonManager.cpp │ ├── StaticSingletonManager.h │ ├── ThreadLocalDetail.cpp │ ├── ThreadLocalDetail.h │ ├── TrapOnAvx512.cpp │ ├── TrapOnAvx512.h │ ├── TurnSequencer.h │ ├── TypeList.h │ ├── UniqueInstance.cpp │ ├── UniqueInstance.h │ ├── base64_detail/ │ │ ├── BUCK │ │ ├── Base64Api.cpp │ │ ├── Base64Api.h │ │ ├── Base64Common.h │ │ ├── Base64Constants.h │ │ ├── Base64HiddenConstants.h │ │ ├── Base64SWAR.cpp │ │ ├── Base64SWAR.h │ │ ├── Base64Scalar.h │ │ ├── Base64Simd.h │ │ ├── Base64_SSE4_2.cpp │ │ ├── Base64_SSE4_2.h │ │ ├── Base64_SSE4_2_Platform.h │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ └── test/ │ │ ├── BUCK │ │ ├── Base64AgainstScalarTest.cpp │ │ ├── Base64PlatformTest.cpp │ │ └── Base64SpecialCasesTest.cpp │ ├── test/ │ │ ├── AsyncTraceTest.cpp │ │ ├── BUCK │ │ ├── BenchmarkAdaptiveTest.cpp │ │ ├── FileUtilDetailTest.cpp │ │ ├── PerfScopedTest.cpp │ │ ├── SimpleSimdStringUtilsTest.cpp │ │ ├── SplitStringSimdTest.cpp │ │ ├── StaticSingletonManagerTest.cpp │ │ ├── ThreadLocalBenchmark.cpp │ │ ├── ThreadLocalDetailTest.cpp │ │ ├── TrapOnAvx512TestNegative.cpp │ │ ├── TypeListTest.cpp │ │ ├── UniqueInstanceTest.cpp │ │ └── tuple_test.cpp │ ├── thread_local_globals.cpp │ ├── thread_local_globals.h │ └── tuple.h ├── docs/ │ ├── .gitignore │ ├── AtomicHashMap.md │ ├── BUCK │ ├── Benchmark.md │ ├── BenchmarkAdaptive.md │ ├── Conv.md │ ├── Dynamic.md │ ├── DynamicConverter.md │ ├── Executors.md │ ├── FBString.md │ ├── FBVector.md │ ├── Format.md │ ├── Function.md │ ├── Futures.md │ ├── GroupVarint.md │ ├── Hazptr.md │ ├── Histogram.md │ ├── MapUtils.md │ ├── Overview.md │ ├── PackedSyncPtr.md │ ├── Poly.md │ ├── ProducerConsumerQueue.md │ ├── Rcu.md │ ├── SmallLocks.md │ ├── Switch.md │ ├── Synchronized.md │ ├── ThreadCachedInt.md │ ├── ThreadLocal.md │ ├── Traits.md │ ├── defs.bzl │ ├── examples/ │ │ └── folly/ │ │ ├── BUCK │ │ ├── CancellationCallback.cpp │ │ ├── CancellationSource.cpp │ │ ├── CancellationToken.cpp │ │ ├── DynamicConverter.cpp │ │ ├── ExecutorGuide.cpp │ │ ├── File.cpp │ │ ├── Format.cpp │ │ ├── Function.cpp │ │ ├── Likely.cpp │ │ ├── MapUtil.cpp │ │ ├── ScopeGuard.cpp │ │ ├── ScopeGuard2.cpp │ │ ├── ScopedEventBaseThread.cpp │ │ ├── ScopedEventBaseThread2.cpp │ │ ├── Synchronized.cpp │ │ ├── container/ │ │ │ ├── Array.cpp │ │ │ └── BUCK │ │ ├── coro/ │ │ │ ├── AsyncScope.cpp │ │ │ ├── BUCK │ │ │ ├── CancellableAsyncScope.cpp │ │ │ ├── DetachOnCancel.cpp │ │ │ ├── Promise.cpp │ │ │ ├── Retry.cpp │ │ │ ├── Task.cpp │ │ │ └── WithCancellation.cpp │ │ ├── dynamic/ │ │ │ ├── BUCK │ │ │ ├── array.cpp │ │ │ └── object.cpp │ │ ├── dynamic.cpp │ │ ├── hash/ │ │ │ ├── BUCK │ │ │ └── Hash.cpp │ │ ├── io/ │ │ │ ├── BUCK │ │ │ └── IOBuf.cpp │ │ ├── ipaddress.cpp │ │ ├── synchronization/ │ │ │ └── Baton.cpp │ │ └── test/ │ │ ├── BUCK │ │ ├── TestUtils.cpp │ │ └── common/ │ │ ├── BUCK │ │ └── TestMainDemo.cpp │ ├── small_vector.md │ └── style.css ├── dynamic-inl.h ├── dynamic.h ├── executors/ │ ├── Async.h │ ├── BUCK │ ├── CMakeLists.txt │ ├── CPUThreadPoolExecutor.cpp │ ├── CPUThreadPoolExecutor.h │ ├── Codel.cpp │ ├── Codel.h │ ├── DrivableExecutor.h │ ├── EDFThreadPoolExecutor.cpp │ ├── EDFThreadPoolExecutor.h │ ├── ExecutionObserver.cpp │ ├── ExecutionObserver.h │ ├── ExecutorWithPriority-inl.h │ ├── ExecutorWithPriority.cpp │ ├── ExecutorWithPriority.h │ ├── FiberIOExecutor.h │ ├── FunctionScheduler.cpp │ ├── FunctionScheduler.h │ ├── FutureExecutor.h │ ├── GlobalExecutor.cpp │ ├── GlobalExecutor.h │ ├── GlobalThreadPoolList.cpp │ ├── GlobalThreadPoolList.h │ ├── IOExecutor.h │ ├── IOObjectCache.h │ ├── IOThreadPoolDeadlockDetectorObserver.cpp │ ├── IOThreadPoolDeadlockDetectorObserver.h │ ├── IOThreadPoolExecutor.cpp │ ├── IOThreadPoolExecutor.h │ ├── InlineExecutor.cpp │ ├── InlineExecutor.h │ ├── ManualExecutor.cpp │ ├── ManualExecutor.h │ ├── MeteredExecutor-inl.h │ ├── MeteredExecutor.h │ ├── QueueObserver.cpp │ ├── QueueObserver.h │ ├── QueuedImmediateExecutor.cpp │ ├── QueuedImmediateExecutor.h │ ├── ScheduledExecutor.h │ ├── SequencedExecutor.h │ ├── SerialExecutor-inl.h │ ├── SerialExecutor.h │ ├── SerializedExecutor.h │ ├── SoftRealTimeExecutor.cpp │ ├── SoftRealTimeExecutor.h │ ├── StrandExecutor.cpp │ ├── StrandExecutor.h │ ├── StripedEDFThreadPoolExecutor.cpp │ ├── StripedEDFThreadPoolExecutor.h │ ├── ThreadPoolExecutor.cpp │ ├── ThreadPoolExecutor.h │ ├── ThreadedExecutor.cpp │ ├── ThreadedExecutor.h │ ├── ThreadedRepeatingFunctionRunner.cpp │ ├── ThreadedRepeatingFunctionRunner.h │ ├── TimedDrivableExecutor.cpp │ ├── TimedDrivableExecutor.h │ ├── TimekeeperScheduledExecutor.cpp │ ├── TimekeeperScheduledExecutor.h │ ├── VirtualExecutor.h │ ├── task_queue/ │ │ ├── BUCK │ │ ├── BlockingQueue.h │ │ ├── CMakeLists.txt │ │ ├── LifoSemMPMCQueue.h │ │ ├── PriorityLifoSemMPMCQueue.h │ │ ├── PriorityUnboundedBlockingQueue.h │ │ ├── StripedPriorityUnboundedBlockingQueue.h │ │ ├── UnboundedBlockingQueue.h │ │ └── test/ │ │ ├── BUCK │ │ ├── PriorityLifoSemMPMCQueueTest.cpp │ │ ├── PriorityUnboundedBlockingQueueTest.cpp │ │ ├── StripedPriorityUnboundedBlockingQueueTest.cpp │ │ ├── UnboundedBlockingQueueBench.cpp │ │ └── UnboundedBlockingQueueTest.cpp │ ├── test/ │ │ ├── AsyncTest.cpp │ │ ├── BUCK │ │ ├── CodelTest.cpp │ │ ├── EDFThreadPoolExecutorBenchmark.cpp │ │ ├── ExecutorTest.cpp │ │ ├── ExecutorWithPriorityTest.cpp │ │ ├── FiberIOExecutorTest.cpp │ │ ├── FunctionSchedulerTest.cpp │ │ ├── GlobalCPUExecutorTest.cpp │ │ ├── GlobalExecutorAssignmentTest.cpp │ │ ├── GlobalExecutorTest.cpp │ │ ├── GlobalIOExecutorTest.cpp │ │ ├── IOThreadPoolDeadlockDetectorObserverTest.cpp │ │ ├── IOThreadPoolExecutorBaseTestLib.h │ │ ├── IOThreadPoolExecutorTest.cpp │ │ ├── MeteredExecutorTest.cpp │ │ ├── SequencedExecutorTest.cpp │ │ ├── SerialExecutorTest.cpp │ │ ├── StrandExecutorTest.cpp │ │ ├── StripedEDFThreadPoolExecutorTest.cpp │ │ ├── ThreadPoolExecutorTest.cpp │ │ ├── ThreadedExecutorTest.cpp │ │ ├── ThreadedRepeatingFunctionRunnerTest.cpp │ │ ├── TimedDrivableExecutorTest.cpp │ │ └── TimekeeperScheduledExecutorTest.cpp │ └── thread_factory/ │ ├── BUCK │ ├── CMakeLists.txt │ ├── InitThreadFactory.h │ ├── NamedThreadFactory.h │ ├── PriorityThreadFactory.cpp │ ├── PriorityThreadFactory.h │ └── ThreadFactory.h ├── experimental/ │ ├── BUCK │ ├── CMakeLists.txt │ ├── EventCount.h │ ├── FlatCombiningPriorityQueue.h │ ├── FunctionScheduler.h │ ├── ThreadedRepeatingFunctionRunner.h │ ├── channels/ │ │ ├── BUCK │ │ ├── CMakeLists.txt │ │ ├── Channel-fwd.h │ │ ├── Channel-inl.h │ │ ├── Channel.h │ │ ├── ChannelCallbackHandle.h │ │ ├── ChannelProcessor-inl.h │ │ ├── ChannelProcessor.h │ │ ├── ConsumeChannel-inl.h │ │ ├── ConsumeChannel.h │ │ ├── FanoutChannel-inl.h │ │ ├── FanoutChannel.h │ │ ├── FanoutSender-inl.h │ │ ├── FanoutSender.h │ │ ├── MaxConcurrentRateLimiter.h │ │ ├── Merge-inl.h │ │ ├── Merge.h │ │ ├── MergeChannel-inl.h │ │ ├── MergeChannel.h │ │ ├── MultiplexChannel-inl.h │ │ ├── MultiplexChannel.h │ │ ├── OnClosedException.h │ │ ├── Producer-inl.h │ │ ├── Producer.h │ │ ├── ProxyChannel-inl.h │ │ ├── ProxyChannel.h │ │ ├── RateLimiter.h │ │ ├── Transform-inl.h │ │ ├── Transform.h │ │ ├── detail/ │ │ │ ├── AtomicQueue.h │ │ │ ├── BUCK │ │ │ ├── CMakeLists.txt │ │ │ ├── ChannelBridge.h │ │ │ ├── FunctionTraits.h │ │ │ ├── IntrusivePtr.h │ │ │ ├── MultiplexerTraits.h │ │ │ ├── PointerVariant.h │ │ │ └── Utility.h │ │ └── test/ │ │ ├── BUCK │ │ └── ChannelTestUtil.h │ ├── coro/ │ │ ├── AsyncGenerator.h │ │ ├── AsyncPipe.h │ │ ├── AsyncScope.h │ │ ├── AsyncStack.h │ │ ├── AutoCleanup-fwd.h │ │ ├── AutoCleanup.h │ │ ├── BUCK │ │ ├── Baton.h │ │ ├── BlockingWait.h │ │ ├── BoundedQueue.h │ │ ├── CMakeLists.txt │ │ ├── Cleanup.h │ │ ├── Collect-inl.h │ │ ├── Collect.h │ │ ├── Concat-inl.h │ │ ├── Concat.h │ │ ├── Coroutine.h │ │ ├── CurrentExecutor.h │ │ ├── DetachOnCancel.h │ │ ├── Filter-inl.h │ │ ├── Filter.h │ │ ├── FutureUtil.h │ │ ├── Generator.h │ │ ├── GmockHelpers.h │ │ ├── GtestHelpers.h │ │ ├── Invoke.h │ │ ├── Merge-inl.h │ │ ├── Merge.h │ │ ├── Mutex.h │ │ ├── Promise.h │ │ ├── Result.h │ │ ├── Retry.h │ │ ├── RustAdaptors.h │ │ ├── ScopeExit.h │ │ ├── SharedLock.h │ │ ├── SharedMutex.h │ │ ├── SharedPromise.h │ │ ├── Sleep-inl.h │ │ ├── Sleep.h │ │ ├── SmallUnboundedQueue.h │ │ ├── Task.h │ │ ├── TimedWait.h │ │ ├── Timeout-inl.h │ │ ├── Timeout.h │ │ ├── Traits.h │ │ ├── Transform-inl.h │ │ ├── Transform.h │ │ ├── UnboundedQueue.h │ │ ├── ViaIfAsync.h │ │ ├── WithAsyncStack.h │ │ ├── WithCancellation.h │ │ └── detail/ │ │ ├── Barrier.h │ │ ├── BarrierTask.h │ │ ├── CurrentAsyncFrame.h │ │ ├── Helpers.h │ │ ├── InlineTask.h │ │ ├── Malloc.h │ │ ├── ManualLifetime.h │ │ └── Traits.h │ ├── crypto/ │ │ ├── BUCK │ │ ├── Blake2xb.h │ │ ├── CMakeLists.txt │ │ └── LtHash.h │ ├── exception_tracer/ │ │ ├── BUCK │ │ ├── CMakeLists.txt │ │ ├── ExceptionAbi.h │ │ ├── ExceptionCounterLib.h │ │ ├── ExceptionTracer.h │ │ ├── ExceptionTracerLib.h │ │ ├── SmartExceptionTracer.h │ │ ├── SmartExceptionTracerSingleton.h │ │ └── StackTrace.h │ ├── flat_combining/ │ │ ├── BUCK │ │ ├── CMakeLists.txt │ │ ├── FlatCombining.h │ │ └── test/ │ │ ├── BUCK │ │ ├── FlatCombiningExamples.h │ │ └── FlatCombiningTestHelpers.h │ ├── observer/ │ │ ├── BUCK │ │ ├── CMakeLists.txt │ │ ├── CoreCachedObserver.h │ │ ├── HazptrObserver.h │ │ ├── Observable-inl.h │ │ ├── Observable.h │ │ ├── Observer-inl.h │ │ ├── Observer-pre.h │ │ ├── Observer.h │ │ ├── ReadMostlyTLObserver.h │ │ ├── SimpleObservable-inl.h │ │ ├── SimpleObservable.h │ │ ├── WithJitter-inl.h │ │ ├── WithJitter.h │ │ ├── detail/ │ │ │ ├── BUCK │ │ │ ├── CMakeLists.txt │ │ │ ├── Core.h │ │ │ ├── GraphCycleDetector.h │ │ │ └── ObserverManager.h │ │ └── test/ │ │ └── BUCK │ ├── settings/ │ │ ├── BUCK │ │ ├── CMakeLists.txt │ │ ├── Immutables.h │ │ ├── Settings.h │ │ ├── Types.h │ │ └── detail/ │ │ └── SettingsImpl.h │ ├── symbolizer/ │ │ ├── BUCK │ │ ├── CMakeLists.txt │ │ ├── Dwarf.h │ │ ├── DwarfImpl.h │ │ ├── DwarfLineNumberVM.h │ │ ├── DwarfSection.h │ │ ├── DwarfUtil.h │ │ ├── Elf-inl.h │ │ ├── Elf.h │ │ ├── ElfCache.h │ │ ├── LineReader.h │ │ ├── SignalHandler.h │ │ ├── StackTrace.h │ │ ├── SymbolizePrinter.h │ │ ├── SymbolizedFrame.h │ │ ├── Symbolizer.h │ │ └── detail/ │ │ ├── BUCK │ │ ├── CMakeLists.txt │ │ └── Debug.h │ └── test/ │ ├── BUCK │ └── CodingTestUtils.h ├── ext/ │ ├── BUCK │ ├── CMakeLists.txt │ ├── buck2/ │ │ ├── BUCK │ │ ├── CMakeLists.txt │ │ └── test_ext.cpp │ ├── test_ext.cpp │ └── test_ext.h ├── external/ │ ├── .clang-format │ ├── CMakeLists.txt │ ├── aor/ │ │ ├── BUCK │ │ ├── CMakeLists.txt │ │ ├── asmdefs.h │ │ ├── memcpy-advsimd.S │ │ ├── memcpy-armv8.S │ │ ├── memcpy-mops.S │ │ ├── memcpy_sve.S │ │ ├── memmove-mops.S │ │ ├── memset-advsimd.S │ │ ├── memset-mops.S │ │ └── memset-sve.S │ ├── farmhash/ │ │ ├── BUCK │ │ ├── CMakeLists.txt │ │ ├── farmhash.cpp │ │ ├── farmhash.h │ │ └── test/ │ │ ├── BUCK │ │ └── farmhash_test.cpp │ ├── fast-crc32/ │ │ ├── BUCK │ │ ├── CMakeLists.txt │ │ ├── avx512_crc32c_v8s3x4.cpp │ │ ├── avx512_crc32c_v8s3x4.h │ │ ├── neon_crc32c_v3s4x2e_v2.cpp │ │ ├── neon_crc32c_v3s4x2e_v2.h │ │ ├── neon_eor3_crc32_v8s2x4e_s1x2.cpp │ │ ├── neon_eor3_crc32_v8s2x4e_s1x2.h │ │ ├── neon_eor3_crc32c_v8s2x4e_s2x1.cpp │ │ ├── neon_eor3_crc32c_v8s2x4e_s2x1.h │ │ ├── sse_crc32c_v8s3x3.cpp │ │ └── sse_crc32c_v8s3x3.h │ ├── nvidia/ │ │ ├── CMakeLists.txt │ │ ├── detail/ │ │ │ ├── BUCK │ │ │ ├── CMakeLists.txt │ │ │ ├── RangeSve2.cpp │ │ │ └── RangeSve2.h │ │ └── hash/ │ │ ├── BUCK │ │ ├── CMakeLists.txt │ │ ├── Checksum.cpp │ │ └── detail/ │ │ ├── BUCK │ │ ├── CMakeLists.txt │ │ ├── Crc32cCombineDetail.h │ │ └── Crc32cDetail.cpp │ └── rapidhash/ │ ├── BUCK │ ├── CMakeLists.txt │ └── rapidhash.h ├── fibers/ │ ├── AddTasks-inl.h │ ├── AddTasks.h │ ├── AtomicBatchDispatcher-inl.h │ ├── AtomicBatchDispatcher.h │ ├── BUCK │ ├── BatchDispatcher.h │ ├── BatchSemaphore.cpp │ ├── BatchSemaphore.h │ ├── Baton-inl.h │ ├── Baton.cpp │ ├── Baton.h │ ├── BoostContextCompatibility.h │ ├── CMakeLists.txt │ ├── CallOnce.h │ ├── EventBaseLoopController-inl.h │ ├── EventBaseLoopController.h │ ├── ExecutorBasedLoopController.h │ ├── ExecutorLoopController-inl.h │ ├── ExecutorLoopController.h │ ├── Fiber-inl.h │ ├── Fiber.cpp │ ├── Fiber.h │ ├── FiberManager-inl.h │ ├── FiberManager.cpp │ ├── FiberManager.h │ ├── FiberManagerInternal-inl.h │ ├── FiberManagerInternal.h │ ├── FiberManagerMap-inl.h │ ├── FiberManagerMap.h │ ├── ForEach-inl.h │ ├── ForEach.h │ ├── GenericBaton.h │ ├── GuardPageAllocator.cpp │ ├── GuardPageAllocator.h │ ├── LoopController.h │ ├── Promise-inl.h │ ├── Promise.h │ ├── README.md │ ├── Semaphore.cpp │ ├── Semaphore.h │ ├── SemaphoreBase.cpp │ ├── SemaphoreBase.h │ ├── SimpleLoopController.cpp │ ├── SimpleLoopController.h │ ├── TimedMutex-inl.h │ ├── TimedMutex.h │ ├── WhenN-inl.h │ ├── WhenN.h │ ├── async/ │ │ ├── Async.cpp │ │ ├── Async.h │ │ ├── AsyncStack.h │ │ ├── BUCK │ │ ├── Baton.h │ │ ├── CMakeLists.txt │ │ ├── Collect-inl.h │ │ ├── Collect.h │ │ ├── FiberManager.h │ │ ├── Future.h │ │ ├── Promise.h │ │ ├── README.md │ │ ├── Task.h │ │ ├── WaitUtils.h │ │ └── test/ │ │ ├── AsyncTest.cpp │ │ └── BUCK │ ├── detail/ │ │ ├── AtomicBatchDispatcher.cpp │ │ ├── AtomicBatchDispatcher.h │ │ ├── BUCK │ │ └── CMakeLists.txt │ ├── scripts/ │ │ └── gdb.py │ ├── test/ │ │ ├── BUCK │ │ ├── BatchSemaphoreTest.cpp │ │ ├── FibersBenchmark.cpp │ │ ├── FibersTest.cpp │ │ ├── FibersTestApp.cpp │ │ ├── SemaphoreTest.cpp │ │ ├── StackOverflow.cpp │ │ └── TimedMutexBenchmark.cpp │ └── traits.h ├── folly_extended_library.bzl ├── functional/ │ ├── ApplyTuple.h │ ├── BUCK │ ├── CMakeLists.txt │ ├── Invoke.h │ ├── Partial.h │ ├── protocol.h │ ├── test/ │ │ ├── ApplyTupleTest.cpp │ │ ├── BUCK │ │ ├── InvokeTest.cpp │ │ ├── PartialTest.cpp │ │ ├── protocol_test.cpp │ │ └── traits_test.cpp │ └── traits.h ├── futures/ │ ├── BUCK │ ├── Barrier.cpp │ ├── Barrier.h │ ├── CMakeLists.txt │ ├── Cleanup.h │ ├── Future-inl.h │ ├── Future-pre.h │ ├── Future.cpp │ ├── Future.h │ ├── FutureSplitter.h │ ├── HeapTimekeeper.cpp │ ├── HeapTimekeeper.h │ ├── ManualTimekeeper.cpp │ ├── ManualTimekeeper.h │ ├── Portability.h │ ├── Promise-inl.h │ ├── Promise.cpp │ ├── Promise.h │ ├── Retrying.h │ ├── SharedPromise-inl.h │ ├── SharedPromise.cpp │ ├── SharedPromise.h │ ├── ThreadWheelTimekeeper.cpp │ ├── ThreadWheelTimekeeper.h │ ├── WTCallback.h │ ├── detail/ │ │ ├── BUCK │ │ ├── CMakeLists.txt │ │ ├── Core.cpp │ │ ├── Core.h │ │ └── Types.h │ └── test/ │ ├── BUCK │ ├── BarrierTest.cpp │ ├── Benchmark.cpp │ ├── CallbackLifetimeTest.cpp │ ├── CleanupTest.cpp │ ├── CollectTest.cpp │ ├── ContextTest.cpp │ ├── ConversionOperatorTest.cpp │ ├── CoreTest.cpp │ ├── EnsureTest.cpp │ ├── FilterTest.cpp │ ├── FutureSplitterTest.cpp │ ├── FutureTest.cpp │ ├── HeaderCompileTest.cpp │ ├── HeapTimekeeperTest.cpp │ ├── InterruptTest.cpp │ ├── ManualTimekeeperTest.cpp │ ├── MapTest.cpp │ ├── NonCopyableLambdaTest.cpp │ ├── PollTest.cpp │ ├── PromiseTest.cpp │ ├── ReduceTest.cpp │ ├── RetryingTest.cpp │ ├── SelfDestructTest.cpp │ ├── SemiFutureTest.cpp │ ├── SharedPromiseTest.cpp │ ├── TestExecutor.cpp │ ├── TestExecutor.h │ ├── TestExecutorTest.cpp │ ├── ThenCompileTest.cpp │ ├── ThenCompileTest.h │ ├── ThenTest.cpp │ ├── ThreadWheelTimekeeperTest.cpp │ ├── TimekeeperTest.cpp │ ├── TimekeeperTestLib.h │ ├── TimesTest.cpp │ ├── UnwrapTest.cpp │ ├── ViaTest.cpp │ ├── WaitTest.cpp │ ├── WhenTest.cpp │ ├── WhileDoTest.cpp │ ├── WillEqualTest.cpp │ ├── WindowTest.cpp │ └── then_compile_test.rb ├── gen/ │ ├── BUCK │ ├── Base-inl.h │ ├── Base.h │ ├── CMakeLists.txt │ ├── Combine-inl.h │ ├── Combine.h │ ├── Core-inl.h │ ├── Core.h │ ├── File-inl.h │ ├── File.h │ ├── IStream.h │ ├── Parallel-inl.h │ ├── Parallel.h │ ├── ParallelMap-inl.h │ ├── ParallelMap.h │ ├── String-inl.h │ ├── String.h │ └── test/ │ ├── BUCK │ ├── BaseBenchmark.cpp │ ├── BaseTest.cpp │ ├── Bench.h │ ├── CombineTest.cpp │ ├── FileBenchmark.cpp │ ├── FileTest.cpp │ ├── IStreamTest.cpp │ ├── ParallelBenchmark.cpp │ ├── ParallelMapBenchmark.cpp │ ├── ParallelMapTest.cpp │ ├── ParallelTest.cpp │ ├── StringBenchmark.cpp │ └── StringTest.cpp ├── hash/ │ ├── BUCK │ ├── CMakeLists.txt │ ├── Checksum.cpp │ ├── Checksum.h │ ├── FarmHash.h │ ├── FnvHash.h │ ├── Hash.h │ ├── HsiehHash.h │ ├── MurmurHash.h │ ├── SpookyHashV1.cpp │ ├── SpookyHashV1.h │ ├── SpookyHashV2.cpp │ ├── SpookyHashV2.h │ ├── UniqueHashKey.cpp │ ├── UniqueHashKey.h │ ├── detail/ │ │ ├── BUCK │ │ ├── CMakeLists.txt │ │ ├── ChecksumDetail.cpp │ │ ├── ChecksumDetail.h │ │ ├── Crc32CombineDetail.cpp │ │ ├── Crc32cDetail.cpp │ │ ├── RandomSeed.h │ │ └── test/ │ │ ├── BUCK │ │ └── RandomSeedTest.cpp │ ├── rapidhash.h │ ├── test/ │ │ ├── BUCK │ │ ├── ChecksumBenchmark.cpp │ │ ├── ChecksumTest.cpp │ │ ├── FarmHashTest.cpp │ │ ├── FnvHashTest.cpp │ │ ├── HashBenchmark.cpp │ │ ├── HashTest.cpp │ │ ├── HsiehHashTest.cpp │ │ ├── MurmurHashTest.cpp │ │ ├── RapidHashTest.cpp │ │ ├── SpookyHashV1Test.cpp │ │ ├── SpookyHashV2Test.cpp │ │ ├── StdCompatibleHashTest.cpp │ │ ├── UniqueHashKeyTest.cpp │ │ └── traits_test.cpp │ └── traits.h ├── init/ │ ├── BUCK │ ├── CMakeLists.txt │ ├── Init.cpp │ ├── Init.h │ ├── Phase.cpp │ ├── Phase.h │ └── test/ │ ├── BUCK │ └── PhaseTest.cpp ├── io/ │ ├── BUCK │ ├── CMakeLists.txt │ ├── Cursor-inl.h │ ├── Cursor.cpp │ ├── Cursor.h │ ├── FsUtil.cpp │ ├── FsUtil.h │ ├── GlobalShutdownSocketSet.cpp │ ├── GlobalShutdownSocketSet.h │ ├── HugePages.cpp │ ├── HugePages.h │ ├── IOBuf.cpp │ ├── IOBuf.h │ ├── IOBufIovecBuilder.cpp │ ├── IOBufIovecBuilder.h │ ├── IOBufQueue.cpp │ ├── IOBufQueue.h │ ├── RecordIO-inl.h │ ├── RecordIO.cpp │ ├── RecordIO.h │ ├── ShutdownSocketSet.cpp │ ├── ShutdownSocketSet.h │ ├── SocketOptionMap.cpp │ ├── SocketOptionMap.h │ ├── SocketOptionValue.cpp │ ├── SocketOptionValue.h │ ├── TypedIOBuf.h │ ├── async/ │ │ ├── AsyncBase.cpp │ │ ├── AsyncBase.h │ │ ├── AsyncIO.cpp │ │ ├── AsyncIO.h │ │ ├── AsyncIoUringSocket.cpp │ │ ├── AsyncIoUringSocket.h │ │ ├── AsyncIoUringSocketFactory.cpp │ │ ├── AsyncIoUringSocketFactory.h │ │ ├── AsyncPipe.cpp │ │ ├── AsyncPipe.h │ │ ├── AsyncSSLSocket.cpp │ │ ├── AsyncSSLSocket.h │ │ ├── AsyncServerSocket.cpp │ │ ├── AsyncServerSocket.h │ │ ├── AsyncSignalHandler.cpp │ │ ├── AsyncSignalHandler.h │ │ ├── AsyncSocket.cpp │ │ ├── AsyncSocket.h │ │ ├── AsyncSocketBase.h │ │ ├── AsyncSocketException.cpp │ │ ├── AsyncSocketException.h │ │ ├── AsyncSocketTransport.cpp │ │ ├── AsyncSocketTransport.h │ │ ├── AsyncTimeout.cpp │ │ ├── AsyncTimeout.h │ │ ├── AsyncTransport.h │ │ ├── AsyncTransportCertificate.h │ │ ├── AsyncUDPServerSocket.h │ │ ├── AsyncUDPSocket.cpp │ │ ├── AsyncUDPSocket.h │ │ ├── AtomicNotificationQueue-inl.h │ │ ├── AtomicNotificationQueue.h │ │ ├── BUCK │ │ ├── CMakeLists.txt │ │ ├── CertificateIdentityVerifier.h │ │ ├── DecoratedAsyncTransportWrapper.h │ │ ├── DelayedDestruction.cpp │ │ ├── DelayedDestruction.h │ │ ├── DelayedDestructionBase.h │ │ ├── DestructorCheck.h │ │ ├── Epoll.h │ │ ├── EpollBackend.cpp │ │ ├── EpollBackend.h │ │ ├── EventBase.cpp │ │ ├── EventBase.h │ │ ├── EventBaseAtomicNotificationQueue-inl.h │ │ ├── EventBaseAtomicNotificationQueue.h │ │ ├── EventBaseBackendBase.cpp │ │ ├── EventBaseBackendBase.h │ │ ├── EventBaseLocal.cpp │ │ ├── EventBaseLocal.h │ │ ├── EventBaseManager.cpp │ │ ├── EventBaseManager.h │ │ ├── EventBasePoller.cpp │ │ ├── EventBasePoller.h │ │ ├── EventBaseThread.cpp │ │ ├── EventBaseThread.h │ │ ├── EventHandler.cpp │ │ ├── EventHandler.h │ │ ├── EventUtil.h │ │ ├── HHWheelTimer-fwd.h │ │ ├── HHWheelTimer.cpp │ │ ├── HHWheelTimer.h │ │ ├── IoUring.cpp │ │ ├── IoUring.h │ │ ├── IoUringBackend.cpp │ │ ├── IoUringBackend.h │ │ ├── IoUringBase.h │ │ ├── IoUringBufferPoolSharing.cpp │ │ ├── IoUringBufferPoolSharing.h │ │ ├── IoUringConnect.cpp │ │ ├── IoUringConnect.h │ │ ├── IoUringEvent.cpp │ │ ├── IoUringEvent.h │ │ ├── IoUringEventBaseLocal.cpp │ │ ├── IoUringEventBaseLocal.h │ │ ├── IoUringOptions.h │ │ ├── IoUringProvidedBufferRing.cpp │ │ ├── IoUringProvidedBufferRing.h │ │ ├── IoUringRecv.cpp │ │ ├── IoUringRecv.h │ │ ├── IoUringSend.cpp │ │ ├── IoUringSend.h │ │ ├── IoUringZeroCopyBufferPool.cpp │ │ ├── IoUringZeroCopyBufferPool.h │ │ ├── Liburing.h │ │ ├── MuxIOThreadPoolExecutor.cpp │ │ ├── MuxIOThreadPoolExecutor.h │ │ ├── NotificationQueue.h │ │ ├── PasswordInFile.cpp │ │ ├── PasswordInFile.h │ │ ├── README.md │ │ ├── Request.cpp │ │ ├── Request.h │ │ ├── SSLContext.cpp │ │ ├── SSLContext.h │ │ ├── SSLOptions.cpp │ │ ├── SSLOptions.h │ │ ├── STTimerFDTimeoutManager.cpp │ │ ├── STTimerFDTimeoutManager.h │ │ ├── ScopedEventBaseThread.cpp │ │ ├── ScopedEventBaseThread.h │ │ ├── SimpleAsyncIO.cpp │ │ ├── SimpleAsyncIO.h │ │ ├── TerminateCancellationToken.cpp │ │ ├── TerminateCancellationToken.h │ │ ├── TimeoutManager.cpp │ │ ├── TimeoutManager.h │ │ ├── TimerFD.cpp │ │ ├── TimerFD.h │ │ ├── TimerFDTimeoutManager.cpp │ │ ├── TimerFDTimeoutManager.h │ │ ├── VirtualEventBase.cpp │ │ ├── VirtualEventBase.h │ │ ├── WriteChainAsyncTransportWrapper.h │ │ ├── WriteFlags.h │ │ ├── fdsock/ │ │ │ ├── AsyncFdSocket.cpp │ │ │ ├── AsyncFdSocket.h │ │ │ ├── BUCK │ │ │ ├── CMakeLists.txt │ │ │ ├── SocketFds.cpp │ │ │ ├── SocketFds.h │ │ │ └── test/ │ │ │ ├── AsyncFdSocketTest.cpp │ │ │ └── BUCK │ │ ├── observer/ │ │ │ ├── AsyncSocketObserverContainer.h │ │ │ ├── AsyncSocketObserverInterface.h │ │ │ ├── BUCK │ │ │ └── CMakeLists.txt │ │ ├── ssl/ │ │ │ ├── BUCK │ │ │ ├── BasicTransportCertificate.h │ │ │ ├── CMakeLists.txt │ │ │ ├── OpenSSLTransportCertificate.h │ │ │ ├── OpenSSLUtils.cpp │ │ │ ├── OpenSSLUtils.h │ │ │ ├── SSLErrors.cpp │ │ │ ├── SSLErrors.h │ │ │ ├── TLSDefinitions.h │ │ │ └── test/ │ │ │ ├── BUCK │ │ │ ├── BasicTransportCertificateTest.cpp │ │ │ ├── OpenSSLUtilsTest.cpp │ │ │ └── SSLErrorsTest.cpp │ │ └── test/ │ │ ├── AsyncBaseTestLib.cpp │ │ ├── AsyncBaseTestLib.h │ │ ├── AsyncIOTest.cpp │ │ ├── AsyncIoUringSocketTest.cpp │ │ ├── AsyncPipeTest.cpp │ │ ├── AsyncSSLSocketTest.cpp │ │ ├── AsyncSSLSocketTest.h │ │ ├── AsyncSSLSocketTest2.cpp │ │ ├── AsyncSSLSocketWriteTest.cpp │ │ ├── AsyncSignalHandlerTestLib.h │ │ ├── AsyncSocketExceptionTest.cpp │ │ ├── AsyncSocketObserverTest.cpp │ │ ├── AsyncSocketTest.cpp │ │ ├── AsyncSocketTest.h │ │ ├── AsyncSocketTest2.cpp │ │ ├── AsyncSocketTest2.h │ │ ├── AsyncTimeoutTest.cpp │ │ ├── AsyncTransportTest.cpp │ │ ├── AsyncUDPSocketGSOGROTest.cpp │ │ ├── AsyncUDPSocketSendmmsgTest.cpp │ │ ├── AsyncUDPSocketTest.cpp │ │ ├── AtomicNotificationQueueTest.cpp │ │ ├── BUCK │ │ ├── BlockingSocket.h │ │ ├── CallbackStateEnum.h │ │ ├── ConnCallback.h │ │ ├── DecoratedAsyncTransportWrapperTest.cpp │ │ ├── DelayedDestructionBaseTest.cpp │ │ ├── DelayedDestructionTest.cpp │ │ ├── DestructorCheckTest.cpp │ │ ├── EpollBackendTest.cpp │ │ ├── EventBaseBenchmark.cpp │ │ ├── EventBaseLocalTest.cpp │ │ ├── EventBaseTest.cpp │ │ ├── EventBaseTestLib.h │ │ ├── EventBaseThreadTest.cpp │ │ ├── EventHandlerTest.cpp │ │ ├── HHWheelTimerHighResBenchmark.cpp │ │ ├── HHWheelTimerHighResTest.cpp │ │ ├── HHWheelTimerSlowTests.cpp │ │ ├── HHWheelTimerTest.cpp │ │ ├── IOBenchmark.cpp │ │ ├── IoTestTempFileUtil.cpp │ │ ├── IoTestTempFileUtil.h │ │ ├── IoUringBackendBench.cpp │ │ ├── IoUringBackendSetupTest.cpp │ │ ├── IoUringBackendTest.cpp │ │ ├── IoUringEventBaseLocalTest.cpp │ │ ├── IoUringEventTest.cpp │ │ ├── IoUringProvidedBufferRingTest.cpp │ │ ├── IoUringTest.cpp │ │ ├── IoUringZeroCopyBufferPoolTest.cpp │ │ ├── MockAsyncSSLSocket.h │ │ ├── MockAsyncServerSocket.h │ │ ├── MockAsyncSocket.h │ │ ├── MockAsyncSocketLegacyObserver.h │ │ ├── MockAsyncSocketObserver.h │ │ ├── MockAsyncTransport.h │ │ ├── MockAsyncUDPSocket.h │ │ ├── MockTimeoutManager.h │ │ ├── MuxIOThreadPoolExecutorTest.cpp │ │ ├── NotificationQueueBenchmark.cpp │ │ ├── NotificationQueueTest.cpp │ │ ├── PollLoopHooksTest.cpp │ │ ├── RegisteredFdBenchmark.cpp │ │ ├── RequestContextBenchmark.cpp │ │ ├── RequestContextHelper.h │ │ ├── RequestContextTest.cpp │ │ ├── SSLContextRegressionTest.cpp │ │ ├── SSLContextTest.cpp │ │ ├── SSLOptionsTest.cpp │ │ ├── SSLSessionTest.cpp │ │ ├── SSLUtil.cpp │ │ ├── SSLUtil.h │ │ ├── ScopedBoundPort.cpp │ │ ├── ScopedBoundPort.h │ │ ├── ScopedEventBaseThreadTest.cpp │ │ ├── SimpleAsyncIOTest.cpp │ │ ├── SocketClient.cpp │ │ ├── SocketPair.cpp │ │ ├── SocketPair.h │ │ ├── TFOUtil.cpp │ │ ├── TFOUtil.h │ │ ├── TerminateCancellationTokenTest.cpp │ │ ├── TestSSLServer.cpp │ │ ├── TestSSLServer.h │ │ ├── TimeUtil.cpp │ │ ├── TimeUtil.h │ │ ├── TimeUtilTest.cpp │ │ ├── TimerFDTimeoutManagerTest.cpp │ │ ├── UndelayedDestruction.h │ │ ├── Util.h │ │ ├── WriteChainAsyncTransportWrapperTest.cpp │ │ ├── WriteFlagsTest.cpp │ │ ├── ZeroCopy.cpp │ │ ├── ZeroCopy.h │ │ ├── ZeroCopyBenchmark.cpp │ │ ├── ZeroCopyTest.cpp │ │ └── certs/ │ │ ├── BUCK │ │ ├── ca-cert.pem │ │ ├── ca-key.pem │ │ ├── client_ca_cert.pem │ │ ├── client_ca_key.pem │ │ ├── client_cert.pem │ │ ├── client_chain.pem │ │ ├── client_intermediate_ca.pem │ │ ├── client_intermediate_ca_key.pem │ │ ├── client_key.pem │ │ ├── clienti_cert.pem │ │ ├── clienti_key.pem │ │ ├── defs.bzl │ │ ├── generate_certs.sh │ │ ├── tests-cert.pem │ │ └── tests-key.pem │ ├── coro/ │ │ ├── BUCK │ │ ├── CMakeLists.txt │ │ ├── ServerSocket.cpp │ │ ├── ServerSocket.h │ │ ├── Transport.cpp │ │ ├── Transport.h │ │ ├── TransportCallbackBase.h │ │ ├── TransportCallbacks.h │ │ └── test/ │ │ ├── BUCK │ │ └── TransportTest.cpp │ ├── test/ │ │ ├── BUCK │ │ ├── FsUtilTest.cpp │ │ ├── IOBufBenchmark.cpp │ │ ├── IOBufCBTest.cpp │ │ ├── IOBufCursorBenchmark.cpp │ │ ├── IOBufCursorTest.cpp │ │ ├── IOBufIovecBuilderTest.cpp │ │ ├── IOBufQueueTest.cpp │ │ ├── IOBufTest.cpp │ │ ├── NetworkBenchmark.cpp │ │ ├── QueueAppenderBenchmark.cpp │ │ ├── RecordIOTest.cpp │ │ ├── ShutdownSocketSetTest.cpp │ │ └── SocketOptionValueTest.cpp │ └── tool/ │ ├── BUCK │ └── HugePageUtil.cpp ├── json/ │ ├── BUCK │ ├── CMakeLists.txt │ ├── DynamicConverter.h │ ├── DynamicParser-inl.h │ ├── DynamicParser.cpp │ ├── DynamicParser.h │ ├── JSONSchema.cpp │ ├── JSONSchema.h │ ├── JsonMockUtil.h │ ├── JsonTestUtil.cpp │ ├── JsonTestUtil.h │ ├── bser/ │ │ ├── BUCK │ │ ├── Bser.h │ │ ├── CMakeLists.txt │ │ ├── Dump.cpp │ │ ├── Load.cpp │ │ └── test/ │ │ ├── BUCK │ │ └── BserTest.cpp │ ├── dynamic-inl.h │ ├── dynamic.cpp │ ├── dynamic.h │ ├── json.cpp │ ├── json.h │ ├── json_patch.cpp │ ├── json_patch.h │ ├── json_pointer.cpp │ ├── json_pointer.h │ ├── test/ │ │ ├── BUCK │ │ ├── DynamicBenchmark.cpp │ │ ├── DynamicConverterTest.cpp │ │ ├── DynamicOtherTest.cpp │ │ ├── DynamicParserTest.cpp │ │ ├── DynamicTest.cpp │ │ ├── JSONSchemaTest.cpp │ │ ├── Json5Test.cpp │ │ ├── JsonBenchmark.cpp │ │ ├── JsonOtherTest.cpp │ │ ├── JsonTest.cpp │ │ ├── JsonTestUtilTest.cpp │ │ ├── json_patch_test.cpp │ │ └── json_pointer_test.cpp │ └── tool/ │ ├── BUCK │ └── JSONSchemaTester.cpp ├── json.h ├── json_patch.h ├── json_pointer.h ├── lang/ │ ├── Access.h │ ├── Align.h │ ├── Aligned.h │ ├── Assume.h │ ├── BUCK │ ├── Badge.h │ ├── Bits.h │ ├── BitsClass.h │ ├── Builtin.h │ ├── CArray.h │ ├── CMakeLists.txt │ ├── CString.cpp │ ├── CString.h │ ├── Cast.h │ ├── CheckedMath.h │ ├── CustomizationPoint.h │ ├── Exception.cpp │ ├── Exception.h │ ├── Extern.h │ ├── Hint-inl.h │ ├── Hint.h │ ├── Keep.h │ ├── MustUseImmediately.h │ ├── New.h │ ├── Ordering.h │ ├── Pretty.h │ ├── PropagateConst.h │ ├── RValueReferenceWrapper.h │ ├── SafeAlias-fwd.h │ ├── SafeAlias.h │ ├── SafeAssert.cpp │ ├── SafeAssert.h │ ├── SafeClosure.h │ ├── SafeClosure.md │ ├── StaticConst.h │ ├── Strong.h │ ├── Switch.h │ ├── Thunk.h │ ├── ToAscii.cpp │ ├── ToAscii.h │ ├── TypeInfo.h │ ├── UncaughtExceptions.cpp │ ├── UncaughtExceptions.h │ ├── VectorTraits.h │ ├── bind/ │ │ ├── AsArgument.h │ │ ├── BUCK │ │ ├── Bind.h │ │ ├── Bind.md │ │ ├── CMakeLists.txt │ │ ├── Named.h │ │ ├── NamedToStorage.h │ │ ├── ToStorage.h │ │ └── test/ │ │ ├── AsArgumentTest.cpp │ │ ├── BUCK │ │ ├── BindTest.cpp │ │ ├── NamedTest.cpp │ │ ├── NamedToStorageTest.cpp │ │ └── ToStorageTest.cpp │ ├── cstring_view.h │ └── test/ │ ├── AlignTest.cpp │ ├── AlignedTest.cpp │ ├── BUCK │ ├── BadgeTest.cpp │ ├── BitsBenchmark.cpp │ ├── BitsClassBenchmark.cpp │ ├── BitsClassTest.cpp │ ├── BitsTest.cpp │ ├── CStringTest.cpp │ ├── CastTest.cpp │ ├── CheckedMathTest.cpp │ ├── ExceptionBench.cpp │ ├── ExceptionTest.cpp │ ├── ExternTest.cpp │ ├── HintTest.cpp │ ├── MustUseImmediatelyTest.cpp │ ├── NewTest.cpp │ ├── OrderingTest.cpp │ ├── PrettyTest.cpp │ ├── PropagateConstTest.cpp │ ├── RValueReferenceWrapperTest.cpp │ ├── SafeAliasTest.cpp │ ├── SafeAssertTest.cpp │ ├── SafeClosureTest.cpp │ ├── StrongTest.cpp │ ├── SwitchTest.cpp │ ├── ThunkTest.cpp │ ├── ToAsciiBench.cpp │ ├── ToAsciiTest.cpp │ ├── TypeInfoTest.cpp │ ├── VectorTraitsTest.cpp │ └── cstring_view_test.cpp ├── logging/ │ ├── AsyncFileWriter.cpp │ ├── AsyncFileWriter.h │ ├── AsyncLogWriter.cpp │ ├── AsyncLogWriter.h │ ├── AutoTimer.h │ ├── BUCK │ ├── BridgeFromGoogleLogging.cpp │ ├── BridgeFromGoogleLogging.h │ ├── CMakeLists.txt │ ├── CustomLogFormatter.cpp │ ├── CustomLogFormatter.h │ ├── FileHandlerFactory.cpp │ ├── FileHandlerFactory.h │ ├── FileWriterFactory.cpp │ ├── FileWriterFactory.h │ ├── GlogStyleFormatter.cpp │ ├── GlogStyleFormatter.h │ ├── ImmediateFileWriter.cpp │ ├── ImmediateFileWriter.h │ ├── Init.cpp │ ├── Init.h │ ├── InitWeak.cpp │ ├── LogCategory.cpp │ ├── LogCategory.h │ ├── LogCategoryConfig.cpp │ ├── LogCategoryConfig.h │ ├── LogConfig.cpp │ ├── LogConfig.h │ ├── LogConfigParser.cpp │ ├── LogConfigParser.h │ ├── LogFormatter.h │ ├── LogHandler.h │ ├── LogHandlerConfig.cpp │ ├── LogHandlerConfig.h │ ├── LogHandlerFactory.h │ ├── LogLevel.cpp │ ├── LogLevel.h │ ├── LogMessage.cpp │ ├── LogMessage.h │ ├── LogName.cpp │ ├── LogName.h │ ├── LogStream.cpp │ ├── LogStream.h │ ├── LogStreamProcessor.cpp │ ├── LogStreamProcessor.h │ ├── LogWriter.h │ ├── Logger.cpp │ ├── Logger.h │ ├── LoggerDB.cpp │ ├── LoggerDB.h │ ├── ObjectToString.cpp │ ├── ObjectToString.h │ ├── README.md │ ├── RateLimiter.cpp │ ├── RateLimiter.h │ ├── StandardLogHandler.cpp │ ├── StandardLogHandler.h │ ├── StandardLogHandlerFactory.cpp │ ├── StandardLogHandlerFactory.h │ ├── StreamHandlerFactory.cpp │ ├── StreamHandlerFactory.h │ ├── docs/ │ │ ├── Comparisons.md │ │ ├── Config.md │ │ ├── LogCategories.md │ │ ├── LogHandlers.md │ │ ├── LogLevels.md │ │ ├── Overview.md │ │ └── Usage.md │ ├── example/ │ │ ├── BUCK │ │ ├── CMakeLists.txt │ │ ├── lib.cpp │ │ ├── lib.h │ │ └── main.cpp │ ├── test/ │ │ ├── AsyncFileWriterTest.cpp │ │ ├── AsyncLogWriterTest.cpp │ │ ├── AutoTimerTest.cpp │ │ ├── BUCK │ │ ├── BridgeFromGoogleLoggingTest.cpp │ │ ├── ConfigHelpers.cpp │ │ ├── ConfigHelpers.h │ │ ├── ConfigParserTest.cpp │ │ ├── ConfigUpdateTest.cpp │ │ ├── CustomLogFormatterTest.cpp │ │ ├── FileHandlerFactoryTest.cpp │ │ ├── GlogFormatterTest.cpp │ │ ├── ImmediateFileWriterTest.cpp │ │ ├── InitTest.cpp │ │ ├── LogCategoryTest.cpp │ │ ├── LogLevelTest.cpp │ │ ├── LogMessageTest.cpp │ │ ├── LogNameTest.cpp │ │ ├── LogStreamTest.cpp │ │ ├── LoggerDBTest.cpp │ │ ├── LoggerTest.cpp │ │ ├── RateLimiterTest.cpp │ │ ├── StandardLogHandlerFactoryTest.cpp │ │ ├── StandardLogHandlerTest.cpp │ │ ├── SyncLevelTest.cpp │ │ ├── TestLogHandler.cpp │ │ ├── TestLogHandler.h │ │ ├── XlogBench.cpp │ │ ├── XlogFile1.cpp │ │ ├── XlogFile2.cpp │ │ ├── XlogHeader1.h │ │ ├── XlogHeader2.h │ │ ├── XlogTest.cpp │ │ ├── fatal_test.py │ │ ├── helpers/ │ │ │ ├── BUCK │ │ │ ├── FatalHelper.cpp │ │ │ ├── LogAfterMain.cpp │ │ │ ├── LogAfterMainNoInit.cpp │ │ │ ├── LogOnShutdownLib.cpp │ │ │ ├── LogOnShutdownLib.h │ │ │ └── helpers.h │ │ └── log_after_main.py │ ├── xlog.cpp │ └── xlog.h ├── memcpy.S ├── memcpy_select_aarch64.cpp ├── memory/ │ ├── Arena-inl.h │ ├── Arena.h │ ├── BUCK │ ├── CMakeLists.txt │ ├── IOBufArenaFactory.h │ ├── IoUringArena.cpp │ ├── IoUringArena.h │ ├── IoUringArenaTest.cpp │ ├── JemallocHugePageAllocator.cpp │ ├── JemallocHugePageAllocator.h │ ├── JemallocHugePageAllocatorTest.cpp │ ├── JemallocNodumpAllocator.cpp │ ├── JemallocNodumpAllocator.h │ ├── JemallocNodumpAllocatorTest.cpp │ ├── MallctlHelper.cpp │ ├── MallctlHelper.h │ ├── Malloc.cpp │ ├── Malloc.h │ ├── MemoryResource.h │ ├── ReentrantAllocator.cpp │ ├── ReentrantAllocator.h │ ├── SanitizeAddress.cpp │ ├── SanitizeAddress.h │ ├── SanitizeLeak.cpp │ ├── SanitizeLeak.h │ ├── ThreadCachedArena.cpp │ ├── ThreadCachedArena.h │ ├── UninitializedMemoryHacks.h │ ├── detail/ │ │ ├── BUCK │ │ ├── CMakeLists.txt │ │ ├── MallocImpl.cpp │ │ └── MallocImpl.h │ ├── not_null-inl.h │ ├── not_null.h │ ├── shared_from_this_ptr.h │ └── test/ │ ├── ArenaTest.cpp │ ├── BUCK │ ├── IOBufArenaFactoryTest.cpp │ ├── MallctlHelperTest.cpp │ ├── MallocBenchmark.cpp │ ├── MallocTest.cpp │ ├── MemoryResourceTest.cpp │ ├── ReentrantAllocatorTest.cpp │ ├── SanitizeAddressTest.cpp │ ├── SanitizeLeakTest.cpp │ ├── ThreadCachedArenaTest.cpp │ ├── UninitializedMemoryHacksODR.cpp │ ├── UninitializedMemoryHacksTest.cpp │ ├── not_null_test.cpp │ └── shared_from_this_ptr_test.cpp ├── memset.S ├── memset_select_aarch64.cpp ├── net/ │ ├── BUCK │ ├── CMakeLists.txt │ ├── NetOps.cpp │ ├── NetOps.h │ ├── NetOpsDispatcher.cpp │ ├── NetOpsDispatcher.h │ ├── NetworkSocket.h │ ├── TcpInfo.cpp │ ├── TcpInfo.h │ ├── TcpInfoDispatcher.cpp │ ├── TcpInfoDispatcher.h │ ├── TcpInfoTypes.h │ ├── detail/ │ │ ├── BUCK │ │ ├── CMakeLists.txt │ │ ├── SocketFileDescriptorMap.cpp │ │ ├── SocketFileDescriptorMap.h │ │ └── test/ │ │ ├── BUCK │ │ └── SocketFileDescriptorMapTest.cpp │ └── test/ │ ├── BUCK │ ├── MockNetOpsDispatcher.h │ ├── MockTcpInfoDispatcher.h │ ├── NetOpsTest.cpp │ ├── TcpInfoTest.cpp │ └── TcpInfoTestUtil.h ├── observer/ │ ├── BUCK │ ├── CMakeLists.txt │ ├── CoreCachedObserver.h │ ├── HazptrObserver.h │ ├── Observable-inl.h │ ├── Observable.h │ ├── Observer-inl.h │ ├── Observer-pre.h │ ├── Observer.h │ ├── ReadMostlyTLObserver.h │ ├── SimpleObservable-inl.h │ ├── SimpleObservable.h │ ├── WithJitter-inl.h │ ├── WithJitter.h │ ├── detail/ │ │ ├── BUCK │ │ ├── CMakeLists.txt │ │ ├── Core.cpp │ │ ├── Core.h │ │ ├── GraphCycleDetector.h │ │ ├── ObserverManager.cpp │ │ └── ObserverManager.h │ └── test/ │ ├── BUCK │ ├── ObserverBenchmark.cpp │ └── ObserverTest.cpp ├── poly/ │ ├── BUCK │ ├── CMakeLists.txt │ ├── Nullable.h │ └── Regular.h ├── portability/ │ ├── Asm.h │ ├── Atomic.h │ ├── BUCK │ ├── Builtins.cpp │ ├── Builtins.h │ ├── CMakeLists.txt │ ├── Config.h │ ├── Constexpr.h │ ├── Dirent.cpp │ ├── Dirent.h │ ├── Event.h │ ├── Fcntl.cpp │ ├── Fcntl.h │ ├── Filesystem.cpp │ ├── Filesystem.h │ ├── FmtCompile.h │ ├── GFlags.cpp │ ├── GFlags.h │ ├── GMock.h │ ├── GTest.h │ ├── GTestProd.h │ ├── IOVec.h │ ├── Libgen.cpp │ ├── Libgen.h │ ├── Libunwind.h │ ├── Malloc.cpp │ ├── Malloc.h │ ├── Math.h │ ├── Memory.h │ ├── OpenSSL.cpp │ ├── OpenSSL.h │ ├── PThread.cpp │ ├── PThread.h │ ├── README.md │ ├── Sched.cpp │ ├── Sched.h │ ├── Sockets.cpp │ ├── Sockets.h │ ├── SourceLocation.h │ ├── Stdio.cpp │ ├── Stdio.h │ ├── Stdlib.cpp │ ├── Stdlib.h │ ├── String.cpp │ ├── String.h │ ├── SysFile.cpp │ ├── SysFile.h │ ├── SysMembarrier.cpp │ ├── SysMembarrier.h │ ├── SysMman.cpp │ ├── SysMman.h │ ├── SysResource.cpp │ ├── SysResource.h │ ├── SysStat.cpp │ ├── SysStat.h │ ├── SysSyscall.h │ ├── SysTime.cpp │ ├── SysTime.h │ ├── SysTypes.h │ ├── SysUio.cpp │ ├── SysUio.h │ ├── Syslog.h │ ├── Time.cpp │ ├── Time.h │ ├── Unistd.cpp │ ├── Unistd.h │ ├── Windows.h │ ├── provide/ │ │ ├── BUCK │ │ └── CMakeLists.txt │ └── test/ │ ├── BUCK │ ├── ConstexprTest.cpp │ ├── FcntlTest.cpp │ ├── FilesystemTest.cpp │ ├── LibgenTest.cpp │ ├── OpenSSLPortabilityTest.cpp │ ├── PThreadTest.cpp │ ├── TimeTest.cpp │ └── UnistdTest.cpp ├── python/ │ ├── AsyncioExecutor.cpp │ ├── AsyncioExecutor.h │ ├── BUCK │ ├── CMakeLists.txt │ ├── GILAwareManualExecutor.cpp │ ├── GILAwareManualExecutor.h │ ├── ProactorExecutor.cpp │ ├── ProactorExecutor.h │ ├── ScopedGILRelease.h │ ├── Weak.h │ ├── __init__.pxd │ ├── async_generator.h │ ├── async_generator.pxd │ ├── build_mode.pyi │ ├── build_mode.pyx │ ├── cast.pxd │ ├── coro.h │ ├── coro.pxd │ ├── error.cpp │ ├── error.h │ ├── executor.cpp │ ├── executor.h │ ├── executor.pxd │ ├── executor.pyx │ ├── executor_detail.pxd │ ├── expected.pxd │ ├── fbstring.pxd │ ├── fiber_manager.pxd │ ├── fiber_manager.pyx │ ├── fibers.cpp │ ├── fibers.h │ ├── fibers.pxd │ ├── function.pxd │ ├── futures.h │ ├── futures.pxd │ ├── import.h │ ├── iobuf.cpp │ ├── iobuf.h │ ├── iobuf.pxd │ ├── iobuf.pyi │ ├── iobuf.pyx │ ├── iobuf_ext.cpp │ ├── iobuf_ext.h │ ├── memory.pxd │ ├── optional.pxd │ ├── range.pxd │ ├── request_context.h │ ├── request_context.pxd │ ├── request_context.pyi │ ├── request_context.pyx │ ├── setup.py │ ├── test/ │ │ ├── AsyncGeneratorTest.cpp │ │ ├── BUCK │ │ ├── ErrorTest.cpp │ │ ├── IOBufTestUtils.cpp │ │ ├── IOBufTestUtils.h │ │ ├── WeakTest.cpp │ │ ├── coro.py │ │ ├── futures.py │ │ ├── generator.py │ │ ├── iobuf.py │ │ ├── iobuf_helper.pxd │ │ ├── iobuf_helper.pyi │ │ ├── iobuf_helper.pyx │ │ ├── request_context.py │ │ ├── request_context_helper.pyi │ │ ├── request_context_helper.pyx │ │ ├── simple.h │ │ ├── simplebridge.pyx │ │ ├── simplebridgecoro.pyx │ │ ├── simplecoro.h │ │ ├── simplegenerator.h │ │ ├── simplegenerator.pyx │ │ ├── teardown.py │ │ ├── test_set_executor.h │ │ ├── test_set_executor.py │ │ └── test_set_executor_cython.pyx │ └── windows/ │ └── iocp.pyx ├── random/ │ ├── BUCK │ ├── CMakeLists.txt │ ├── hash.h │ ├── seed_seq.h │ ├── test/ │ │ ├── BUCK │ │ ├── Xoshiro256ppTest.cpp │ │ ├── engine_bench.cpp │ │ ├── hash_test.cpp │ │ └── seed_seq_test.cpp │ └── xoshiro256pp.h ├── result/ │ ├── BUCK │ ├── CMakeLists.txt │ ├── README.md │ ├── coded_rich_error.h │ ├── coro.h │ ├── demo/ │ │ ├── BUCK │ │ └── basketball.cpp │ ├── detail/ │ │ ├── BUCK │ │ ├── CMakeLists.txt │ │ ├── immortal_exception_storage.h │ │ ├── ptr_immortal_exception_storage.h │ │ ├── result_or_unwind.h │ │ ├── result_promise.h │ │ ├── rich_error_common.h │ │ ├── rich_exception_ptr_storage.cpp │ │ └── rich_exception_ptr_storage.h │ ├── docs/ │ │ ├── design_notes.md │ │ ├── epitaphs.md │ │ ├── future_epitaph_in_place.md │ │ ├── future_fast_rtti.md │ │ ├── future_ideas.md │ │ ├── future_small_value.md │ │ ├── result.md │ │ ├── rich_error.md │ │ ├── rich_error_code.md │ │ └── rich_exception_ptr.md │ ├── epitaph.cpp │ ├── epitaph.h │ ├── errc_rich_error.cpp │ ├── errc_rich_error.h │ ├── gtest_helpers.h │ ├── immortal_rich_error.h │ ├── nestable_coded_rich_error.h │ ├── or_unwind_epitaph.h │ ├── result.cpp │ ├── result.h │ ├── rich_error.h │ ├── rich_error_base.cpp │ ├── rich_error_base.h │ ├── rich_error_code.cpp │ ├── rich_error_code.h │ ├── rich_error_fwd.h │ ├── rich_exception_ptr.cpp │ ├── rich_exception_ptr.h │ ├── rich_msg.h │ ├── test/ │ │ ├── BUCK │ │ ├── coded_rich_error_test.cpp │ │ ├── common.h │ │ ├── epitaph_bench.cpp │ │ ├── epitaph_test.cpp │ │ ├── errc_rich_error_test.cpp │ │ ├── immortal_rich_error_test.cpp │ │ ├── nestable_coded_rich_error_test.cpp │ │ ├── or_unwind_epitaph_test.cpp │ │ ├── or_unwind_test.cpp │ │ ├── result_bench.cpp │ │ ├── result_coro_bench.cpp │ │ ├── result_test.cpp │ │ ├── rich_error_code_bench.cpp │ │ ├── rich_error_code_test.cpp │ │ ├── rich_error_codes.h │ │ ├── rich_error_test.cpp │ │ ├── rich_exception_ptr_bench.cpp │ │ ├── rich_exception_ptr_check_get.h │ │ ├── rich_exception_ptr_common.h │ │ ├── rich_exception_ptr_constexpr.cpp │ │ ├── rich_exception_ptr_fmt_bench.cpp │ │ ├── rich_exception_ptr_fmt_test.cpp │ │ ├── rich_exception_ptr_fundamentals_test.cpp │ │ ├── rich_exception_ptr_immortal_test.cpp │ │ ├── rich_exception_ptr_misc_test.cpp │ │ ├── rich_exception_ptr_owned_test.cpp │ │ ├── rich_msg_test.cpp │ │ ├── stack_epitaph_test.cpp │ │ ├── try_test.cpp │ │ └── value_only_result_test.cpp │ ├── try.h │ ├── value_only_result.h │ └── value_only_result_coro.h ├── rust/ │ ├── BUCK │ ├── compression/ │ │ ├── BUCK │ │ ├── compression.cpp │ │ ├── compression.h │ │ └── compression.rs │ ├── dynamic/ │ │ ├── BUCK │ │ ├── dynamic.cpp │ │ ├── dynamic.h │ │ └── dynamic.rs │ ├── iobuf/ │ │ ├── BUCK │ │ ├── iobuf.cpp │ │ ├── iobuf.h │ │ ├── iobuf_sys.rs │ │ └── src/ │ │ ├── cursor.rs │ │ ├── iobuf.rs │ │ ├── iobufmut.rs │ │ ├── lib.rs │ │ └── test.rs │ ├── lib.rs │ ├── logging/ │ │ ├── BUCK │ │ ├── lib.rs │ │ ├── logging.cpp │ │ └── logging.h │ ├── memory/ │ │ ├── BUCK │ │ ├── lib.rs │ │ ├── memory.cpp │ │ └── memory.h │ ├── network_address/ │ │ ├── BUCK │ │ ├── FollyWrapper.cpp │ │ ├── FollyWrapper.h │ │ └── src/ │ │ └── lib.rs │ ├── request_context/ │ │ ├── BUCK │ │ ├── benches/ │ │ │ └── request_context_tokio_bench.rs │ │ ├── request_context.cpp │ │ ├── request_context.h │ │ ├── request_context.rs │ │ ├── request_context_future.rs │ │ └── request_context_tokio.rs │ ├── singleton_vault/ │ │ ├── BUCK │ │ ├── lib.rs │ │ ├── singleton.cpp │ │ ├── singleton.h │ │ ├── singleton_test.cpp │ │ ├── singleton_test.h │ │ └── test.rs │ ├── socket_address/ │ │ ├── BUCK │ │ ├── RustSocketAddress.cpp │ │ ├── RustSocketAddress.h │ │ └── lib.rs │ ├── string/ │ │ ├── BUCK │ │ ├── lib.rs │ │ ├── string.cpp │ │ ├── string.h │ │ └── string.rs │ └── tdigest/ │ ├── BUCK │ ├── tdigest.cpp │ ├── tdigest.h │ └── tdigest.rs ├── settings/ │ ├── BUCK │ ├── CMakeLists.txt │ ├── CommandLineParser.cpp │ ├── CommandLineParser.h │ ├── Immutables.cpp │ ├── Immutables.h │ ├── Observer.h │ ├── Settings.cpp │ ├── Settings.h │ ├── SettingsAccessorProxy.cpp │ ├── SettingsAccessorProxy.h │ ├── Types.cpp │ ├── Types.h │ ├── detail/ │ │ └── SettingsImpl.h │ └── test/ │ ├── BUCK │ ├── CommandLineParserTest.cpp │ ├── SettingsBenchmarks.cpp │ ├── SettingsTest.cpp │ ├── a.cpp │ ├── a.h │ ├── b.cpp │ └── b.h ├── small_vector.h ├── somerge_defs.bzl ├── sorted_vector_types.h ├── ssl/ │ ├── BUCK │ ├── CMakeLists.txt │ ├── OpenSSLCertUtils.cpp │ ├── OpenSSLCertUtils.h │ ├── OpenSSLHash.cpp │ ├── OpenSSLHash.h │ ├── OpenSSLKeyUtils.cpp │ ├── OpenSSLKeyUtils.h │ ├── OpenSSLPtrTypes.h │ ├── OpenSSLTicketHandler.h │ ├── OpenSSLVersionFinder.h │ ├── PasswordCollector.cpp │ ├── PasswordCollector.h │ ├── SSLSession.h │ ├── SSLSessionManager.cpp │ ├── SSLSessionManager.h │ ├── detail/ │ │ ├── BUCK │ │ ├── CMakeLists.txt │ │ ├── OpenSSLSession.cpp │ │ └── OpenSSLSession.h │ └── test/ │ ├── BUCK │ ├── OpenSSLCertUtilsTest.cpp │ ├── OpenSSLHashTest.cpp │ ├── OpenSSLKeyUtilsTest.cpp │ └── SSLSessionManagerTest.cpp ├── stats/ │ ├── BUCK │ ├── BucketedTimeSeries-inl.h │ ├── BucketedTimeSeries.h │ ├── CMakeLists.txt │ ├── DigestBuilder-inl.h │ ├── DigestBuilder.h │ ├── Histogram-inl.h │ ├── Histogram.h │ ├── MultiLevelTimeSeries-inl.h │ ├── MultiLevelTimeSeries.h │ ├── QuantileEstimator-inl.h │ ├── QuantileEstimator.cpp │ ├── QuantileEstimator.h │ ├── QuantileHistogram-inl.h │ ├── QuantileHistogram.h │ ├── StreamingStats.h │ ├── TDigest.cpp │ ├── TDigest.h │ ├── TimeseriesHistogram-inl.h │ ├── TimeseriesHistogram.h │ ├── detail/ │ │ ├── BUCK │ │ ├── Bucket.h │ │ ├── BufferedStat-inl.h │ │ ├── BufferedStat.h │ │ ├── CMakeLists.txt │ │ ├── DoubleRadixSort.cpp │ │ ├── DoubleRadixSort.h │ │ ├── SlidingWindow-inl.h │ │ ├── SlidingWindow.h │ │ └── test/ │ │ ├── BUCK │ │ └── DoubleRadixSortTest.cpp │ └── test/ │ ├── BUCK │ ├── BucketedTimeSeriesBenchmark.cpp │ ├── BufferedStatTest.cpp │ ├── DigestBuilderBenchmark.cpp │ ├── DigestBuilderTest.cpp │ ├── HistogramBenchmark.cpp │ ├── HistogramTest.cpp │ ├── MultiLevelTimeSeriesBenchmark.cpp │ ├── QuantileEstimatorBenchmark.cpp │ ├── QuantileEstimatorTest.cpp │ ├── QuantileHistogramBenchmark.cpp │ ├── QuantileHistogramTest.cpp │ ├── SlidingWindowTest.cpp │ ├── StreamingStatsTest.cpp │ ├── TDigestBenchmark.cpp │ ├── TDigestTest.cpp │ ├── TimeSeriesTest.cpp │ └── TimeseriesHistogramTest.cpp ├── stop_watch.h ├── support/ │ ├── gdb.py │ └── test/ │ ├── BUCK │ ├── GdbUtil.h │ ├── main.cpp │ └── runtest.gdb ├── synchronization/ │ ├── AsymmetricThreadFence.cpp │ ├── AsymmetricThreadFence.h │ ├── AtomicNotification-inl.h │ ├── AtomicNotification.cpp │ ├── AtomicNotification.h │ ├── AtomicRef.h │ ├── AtomicStruct.h │ ├── AtomicUtil-inl.h │ ├── AtomicUtil.h │ ├── BUCK │ ├── Baton.h │ ├── CMakeLists.txt │ ├── CallOnce.h │ ├── DelayedInit.h │ ├── DistributedMutex-inl.h │ ├── DistributedMutex.cpp │ ├── DistributedMutex.h │ ├── EventCount.h │ ├── FlatCombining.h │ ├── Hazptr-fwd.h │ ├── Hazptr.cpp │ ├── Hazptr.h │ ├── HazptrDomain.cpp │ ├── HazptrDomain.h │ ├── HazptrHolder.h │ ├── HazptrObj.h │ ├── HazptrObjLinked.h │ ├── HazptrRec.h │ ├── HazptrThrLocal.h │ ├── HazptrThreadPoolExecutor.cpp │ ├── HazptrThreadPoolExecutor.h │ ├── Latch.h │ ├── LifoSem.h │ ├── Lock.h │ ├── MicroSpinLock.h │ ├── NativeSemaphore.h │ ├── ParkingLot.cpp │ ├── ParkingLot.h │ ├── PicoSpinLock.h │ ├── RWSpinLock.h │ ├── Rcu.cpp │ ├── Rcu.h │ ├── RelaxedAtomic.h │ ├── SanitizeThread.cpp │ ├── SanitizeThread.h │ ├── SaturatingSemaphore.h │ ├── SmallLocks.h │ ├── StripedThrottledLifoSem.cpp │ ├── StripedThrottledLifoSem.h │ ├── ThrottledLifoSem.h │ ├── WaitOptions.h │ ├── detail/ │ │ ├── AtomicUtils.h │ │ ├── BUCK │ │ ├── CMakeLists.txt │ │ ├── HazptrUtils.h │ │ ├── InlineFunctionRef.h │ │ ├── Sleeper.cpp │ │ ├── Sleeper.h │ │ ├── Spin.h │ │ ├── ThreadCachedLists.h │ │ ├── ThreadCachedReaders.h │ │ ├── ThreadCachedTag.h │ │ └── test/ │ │ ├── BUCK │ │ └── InlineFunctionRefTest.cpp │ ├── example/ │ │ ├── BUCK │ │ ├── HazptrLockFreeLIFO.h │ │ ├── HazptrSWMRSet.h │ │ └── HazptrWideCAS.h │ └── test/ │ ├── AtomicNotificationTest.cpp │ ├── AtomicRefTest.cpp │ ├── AtomicStructTest.cpp │ ├── AtomicUtilBench.cpp │ ├── AtomicUtilTest.cpp │ ├── BUCK │ ├── Barrier.h │ ├── BarrierTest.cpp │ ├── BatonBenchmark.cpp │ ├── BatonTest.cpp │ ├── BatonTestHelpers.h │ ├── CallOnceBenchmark.cpp │ ├── CallOnceTest.cpp │ ├── DelayedInitTest.cpp │ ├── DistributedMutexTest.cpp │ ├── EventCountTest.cpp │ ├── FlatCombiningBenchmark.cpp │ ├── FlatCombiningExamples.h │ ├── FlatCombiningTest.cpp │ ├── FlatCombiningTestHelpers.h │ ├── HazptrBench.cpp │ ├── HazptrTest.cpp │ ├── LatchTest.cpp │ ├── LifoSemBench.cpp │ ├── LifoSemTests.cpp │ ├── LockTest.cpp │ ├── NativeSemaphoreTest.cpp │ ├── ParkingLotBenchmark.cpp │ ├── ParkingLotTest.cpp │ ├── RWSpinLockTest.cpp │ ├── RcuBench.cpp │ ├── RcuTest.cpp │ ├── RelaxedAtomicTest.cpp │ ├── SaturatingSemaphoreTest.cpp │ ├── Semaphore.h │ ├── SemaphoreTest.cpp │ ├── SmallLocksBenchmark.cpp │ ├── SmallLocksTest.cpp │ ├── StripedThrottledLifoSemTest.cpp │ ├── ThreadCachedEpochBench.h │ ├── ThreadCachedReadersBench.cpp │ ├── ThreadCachedReadersTest.cpp │ └── ThrottledLifoSemTest.cpp ├── system/ │ ├── AtFork.cpp │ ├── AtFork.h │ ├── AuxVector.h │ ├── BUCK │ ├── CMakeLists.txt │ ├── EnvUtil.cpp │ ├── EnvUtil.h │ ├── HardwareConcurrency.cpp │ ├── HardwareConcurrency.h │ ├── MemoryMapping.cpp │ ├── MemoryMapping.h │ ├── Pid.cpp │ ├── Pid.h │ ├── Shell.cpp │ ├── Shell.h │ ├── ThreadId.cpp │ ├── ThreadId.h │ ├── ThreadName.cpp │ ├── ThreadName.h │ ├── arch/ │ │ ├── BUCK │ │ ├── CMakeLists.txt │ │ ├── test/ │ │ │ ├── BUCK │ │ │ └── x86_test.cpp │ │ └── x86.h │ ├── os/ │ │ ├── BUCK │ │ ├── CMakeLists.txt │ │ ├── linux.cpp │ │ └── linux.h │ └── test/ │ ├── AtForkTest.cpp │ ├── AuxVectorTest.cpp │ ├── BUCK │ ├── EnvUtilTest.cpp │ ├── HardwareConcurrencyTest.cpp │ ├── MemoryMappingTest.cpp │ ├── PidTest.cpp │ ├── ShellTest.cpp │ ├── ThreadIdTest.cpp │ └── ThreadNameTest.cpp ├── test/ │ ├── AHMIntStressTest.cpp │ ├── ArenaSmartPtrTest.cpp │ ├── AsciiCaseInsensitiveBenchmark.cpp │ ├── AsciiCaseInsensitiveTest.cpp │ ├── AtomicHashArrayTest.cpp │ ├── AtomicHashMapTest.cpp │ ├── AtomicLinkedListTest.cpp │ ├── AtomicUnorderedMapTest.cpp │ ├── BUCK │ ├── BenchmarkIntegrationTest.cpp │ ├── BenchmarkTest.cpp │ ├── BenchmarkUtilTest.cpp │ ├── BufferedAtomic.cpp │ ├── BufferedAtomic.h │ ├── BufferedAtomicTest.cpp │ ├── CancellationTokenBench.cpp │ ├── CancellationTokenTest.cpp │ ├── ChronoBench.cpp │ ├── ChronoTest.cpp │ ├── ClockGettimeWrappersTest.cpp │ ├── ComparisonOperatorTestUtil.h │ ├── ConcurrentBitSetTest.cpp │ ├── ConcurrentLazyTest.cpp │ ├── ConcurrentSkipListBenchmark.cpp │ ├── ConcurrentSkipListTest.cpp │ ├── ConstexprMathBenchmark.cpp │ ├── ConstexprMathTest.cpp │ ├── ConstructorCallbackListTest.cpp │ ├── ConvBenchmark.cpp │ ├── ConvTest.cpp │ ├── CpuIdTest.cpp │ ├── DemangleTest.cpp │ ├── DeterministicSchedule.cpp │ ├── DeterministicSchedule.h │ ├── DeterministicScheduleTest.cpp │ ├── DiscriminatedPtrTest.cpp │ ├── EndianTest.cpp │ ├── ExceptionStringTest.cpp │ ├── ExceptionTest.cpp │ ├── ExceptionWrapperBenchmark.cpp │ ├── ExceptionWrapperTest.cpp │ ├── ExecutorTest.cpp │ ├── ExpectedCoroutinesBench.cpp │ ├── ExpectedCoroutinesTest.cpp │ ├── ExpectedTest.cpp │ ├── FBStringBenchmark.cpp │ ├── FBStringTest.cpp │ ├── FBStringTestBenchmarks.cpp.h │ ├── FBVectorTestUtil.cpp │ ├── FBVectorTestUtil.h │ ├── FileLockTest.cpp │ ├── FileTest.cpp │ ├── FileTestLockHelper.cpp │ ├── FileUtilTest.cpp │ ├── FingerprintBenchmark.cpp │ ├── FingerprintTest.cpp │ ├── FixedStringTest.cpp │ ├── FmtUtilityTest.cpp │ ├── FormatBenchmark.cpp │ ├── FormatOtherTest.cpp │ ├── FormatTest.cpp │ ├── FunctionRefBenchmark.cpp │ ├── FunctionRefTest.cpp │ ├── FunctionTest.cpp │ ├── FutexTest.cpp │ ├── GLogBenchmark.cpp │ ├── GLogTest.cpp │ ├── GroupVarintTest.cpp │ ├── IPAddressBenchmark.cpp │ ├── IPAddressTest.cpp │ ├── IndestructibleTest.cpp │ ├── IndexedMemPoolTest.cpp │ ├── IteratorsTest.cpp │ ├── JsonMockUtil.h │ ├── JsonTestUtil.h │ ├── LazyTest.cpp │ ├── MPMCPipelineTest.cpp │ ├── MPMCQueueTest.cpp │ ├── MacAddressTest.cpp │ ├── MathBenchmark.cpp │ ├── MathTest.cpp │ ├── MaybeManagedPtrTest.cpp │ ├── MemcpyBenchmark.cpp │ ├── MemcpyTest.cpp │ ├── MemcpyUseTest.cpp │ ├── MemoryIdlerBenchmark.cpp │ ├── MemoryIdlerTest.cpp │ ├── MemoryTest.cpp │ ├── MemsetBenchmark.cpp │ ├── MemsetTest.cpp │ ├── MoveWrapperTest.cpp │ ├── ObserverContainerTest.cpp │ ├── ObserverContainerTestUtil.h │ ├── OptionalCoroutinesTest.cpp │ ├── OptionalTest.cpp │ ├── OverloadTest.cpp │ ├── PackedSyncPtrTest.cpp │ ├── PaddedTest.cpp │ ├── PolyTest.cpp │ ├── PortabilityTest.cpp │ ├── ProducerConsumerQueueBenchmark.cpp │ ├── ProducerConsumerQueueTest.cpp │ ├── RandomBenchmark.cpp │ ├── RandomTest.cpp │ ├── RangeFindBenchmark.cpp │ ├── RangeTest.cpp │ ├── ReplaceableTest.cpp │ ├── ScopeGuardTest.cpp │ ├── SharedMutexTest.cpp │ ├── SingletonBenchmark.cpp │ ├── SingletonTest.cpp │ ├── SingletonTestGlobal.cpp │ ├── SingletonTestStructs.cpp │ ├── SingletonTestStructs.h │ ├── SingletonThreadLocalTest.cpp │ ├── SingletonThreadLocalTestOverload.cpp │ ├── SocketAddressTest.cpp │ ├── SocketAddressTestHelper.cpp │ ├── SocketAddressTestHelper.h │ ├── SpinLockTest.cpp │ ├── StringBenchmark.cpp │ ├── StringTest.cpp │ ├── StringToFloatBenchmark.cpp │ ├── SubprocessBench.cpp │ ├── SubprocessTest.cpp │ ├── SubprocessTestParentDeathHelper.cpp │ ├── SynchronizedBenchmark.cpp │ ├── SynchronizedPtrTest.cpp │ ├── SynchronizedTest.cpp │ ├── SynchronizedTestLib-inl.h │ ├── SynchronizedTestLib.h │ ├── TestUtils.h │ ├── TestUtilsTest.cpp │ ├── ThreadCachedIntTest.cpp │ ├── ThreadLocalAccessBenchmark.cpp │ ├── ThreadLocalBenchmark.cpp │ ├── ThreadLocalDestroyBenchmark.cpp │ ├── ThreadLocalTest.cpp │ ├── ThreadLocalTestLib.cpp │ ├── TimeoutQueueTest.cpp │ ├── TokenBucketTest.cpp │ ├── TokenBucketTest.h │ ├── TraitsTest.cpp │ ├── TryTest.cpp │ ├── UTF8StringTest.cpp │ ├── UnicodeTest.cpp │ ├── UnitTest.cpp │ ├── UriBenchmark.cpp │ ├── UriTest.cpp │ ├── UtilityTest.cpp │ ├── VarintTest.cpp │ ├── base64_test.cpp │ ├── common/ │ │ ├── BUCK │ │ └── TestMain.cpp │ └── stl_tests/ │ ├── BUCK │ └── StlVectorTest.cpp ├── testing/ │ ├── BUCK │ ├── CMakeLists.txt │ ├── TestUtil.cpp │ ├── TestUtil.h │ └── test/ │ ├── BUCK │ └── TestUtilTest.cpp ├── tool/ │ ├── BUCK │ └── BenchmarkCompare.cpp └── tracing/ ├── AsyncStack-inl.h ├── AsyncStack.cpp ├── AsyncStack.h ├── BUCK ├── CMakeLists.txt ├── README.md ├── ScopedTraceSection.h ├── StaticTracepoint-ELF.h ├── StaticTracepoint.h └── test/ ├── AsyncStackTest.cpp ├── BUCK ├── StaticTracepointSectionTest.cpp ├── StaticTracepointSectionTest.lds ├── StaticTracepointTest.cpp ├── StaticTracepointTestModule.cpp └── StaticTracepointTestModule.h ================================================ FILE CONTENTS ================================================ ================================================ FILE: .buckconfig ================================================ [cells] gh_facebook_folly = . shim_DISABLED = shim [cell_aliases] root = gh_facebook_folly [oss] internal_cell = fbcode project_dirs = folly [buck2] directories_to_allow_relative_paths = //folly ================================================ FILE: .buckconfig.d/common.buckconfig ================================================ [cells] prelude = prelude none = none [cell_aliases] config = prelude ovr_config = prelude bazel_skylib = gh_facebook_buck2_shims_meta buck = gh_facebook_buck2_shims_meta fbcode = gh_facebook_buck2_shims_meta fbcode_macros = gh_facebook_buck2_shims_meta fbsource = gh_facebook_buck2_shims_meta shim = gh_facebook_buck2_shims_meta toolchains = gh_facebook_buck2_shims_meta [external_cells] prelude = bundled [build] execution_platforms = prelude//platforms:default [parser] target_platform_detector_spec = target:root//...->prelude//platforms:default target:shim//...->prelude//platforms:default ================================================ FILE: .buckroot ================================================ ================================================ FILE: .devcontainer/Containerfile ================================================ FROM mcr.microsoft.com/devcontainers/base:jammy RUN apt-get update \ && apt-get upgrade -y \ && apt-get install -y --no-install-recommends \ watchman \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* ================================================ FILE: .devcontainer/devcontainer.json ================================================ { "name": "Ubuntu", "build": { "cacheFrom": "ghcr.io/marinatedconcrete/config-devcontainer", "dockerfile": "Containerfile" }, "customizations": { "vscode": { "extensions": [ "GitHub.vscode-github-actions" ] } }, "features": { "ghcr.io/facebook/devcontainers/features/dotslash:1": {} } } ================================================ FILE: .github/dependabot.yml ================================================ --- version: 2 updates: - package-ecosystem: "devcontainers" directory: "/" schedule: interval: weekly ================================================ FILE: .github/scripts/bad_targets ================================================ # This is a list of bad targets that do not build in OSS. Ideally this list goes away # with time. root//folly/debugging/exception_tracer:exception_counter root//folly/debugging/exception_tracer:exception_tracer root//folly/debugging/exception_tracer:exception_tracer_callbacks root//folly/debugging/exception_tracer:smart_exception_stack_trace_hooks root//folly/debugging/exception_tracer:smart_exception_tracer root//folly/debugging/symbolizer/tool:folly-addr2line root//folly/debugging/symbolizer/tool:libFollySegFault.so root//folly/docs/examples/folly:baton_demo root//folly/docs/examples/folly:cancellation_callback_demo root//folly/docs/examples/folly:cancellation_source_demo root//folly/docs/examples/folly:cancellation_token_demo root//folly/docs/examples/folly:dynamic_converter_demo root//folly/docs/examples/folly:dynamic_demo root//folly/docs/examples/folly:executor_guide root//folly/docs/examples/folly:file_demo root//folly/docs/examples/folly:format_demo root//folly/docs/examples/folly:function_demo root//folly/docs/examples/folly:ipaddress_demo root//folly/docs/examples/folly:likely_demo root//folly/docs/examples/folly:map_util_demo root//folly/docs/examples/folly:scope_guard_demo root//folly/docs/examples/folly:scope_guard2_demo root//folly/docs/examples/folly:scoped_event_base_thread_demo root//folly/docs/examples/folly:scoped_event_base_thread2_demo root//folly/docs/examples/folly:synchronized_demo root//folly/docs/examples/folly/container:array_demo root//folly/docs/examples/folly/dynamic:array_demo root//folly/docs/examples/folly/dynamic:object_demo root//folly/docs/examples/folly/coro:async_scope_demo root//folly/docs/examples/folly/coro:cancellable_async_scope_demo root//folly/docs/examples/folly/coro:detach_on_cancel_demo root//folly/docs/examples/folly/coro:promise_demo root//folly/docs/examples/folly/coro:retry_demo root//folly/docs/examples/folly/coro:task_demo root//folly/docs/examples/folly/coro:with_cancellation_demo root//folly/docs/examples/folly/hash:hash_demo root//folly/docs/examples/folly/io:i_o_buf_demo root//folly/logging/example:example root//folly/python:executor_lib root//folly/python:fibers_lib root//folly/python:iobuf_lib root//folly/tool:benchmark_compare ================================================ FILE: .github/scripts/buck_build_and_test.sh ================================================ #!/bin/bash # Copyright (c) Meta Platforms, Inc. and affiliates. # # 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. TARGETS_FILE=$(mktemp) ./buck2 targets //... | grep -F -v -f .github/scripts/bad_targets | grep -v test >"$TARGETS_FILE" ./buck2 build @"$TARGETS_FILE" # ./buck2 test @"$TARGETS_FILE" ================================================ FILE: .github/workflows/TagIt.yml ================================================ on: push: tags: # Only match TagIt tags, which always start with this prefix - 'v20*' name: TagIt permissions: contents: read jobs: build: permissions: contents: write # for actions/create-release to create a release name: Release runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v6 - name: Archive project id: archive_project run: | FILE_NAME=${GITHUB_REPOSITORY#*/}-${GITHUB_REF##*/} git archive ${{ github.ref }} -o ${FILE_NAME}.zip git archive ${{ github.ref }} -o ${FILE_NAME}.tar.gz echo "file_name=${FILE_NAME}" >> $GITHUB_OUTPUT - name: Compute digests id: compute_digests run: | echo "tgz_256=$(openssl dgst -sha256 ${{ steps.archive_project.outputs.file_name }}.tar.gz)" >> $GITHUB_OUTPUT echo "tgz_512=$(openssl dgst -sha512 ${{ steps.archive_project.outputs.file_name }}.tar.gz)" >> $GITHUB_OUTPUT echo "zip_256=$(openssl dgst -sha256 ${{ steps.archive_project.outputs.file_name }}.zip)" >> $GITHUB_OUTPUT echo "zip_512=$(openssl dgst -sha512 ${{ steps.archive_project.outputs.file_name }}.zip)" >> $GITHUB_OUTPUT - name: Create Release id: create_release uses: actions/create-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: ${{ github.ref }} release_name: ${{ github.ref }} body: | Automated release from TagIt
File Hashes
draft: false prerelease: false - name: Upload zip uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.create_release.outputs.upload_url }} asset_path: ./${{ steps.archive_project.outputs.file_name }}.zip asset_name: ${{ steps.archive_project.outputs.file_name }}.zip asset_content_type: application/zip - name: Upload tar.gz uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.create_release.outputs.upload_url }} asset_path: ./${{ steps.archive_project.outputs.file_name }}.tar.gz asset_name: ${{ steps.archive_project.outputs.file_name }}.tar.gz asset_content_type: application/gzip ================================================ FILE: .github/workflows/devcontainer.yml ================================================ --- name: Development Container-Based CI on: pull_request: push: branches: - main workflow_dispatch: jobs: build: permissions: contents: read packages: write runs-on: ${{ matrix.runsOn }} strategy: matrix: runsOn: - ubuntu-24.04-arm - ubuntu-latest steps: - name: Checkout Repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Login to GitHub Container Registry uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Buildx uses: docker/setup-buildx-action@v3 with: driver: docker-container - if: ${{ github.event_name != 'pull_request' }} id: cache_to_helper run: echo "cacheTo=ghcr.io/${{ github.repository_owner }}/devcontainers/folly" >> $GITHUB_OUTPUT - name: Build devcontainer image uses: devcontainers/ci@8bf61b26e9c3a98f69cb6ce2f88d24ff59b785c6 # v0.3.1900000417 with: cacheFrom: ghcr.io/${{ github.repository_owner }}/devcontainers/folly cacheTo: ${{ steps.cache_to_helper.outputs.cacheTo }} env: | CI=1 imageName: ghcr.io/${{ github.repository_owner }}/devcontainers/folly push: filter refFilterForPush: refs/heads/main ================================================ FILE: .github/workflows/getdeps_linux.yml ================================================ # This file was @generated by getdeps.py name: linux on: push: branches: - main pull_request: branches: - main permissions: contents: read # to fetch code (actions/checkout) jobs: build: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v6 - name: Show disk space at start run: df -h - name: Free up disk space run: sudo rm -rf /usr/local/lib/android - name: Show disk space after freeing up run: df -h - name: Update system package info run: sudo --preserve-env=http_proxy apt-get update - name: Install system deps run: sudo --preserve-env=http_proxy python3 build/fbcode_builder/getdeps.py --allow-system-packages install-system-deps --recursive folly && sudo --preserve-env=http_proxy python3 build/fbcode_builder/getdeps.py --allow-system-packages install-system-deps --recursive patchelf - id: paths name: Query paths run: python3 build/fbcode_builder/getdeps.py --allow-system-packages query-paths --recursive --src-dir=. folly >> "$GITHUB_OUTPUT" - name: Fetch boost if: ${{ steps.paths.outputs.boost_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests boost - name: Fetch libaio if: ${{ steps.paths.outputs.libaio_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests libaio - name: Fetch ninja if: ${{ steps.paths.outputs.ninja_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests ninja - name: Fetch cmake if: ${{ steps.paths.outputs.cmake_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests cmake - name: Fetch double-conversion if: ${{ steps.paths.outputs.double-conversion_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests double-conversion - name: Fetch fast_float if: ${{ steps.paths.outputs.fast_float_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests fast_float - name: Fetch fmt if: ${{ steps.paths.outputs.fmt_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests fmt - name: Fetch gflags if: ${{ steps.paths.outputs.gflags_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests gflags - name: Fetch glog if: ${{ steps.paths.outputs.glog_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests glog - name: Fetch googletest if: ${{ steps.paths.outputs.googletest_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests googletest - name: Fetch libdwarf if: ${{ steps.paths.outputs.libdwarf_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests libdwarf - name: Fetch libevent if: ${{ steps.paths.outputs.libevent_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests libevent - name: Fetch zlib if: ${{ steps.paths.outputs.zlib_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests zlib - name: Fetch lz4 if: ${{ steps.paths.outputs.lz4_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests lz4 - name: Fetch snappy if: ${{ steps.paths.outputs.snappy_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests snappy - name: Fetch zstd if: ${{ steps.paths.outputs.zstd_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests zstd - name: Fetch autoconf if: ${{ steps.paths.outputs.autoconf_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests autoconf - name: Fetch automake if: ${{ steps.paths.outputs.automake_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests automake - name: Fetch libtool if: ${{ steps.paths.outputs.libtool_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests libtool - name: Fetch libiberty if: ${{ steps.paths.outputs.libiberty_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests libiberty - name: Fetch libsodium if: ${{ steps.paths.outputs.libsodium_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests libsodium - name: Fetch libunwind if: ${{ steps.paths.outputs.libunwind_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests libunwind - name: Fetch xz if: ${{ steps.paths.outputs.xz_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests xz - name: Restore boost from cache id: restore_boost if: ${{ steps.paths.outputs.boost_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.boost_INSTALL }} key: ${{ steps.paths.outputs.boost_CACHE_KEY }}-install - name: Build boost if: ${{ steps.paths.outputs.boost_SOURCE && ! steps.restore_boost.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --free-up-disk --no-tests boost - name: Save boost to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.boost_SOURCE && ! steps.restore_boost.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.boost_INSTALL }} key: ${{ steps.paths.outputs.boost_CACHE_KEY }}-install - name: Restore libaio from cache id: restore_libaio if: ${{ steps.paths.outputs.libaio_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.libaio_INSTALL }} key: ${{ steps.paths.outputs.libaio_CACHE_KEY }}-install - name: Build libaio if: ${{ steps.paths.outputs.libaio_SOURCE && ! steps.restore_libaio.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --free-up-disk --no-tests libaio - name: Save libaio to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.libaio_SOURCE && ! steps.restore_libaio.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.libaio_INSTALL }} key: ${{ steps.paths.outputs.libaio_CACHE_KEY }}-install - name: Restore ninja from cache id: restore_ninja if: ${{ steps.paths.outputs.ninja_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.ninja_INSTALL }} key: ${{ steps.paths.outputs.ninja_CACHE_KEY }}-install - name: Build ninja if: ${{ steps.paths.outputs.ninja_SOURCE && ! steps.restore_ninja.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --free-up-disk --no-tests ninja - name: Save ninja to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.ninja_SOURCE && ! steps.restore_ninja.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.ninja_INSTALL }} key: ${{ steps.paths.outputs.ninja_CACHE_KEY }}-install - name: Restore cmake from cache id: restore_cmake if: ${{ steps.paths.outputs.cmake_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.cmake_INSTALL }} key: ${{ steps.paths.outputs.cmake_CACHE_KEY }}-install - name: Build cmake if: ${{ steps.paths.outputs.cmake_SOURCE && ! steps.restore_cmake.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --free-up-disk --no-tests cmake - name: Save cmake to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.cmake_SOURCE && ! steps.restore_cmake.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.cmake_INSTALL }} key: ${{ steps.paths.outputs.cmake_CACHE_KEY }}-install - name: Restore double-conversion from cache id: restore_double-conversion if: ${{ steps.paths.outputs.double-conversion_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.double-conversion_INSTALL }} key: ${{ steps.paths.outputs.double-conversion_CACHE_KEY }}-install - name: Build double-conversion if: ${{ steps.paths.outputs.double-conversion_SOURCE && ! steps.restore_double-conversion.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --free-up-disk --no-tests double-conversion - name: Save double-conversion to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.double-conversion_SOURCE && ! steps.restore_double-conversion.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.double-conversion_INSTALL }} key: ${{ steps.paths.outputs.double-conversion_CACHE_KEY }}-install - name: Restore fast_float from cache id: restore_fast_float if: ${{ steps.paths.outputs.fast_float_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.fast_float_INSTALL }} key: ${{ steps.paths.outputs.fast_float_CACHE_KEY }}-install - name: Build fast_float if: ${{ steps.paths.outputs.fast_float_SOURCE && ! steps.restore_fast_float.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --free-up-disk --no-tests fast_float - name: Save fast_float to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.fast_float_SOURCE && ! steps.restore_fast_float.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.fast_float_INSTALL }} key: ${{ steps.paths.outputs.fast_float_CACHE_KEY }}-install - name: Restore fmt from cache id: restore_fmt if: ${{ steps.paths.outputs.fmt_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.fmt_INSTALL }} key: ${{ steps.paths.outputs.fmt_CACHE_KEY }}-install - name: Build fmt if: ${{ steps.paths.outputs.fmt_SOURCE && ! steps.restore_fmt.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --free-up-disk --no-tests fmt - name: Save fmt to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.fmt_SOURCE && ! steps.restore_fmt.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.fmt_INSTALL }} key: ${{ steps.paths.outputs.fmt_CACHE_KEY }}-install - name: Restore gflags from cache id: restore_gflags if: ${{ steps.paths.outputs.gflags_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.gflags_INSTALL }} key: ${{ steps.paths.outputs.gflags_CACHE_KEY }}-install - name: Build gflags if: ${{ steps.paths.outputs.gflags_SOURCE && ! steps.restore_gflags.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --free-up-disk --no-tests gflags - name: Save gflags to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.gflags_SOURCE && ! steps.restore_gflags.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.gflags_INSTALL }} key: ${{ steps.paths.outputs.gflags_CACHE_KEY }}-install - name: Restore glog from cache id: restore_glog if: ${{ steps.paths.outputs.glog_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.glog_INSTALL }} key: ${{ steps.paths.outputs.glog_CACHE_KEY }}-install - name: Build glog if: ${{ steps.paths.outputs.glog_SOURCE && ! steps.restore_glog.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --free-up-disk --no-tests glog - name: Save glog to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.glog_SOURCE && ! steps.restore_glog.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.glog_INSTALL }} key: ${{ steps.paths.outputs.glog_CACHE_KEY }}-install - name: Restore googletest from cache id: restore_googletest if: ${{ steps.paths.outputs.googletest_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.googletest_INSTALL }} key: ${{ steps.paths.outputs.googletest_CACHE_KEY }}-install - name: Build googletest if: ${{ steps.paths.outputs.googletest_SOURCE && ! steps.restore_googletest.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --free-up-disk --no-tests googletest - name: Save googletest to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.googletest_SOURCE && ! steps.restore_googletest.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.googletest_INSTALL }} key: ${{ steps.paths.outputs.googletest_CACHE_KEY }}-install - name: Restore libdwarf from cache id: restore_libdwarf if: ${{ steps.paths.outputs.libdwarf_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.libdwarf_INSTALL }} key: ${{ steps.paths.outputs.libdwarf_CACHE_KEY }}-install - name: Build libdwarf if: ${{ steps.paths.outputs.libdwarf_SOURCE && ! steps.restore_libdwarf.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --free-up-disk --no-tests libdwarf - name: Save libdwarf to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.libdwarf_SOURCE && ! steps.restore_libdwarf.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.libdwarf_INSTALL }} key: ${{ steps.paths.outputs.libdwarf_CACHE_KEY }}-install - name: Restore libevent from cache id: restore_libevent if: ${{ steps.paths.outputs.libevent_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.libevent_INSTALL }} key: ${{ steps.paths.outputs.libevent_CACHE_KEY }}-install - name: Build libevent if: ${{ steps.paths.outputs.libevent_SOURCE && ! steps.restore_libevent.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --free-up-disk --no-tests libevent - name: Save libevent to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.libevent_SOURCE && ! steps.restore_libevent.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.libevent_INSTALL }} key: ${{ steps.paths.outputs.libevent_CACHE_KEY }}-install - name: Restore zlib from cache id: restore_zlib if: ${{ steps.paths.outputs.zlib_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.zlib_INSTALL }} key: ${{ steps.paths.outputs.zlib_CACHE_KEY }}-install - name: Build zlib if: ${{ steps.paths.outputs.zlib_SOURCE && ! steps.restore_zlib.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --free-up-disk --no-tests zlib - name: Save zlib to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.zlib_SOURCE && ! steps.restore_zlib.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.zlib_INSTALL }} key: ${{ steps.paths.outputs.zlib_CACHE_KEY }}-install - name: Restore lz4 from cache id: restore_lz4 if: ${{ steps.paths.outputs.lz4_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.lz4_INSTALL }} key: ${{ steps.paths.outputs.lz4_CACHE_KEY }}-install - name: Build lz4 if: ${{ steps.paths.outputs.lz4_SOURCE && ! steps.restore_lz4.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --free-up-disk --no-tests lz4 - name: Save lz4 to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.lz4_SOURCE && ! steps.restore_lz4.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.lz4_INSTALL }} key: ${{ steps.paths.outputs.lz4_CACHE_KEY }}-install - name: Restore snappy from cache id: restore_snappy if: ${{ steps.paths.outputs.snappy_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.snappy_INSTALL }} key: ${{ steps.paths.outputs.snappy_CACHE_KEY }}-install - name: Build snappy if: ${{ steps.paths.outputs.snappy_SOURCE && ! steps.restore_snappy.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --free-up-disk --no-tests snappy - name: Save snappy to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.snappy_SOURCE && ! steps.restore_snappy.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.snappy_INSTALL }} key: ${{ steps.paths.outputs.snappy_CACHE_KEY }}-install - name: Restore zstd from cache id: restore_zstd if: ${{ steps.paths.outputs.zstd_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.zstd_INSTALL }} key: ${{ steps.paths.outputs.zstd_CACHE_KEY }}-install - name: Build zstd if: ${{ steps.paths.outputs.zstd_SOURCE && ! steps.restore_zstd.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --free-up-disk --no-tests zstd - name: Save zstd to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.zstd_SOURCE && ! steps.restore_zstd.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.zstd_INSTALL }} key: ${{ steps.paths.outputs.zstd_CACHE_KEY }}-install - name: Restore autoconf from cache id: restore_autoconf if: ${{ steps.paths.outputs.autoconf_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.autoconf_INSTALL }} key: ${{ steps.paths.outputs.autoconf_CACHE_KEY }}-install - name: Build autoconf if: ${{ steps.paths.outputs.autoconf_SOURCE && ! steps.restore_autoconf.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --free-up-disk --no-tests autoconf - name: Save autoconf to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.autoconf_SOURCE && ! steps.restore_autoconf.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.autoconf_INSTALL }} key: ${{ steps.paths.outputs.autoconf_CACHE_KEY }}-install - name: Restore automake from cache id: restore_automake if: ${{ steps.paths.outputs.automake_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.automake_INSTALL }} key: ${{ steps.paths.outputs.automake_CACHE_KEY }}-install - name: Build automake if: ${{ steps.paths.outputs.automake_SOURCE && ! steps.restore_automake.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --free-up-disk --no-tests automake - name: Save automake to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.automake_SOURCE && ! steps.restore_automake.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.automake_INSTALL }} key: ${{ steps.paths.outputs.automake_CACHE_KEY }}-install - name: Restore libtool from cache id: restore_libtool if: ${{ steps.paths.outputs.libtool_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.libtool_INSTALL }} key: ${{ steps.paths.outputs.libtool_CACHE_KEY }}-install - name: Build libtool if: ${{ steps.paths.outputs.libtool_SOURCE && ! steps.restore_libtool.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --free-up-disk --no-tests libtool - name: Save libtool to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.libtool_SOURCE && ! steps.restore_libtool.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.libtool_INSTALL }} key: ${{ steps.paths.outputs.libtool_CACHE_KEY }}-install - name: Restore libiberty from cache id: restore_libiberty if: ${{ steps.paths.outputs.libiberty_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.libiberty_INSTALL }} key: ${{ steps.paths.outputs.libiberty_CACHE_KEY }}-install - name: Build libiberty if: ${{ steps.paths.outputs.libiberty_SOURCE && ! steps.restore_libiberty.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --free-up-disk --no-tests libiberty - name: Save libiberty to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.libiberty_SOURCE && ! steps.restore_libiberty.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.libiberty_INSTALL }} key: ${{ steps.paths.outputs.libiberty_CACHE_KEY }}-install - name: Restore libsodium from cache id: restore_libsodium if: ${{ steps.paths.outputs.libsodium_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.libsodium_INSTALL }} key: ${{ steps.paths.outputs.libsodium_CACHE_KEY }}-install - name: Build libsodium if: ${{ steps.paths.outputs.libsodium_SOURCE && ! steps.restore_libsodium.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --free-up-disk --no-tests libsodium - name: Save libsodium to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.libsodium_SOURCE && ! steps.restore_libsodium.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.libsodium_INSTALL }} key: ${{ steps.paths.outputs.libsodium_CACHE_KEY }}-install - name: Restore libunwind from cache id: restore_libunwind if: ${{ steps.paths.outputs.libunwind_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.libunwind_INSTALL }} key: ${{ steps.paths.outputs.libunwind_CACHE_KEY }}-install - name: Build libunwind if: ${{ steps.paths.outputs.libunwind_SOURCE && ! steps.restore_libunwind.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --free-up-disk --no-tests libunwind - name: Save libunwind to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.libunwind_SOURCE && ! steps.restore_libunwind.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.libunwind_INSTALL }} key: ${{ steps.paths.outputs.libunwind_CACHE_KEY }}-install - name: Restore xz from cache id: restore_xz if: ${{ steps.paths.outputs.xz_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.xz_INSTALL }} key: ${{ steps.paths.outputs.xz_CACHE_KEY }}-install - name: Build xz if: ${{ steps.paths.outputs.xz_SOURCE && ! steps.restore_xz.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --free-up-disk --no-tests xz - name: Save xz to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.xz_SOURCE && ! steps.restore_xz.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.xz_INSTALL }} key: ${{ steps.paths.outputs.xz_CACHE_KEY }}-install - name: Build folly run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --src-dir=. folly --project-install-prefix folly:/usr/local - name: Copy artifacts run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fixup-dyn-deps --strip --src-dir=. folly _artifacts/linux --project-install-prefix folly:/usr/local --final-install-prefix /usr/local - uses: actions/upload-artifact@v6 with: name: folly path: _artifacts - name: Test folly run: python3 build/fbcode_builder/getdeps.py --allow-system-packages test --src-dir=. folly --project-install-prefix folly:/usr/local - name: Show disk space at end if: always() run: df -h ================================================ FILE: .github/workflows/getdeps_mac.yml ================================================ # This file was @generated by getdeps.py name: mac on: push: branches: - main pull_request: branches: - main permissions: contents: read # to fetch code (actions/checkout) jobs: build: runs-on: macOS-latest steps: - uses: actions/checkout@v6 - name: Show disk space at start run: df -h - name: Free up disk space run: sudo rm -rf /usr/local/lib/android - name: Show disk space after freeing up run: df -h - name: Install system deps run: python3 build/fbcode_builder/getdeps.py --allow-system-packages install-system-deps --recursive folly - id: paths name: Query paths run: python3 build/fbcode_builder/getdeps.py --allow-system-packages query-paths --recursive --src-dir=. folly >> "$GITHUB_OUTPUT" - name: Fetch boost if: ${{ steps.paths.outputs.boost_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests boost - name: Fetch openssl if: ${{ steps.paths.outputs.openssl_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests openssl - name: Fetch ninja if: ${{ steps.paths.outputs.ninja_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests ninja - name: Fetch cmake if: ${{ steps.paths.outputs.cmake_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests cmake - name: Fetch double-conversion if: ${{ steps.paths.outputs.double-conversion_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests double-conversion - name: Fetch fast_float if: ${{ steps.paths.outputs.fast_float_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests fast_float - name: Fetch fmt if: ${{ steps.paths.outputs.fmt_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests fmt - name: Fetch gflags if: ${{ steps.paths.outputs.gflags_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests gflags - name: Fetch glog if: ${{ steps.paths.outputs.glog_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests glog - name: Fetch googletest if: ${{ steps.paths.outputs.googletest_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests googletest - name: Fetch libdwarf if: ${{ steps.paths.outputs.libdwarf_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests libdwarf - name: Fetch libevent if: ${{ steps.paths.outputs.libevent_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests libevent - name: Fetch lz4 if: ${{ steps.paths.outputs.lz4_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests lz4 - name: Fetch snappy if: ${{ steps.paths.outputs.snappy_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests snappy - name: Fetch zstd if: ${{ steps.paths.outputs.zstd_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests zstd - name: Fetch autoconf if: ${{ steps.paths.outputs.autoconf_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests autoconf - name: Fetch automake if: ${{ steps.paths.outputs.automake_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests automake - name: Fetch libtool if: ${{ steps.paths.outputs.libtool_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests libtool - name: Fetch libsodium if: ${{ steps.paths.outputs.libsodium_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests libsodium - name: Fetch xz if: ${{ steps.paths.outputs.xz_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests xz - name: Restore boost from cache id: restore_boost if: ${{ steps.paths.outputs.boost_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.boost_INSTALL }} key: ${{ steps.paths.outputs.boost_CACHE_KEY }}-install - name: Build boost if: ${{ steps.paths.outputs.boost_SOURCE && ! steps.restore_boost.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --free-up-disk --no-tests boost - name: Save boost to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.boost_SOURCE && ! steps.restore_boost.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.boost_INSTALL }} key: ${{ steps.paths.outputs.boost_CACHE_KEY }}-install - name: Restore openssl from cache id: restore_openssl if: ${{ steps.paths.outputs.openssl_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.openssl_INSTALL }} key: ${{ steps.paths.outputs.openssl_CACHE_KEY }}-install - name: Build openssl if: ${{ steps.paths.outputs.openssl_SOURCE && ! steps.restore_openssl.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --free-up-disk --no-tests openssl - name: Save openssl to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.openssl_SOURCE && ! steps.restore_openssl.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.openssl_INSTALL }} key: ${{ steps.paths.outputs.openssl_CACHE_KEY }}-install - name: Restore ninja from cache id: restore_ninja if: ${{ steps.paths.outputs.ninja_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.ninja_INSTALL }} key: ${{ steps.paths.outputs.ninja_CACHE_KEY }}-install - name: Build ninja if: ${{ steps.paths.outputs.ninja_SOURCE && ! steps.restore_ninja.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --free-up-disk --no-tests ninja - name: Save ninja to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.ninja_SOURCE && ! steps.restore_ninja.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.ninja_INSTALL }} key: ${{ steps.paths.outputs.ninja_CACHE_KEY }}-install - name: Restore cmake from cache id: restore_cmake if: ${{ steps.paths.outputs.cmake_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.cmake_INSTALL }} key: ${{ steps.paths.outputs.cmake_CACHE_KEY }}-install - name: Build cmake if: ${{ steps.paths.outputs.cmake_SOURCE && ! steps.restore_cmake.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --free-up-disk --no-tests cmake - name: Save cmake to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.cmake_SOURCE && ! steps.restore_cmake.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.cmake_INSTALL }} key: ${{ steps.paths.outputs.cmake_CACHE_KEY }}-install - name: Restore double-conversion from cache id: restore_double-conversion if: ${{ steps.paths.outputs.double-conversion_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.double-conversion_INSTALL }} key: ${{ steps.paths.outputs.double-conversion_CACHE_KEY }}-install - name: Build double-conversion if: ${{ steps.paths.outputs.double-conversion_SOURCE && ! steps.restore_double-conversion.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --free-up-disk --no-tests double-conversion - name: Save double-conversion to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.double-conversion_SOURCE && ! steps.restore_double-conversion.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.double-conversion_INSTALL }} key: ${{ steps.paths.outputs.double-conversion_CACHE_KEY }}-install - name: Restore fast_float from cache id: restore_fast_float if: ${{ steps.paths.outputs.fast_float_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.fast_float_INSTALL }} key: ${{ steps.paths.outputs.fast_float_CACHE_KEY }}-install - name: Build fast_float if: ${{ steps.paths.outputs.fast_float_SOURCE && ! steps.restore_fast_float.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --free-up-disk --no-tests fast_float - name: Save fast_float to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.fast_float_SOURCE && ! steps.restore_fast_float.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.fast_float_INSTALL }} key: ${{ steps.paths.outputs.fast_float_CACHE_KEY }}-install - name: Restore fmt from cache id: restore_fmt if: ${{ steps.paths.outputs.fmt_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.fmt_INSTALL }} key: ${{ steps.paths.outputs.fmt_CACHE_KEY }}-install - name: Build fmt if: ${{ steps.paths.outputs.fmt_SOURCE && ! steps.restore_fmt.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --free-up-disk --no-tests fmt - name: Save fmt to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.fmt_SOURCE && ! steps.restore_fmt.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.fmt_INSTALL }} key: ${{ steps.paths.outputs.fmt_CACHE_KEY }}-install - name: Restore gflags from cache id: restore_gflags if: ${{ steps.paths.outputs.gflags_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.gflags_INSTALL }} key: ${{ steps.paths.outputs.gflags_CACHE_KEY }}-install - name: Build gflags if: ${{ steps.paths.outputs.gflags_SOURCE && ! steps.restore_gflags.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --free-up-disk --no-tests gflags - name: Save gflags to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.gflags_SOURCE && ! steps.restore_gflags.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.gflags_INSTALL }} key: ${{ steps.paths.outputs.gflags_CACHE_KEY }}-install - name: Restore glog from cache id: restore_glog if: ${{ steps.paths.outputs.glog_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.glog_INSTALL }} key: ${{ steps.paths.outputs.glog_CACHE_KEY }}-install - name: Build glog if: ${{ steps.paths.outputs.glog_SOURCE && ! steps.restore_glog.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --free-up-disk --no-tests glog - name: Save glog to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.glog_SOURCE && ! steps.restore_glog.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.glog_INSTALL }} key: ${{ steps.paths.outputs.glog_CACHE_KEY }}-install - name: Restore googletest from cache id: restore_googletest if: ${{ steps.paths.outputs.googletest_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.googletest_INSTALL }} key: ${{ steps.paths.outputs.googletest_CACHE_KEY }}-install - name: Build googletest if: ${{ steps.paths.outputs.googletest_SOURCE && ! steps.restore_googletest.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --free-up-disk --no-tests googletest - name: Save googletest to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.googletest_SOURCE && ! steps.restore_googletest.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.googletest_INSTALL }} key: ${{ steps.paths.outputs.googletest_CACHE_KEY }}-install - name: Restore libdwarf from cache id: restore_libdwarf if: ${{ steps.paths.outputs.libdwarf_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.libdwarf_INSTALL }} key: ${{ steps.paths.outputs.libdwarf_CACHE_KEY }}-install - name: Build libdwarf if: ${{ steps.paths.outputs.libdwarf_SOURCE && ! steps.restore_libdwarf.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --free-up-disk --no-tests libdwarf - name: Save libdwarf to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.libdwarf_SOURCE && ! steps.restore_libdwarf.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.libdwarf_INSTALL }} key: ${{ steps.paths.outputs.libdwarf_CACHE_KEY }}-install - name: Restore libevent from cache id: restore_libevent if: ${{ steps.paths.outputs.libevent_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.libevent_INSTALL }} key: ${{ steps.paths.outputs.libevent_CACHE_KEY }}-install - name: Build libevent if: ${{ steps.paths.outputs.libevent_SOURCE && ! steps.restore_libevent.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --free-up-disk --no-tests libevent - name: Save libevent to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.libevent_SOURCE && ! steps.restore_libevent.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.libevent_INSTALL }} key: ${{ steps.paths.outputs.libevent_CACHE_KEY }}-install - name: Restore lz4 from cache id: restore_lz4 if: ${{ steps.paths.outputs.lz4_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.lz4_INSTALL }} key: ${{ steps.paths.outputs.lz4_CACHE_KEY }}-install - name: Build lz4 if: ${{ steps.paths.outputs.lz4_SOURCE && ! steps.restore_lz4.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --free-up-disk --no-tests lz4 - name: Save lz4 to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.lz4_SOURCE && ! steps.restore_lz4.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.lz4_INSTALL }} key: ${{ steps.paths.outputs.lz4_CACHE_KEY }}-install - name: Restore snappy from cache id: restore_snappy if: ${{ steps.paths.outputs.snappy_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.snappy_INSTALL }} key: ${{ steps.paths.outputs.snappy_CACHE_KEY }}-install - name: Build snappy if: ${{ steps.paths.outputs.snappy_SOURCE && ! steps.restore_snappy.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --free-up-disk --no-tests snappy - name: Save snappy to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.snappy_SOURCE && ! steps.restore_snappy.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.snappy_INSTALL }} key: ${{ steps.paths.outputs.snappy_CACHE_KEY }}-install - name: Restore zstd from cache id: restore_zstd if: ${{ steps.paths.outputs.zstd_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.zstd_INSTALL }} key: ${{ steps.paths.outputs.zstd_CACHE_KEY }}-install - name: Build zstd if: ${{ steps.paths.outputs.zstd_SOURCE && ! steps.restore_zstd.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --free-up-disk --no-tests zstd - name: Save zstd to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.zstd_SOURCE && ! steps.restore_zstd.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.zstd_INSTALL }} key: ${{ steps.paths.outputs.zstd_CACHE_KEY }}-install - name: Restore autoconf from cache id: restore_autoconf if: ${{ steps.paths.outputs.autoconf_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.autoconf_INSTALL }} key: ${{ steps.paths.outputs.autoconf_CACHE_KEY }}-install - name: Build autoconf if: ${{ steps.paths.outputs.autoconf_SOURCE && ! steps.restore_autoconf.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --free-up-disk --no-tests autoconf - name: Save autoconf to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.autoconf_SOURCE && ! steps.restore_autoconf.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.autoconf_INSTALL }} key: ${{ steps.paths.outputs.autoconf_CACHE_KEY }}-install - name: Restore automake from cache id: restore_automake if: ${{ steps.paths.outputs.automake_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.automake_INSTALL }} key: ${{ steps.paths.outputs.automake_CACHE_KEY }}-install - name: Build automake if: ${{ steps.paths.outputs.automake_SOURCE && ! steps.restore_automake.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --free-up-disk --no-tests automake - name: Save automake to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.automake_SOURCE && ! steps.restore_automake.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.automake_INSTALL }} key: ${{ steps.paths.outputs.automake_CACHE_KEY }}-install - name: Restore libtool from cache id: restore_libtool if: ${{ steps.paths.outputs.libtool_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.libtool_INSTALL }} key: ${{ steps.paths.outputs.libtool_CACHE_KEY }}-install - name: Build libtool if: ${{ steps.paths.outputs.libtool_SOURCE && ! steps.restore_libtool.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --free-up-disk --no-tests libtool - name: Save libtool to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.libtool_SOURCE && ! steps.restore_libtool.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.libtool_INSTALL }} key: ${{ steps.paths.outputs.libtool_CACHE_KEY }}-install - name: Restore libsodium from cache id: restore_libsodium if: ${{ steps.paths.outputs.libsodium_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.libsodium_INSTALL }} key: ${{ steps.paths.outputs.libsodium_CACHE_KEY }}-install - name: Build libsodium if: ${{ steps.paths.outputs.libsodium_SOURCE && ! steps.restore_libsodium.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --free-up-disk --no-tests libsodium - name: Save libsodium to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.libsodium_SOURCE && ! steps.restore_libsodium.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.libsodium_INSTALL }} key: ${{ steps.paths.outputs.libsodium_CACHE_KEY }}-install - name: Restore xz from cache id: restore_xz if: ${{ steps.paths.outputs.xz_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.xz_INSTALL }} key: ${{ steps.paths.outputs.xz_CACHE_KEY }}-install - name: Build xz if: ${{ steps.paths.outputs.xz_SOURCE && ! steps.restore_xz.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --free-up-disk --no-tests xz - name: Save xz to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.xz_SOURCE && ! steps.restore_xz.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.xz_INSTALL }} key: ${{ steps.paths.outputs.xz_CACHE_KEY }}-install - name: Build folly run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --src-dir=. folly --project-install-prefix folly:/usr/local - name: Copy artifacts run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fixup-dyn-deps --src-dir=. folly _artifacts/mac --project-install-prefix folly:/usr/local --final-install-prefix /usr/local - uses: actions/upload-artifact@v6 with: name: folly path: _artifacts - name: Test folly run: python3 build/fbcode_builder/getdeps.py --allow-system-packages test --src-dir=. folly --project-install-prefix folly:/usr/local - name: Show disk space at end if: always() run: df -h ================================================ FILE: .github/workflows/getdeps_shared-libs_linux.yml ================================================ # This file was @generated by getdeps.py name: Shared Libs Linux on: push: branches: - main pull_request: branches: - main permissions: contents: read # to fetch code (actions/checkout) jobs: build: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v6 - name: Show disk space at start run: df -h - name: Free up disk space run: sudo rm -rf /usr/local/lib/android - name: Show disk space after freeing up run: df -h - name: Update system package info run: sudo --preserve-env=http_proxy apt-get update - name: Install system deps run: sudo --preserve-env=http_proxy python3 build/fbcode_builder/getdeps.py --allow-system-packages install-system-deps --recursive folly && sudo --preserve-env=http_proxy python3 build/fbcode_builder/getdeps.py --allow-system-packages install-system-deps --recursive patchelf - id: paths name: Query paths run: python3 build/fbcode_builder/getdeps.py --allow-system-packages query-paths --recursive --src-dir=. folly >> "$GITHUB_OUTPUT" - name: Fetch boost if: ${{ steps.paths.outputs.boost_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests boost - name: Fetch libaio if: ${{ steps.paths.outputs.libaio_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests libaio - name: Fetch ninja if: ${{ steps.paths.outputs.ninja_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests ninja - name: Fetch cmake if: ${{ steps.paths.outputs.cmake_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests cmake - name: Fetch double-conversion if: ${{ steps.paths.outputs.double-conversion_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests double-conversion - name: Fetch fast_float if: ${{ steps.paths.outputs.fast_float_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests fast_float - name: Fetch fmt if: ${{ steps.paths.outputs.fmt_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests fmt - name: Fetch gflags if: ${{ steps.paths.outputs.gflags_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests gflags - name: Fetch glog if: ${{ steps.paths.outputs.glog_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests glog - name: Fetch googletest if: ${{ steps.paths.outputs.googletest_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests googletest - name: Fetch libdwarf if: ${{ steps.paths.outputs.libdwarf_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests libdwarf - name: Fetch libevent if: ${{ steps.paths.outputs.libevent_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests libevent - name: Fetch zlib if: ${{ steps.paths.outputs.zlib_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests zlib - name: Fetch lz4 if: ${{ steps.paths.outputs.lz4_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests lz4 - name: Fetch snappy if: ${{ steps.paths.outputs.snappy_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests snappy - name: Fetch zstd if: ${{ steps.paths.outputs.zstd_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests zstd - name: Fetch autoconf if: ${{ steps.paths.outputs.autoconf_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests autoconf - name: Fetch automake if: ${{ steps.paths.outputs.automake_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests automake - name: Fetch libtool if: ${{ steps.paths.outputs.libtool_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests libtool - name: Fetch libiberty if: ${{ steps.paths.outputs.libiberty_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests libiberty - name: Fetch libsodium if: ${{ steps.paths.outputs.libsodium_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests libsodium - name: Fetch libunwind if: ${{ steps.paths.outputs.libunwind_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests libunwind - name: Fetch xz if: ${{ steps.paths.outputs.xz_SOURCE }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fetch --no-tests xz - name: Restore boost from cache id: restore_boost if: ${{ steps.paths.outputs.boost_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.boost_INSTALL }} key: ${{ steps.paths.outputs.boost_CACHE_KEY }}-install - name: Build boost if: ${{ steps.paths.outputs.boost_SOURCE && ! steps.restore_boost.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --shared-libs --free-up-disk --no-tests boost - name: Save boost to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.boost_SOURCE && ! steps.restore_boost.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.boost_INSTALL }} key: ${{ steps.paths.outputs.boost_CACHE_KEY }}-install - name: Restore libaio from cache id: restore_libaio if: ${{ steps.paths.outputs.libaio_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.libaio_INSTALL }} key: ${{ steps.paths.outputs.libaio_CACHE_KEY }}-install - name: Build libaio if: ${{ steps.paths.outputs.libaio_SOURCE && ! steps.restore_libaio.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --shared-libs --free-up-disk --no-tests libaio - name: Save libaio to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.libaio_SOURCE && ! steps.restore_libaio.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.libaio_INSTALL }} key: ${{ steps.paths.outputs.libaio_CACHE_KEY }}-install - name: Restore ninja from cache id: restore_ninja if: ${{ steps.paths.outputs.ninja_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.ninja_INSTALL }} key: ${{ steps.paths.outputs.ninja_CACHE_KEY }}-install - name: Build ninja if: ${{ steps.paths.outputs.ninja_SOURCE && ! steps.restore_ninja.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --shared-libs --free-up-disk --no-tests ninja - name: Save ninja to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.ninja_SOURCE && ! steps.restore_ninja.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.ninja_INSTALL }} key: ${{ steps.paths.outputs.ninja_CACHE_KEY }}-install - name: Restore cmake from cache id: restore_cmake if: ${{ steps.paths.outputs.cmake_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.cmake_INSTALL }} key: ${{ steps.paths.outputs.cmake_CACHE_KEY }}-install - name: Build cmake if: ${{ steps.paths.outputs.cmake_SOURCE && ! steps.restore_cmake.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --shared-libs --free-up-disk --no-tests cmake - name: Save cmake to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.cmake_SOURCE && ! steps.restore_cmake.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.cmake_INSTALL }} key: ${{ steps.paths.outputs.cmake_CACHE_KEY }}-install - name: Restore double-conversion from cache id: restore_double-conversion if: ${{ steps.paths.outputs.double-conversion_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.double-conversion_INSTALL }} key: ${{ steps.paths.outputs.double-conversion_CACHE_KEY }}-install - name: Build double-conversion if: ${{ steps.paths.outputs.double-conversion_SOURCE && ! steps.restore_double-conversion.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --shared-libs --free-up-disk --no-tests double-conversion - name: Save double-conversion to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.double-conversion_SOURCE && ! steps.restore_double-conversion.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.double-conversion_INSTALL }} key: ${{ steps.paths.outputs.double-conversion_CACHE_KEY }}-install - name: Restore fast_float from cache id: restore_fast_float if: ${{ steps.paths.outputs.fast_float_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.fast_float_INSTALL }} key: ${{ steps.paths.outputs.fast_float_CACHE_KEY }}-install - name: Build fast_float if: ${{ steps.paths.outputs.fast_float_SOURCE && ! steps.restore_fast_float.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --shared-libs --free-up-disk --no-tests fast_float - name: Save fast_float to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.fast_float_SOURCE && ! steps.restore_fast_float.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.fast_float_INSTALL }} key: ${{ steps.paths.outputs.fast_float_CACHE_KEY }}-install - name: Restore fmt from cache id: restore_fmt if: ${{ steps.paths.outputs.fmt_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.fmt_INSTALL }} key: ${{ steps.paths.outputs.fmt_CACHE_KEY }}-install - name: Build fmt if: ${{ steps.paths.outputs.fmt_SOURCE && ! steps.restore_fmt.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --shared-libs --free-up-disk --no-tests fmt - name: Save fmt to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.fmt_SOURCE && ! steps.restore_fmt.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.fmt_INSTALL }} key: ${{ steps.paths.outputs.fmt_CACHE_KEY }}-install - name: Restore gflags from cache id: restore_gflags if: ${{ steps.paths.outputs.gflags_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.gflags_INSTALL }} key: ${{ steps.paths.outputs.gflags_CACHE_KEY }}-install - name: Build gflags if: ${{ steps.paths.outputs.gflags_SOURCE && ! steps.restore_gflags.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --shared-libs --free-up-disk --no-tests gflags - name: Save gflags to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.gflags_SOURCE && ! steps.restore_gflags.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.gflags_INSTALL }} key: ${{ steps.paths.outputs.gflags_CACHE_KEY }}-install - name: Restore glog from cache id: restore_glog if: ${{ steps.paths.outputs.glog_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.glog_INSTALL }} key: ${{ steps.paths.outputs.glog_CACHE_KEY }}-install - name: Build glog if: ${{ steps.paths.outputs.glog_SOURCE && ! steps.restore_glog.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --shared-libs --free-up-disk --no-tests glog - name: Save glog to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.glog_SOURCE && ! steps.restore_glog.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.glog_INSTALL }} key: ${{ steps.paths.outputs.glog_CACHE_KEY }}-install - name: Restore googletest from cache id: restore_googletest if: ${{ steps.paths.outputs.googletest_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.googletest_INSTALL }} key: ${{ steps.paths.outputs.googletest_CACHE_KEY }}-install - name: Build googletest if: ${{ steps.paths.outputs.googletest_SOURCE && ! steps.restore_googletest.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --shared-libs --free-up-disk --no-tests googletest - name: Save googletest to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.googletest_SOURCE && ! steps.restore_googletest.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.googletest_INSTALL }} key: ${{ steps.paths.outputs.googletest_CACHE_KEY }}-install - name: Restore libdwarf from cache id: restore_libdwarf if: ${{ steps.paths.outputs.libdwarf_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.libdwarf_INSTALL }} key: ${{ steps.paths.outputs.libdwarf_CACHE_KEY }}-install - name: Build libdwarf if: ${{ steps.paths.outputs.libdwarf_SOURCE && ! steps.restore_libdwarf.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --shared-libs --free-up-disk --no-tests libdwarf - name: Save libdwarf to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.libdwarf_SOURCE && ! steps.restore_libdwarf.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.libdwarf_INSTALL }} key: ${{ steps.paths.outputs.libdwarf_CACHE_KEY }}-install - name: Restore libevent from cache id: restore_libevent if: ${{ steps.paths.outputs.libevent_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.libevent_INSTALL }} key: ${{ steps.paths.outputs.libevent_CACHE_KEY }}-install - name: Build libevent if: ${{ steps.paths.outputs.libevent_SOURCE && ! steps.restore_libevent.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --shared-libs --free-up-disk --no-tests libevent - name: Save libevent to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.libevent_SOURCE && ! steps.restore_libevent.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.libevent_INSTALL }} key: ${{ steps.paths.outputs.libevent_CACHE_KEY }}-install - name: Restore zlib from cache id: restore_zlib if: ${{ steps.paths.outputs.zlib_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.zlib_INSTALL }} key: ${{ steps.paths.outputs.zlib_CACHE_KEY }}-install - name: Build zlib if: ${{ steps.paths.outputs.zlib_SOURCE && ! steps.restore_zlib.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --shared-libs --free-up-disk --no-tests zlib - name: Save zlib to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.zlib_SOURCE && ! steps.restore_zlib.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.zlib_INSTALL }} key: ${{ steps.paths.outputs.zlib_CACHE_KEY }}-install - name: Restore lz4 from cache id: restore_lz4 if: ${{ steps.paths.outputs.lz4_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.lz4_INSTALL }} key: ${{ steps.paths.outputs.lz4_CACHE_KEY }}-install - name: Build lz4 if: ${{ steps.paths.outputs.lz4_SOURCE && ! steps.restore_lz4.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --shared-libs --free-up-disk --no-tests lz4 - name: Save lz4 to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.lz4_SOURCE && ! steps.restore_lz4.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.lz4_INSTALL }} key: ${{ steps.paths.outputs.lz4_CACHE_KEY }}-install - name: Restore snappy from cache id: restore_snappy if: ${{ steps.paths.outputs.snappy_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.snappy_INSTALL }} key: ${{ steps.paths.outputs.snappy_CACHE_KEY }}-install - name: Build snappy if: ${{ steps.paths.outputs.snappy_SOURCE && ! steps.restore_snappy.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --shared-libs --free-up-disk --no-tests snappy - name: Save snappy to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.snappy_SOURCE && ! steps.restore_snappy.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.snappy_INSTALL }} key: ${{ steps.paths.outputs.snappy_CACHE_KEY }}-install - name: Restore zstd from cache id: restore_zstd if: ${{ steps.paths.outputs.zstd_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.zstd_INSTALL }} key: ${{ steps.paths.outputs.zstd_CACHE_KEY }}-install - name: Build zstd if: ${{ steps.paths.outputs.zstd_SOURCE && ! steps.restore_zstd.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --shared-libs --free-up-disk --no-tests zstd - name: Save zstd to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.zstd_SOURCE && ! steps.restore_zstd.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.zstd_INSTALL }} key: ${{ steps.paths.outputs.zstd_CACHE_KEY }}-install - name: Restore autoconf from cache id: restore_autoconf if: ${{ steps.paths.outputs.autoconf_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.autoconf_INSTALL }} key: ${{ steps.paths.outputs.autoconf_CACHE_KEY }}-install - name: Build autoconf if: ${{ steps.paths.outputs.autoconf_SOURCE && ! steps.restore_autoconf.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --shared-libs --free-up-disk --no-tests autoconf - name: Save autoconf to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.autoconf_SOURCE && ! steps.restore_autoconf.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.autoconf_INSTALL }} key: ${{ steps.paths.outputs.autoconf_CACHE_KEY }}-install - name: Restore automake from cache id: restore_automake if: ${{ steps.paths.outputs.automake_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.automake_INSTALL }} key: ${{ steps.paths.outputs.automake_CACHE_KEY }}-install - name: Build automake if: ${{ steps.paths.outputs.automake_SOURCE && ! steps.restore_automake.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --shared-libs --free-up-disk --no-tests automake - name: Save automake to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.automake_SOURCE && ! steps.restore_automake.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.automake_INSTALL }} key: ${{ steps.paths.outputs.automake_CACHE_KEY }}-install - name: Restore libtool from cache id: restore_libtool if: ${{ steps.paths.outputs.libtool_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.libtool_INSTALL }} key: ${{ steps.paths.outputs.libtool_CACHE_KEY }}-install - name: Build libtool if: ${{ steps.paths.outputs.libtool_SOURCE && ! steps.restore_libtool.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --shared-libs --free-up-disk --no-tests libtool - name: Save libtool to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.libtool_SOURCE && ! steps.restore_libtool.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.libtool_INSTALL }} key: ${{ steps.paths.outputs.libtool_CACHE_KEY }}-install - name: Restore libiberty from cache id: restore_libiberty if: ${{ steps.paths.outputs.libiberty_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.libiberty_INSTALL }} key: ${{ steps.paths.outputs.libiberty_CACHE_KEY }}-install - name: Build libiberty if: ${{ steps.paths.outputs.libiberty_SOURCE && ! steps.restore_libiberty.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --shared-libs --free-up-disk --no-tests libiberty - name: Save libiberty to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.libiberty_SOURCE && ! steps.restore_libiberty.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.libiberty_INSTALL }} key: ${{ steps.paths.outputs.libiberty_CACHE_KEY }}-install - name: Restore libsodium from cache id: restore_libsodium if: ${{ steps.paths.outputs.libsodium_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.libsodium_INSTALL }} key: ${{ steps.paths.outputs.libsodium_CACHE_KEY }}-install - name: Build libsodium if: ${{ steps.paths.outputs.libsodium_SOURCE && ! steps.restore_libsodium.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --shared-libs --free-up-disk --no-tests libsodium - name: Save libsodium to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.libsodium_SOURCE && ! steps.restore_libsodium.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.libsodium_INSTALL }} key: ${{ steps.paths.outputs.libsodium_CACHE_KEY }}-install - name: Restore libunwind from cache id: restore_libunwind if: ${{ steps.paths.outputs.libunwind_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.libunwind_INSTALL }} key: ${{ steps.paths.outputs.libunwind_CACHE_KEY }}-install - name: Build libunwind if: ${{ steps.paths.outputs.libunwind_SOURCE && ! steps.restore_libunwind.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --shared-libs --free-up-disk --no-tests libunwind - name: Save libunwind to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.libunwind_SOURCE && ! steps.restore_libunwind.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.libunwind_INSTALL }} key: ${{ steps.paths.outputs.libunwind_CACHE_KEY }}-install - name: Restore xz from cache id: restore_xz if: ${{ steps.paths.outputs.xz_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.xz_INSTALL }} key: ${{ steps.paths.outputs.xz_CACHE_KEY }}-install - name: Build xz if: ${{ steps.paths.outputs.xz_SOURCE && ! steps.restore_xz.outputs.cache-hit }} run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --shared-libs --free-up-disk --no-tests xz - name: Save xz to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.xz_SOURCE && ! steps.restore_xz.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.xz_INSTALL }} key: ${{ steps.paths.outputs.xz_CACHE_KEY }}-install - name: Build folly run: python3 build/fbcode_builder/getdeps.py --allow-system-packages build --shared-libs --src-dir=. folly --project-install-prefix folly:/usr/local - name: Copy artifacts run: python3 build/fbcode_builder/getdeps.py --allow-system-packages fixup-dyn-deps --strip --src-dir=. folly _artifacts/linux --project-install-prefix folly:/usr/local --final-install-prefix /usr/local - uses: actions/upload-artifact@v6 with: name: folly path: _artifacts - name: Test folly run: python3 build/fbcode_builder/getdeps.py --allow-system-packages test --shared-libs --src-dir=. folly --project-install-prefix folly:/usr/local - name: Show disk space at end if: always() run: df -h ================================================ FILE: .github/workflows/getdeps_windows.yml ================================================ # This file was @generated by getdeps.py name: windows on: push: branches: - main pull_request: branches: - main permissions: contents: read # to fetch code (actions/checkout) jobs: build: runs-on: windows-2022 steps: - name: Export boost environment run: "echo BOOST_ROOT=%BOOST_ROOT_1_83_0% >> %GITHUB_ENV%" shell: cmd - name: Fix Git config run: > git config --system core.longpaths true && git config --system core.autocrlf false && git config --system core.symlinks true shell: cmd - uses: actions/checkout@v6 - id: paths name: Query paths run: python build/fbcode_builder/getdeps.py query-paths --recursive --src-dir=. folly >> $env:GITHUB_OUTPUT shell: pwsh - name: Fetch boost if: ${{ steps.paths.outputs.boost_SOURCE }} run: python build/fbcode_builder/getdeps.py fetch --no-tests boost - name: Fetch libsodium if: ${{ steps.paths.outputs.libsodium_SOURCE }} run: python build/fbcode_builder/getdeps.py fetch --no-tests libsodium - name: Fetch ninja if: ${{ steps.paths.outputs.ninja_SOURCE }} run: python build/fbcode_builder/getdeps.py fetch --no-tests ninja - name: Fetch cmake if: ${{ steps.paths.outputs.cmake_SOURCE }} run: python build/fbcode_builder/getdeps.py fetch --no-tests cmake - name: Fetch double-conversion if: ${{ steps.paths.outputs.double-conversion_SOURCE }} run: python build/fbcode_builder/getdeps.py fetch --no-tests double-conversion - name: Fetch fast_float if: ${{ steps.paths.outputs.fast_float_SOURCE }} run: python build/fbcode_builder/getdeps.py fetch --no-tests fast_float - name: Fetch fmt if: ${{ steps.paths.outputs.fmt_SOURCE }} run: python build/fbcode_builder/getdeps.py fetch --no-tests fmt - name: Fetch gflags if: ${{ steps.paths.outputs.gflags_SOURCE }} run: python build/fbcode_builder/getdeps.py fetch --no-tests gflags - name: Fetch glog if: ${{ steps.paths.outputs.glog_SOURCE }} run: python build/fbcode_builder/getdeps.py fetch --no-tests glog - name: Fetch googletest if: ${{ steps.paths.outputs.googletest_SOURCE }} run: python build/fbcode_builder/getdeps.py fetch --no-tests googletest - name: Fetch libdwarf if: ${{ steps.paths.outputs.libdwarf_SOURCE }} run: python build/fbcode_builder/getdeps.py fetch --no-tests libdwarf - name: Fetch lz4 if: ${{ steps.paths.outputs.lz4_SOURCE }} run: python build/fbcode_builder/getdeps.py fetch --no-tests lz4 - name: Fetch jom if: ${{ steps.paths.outputs.jom_SOURCE }} run: python build/fbcode_builder/getdeps.py fetch --no-tests jom - name: Fetch perl if: ${{ steps.paths.outputs.perl_SOURCE }} run: python build/fbcode_builder/getdeps.py fetch --no-tests perl - name: Fetch openssl if: ${{ steps.paths.outputs.openssl_SOURCE }} run: python build/fbcode_builder/getdeps.py fetch --no-tests openssl - name: Fetch snappy if: ${{ steps.paths.outputs.snappy_SOURCE }} run: python build/fbcode_builder/getdeps.py fetch --no-tests snappy - name: Fetch zlib if: ${{ steps.paths.outputs.zlib_SOURCE }} run: python build/fbcode_builder/getdeps.py fetch --no-tests zlib - name: Fetch zstd if: ${{ steps.paths.outputs.zstd_SOURCE }} run: python build/fbcode_builder/getdeps.py fetch --no-tests zstd - name: Fetch libevent if: ${{ steps.paths.outputs.libevent_SOURCE }} run: python build/fbcode_builder/getdeps.py fetch --no-tests libevent - name: Restore boost from cache id: restore_boost if: ${{ steps.paths.outputs.boost_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.boost_INSTALL }} key: ${{ steps.paths.outputs.boost_CACHE_KEY }}-install - name: Build boost if: ${{ steps.paths.outputs.boost_SOURCE && ! steps.restore_boost.outputs.cache-hit }} run: python build/fbcode_builder/getdeps.py build --free-up-disk --no-tests boost - name: Save boost to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.boost_SOURCE && ! steps.restore_boost.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.boost_INSTALL }} key: ${{ steps.paths.outputs.boost_CACHE_KEY }}-install - name: Restore libsodium from cache id: restore_libsodium if: ${{ steps.paths.outputs.libsodium_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.libsodium_INSTALL }} key: ${{ steps.paths.outputs.libsodium_CACHE_KEY }}-install - name: Build libsodium if: ${{ steps.paths.outputs.libsodium_SOURCE && ! steps.restore_libsodium.outputs.cache-hit }} run: python build/fbcode_builder/getdeps.py build --free-up-disk --no-tests libsodium - name: Save libsodium to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.libsodium_SOURCE && ! steps.restore_libsodium.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.libsodium_INSTALL }} key: ${{ steps.paths.outputs.libsodium_CACHE_KEY }}-install - name: Restore ninja from cache id: restore_ninja if: ${{ steps.paths.outputs.ninja_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.ninja_INSTALL }} key: ${{ steps.paths.outputs.ninja_CACHE_KEY }}-install - name: Build ninja if: ${{ steps.paths.outputs.ninja_SOURCE && ! steps.restore_ninja.outputs.cache-hit }} run: python build/fbcode_builder/getdeps.py build --free-up-disk --no-tests ninja - name: Save ninja to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.ninja_SOURCE && ! steps.restore_ninja.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.ninja_INSTALL }} key: ${{ steps.paths.outputs.ninja_CACHE_KEY }}-install - name: Restore cmake from cache id: restore_cmake if: ${{ steps.paths.outputs.cmake_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.cmake_INSTALL }} key: ${{ steps.paths.outputs.cmake_CACHE_KEY }}-install - name: Build cmake if: ${{ steps.paths.outputs.cmake_SOURCE && ! steps.restore_cmake.outputs.cache-hit }} run: python build/fbcode_builder/getdeps.py build --free-up-disk --no-tests cmake - name: Save cmake to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.cmake_SOURCE && ! steps.restore_cmake.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.cmake_INSTALL }} key: ${{ steps.paths.outputs.cmake_CACHE_KEY }}-install - name: Restore double-conversion from cache id: restore_double-conversion if: ${{ steps.paths.outputs.double-conversion_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.double-conversion_INSTALL }} key: ${{ steps.paths.outputs.double-conversion_CACHE_KEY }}-install - name: Build double-conversion if: ${{ steps.paths.outputs.double-conversion_SOURCE && ! steps.restore_double-conversion.outputs.cache-hit }} run: python build/fbcode_builder/getdeps.py build --free-up-disk --no-tests double-conversion - name: Save double-conversion to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.double-conversion_SOURCE && ! steps.restore_double-conversion.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.double-conversion_INSTALL }} key: ${{ steps.paths.outputs.double-conversion_CACHE_KEY }}-install - name: Restore fast_float from cache id: restore_fast_float if: ${{ steps.paths.outputs.fast_float_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.fast_float_INSTALL }} key: ${{ steps.paths.outputs.fast_float_CACHE_KEY }}-install - name: Build fast_float if: ${{ steps.paths.outputs.fast_float_SOURCE && ! steps.restore_fast_float.outputs.cache-hit }} run: python build/fbcode_builder/getdeps.py build --free-up-disk --no-tests fast_float - name: Save fast_float to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.fast_float_SOURCE && ! steps.restore_fast_float.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.fast_float_INSTALL }} key: ${{ steps.paths.outputs.fast_float_CACHE_KEY }}-install - name: Restore fmt from cache id: restore_fmt if: ${{ steps.paths.outputs.fmt_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.fmt_INSTALL }} key: ${{ steps.paths.outputs.fmt_CACHE_KEY }}-install - name: Build fmt if: ${{ steps.paths.outputs.fmt_SOURCE && ! steps.restore_fmt.outputs.cache-hit }} run: python build/fbcode_builder/getdeps.py build --free-up-disk --no-tests fmt - name: Save fmt to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.fmt_SOURCE && ! steps.restore_fmt.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.fmt_INSTALL }} key: ${{ steps.paths.outputs.fmt_CACHE_KEY }}-install - name: Restore gflags from cache id: restore_gflags if: ${{ steps.paths.outputs.gflags_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.gflags_INSTALL }} key: ${{ steps.paths.outputs.gflags_CACHE_KEY }}-install - name: Build gflags if: ${{ steps.paths.outputs.gflags_SOURCE && ! steps.restore_gflags.outputs.cache-hit }} run: python build/fbcode_builder/getdeps.py build --free-up-disk --no-tests gflags - name: Save gflags to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.gflags_SOURCE && ! steps.restore_gflags.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.gflags_INSTALL }} key: ${{ steps.paths.outputs.gflags_CACHE_KEY }}-install - name: Restore glog from cache id: restore_glog if: ${{ steps.paths.outputs.glog_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.glog_INSTALL }} key: ${{ steps.paths.outputs.glog_CACHE_KEY }}-install - name: Build glog if: ${{ steps.paths.outputs.glog_SOURCE && ! steps.restore_glog.outputs.cache-hit }} run: python build/fbcode_builder/getdeps.py build --free-up-disk --no-tests glog - name: Save glog to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.glog_SOURCE && ! steps.restore_glog.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.glog_INSTALL }} key: ${{ steps.paths.outputs.glog_CACHE_KEY }}-install - name: Restore googletest from cache id: restore_googletest if: ${{ steps.paths.outputs.googletest_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.googletest_INSTALL }} key: ${{ steps.paths.outputs.googletest_CACHE_KEY }}-install - name: Build googletest if: ${{ steps.paths.outputs.googletest_SOURCE && ! steps.restore_googletest.outputs.cache-hit }} run: python build/fbcode_builder/getdeps.py build --free-up-disk --no-tests googletest - name: Save googletest to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.googletest_SOURCE && ! steps.restore_googletest.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.googletest_INSTALL }} key: ${{ steps.paths.outputs.googletest_CACHE_KEY }}-install - name: Restore libdwarf from cache id: restore_libdwarf if: ${{ steps.paths.outputs.libdwarf_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.libdwarf_INSTALL }} key: ${{ steps.paths.outputs.libdwarf_CACHE_KEY }}-install - name: Build libdwarf if: ${{ steps.paths.outputs.libdwarf_SOURCE && ! steps.restore_libdwarf.outputs.cache-hit }} run: python build/fbcode_builder/getdeps.py build --free-up-disk --no-tests libdwarf - name: Save libdwarf to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.libdwarf_SOURCE && ! steps.restore_libdwarf.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.libdwarf_INSTALL }} key: ${{ steps.paths.outputs.libdwarf_CACHE_KEY }}-install - name: Restore lz4 from cache id: restore_lz4 if: ${{ steps.paths.outputs.lz4_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.lz4_INSTALL }} key: ${{ steps.paths.outputs.lz4_CACHE_KEY }}-install - name: Build lz4 if: ${{ steps.paths.outputs.lz4_SOURCE && ! steps.restore_lz4.outputs.cache-hit }} run: python build/fbcode_builder/getdeps.py build --free-up-disk --no-tests lz4 - name: Save lz4 to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.lz4_SOURCE && ! steps.restore_lz4.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.lz4_INSTALL }} key: ${{ steps.paths.outputs.lz4_CACHE_KEY }}-install - name: Restore jom from cache id: restore_jom if: ${{ steps.paths.outputs.jom_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.jom_INSTALL }} key: ${{ steps.paths.outputs.jom_CACHE_KEY }}-install - name: Build jom if: ${{ steps.paths.outputs.jom_SOURCE && ! steps.restore_jom.outputs.cache-hit }} run: python build/fbcode_builder/getdeps.py build --free-up-disk --no-tests jom - name: Save jom to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.jom_SOURCE && ! steps.restore_jom.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.jom_INSTALL }} key: ${{ steps.paths.outputs.jom_CACHE_KEY }}-install - name: Restore perl from cache id: restore_perl if: ${{ steps.paths.outputs.perl_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.perl_INSTALL }} key: ${{ steps.paths.outputs.perl_CACHE_KEY }}-install - name: Build perl if: ${{ steps.paths.outputs.perl_SOURCE && ! steps.restore_perl.outputs.cache-hit }} run: python build/fbcode_builder/getdeps.py build --free-up-disk --no-tests perl - name: Save perl to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.perl_SOURCE && ! steps.restore_perl.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.perl_INSTALL }} key: ${{ steps.paths.outputs.perl_CACHE_KEY }}-install - name: Restore openssl from cache id: restore_openssl if: ${{ steps.paths.outputs.openssl_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.openssl_INSTALL }} key: ${{ steps.paths.outputs.openssl_CACHE_KEY }}-install - name: Build openssl if: ${{ steps.paths.outputs.openssl_SOURCE && ! steps.restore_openssl.outputs.cache-hit }} run: python build/fbcode_builder/getdeps.py build --free-up-disk --no-tests openssl - name: Save openssl to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.openssl_SOURCE && ! steps.restore_openssl.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.openssl_INSTALL }} key: ${{ steps.paths.outputs.openssl_CACHE_KEY }}-install - name: Restore snappy from cache id: restore_snappy if: ${{ steps.paths.outputs.snappy_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.snappy_INSTALL }} key: ${{ steps.paths.outputs.snappy_CACHE_KEY }}-install - name: Build snappy if: ${{ steps.paths.outputs.snappy_SOURCE && ! steps.restore_snappy.outputs.cache-hit }} run: python build/fbcode_builder/getdeps.py build --free-up-disk --no-tests snappy - name: Save snappy to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.snappy_SOURCE && ! steps.restore_snappy.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.snappy_INSTALL }} key: ${{ steps.paths.outputs.snappy_CACHE_KEY }}-install - name: Restore zlib from cache id: restore_zlib if: ${{ steps.paths.outputs.zlib_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.zlib_INSTALL }} key: ${{ steps.paths.outputs.zlib_CACHE_KEY }}-install - name: Build zlib if: ${{ steps.paths.outputs.zlib_SOURCE && ! steps.restore_zlib.outputs.cache-hit }} run: python build/fbcode_builder/getdeps.py build --free-up-disk --no-tests zlib - name: Save zlib to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.zlib_SOURCE && ! steps.restore_zlib.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.zlib_INSTALL }} key: ${{ steps.paths.outputs.zlib_CACHE_KEY }}-install - name: Restore zstd from cache id: restore_zstd if: ${{ steps.paths.outputs.zstd_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.zstd_INSTALL }} key: ${{ steps.paths.outputs.zstd_CACHE_KEY }}-install - name: Build zstd if: ${{ steps.paths.outputs.zstd_SOURCE && ! steps.restore_zstd.outputs.cache-hit }} run: python build/fbcode_builder/getdeps.py build --free-up-disk --no-tests zstd - name: Save zstd to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.zstd_SOURCE && ! steps.restore_zstd.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.zstd_INSTALL }} key: ${{ steps.paths.outputs.zstd_CACHE_KEY }}-install - name: Restore libevent from cache id: restore_libevent if: ${{ steps.paths.outputs.libevent_SOURCE }} uses: actions/cache/restore@v4 with: path: ${{ steps.paths.outputs.libevent_INSTALL }} key: ${{ steps.paths.outputs.libevent_CACHE_KEY }}-install - name: Build libevent if: ${{ steps.paths.outputs.libevent_SOURCE && ! steps.restore_libevent.outputs.cache-hit }} run: python build/fbcode_builder/getdeps.py build --free-up-disk --no-tests libevent - name: Save libevent to cache uses: actions/cache/save@v4 if: ${{ steps.paths.outputs.libevent_SOURCE && ! steps.restore_libevent.outputs.cache-hit }} with: path: ${{ steps.paths.outputs.libevent_INSTALL }} key: ${{ steps.paths.outputs.libevent_CACHE_KEY }}-install - name: Build folly run: python build/fbcode_builder/getdeps.py build --src-dir=. folly - name: Copy artifacts run: python build/fbcode_builder/getdeps.py fixup-dyn-deps --src-dir=. folly _artifacts/windows --final-install-prefix /usr/local - uses: actions/upload-artifact@v6 with: name: folly path: _artifacts - name: Test folly run: python build/fbcode_builder/getdeps.py test --src-dir=. folly ================================================ FILE: .github/workflows/oss-build-and-test.yml ================================================ name: Buck build and test on: [push, pull_request, workflow_dispatch] jobs: get-toolchains-to-install: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: submodules: 'true' - uses: facebook/install-dotslash@latest - name: get_buck_graph run: | BUCK_GRAPH=$(./buck2 cquery ... --output-attribute '^buck.type$|^name$') echo "$BUCK_GRAPH" > buck_graph_results.json shell: bash - name: Check if rust_binary id: check_rust run: | OUTPUT=$(cat buck_graph_results.json) if [[ "$OUTPUT" == *"rust_binary"* ]]; then echo "uses_rust=true" >> $GITHUB_ENV fi shell: bash - name: Check if cxx_binary id: check_cxx run: | OUTPUT=$(cat buck_graph_results.json) if [[ "$OUTPUT" == *"cxx_binary"* ]]; then echo "uses_cxx=true" >> $GITHUB_ENV fi shell: bash - name: Check if ocaml_binary id: check_ocaml run: | OUTPUT=$(cat buck_graph_results.json) if [[ "$OUTPUT" == *"ocaml_binary"* ]]; then echo "uses_ocaml=true" >> $GITHUB_ENV fi shell: bash - name: Check if python_binary id: check_python run: | OUTPUT=$(cat buck_graph_results.json) if [[ "$OUTPUT" == *"python_binary"* ]]; then echo "uses_python=true" >> $GITHUB_ENV fi shell: bash outputs: uses_rust: ${{ env.uses_rust }} uses_cxx: ${{ env.uses_cxx }} uses_ocaml: ${{env.uses_ocaml}} uses_python: ${{env.uses_python}} ubuntu-os-buck-build-and-test: needs: get-toolchains-to-install runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: submodules: 'true' - run: sudo apt-get update shell: bash - uses: facebook/install-dotslash@latest - name: Install Rust toolchain if: needs.get-toolchains-to-install.outputs.uses_rust == 'true' uses: dtolnay/rust-toolchain@stable - name: Install C++ toolchain if: needs.get-toolchains-to-install.outputs.uses_cxx == 'true' run: | sudo apt-get install cmake llvm cppcheck python3-pip sudo pip3 install conan==1.* shell: bash - name: Install OCaml toolchain if: needs.get-toolchains-to-install.outputs.uses_ocaml == 'true' uses: ocaml/setup-ocaml@v2 with: ocaml-compiler: "5.1" - name: Install Python toolchain if: needs.get-toolchains-to-install.outputs.uses_python == 'true' uses: actions/setup-python@v5 with: python-version: '3.10' - name: buck2 build and test run: bash ./.github/scripts/buck_build_and_test.sh windows-os-buck-build-and-test: needs: get-toolchains-to-install runs-on: windows-latest steps: - uses: actions/checkout@v4 with: submodules: 'true' - uses: facebook/install-dotslash@latest - name: Install Rust toolchain if: needs.get-toolchains-to-install.outputs.uses_rust == 'true' uses: dtolnay/rust-toolchain@stable - name: Install C++ toolchain if: needs.get-toolchains-to-install.outputs.uses_cxx == 'true' run: | choco install llvm cmake conan cppcheck -y if ($LASTEXITCODE -eq 3010) { $LASTEXITCODE = 0 } shell: pwsh - name: Install OCaml toolchain if: needs.get-toolchains-to-install.outputs.uses_ocaml == 'true' uses: ocaml/setup-ocaml@v2 with: ocaml-compiler: "4.12.0" - name: Install Python toolchain if: needs.get-toolchains-to-install.outputs.uses_python == 'true' uses: actions/setup-python@v5 with: python-version: '3.10' - name: buck2 build and test run: bash ./.github/scripts/buck_build_and_test.sh mac-os-buck-build-and-test: needs: get-toolchains-to-install runs-on: macos-latest steps: - uses: actions/checkout@v4 with: submodules: 'true' - uses: facebook/install-dotslash@latest - name: Install Rust toolchain if: needs.get-toolchains-to-install.outputs.uses_rust == 'true' uses: dtolnay/rust-toolchain@stable - name: Install C++ toolchain if: needs.get-toolchains-to-install.outputs.uses_cxx == 'true' run: | brew install cmake llvm cppcheck python3 conan@1 shell: bash - name: Install OCaml toolchain if: needs.get-toolchains-to-install.outputs.uses_ocaml == 'true' uses: ocaml/setup-ocaml@v2 with: ocaml-compiler: "5.1" - name: Install Python toolchain if: needs.get-toolchains-to-install.outputs.uses_python == 'true' uses: actions/setup-python@v5 with: python-version: '3.10' - name: Install homebrew deps run: | BUCK_GRAPH=$(./buck2 cquery "attrregexfilter(labels, 'third-party:homebrew:', deps(//...))" --json --output-attribute=labels) HOMEBREW_PACKAGES=$(echo $BUCK_GRAPH | jq '[.[] | .labels] | flatten | unique | map(select(contains("third-party:homebrew:")) | sub("third-party:homebrew:"; "")) | .[] | @text') echo $HOMEBREW_PACKAGES echo $HOMEBREW_PACKAGES | xargs brew install pkg-config - name: buck2 build and test run: bash ./.github/scripts/buck_build_and_test.sh ================================================ FILE: .gitignore ================================================ *.o *.lo *.la .dirstamp Makefile Makefile.in .libs .deps stamp-h1 folly-config.h _configs.sed aclocal.m4 autom4te.cache build-aux libtool folly/test/gtest folly/folly-config.h folly/**/test/*_benchmark folly/**/test/*.log folly/**/test/*_test folly/**/test/*_test_using_jemalloc folly/**/test/*.trs folly/config.* folly/configure folly/logging/example/logging_example folly/libfolly.pc folly/m4/libtool.m4 folly/m4/ltoptions.m4 folly/m4/ltsugar.m4 folly/m4/ltversion.m4 folly/m4/lt~obsolete.m4 folly/generate_fingerprint_tables folly/FingerprintTables.cpp _build # Ignore all files generated by Buck2 buck-out/ ================================================ FILE: .projectid ================================================ folly ================================================ FILE: BUCK ================================================ load("@fbcode_macros//build_defs:native_rules.bzl", "buck_genrule") oncall("fbcode_entropy_wardens_folly") buck_genrule( name = "folly-config.h", srcs = {file: file for file in glob([ "CMake/*", "build/fbcode_builder/CMake/*", ])} | {"CMakeLists.txt": "CMakeListsForBuck2.txt"}, out = "folly-config.h", cmd = "cmake . && mv folly/folly-config.h $OUT", default_target_platform = "prelude//platforms:default", labels = [ "third-party:fedora:cmake", "third-party:homebrew:cmake", "third-party:ubuntu:cmake", ], remote = False, ) ================================================ FILE: CMake/FindCython.cmake ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # # 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. # Find Cython # # This module sets the following variables: # - Cython_FOUND # - CYTHON_EXE # - CYTHON_VERSION_STRING # find_program(CYTHON_EXE NAMES cython cython3) if (CYTHON_EXE) execute_process(COMMAND ${CYTHON_EXE} --version RESULT_VARIABLE _cython_retcode OUTPUT_VARIABLE _cython_output ERROR_VARIABLE _cython_output OUTPUT_STRIP_TRAILING_WHITESPACE) if (${_cython_retcode} EQUAL 0) separate_arguments(_cython_output) list(GET _cython_output -1 CYTHON_VERSION_STRING) message(STATUS "Found Cython Version ${CYTHON_VERSION_STRING}") else () message(STATUS "Failed to get Cython version") endif () else () message(STATUS "Cython not found") endif () include(FindPackageHandleStandardArgs) find_package_handle_standard_args( Cython REQUIRED_VARS CYTHON_EXE CYTHON_VERSION_STRING VERSION_VAR CYTHON_VERSION_STRING ) ================================================ FILE: CMake/FindFastFloat.cmake ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # # 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. # # - Try to find fast_float library # This will define # FASTFLOAT_FOUND # FASTFLOAT_INCLUDE_DIR # find_path(FASTFLOAT_INCLUDE_DIR NAMES fast_float/fast_float.h) include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS( FastFloat DEFAULT_MSG FASTFLOAT_INCLUDE_DIR ) mark_as_advanced(FASTFLOAT_INCLUDE_DIR) ================================================ FILE: CMake/FindFmt.cmake ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # # 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. find_path(LIBFMT_INCLUDE_DIR fmt/core.h) mark_as_advanced(LIBFMT_INCLUDE_DIR) find_library(LIBFMT_LIBRARY NAMES fmt fmtd) mark_as_advanced(LIBFMT_LIBRARY) include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS( LIBFMT DEFAULT_MSG LIBFMT_LIBRARY LIBFMT_INCLUDE_DIR) if(LIBFMT_FOUND) set(LIBFMT_LIBRARIES ${LIBFMT_LIBRARY}) set(LIBFMT_INCLUDE_DIRS ${LIBFMT_INCLUDE_DIR}) message(STATUS "Found {fmt}: ${LIBFMT_LIBRARY}") add_library(fmt::fmt UNKNOWN IMPORTED) set_target_properties( fmt::fmt PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${LIBFMT_INCLUDE_DIR}" ) set_target_properties( fmt::fmt PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES "C" IMPORTED_LOCATION "${LIBFMT_LIBRARY}" ) endif() ================================================ FILE: CMake/FindLZ4.cmake ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # # 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. # Finds liblz4. # # This module defines: # LZ4_FOUND # LZ4_INCLUDE_DIR # LZ4_LIBRARY # find_path(LZ4_INCLUDE_DIR NAMES lz4.h) find_library(LZ4_LIBRARY_DEBUG NAMES lz4d) find_library(LZ4_LIBRARY_RELEASE NAMES lz4) include(SelectLibraryConfigurations) SELECT_LIBRARY_CONFIGURATIONS(LZ4) include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS( LZ4 DEFAULT_MSG LZ4_LIBRARY LZ4_INCLUDE_DIR ) if (LZ4_FOUND) message(STATUS "Found LZ4: ${LZ4_LIBRARY}") endif() mark_as_advanced(LZ4_INCLUDE_DIR LZ4_LIBRARY) ================================================ FILE: CMake/FindLibAIO.cmake ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # # 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. find_path(LIBAIO_INCLUDE_DIR NAMES libaio.h) mark_as_advanced(LIBAIO_INCLUDE_DIR) find_library(LIBAIO_LIBRARY NAMES aio) mark_as_advanced(LIBAIO_LIBRARY) include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS( LIBAIO REQUIRED_VARS LIBAIO_LIBRARY LIBAIO_INCLUDE_DIR) if(LIBAIO_FOUND) set(LIBAIO_LIBRARIES ${LIBAIO_LIBRARY}) set(LIBAIO_INCLUDE_DIRS ${LIBAIO_INCLUDE_DIR}) endif() ================================================ FILE: CMake/FindLibDwarf.cmake ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # # 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. # dwarf.h is typically installed in a libdwarf/ subdirectory on Debian-style # Linux distributions. It is not installed in a libdwarf/ subdirectory on Mac # systems when installed with Homebrew. Newer homebrew installations install # it in libdwarf-0. Search for it in all locations. find_path(LIBDWARF_INCLUDE_DIR NAMES dwarf.h PATH_SUFFIXES libdwarf libdwarf-0) mark_as_advanced(LIBDWARF_INCLUDE_DIR) find_library(LIBDWARF_LIBRARY NAMES dwarf) mark_as_advanced(LIBDWARF_LIBRARY) include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS( LIBDWARF REQUIRED_VARS LIBDWARF_LIBRARY LIBDWARF_INCLUDE_DIR) if(LIBDWARF_FOUND) set(LIBDWARF_LIBRARIES ${LIBDWARF_LIBRARY}) set(LIBDWARF_INCLUDE_DIRS ${LIBDWARF_INCLUDE_DIR}) endif() ================================================ FILE: CMake/FindLibUring.cmake ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # # 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. find_path(LIBURING_INCLUDE_DIR NAMES liburing.h) mark_as_advanced(LIBURING_INCLUDE_DIR) find_library(LIBURING_LIBRARY NAMES uring) mark_as_advanced(LIBURING_LIBRARY) include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS( LIBURING REQUIRED_VARS LIBURING_LIBRARY LIBURING_INCLUDE_DIR) if(LIBURING_FOUND) set(LIBURING_LIBRARIES ${LIBURING_LIBRARY}) set(LIBURING_INCLUDE_DIRS ${LIBURING_INCLUDE_DIR}) endif() ================================================ FILE: CMake/FindLibiberty.cmake ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # # 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. find_path(LIBIBERTY_INCLUDE_DIR NAMES libiberty.h PATH_SUFFIXES libiberty) mark_as_advanced(LIBIBERTY_INCLUDE_DIR) find_library(LIBIBERTY_LIBRARY NAMES iberty) mark_as_advanced(LIBIBERTY_LIBRARY) include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS( LIBIBERTY REQUIRED_VARS LIBIBERTY_LIBRARY LIBIBERTY_INCLUDE_DIR) if(LIBIBERTY_FOUND) set(LIBIBERTY_LIBRARIES ${LIBIBERTY_LIBRARY}) set(LIBIBERTY_INCLUDE_DIRS ${LIBIBERTY_INCLUDE_DIR}) endif() ================================================ FILE: CMake/FindLibsodium.cmake ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # # 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. find_path(LIBSODIUM_INCLUDE_DIR NAMES sodium.h) mark_as_advanced(LIBSODIUM_INCLUDE_DIR) find_library(LIBSODIUM_LIBRARY NAMES sodium) mark_as_advanced(LIBSODIUM_LIBRARY) include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS( LIBSODIUM REQUIRED_VARS LIBSODIUM_LIBRARY LIBSODIUM_INCLUDE_DIR) if(LIBSODIUM_FOUND) set(LIBSODIUM_LIBRARIES ${LIBSODIUM_LIBRARY}) set(LIBSODIUM_INCLUDE_DIRS ${LIBSODIUM_INCLUDE_DIR}) message(STATUS "Found Libsodium: ${LIBSODIUM_LIBRARY}") endif() ================================================ FILE: CMake/FindSnappy.cmake ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # # 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. # Find the Snappy libraries # # This module defines: # SNAPPY_FOUND # SNAPPY_INCLUDE_DIR # SNAPPY_LIBRARY find_path(SNAPPY_INCLUDE_DIR NAMES snappy.h) find_library(SNAPPY_LIBRARY_DEBUG NAMES snappyd) find_library(SNAPPY_LIBRARY_RELEASE NAMES snappy) include(SelectLibraryConfigurations) SELECT_LIBRARY_CONFIGURATIONS(SNAPPY) include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS( SNAPPY DEFAULT_MSG SNAPPY_LIBRARY SNAPPY_INCLUDE_DIR ) mark_as_advanced(SNAPPY_INCLUDE_DIR SNAPPY_LIBRARY) ================================================ FILE: CMake/FindZstd.cmake ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # # 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. # # - Try to find Facebook zstd library # This will define # ZSTD_FOUND # ZSTD_INCLUDE_DIR # ZSTD_LIBRARY # find_path(ZSTD_INCLUDE_DIR NAMES zstd.h) find_library(ZSTD_LIBRARY_DEBUG NAMES zstdd zstd_staticd) find_library(ZSTD_LIBRARY_RELEASE NAMES zstd zstd_static) include(SelectLibraryConfigurations) SELECT_LIBRARY_CONFIGURATIONS(ZSTD) include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS( ZSTD DEFAULT_MSG ZSTD_LIBRARY ZSTD_INCLUDE_DIR ) if (ZSTD_FOUND) message(STATUS "Found Zstd: ${ZSTD_LIBRARY}") endif() mark_as_advanced(ZSTD_INCLUDE_DIR ZSTD_LIBRARY) ================================================ FILE: CMake/FollyCompilerMSVC.cmake ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # # 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. # Some additional configuration options. option(MSVC_ENABLE_ALL_WARNINGS "If enabled, pass /Wall to the compiler." ON) option(MSVC_ENABLE_DEBUG_INLINING "If enabled, enable inlining in the debug configuration. This allows /Zc:inline to be far more effective." OFF) option(MSVC_ENABLE_FAST_LINK "If enabled, pass /DEBUG:FASTLINK to the linker. This makes linking faster, but the gtest integration for Visual Studio can't currently handle the .pdbs generated." OFF) option(MSVC_ENABLE_LEAN_AND_MEAN_WINDOWS "If enabled, define WIN32_LEAN_AND_MEAN to include a smaller subset of Windows.h" ON) option(MSVC_ENABLE_LTCG "If enabled, use Link Time Code Generation for Release builds." OFF) option(MSVC_ENABLE_PARALLEL_BUILD "If enabled, build multiple source files in parallel." ON) option(MSVC_ENABLE_STATIC_ANALYSIS "If enabled, do more complex static analysis and generate warnings appropriately." OFF) option(MSVC_USE_STATIC_RUNTIME "If enabled, build against the static, rather than the dynamic, runtime." OFF) option(MSVC_SUPPRESS_BOOST_CONFIG_OUTDATED "If enabled, suppress Boost's warnings about the config being out of date." ON) # Alas, option() doesn't support string values. set(MSVC_FAVORED_ARCHITECTURE "blend" CACHE STRING "One of 'blend', 'AMD64', 'INTEL64', or 'ATOM'. This tells the compiler to generate code optimized to run best on the specified architecture.") # Add a pretty drop-down selector for these values when using the GUI. set_property( CACHE MSVC_FAVORED_ARCHITECTURE PROPERTY STRINGS blend AMD64 ATOM INTEL64 ) # Validate, and then add the favored architecture. if (NOT MSVC_FAVORED_ARCHITECTURE STREQUAL "blend" AND NOT MSVC_FAVORED_ARCHITECTURE STREQUAL "AMD64" AND NOT MSVC_FAVORED_ARCHITECTURE STREQUAL "INTEL64" AND NOT MSVC_FAVORED_ARCHITECTURE STREQUAL "ATOM") message(FATAL_ERROR "MSVC_FAVORED_ARCHITECTURE must be set to one of exactly, 'blend', 'AMD64', 'INTEL64', or 'ATOM'! Got '${MSVC_FAVORED_ARCHITECTURE}' instead!") endif() set(MSVC_LANGUAGE_VERSION "c++20" CACHE STRING "One of 'c++20', or 'c++latest'. This determines which version of C++ to compile as.") set_property( CACHE MSVC_LANGUAGE_VERSION PROPERTY STRINGS "c++20" "c++latest" ) ############################################################ # We need to adjust a couple of the default option sets. ############################################################ # If the static runtime is requested, we have to # overwrite some of CMake's defaults. if (MSVC_USE_STATIC_RUNTIME) foreach(flag_var CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO) if (${flag_var} MATCHES "/MD") string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") endif() endforeach() endif() # The Ninja generator doesn't de-dup the exception mode flag, so remove the # default flag so that MSVC doesn't warn about it on every single file. if ("${CMAKE_GENERATOR}" STREQUAL "Ninja") foreach(flag_var CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO) if (${flag_var} MATCHES "/EHsc") string(REGEX REPLACE "/EHsc" "" ${flag_var} "${${flag_var}}") endif() endforeach() endif() # In order for /Zc:inline, which speeds up the build significantly, to work # we need to remove the /Ob0 parameter that CMake adds by default, because that # would normally disable all inlining. foreach(flag_var CMAKE_C_FLAGS_DEBUG CMAKE_CXX_FLAGS_DEBUG) if (${flag_var} MATCHES "/Ob0") string(REGEX REPLACE "/Ob0" "" ${flag_var} "${${flag_var}}") endif() endforeach() # When building with Ninja, or with /MP enabled, there is the potential # for multiple processes to need to lock the same pdb file. # The /FS option (which is implicitly enabled by /MP) is widely believed # to be the solution for this, but even with /FS enabled the problem can # still randomly occur. # https://stackoverflow.com/a/58020501/149111 suggests that /Z7 should be # used; rather than placing the debug info into a .pdb file it embeds it # into the object files in a similar way to gcc/clang which should reduce # contention and potentially make the build faster... but at the cost of # larger object files foreach(flag_var CMAKE_C_FLAGS_DEBUG CMAKE_CXX_FLAGS_DEBUG) if (${flag_var} MATCHES "/Zi") string(REGEX REPLACE "/Zi" "/Z7" ${flag_var} "${${flag_var}}") endif() endforeach() # Apply the option set for Folly to the specified target. function(apply_folly_compile_options_to_target THETARGET) # The general options passed: target_compile_options(${THETARGET} PUBLIC /EHs # Don't catch structured exceptions with catch (...) /GF # There are bugs with constexpr StringPiece when string pooling is disabled. /Zc:referenceBinding # Disallow temporaries from binding to non-const lvalue references. /Zc:rvalueCast # Enforce the standard rules for explicit type conversion. /Zc:implicitNoexcept # Enable implicit noexcept specifications where required, such as destructors. /Zc:strictStrings # Don't allow conversion from a string literal to mutable characters. /Zc:threadSafeInit # Enable thread-safe function-local statics initialization. /Zc:throwingNew # Assume operator new throws on failure. /permissive- # Be mean, don't allow bad non-standard stuff (C++/CLI, __declspec, etc. are all left intact). /std:${MSVC_LANGUAGE_VERSION} # Build in the requested version of C++ /utf-8 # fmt needs unicode support, which requires compiling with /utf-8 PRIVATE /bigobj # Support objects with > 65k sections. Needed due to templates. /favor:${MSVC_FAVORED_ARCHITECTURE} # Architecture to prefer when generating code. /Zc:inline # Have the compiler eliminate unreferenced COMDAT functions and data before emitting the object file. $<$:/Wall> # Enable all warnings if requested. $<$:/MP> # Enable multi-processor compilation if requested. $<$:/analyze> # Enable static analysis if requested. # Debug builds $<$: /Gy- # Disable function level linking. $<$:/Ob2> # Add /Ob2 if allowing inlining in debug mode. > # Non-debug builds $<$>: /Gw # Optimize global data. (-fdata-sections) /Gy # Enable function level linking. (-ffunction-sections) /Qpar # Enable parallel code generation. /Oi # Enable intrinsic functions. /Ot # Favor fast code. $<$:/GL> # Enable link time code generation. > ) target_compile_options(${THETARGET} PUBLIC /wd4191 # 'type cast' unsafe conversion of function pointers /wd4291 # no matching operator delete found /wd4309 # '=' truncation of constant value /wd4310 # cast truncates constant value /wd4366 # result of unary '&' operator may be unaligned /wd4587 # behavior change; constructor no longer implicitly called /wd4592 # symbol will be dynamically initialized (implementation limitation) /wd4628 # digraphs not supported with -Ze /wd4723 # potential divide by 0 /wd4724 # potential mod by 0 /wd4868 # compiler may not enforce left-to-right evaluation order /wd4996 # user deprecated # The warnings that are disabled: /wd4068 # Unknown pragma. /wd4091 # 'typedef' ignored on left of '' when no variable is declared. /wd4146 # Unary minus applied to unsigned type, result still unsigned. /wd4800 # Values being forced to bool, this happens many places, and is a "performance warning". # NOTE: glog/logging.h:1116 change to `size_t pcount() const { return size_t(pptr() - pbase()); }` # NOTE: gmock/gmock-spec-builders.h:1177 change to `*static_cast*>(untyped_actions_[size_t(count - 1)]) :` # NOTE: gmock/gmock-spec-builders.h:1749 change to `const size_t count = untyped_expectations_.size();` # NOTE: gmock/gmock-spec-builders.h:1754 change to `for (size_t i = 0; i < count; i++) {` # NOTE: gtest/gtest-printers.h:173 change to `const internal::BiggestInt kBigInt = internal::BiggestInt(value);` # NOTE: gtest/internal/gtest-internal.h:890 add `GTEST_DISABLE_MSC_WARNINGS_PUSH_(4365)` # NOTE: gtest/internal/gtest-internal.h:894 ass `GTEST_DISABLE_MSC_WARNINGS_POP_()` # NOTE: boost/crc.hpp:578 change to `{ return static_cast(x ^ rem); }` # NOTE: boost/regex/v4/match_results.hpp:126 change to `return m_subs[size_type(sub)].length();` # NOTE: boost/regex/v4/match_results.hpp:226 change to `return m_subs[size_type(sub)];` # NOTE: boost/date_time/adjust_functors.hpp:67 change to `origDayOfMonth_ = short(ymd.day);` # NOTE: boost/date_time/adjust_functors.hpp:75 change to `wrap_int2 wi(short(ymd.month));` # NOTE: boost/date_time/adjust_functors.hpp:82 change to `day_type resultingEndOfMonthDay(cal_type::end_of_month_day(static_cast(year), static_cast(wi.as_int())));` # NOTE: boost/date_time/adjust_functors.hpp:85 change to `return date_type(static_cast(year), static_cast(wi.as_int()), resultingEndOfMonthDay) - d;` # NOTE: boost/date_time/adjust_functors.hpp:87 change to `day_type dayOfMonth = static_cast(origDayOfMonth_);` # NOTE: boost/date_time/adjust_functors.hpp:91 change to `return date_type(static_cast(year), static_cast(wi.as_int()), dayOfMonth) - d;` # NOTE: boost/date_time/adjust_functors.hpp:98 change to `origDayOfMonth_ = short(ymd.day);` # NOTE: boost/date_time/adjust_functors.hpp:106 change to `wrap_int2 wi(short(ymd.month));` # NOTE: boost/date_time/adjust_functors.hpp:111 change to `day_type resultingEndOfMonthDay(cal_type::end_of_month_day(static_cast(year), static_cast(wi.as_int())));` # NOTE: boost/date_time/adjust_functors.hpp:114 change to `return date_type(static_cast(year), static_cast(wi.as_int()), resultingEndOfMonthDay) - d;` # NOTE: boost/date_time/adjust_functors.hpp:116 change to `day_type dayOfMonth = static_cast(origDayOfMonth_);` # NOTE: boost/date_time/adjust_functors.hpp:120 change to `return date_type(static_cast(year), static_cast(wi.as_int()), dayOfMonth) - d;` # NOTE: boost/date_time/gregorian_calendar.ipp:81 change to `unsigned long d = static_cast(ymd.day + ((153*m + 2)/5) + 365*y + (y/4) - (y/100) + (y/400) - 32045);` # NOTE: boost/date_time/gregorian/greg_date.hpp:122 change to `unsigned short eom_day = gregorian_calendar::end_of_month_day(ymd.year, ymd.month);` # NOTE: boost/thread/future.hpp:1050 change to `locks[std::ptrdiff_t(i)]=BOOST_THREAD_MAKE_RV_REF(boost::unique_lock(futures[i].future_->mutex));` # NOTE: boost/thread/future.hpp:1063 change to `locks[std::ptrdiff_t(i)].unlock();` # NOTE: boost/thread/win32/basic_recursive_mutex.hpp:47 change to `long const current_thread_id=long(win32::GetCurrentThreadId());` # NOTE: boost/thread/win32/basic_recursive_mutex.hpp:53 change to `long const current_thread_id=long(win32::GetCurrentThreadId());` # NOTE: boost/thread/win32/basic_recursive_mutex.hpp:64 change to `long const current_thread_id=long(win32::GetCurrentThreadId());` # NOTE: boost/thread/win32/basic_recursive_mutex.hpp:78 change to `long const current_thread_id=long(win32::GetCurrentThreadId());` # NOTE: boost/thread/win32/basic_recursive_mutex.hpp:84 change to `long const current_thread_id=long(win32::GetCurrentThreadId());` # NOTE: boost/thread/win32/condition_variable.hpp:79 change to `detail::win32::ReleaseSemaphore(semaphore,long(count_to_release),0);` # NOTE: boost/thread/win32/condition_variable.hpp:84 change to `release(unsigned(detail::interlocked_read_acquire(&waiters)));` # NOTE: boost/algorithm/string/detail/classification.hpp:85 change to `std::size_t Size=std::size_t(::boost::distance(Range));` /wd4018 # Signed/unsigned mismatch. /wd4365 # Signed/unsigned mismatch. /wd4388 # Signed/unsigned mismatch on relative comparison operator. /wd4389 # Signed/unsigned mismatch on equality comparison operator. # TODO: /wd4100 # Unreferenced formal parameter. /wd4459 # Declaration of parameter hides global declaration. /wd4505 # Unreferenced local function has been removed. /wd4701 # Potentially uninitialized local variable used. /wd4702 # Unreachable code. # These warnings are disabled because we've # enabled all warnings. If all warnings are # not enabled, we still need to disable them # for consuming libs. /wd4061 # Enum value not handled by a case in a switch on an enum. This isn't very helpful because it is produced even if a default statement is present. /wd4127 # Conditional expression is constant. /wd4200 # Non-standard extension, zero sized array. /wd4201 # Non-standard extension used: nameless struct/union. /wd4296 # '<' Expression is always false. /wd4316 # Object allocated on the heap may not be aligned to 128. /wd4324 # Structure was padded due to alignment specifier. /wd4355 # 'this' used in base member initializer list. /wd4371 # Layout of class may have changed due to fixes in packing. /wd4435 # Object layout under /vd2 will change due to virtual base. /wd4514 # Unreferenced inline function has been removed. (caused by /Zc:inline) /wd4548 # Expression before comma has no effect. I wouldn't disable this normally, but malloc.h triggers this warning. /wd4571 # Semantics of catch(...) changed in VC 7.1 /wd4574 # ifdef'd macro was defined to 0. /wd4582 # Constructor is not implicitly called. /wd4583 # Destructor is not implicitly called. /wd4619 # Invalid warning number used in #pragma warning. /wd4623 # Default constructor was implicitly defined as deleted. /wd4625 # Copy constructor was implicitly defined as deleted. /wd4626 # Assignment operator was implicitly defined as deleted. /wd4643 # Forward declaring standard library types is not permitted. /wd4647 # Behavior change in __is_pod. /wd4668 # Macro was not defined, replacing with 0. /wd4706 # Assignment within conditional expression. /wd4710 # Function was not inlined. /wd4711 # Function was selected for automated inlining. /wd4714 # Function marked as __forceinline not inlined. /wd4820 # Padding added after data member. /wd5026 # Move constructor was implicitly defined as deleted. /wd5027 # Move assignment operator was implicitly defined as deleted. /wd5031 # #pragma warning(pop): likely mismatch, popping warning state pushed in different file. This is needed because of how boost does things. /wd5045 # Compiler will insert Spectre mitigation for memory load if /Qspectre switch is specified. # Warnings to treat as errors: /we4099 # Mixed use of struct and class on same type names. /we4129 # Unknown escape sequence. This is usually caused by incorrect escaping. /we4566 # Character cannot be represented in current charset. This is remidied by prefixing string with "u8". PRIVATE # Warnings disabled for /analyze $<$: /wd6001 # Using uninitialized memory. This is disabled because it is wrong 99% of the time. /wd6011 # Dereferencing potentially NULL pointer. /wd6031 # Return value ignored. /wd6235 # ( || ) is always a non-zero constant. /wd6237 # ( && ) is always zero. is never evaluated and may have side effects. /wd6239 # ( && ) always evaluates to the result of . /wd6240 # ( && ) always evaluates to the result of . /wd6246 # Local declaration hides declaration of same name in outer scope. /wd6248 # Setting a SECURITY_DESCRIPTOR's DACL to NULL will result in an unprotected object. This is done by one of the boost headers. /wd6255 # _alloca indicates failure by raising a stack overflow exception. /wd6262 # Function uses more than x bytes of stack space. /wd6271 # Extra parameter passed to format function. The analysis pass doesn't recognize %j or %z, even though the runtime does. /wd6285 # ( || ) is always true. /wd6297 # 32-bit value is shifted then cast to 64-bits. The places this occurs never use more than 32 bits. /wd6308 # Realloc might return null pointer: assigning null pointer to '', which is passed as an argument to 'realloc', will cause the original memory to leak. /wd6326 # Potential comparison of a constant with another constant. /wd6330 # Unsigned/signed mismatch when passed as a parameter. /wd6340 # Mismatch on sign when passed as format string value. /wd6387 # '' could be '0': This does not adhere to the specification for a function. /wd28182 # Dereferencing NULL pointer. '' contains the same NULL value as ''. /wd28251 # Inconsistent annotation for function. This is because we only annotate the declaration and not the definition. /wd28278 # Function appears with no prototype in scope. > ) # And the extra defines: target_compile_definitions(${THETARGET} PUBLIC _CRT_NONSTDC_NO_WARNINGS # Don't deprecate posix names of functions. _CRT_SECURE_NO_WARNINGS # Don't deprecate the non _s versions of various standard library functions, because safety is for chumps. _SCL_SECURE_NO_WARNINGS # Don't deprecate the non _s versions of various standard library functions, because safety is for chumps. _STL_EXTRA_DISABLED_WARNINGS=4774\ 4987 $<$:_HAS_AUTO_PTR_ETC=1> # We're building in C++ 17 or greater mode, but certain dependencies (Boost) still have dependencies on unary_function and binary_function, so we have to make sure not to remove them. $<$:WIN32_LEAN_AND_MEAN> # Don't include most of Windows.h $<$:BOOST_CONFIG_SUPPRESS_OUTDATED_MESSAGE> # MSVC moves faster than boost, so add a quick way to disable the messages. ) # Ignore a warning about an object file not defining any symbols, # these are known, and we don't care. set_property(TARGET ${THETARGET} APPEND_STRING PROPERTY STATIC_LIBRARY_FLAGS " /ignore:4221") # The options to pass to the linker: set_property(TARGET ${THETARGET} APPEND_STRING PROPERTY LINK_FLAGS_DEBUG " /INCREMENTAL") # Do incremental linking. if (NOT $ STREQUAL "STATIC_LIBRARY") set_property(TARGET ${THETARGET} APPEND_STRING PROPERTY LINK_FLAGS_DEBUG " /OPT:NOREF") # No unreferenced data elimination. set_property(TARGET ${THETARGET} APPEND_STRING PROPERTY LINK_FLAGS_DEBUG " /OPT:NOICF") # No Identical COMDAT folding. set_property(TARGET ${THETARGET} APPEND_STRING PROPERTY LINK_FLAGS_RELEASE " /OPT:REF") # Remove unreferenced functions and data. set_property(TARGET ${THETARGET} APPEND_STRING PROPERTY LINK_FLAGS_RELEASE " /OPT:ICF") # Identical COMDAT folding. endif() if (MSVC_ENABLE_FAST_LINK) set_property(TARGET ${THETARGET} APPEND_STRING PROPERTY LINK_FLAGS_DEBUG " /DEBUG:FASTLINK") # Generate a partial PDB file that simply references the original object and library files. endif() # Add /GL to the compiler, and /LTCG to the linker # if link time code generation is enabled. if (MSVC_ENABLE_LTCG) set_property(TARGET ${THETARGET} APPEND_STRING PROPERTY LINK_FLAGS_RELEASE " /LTCG") endif() endfunction() list(APPEND FOLLY_LINK_LIBRARIES Iphlpapi.lib Ws2_32.lib) ================================================ FILE: CMake/FollyCompilerUnix.cmake ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # # 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. set(CMAKE_CXX_FLAGS_COMMON "-g -Wall -Wextra") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${CMAKE_CXX_FLAGS_COMMON}") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${CMAKE_CXX_FLAGS_COMMON} -O3") list(APPEND CMAKE_REQUIRED_DEFINITIONS "-D_GNU_SOURCE") function(apply_folly_compile_options_to_target THETARGET) target_compile_definitions(${THETARGET} PRIVATE _REENTRANT _GNU_SOURCE ) target_compile_options(${THETARGET} PRIVATE -g -finput-charset=UTF-8 -fsigned-char -Wall -Wno-deprecated -Wno-deprecated-declarations -Wno-sign-compare -Wno-unused -Wuninitialized -Wunused-label -Wunused-result ${FOLLY_CXX_FLAGS} ) endfunction() ================================================ FILE: CMake/FollyConfigChecks.cmake ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # # 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(CheckCXXSourceCompiles) include(CheckCXXSourceRuns) include(CheckFunctionExists) include(CheckIncludeFileCXX) include(CheckSymbolExists) include(CheckTypeSize) include(CheckCXXCompilerFlag) if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") list(APPEND FOLLY_CXX_FLAGS -Wno-psabi) endif() if (CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") CHECK_INCLUDE_FILE_CXX(malloc_np.h FOLLY_USE_JEMALLOC) else() CHECK_INCLUDE_FILE_CXX(jemalloc/jemalloc.h FOLLY_USE_JEMALLOC) endif() if(NOT CMAKE_SYSTEM_NAME STREQUAL "Windows") # clang only rejects unknown warning flags if -Werror=unknown-warning-option # is also specified. check_cxx_compiler_flag( -Werror=unknown-warning-option COMPILER_HAS_UNKNOWN_WARNING_OPTION) if (COMPILER_HAS_UNKNOWN_WARNING_OPTION) set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -Werror=unknown-warning-option") endif() check_cxx_compiler_flag(-Wshadow-local COMPILER_HAS_W_SHADOW_LOCAL) check_cxx_compiler_flag( -Wshadow-compatible-local COMPILER_HAS_W_SHADOW_COMPATIBLE_LOCAL) if (COMPILER_HAS_W_SHADOW_LOCAL AND COMPILER_HAS_W_SHADOW_COMPATIBLE_LOCAL) set(FOLLY_HAVE_SHADOW_LOCAL_WARNINGS ON) list(APPEND FOLLY_CXX_FLAGS -Wshadow-compatible-local) endif() check_cxx_compiler_flag( -Wnullability-completeness COMPILER_HAS_W_NULLABILITY_COMPLETENESS) if (COMPILER_HAS_W_NULLABILITY_COMPLETENESS) list(APPEND FOLLY_CXX_FLAGS -Wno-nullability-completeness) endif() check_cxx_compiler_flag( -Winconsistent-missing-override COMPILER_HAS_W_INCONSISTENT_MISSING_OVERRIDE) if (COMPILER_HAS_W_INCONSISTENT_MISSING_OVERRIDE) list(APPEND FOLLY_CXX_FLAGS -Wno-inconsistent-missing-override) endif() check_cxx_compiler_flag(-fopenmp COMPILER_HAS_F_OPENMP) if (COMPILER_HAS_F_OPENMP) list(APPEND FOLLY_CXX_FLAGS -fopenmp) endif() endif() set(FOLLY_ORIGINAL_CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}") string(REGEX REPLACE "-std=(c|gnu)\\+\\+.." "" CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}") check_symbol_exists(pthread_atfork pthread.h FOLLY_HAVE_PTHREAD_ATFORK) list(APPEND CMAKE_REQUIRED_DEFINITIONS -D_GNU_SOURCE) check_symbol_exists(accept4 sys/socket.h FOLLY_HAVE_ACCEPT4) check_symbol_exists(getrandom sys/random.h FOLLY_HAVE_GETRANDOM) check_symbol_exists(preadv sys/uio.h FOLLY_HAVE_PREADV) check_symbol_exists(pwritev sys/uio.h FOLLY_HAVE_PWRITEV) check_symbol_exists(clock_gettime time.h FOLLY_HAVE_CLOCK_GETTIME) check_symbol_exists(pipe2 unistd.h FOLLY_HAVE_PIPE2) check_function_exists(malloc_usable_size FOLLY_HAVE_MALLOC_USABLE_SIZE) set(CMAKE_REQUIRED_FLAGS "${FOLLY_ORIGINAL_CMAKE_REQUIRED_FLAGS}") check_cxx_source_compiles(" #pragma GCC diagnostic error \"-Wattributes\" extern \"C\" void (*test_ifunc(void))() { return 0; } void func() __attribute__((ifunc(\"test_ifunc\"))); int main() { return 0; }" FOLLY_HAVE_IFUNC ) check_cxx_source_runs(" int main(int, char**) { char buf[64] = {0}; unsigned long *ptr = (unsigned long *)(buf + 1); *ptr = 0xdeadbeef; return (*ptr & 0xff) == 0xef ? 0 : 1; }" FOLLY_HAVE_UNALIGNED_ACCESS ) check_cxx_source_compiles(" int main(int argc, char** argv) { unsigned size = argc; char data[size]; return 0; }" FOLLY_HAVE_VLA ) check_cxx_source_runs(" extern \"C\" int folly_example_undefined_weak_symbol() __attribute__((weak)); int main(int argc, char** argv) { auto f = folly_example_undefined_weak_symbol; // null pointer return f ? f() : 0; // must compile, link, and run with null pointer }" FOLLY_HAVE_WEAK_SYMBOLS ) check_cxx_source_runs(" #include int main() { void *h = dlopen(\"linux-vdso.so.1\", RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD); if (h == nullptr) { return -1; } dlclose(h); return 0; }" FOLLY_HAVE_LINUX_VDSO ) check_cxx_source_runs(" #include #include int main(int argc, char** argv) { return wcstol(L\"01\", nullptr, 10) == 1 ? 0 : 1; }" FOLLY_HAVE_WCHAR_SUPPORT ) check_cxx_source_compiles(" #include int main(int argc, char** argv) { __gnu_cxx::sfmt19937 rng; return 0; }" FOLLY_HAVE_EXTRANDOM_SFMT19937 ) check_cxx_source_runs(" #include #include int call_vsnprintf(const char* fmt, ...) { char buf[256]; va_list ap; va_start(ap, fmt); int result = vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); return result; } int main(int argc, char** argv) { return call_vsnprintf(\"%\", 1) < 0 ? 0 : 1; }" HAVE_VSNPRINTF_ERRORS ) ================================================ FILE: CMake/FollyFunctions.cmake ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # # 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. function(auto_sources RETURN_VALUE PATTERN SOURCE_SUBDIRS) if ("${SOURCE_SUBDIRS}" STREQUAL "RECURSE") SET(PATH ".") if (${ARGC} EQUAL 4) list(GET ARGV 3 PATH) endif () endif() if ("${SOURCE_SUBDIRS}" STREQUAL "RECURSE") unset(${RETURN_VALUE}) file(GLOB SUBDIR_FILES "${PATH}/${PATTERN}") list(APPEND ${RETURN_VALUE} ${SUBDIR_FILES}) file(GLOB subdirs RELATIVE ${PATH} ${PATH}/*) foreach(DIR ${subdirs}) if (IS_DIRECTORY ${PATH}/${DIR}) if (NOT "${DIR}" STREQUAL "CMakeFiles") file(GLOB_RECURSE SUBDIR_FILES "${PATH}/${DIR}/${PATTERN}") list(APPEND ${RETURN_VALUE} ${SUBDIR_FILES}) endif() endif() endforeach() else() file(GLOB ${RETURN_VALUE} "${PATTERN}") foreach (PATH ${SOURCE_SUBDIRS}) file(GLOB SUBDIR_FILES "${PATH}/${PATTERN}") list(APPEND ${RETURN_VALUE} ${SUBDIR_FILES}) endforeach() endif () set(${RETURN_VALUE} ${${RETURN_VALUE}} PARENT_SCOPE) endfunction(auto_sources) # Remove all files matching a set of patterns, and, # optionally, not matching a second set of patterns, # from a set of lists. # # Example: # This will remove all files in the CPP_SOURCES list # matching "/test/" or "Test.cpp$", but not matching # "BobTest.cpp$". # REMOVE_MATCHES_FROM_LISTS(CPP_SOURCES MATCHES "/test/" "Test.cpp$" IGNORE_MATCHES "BobTest.cpp$") # # Parameters: # # [...]: # The names of the lists to remove matches from. # # [MATCHES ...]: # The matches to remove from the lists. # # [IGNORE_MATCHES ...]: # The matches not to remove, even if they match # the main set of matches to remove. function(REMOVE_MATCHES_FROM_LISTS) set(LISTS_TO_SEARCH) set(MATCHES_TO_REMOVE) set(MATCHES_TO_IGNORE) set(argumentState 0) foreach (arg ${ARGN}) if ("x${arg}" STREQUAL "xMATCHES") set(argumentState 1) elseif ("x${arg}" STREQUAL "xIGNORE_MATCHES") set(argumentState 2) elseif (argumentState EQUAL 0) list(APPEND LISTS_TO_SEARCH ${arg}) elseif (argumentState EQUAL 1) list(APPEND MATCHES_TO_REMOVE ${arg}) elseif (argumentState EQUAL 2) list(APPEND MATCHES_TO_IGNORE ${arg}) else() message(FATAL_ERROR "Unknown argument state!") endif() endforeach() foreach (theList ${LISTS_TO_SEARCH}) foreach (entry ${${theList}}) foreach (match ${MATCHES_TO_REMOVE}) if (${entry} MATCHES ${match}) set(SHOULD_IGNORE OFF) foreach (ign ${MATCHES_TO_IGNORE}) if (${entry} MATCHES ${ign}) set(SHOULD_IGNORE ON) break() endif() endforeach() if (NOT SHOULD_IGNORE) list(REMOVE_ITEM ${theList} ${entry}) endif() endif() endforeach() endforeach() set(${theList} ${${theList}} PARENT_SCOPE) endforeach() endfunction() # Automatically create source_group directives for the sources passed in. function(auto_source_group rootName rootDir) file(TO_CMAKE_PATH "${rootDir}" rootDir) string(LENGTH "${rootDir}" rootDirLength) set(sourceGroups) foreach (fil ${ARGN}) file(TO_CMAKE_PATH "${fil}" filePath) string(FIND "${filePath}" "/" rIdx REVERSE) if (rIdx EQUAL -1) message(FATAL_ERROR "Unable to locate the final forward slash in '${filePath}'!") endif() string(SUBSTRING "${filePath}" 0 ${rIdx} filePath) string(LENGTH "${filePath}" filePathLength) string(FIND "${filePath}" "${rootDir}" rIdx) if (rIdx EQUAL 0) math(EXPR filePathLength "${filePathLength} - ${rootDirLength}") string(SUBSTRING "${filePath}" ${rootDirLength} ${filePathLength} fileGroup) string(REPLACE "/" "\\" fileGroup "${fileGroup}") set(fileGroup "\\${rootName}${fileGroup}") list(FIND sourceGroups "${fileGroup}" rIdx) if (rIdx EQUAL -1) list(APPEND sourceGroups "${fileGroup}") source_group("${fileGroup}" REGULAR_EXPRESSION "${filePath}/[^/.]+.(cpp|h)$") endif() endif() endforeach() endfunction() # CMake is a pain and doesn't have an easy way to install only the files # we actually included in our build :( function(auto_install_files rootName rootDir) file(TO_CMAKE_PATH "${rootDir}" rootDir) string(LENGTH "${rootDir}" rootDirLength) set(sourceGroups) foreach (fil ${ARGN}) file(TO_CMAKE_PATH "${fil}" filePath) string(FIND "${filePath}" "/" rIdx REVERSE) if (rIdx EQUAL -1) message(FATAL_ERROR "Unable to locate the final forward slash in '${filePath}'!") endif() string(SUBSTRING "${filePath}" 0 ${rIdx} filePath) string(LENGTH "${filePath}" filePathLength) string(FIND "${filePath}" "${rootDir}" rIdx) if (rIdx EQUAL 0) math(EXPR filePathLength "${filePathLength} - ${rootDirLength}") string(SUBSTRING "${filePath}" ${rootDirLength} ${filePathLength} fileGroup) install(FILES ${fil} DESTINATION ${INCLUDE_INSTALL_DIR}/${rootName}${fileGroup}) endif() endforeach() endfunction() function(folly_define_tests) set(directory_count 0) set(test_count 0) set(currentArg 0) while (currentArg LESS ${ARGC}) if ("x${ARGV${currentArg}}" STREQUAL "xDIRECTORY") math(EXPR currentArg "${currentArg} + 1") if (NOT currentArg LESS ${ARGC}) message(FATAL_ERROR "Expected base directory!") endif() set(cur_dir ${directory_count}) math(EXPR directory_count "${directory_count} + 1") set(directory_${cur_dir}_name "${ARGV${currentArg}}") # We need a single list of sources to get source_group to work nicely. set(directory_${cur_dir}_source_list) math(EXPR currentArg "${currentArg} + 1") while (currentArg LESS ${ARGC}) if ("x${ARGV${currentArg}}" STREQUAL "xDIRECTORY") break() elseif ("x${ARGV${currentArg}}" STREQUAL "xTEST" OR "x${ARGV${currentArg}}" STREQUAL "xBENCHMARK") set(cur_test ${test_count}) math(EXPR test_count "${test_count} + 1") set(test_${cur_test}_is_benchmark $) math(EXPR currentArg "${currentArg} + 1") if (NOT currentArg LESS ${ARGC}) message(FATAL_ERROR "Expected test name!") endif() set(test_${cur_test}_name "${ARGV${currentArg}}") math(EXPR currentArg "${currentArg} + 1") set(test_${cur_test}_directory ${cur_dir}) set(test_${cur_test}_content_dir) set(test_${cur_test}_headers) set(test_${cur_test}_sources) set(test_${cur_test}_tag) set(argumentState 0) while (currentArg LESS ${ARGC}) if ("x${ARGV${currentArg}}" STREQUAL "xHEADERS") set(argumentState 1) elseif ("x${ARGV${currentArg}}" STREQUAL "xSOURCES") set(argumentState 2) elseif ("x${ARGV${currentArg}}" STREQUAL "xCONTENT_DIR") math(EXPR currentArg "${currentArg} + 1") if (NOT currentArg LESS ${ARGC}) message(FATAL_ERROR "Expected content directory name!") endif() set(test_${cur_test}_content_dir "${ARGV${currentArg}}") elseif ("x${ARGV${currentArg}}" STREQUAL "xTEST" OR "x${ARGV${currentArg}}" STREQUAL "xBENCHMARK" OR "x${ARGV${currentArg}}" STREQUAL "xDIRECTORY") break() elseif (argumentState EQUAL 0) if ("x${ARGV${currentArg}}" STREQUAL "xBROKEN") list(APPEND test_${cur_test}_tag "BROKEN") elseif ("x${ARGV${currentArg}}" STREQUAL "xHANGING") list(APPEND test_${cur_test}_tag "HANGING") elseif ("x${ARGV${currentArg}}" STREQUAL "xSLOW") list(APPEND test_${cur_test}_tag "SLOW") elseif ("x${ARGV${currentArg}}" STREQUAL "xWINDOWS_DISABLED") list(APPEND test_${cur_test}_tag "WINDOWS_DISABLED") elseif ("x${ARGV${currentArg}}" STREQUAL "xAPPLE_DISABLED") list(APPEND test_${cur_test}_tag "APPLE_DISABLED") else() message(FATAL_ERROR "Unknown test tag '${ARGV${currentArg}}'!") endif() elseif (argumentState EQUAL 1) list(APPEND test_${cur_test}_headers "${FOLLY_DIR}/${directory_${cur_dir}_name}${ARGV${currentArg}}" ) elseif (argumentState EQUAL 2) list(APPEND test_${cur_test}_sources "${FOLLY_DIR}/${directory_${cur_dir}_name}${ARGV${currentArg}}" ) else() message(FATAL_ERROR "Unknown argument state!") endif() math(EXPR currentArg "${currentArg} + 1") endwhile() list(APPEND directory_${cur_dir}_source_list ${test_${cur_test}_sources} ${test_${cur_test}_headers}) else() message(FATAL_ERROR "Unknown argument inside directory '${ARGV${currentArg}}'!") endif() endwhile() else() message(FATAL_ERROR "Unknown argument '${ARGV${currentArg}}'!") endif() endwhile() set(cur_dir 0) while (cur_dir LESS directory_count) source_group("" FILES ${directory_${cur_dir}_source_list}) math(EXPR cur_dir "${cur_dir} + 1") endwhile() set(cur_test 0) while (cur_test LESS test_count) set(cur_test_name ${test_${cur_test}_name}) set(cur_dir_name ${directory_${test_${cur_test}_directory}_name}) if ("BROKEN" IN_LIST test_${cur_test}_tag AND NOT BUILD_BROKEN_TESTS) message("Skipping broken test ${cur_dir_name}${cur_test_name}, enable with BUILD_BROKEN_TESTS") elseif ("SLOW" IN_LIST test_${cur_test}_tag AND NOT BUILD_SLOW_TESTS) message("Skipping slow test ${cur_dir_name}${cur_test_name}, enable with BUILD_SLOW_TESTS") elseif ("HANGING" IN_LIST test_${cur_test}_tag AND NOT BUILD_HANGING_TESTS) message("Skipping hanging test ${cur_dir_name}${cur_test_name}, enable with BUILD_HANGING_TESTS") elseif ("WINDOWS_DISABLED" IN_LIST test_${cur_test}_tag AND WIN32 AND NOT BUILD_WINDOWS_DISABLED) message("Skipping windows disabled test ${cur_dir_name}${cur_test_name}, enable with BUILD_WINDOWS_DISABLED") elseif ("APPLE_DISABLED" IN_LIST test_${cur_test}_tag AND APPLE AND NOT BUILD_APPLE_DISABLED) message("Skipping apple disabled test ${cur_dir_name}${cur_test_name}, enable with BUILD_APPLE_DISABLED") elseif (${test_${cur_test}_is_benchmark} AND NOT BUILD_BENCHMARKS) message("Skipping benchmark ${cur_dir_name}${cur_test_name}, enable with BUILD_BENCHMARKS") else() add_executable(${cur_test_name} ${test_${cur_test}_headers} ${test_${cur_test}_sources} ) if (NOT ${test_${cur_test}_is_benchmark}) if (HAVE_CMAKE_GTEST) # If we have CMake's built-in gtest support use it to add each test # function as a separate test. gtest_add_tests(TARGET ${cur_test_name} WORKING_DIRECTORY "${TOP_DIR}" TEST_PREFIX "${cur_test_name}." TEST_LIST test_cases) set_tests_properties(${test_cases} PROPERTIES TIMEOUT 120) else() # Otherwise add each test executable as a single test. add_test( NAME ${cur_test_name} COMMAND ${cur_test_name} WORKING_DIRECTORY "${TOP_DIR}" ) set_tests_properties(${cur_test_name} PROPERTIES TIMEOUT 120) endif() endif() if (NOT "x${test_${cur_test}_content_dir}" STREQUAL "x") # Copy the content directory to the output directory tree so that # tests can be run easily from Visual Studio without having to change # the working directory for each test individually. file( COPY "${FOLLY_DIR}/${cur_dir_name}${test_${cur_test}_content_dir}" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/folly/${cur_dir_name}${test_${cur_test}_content_dir}" ) add_custom_command(TARGET ${cur_test_name} POST_BUILD COMMAND ${CMAKE_COMMAND} ARGS -E copy_directory "${FOLLY_DIR}/${cur_dir_name}${test_${cur_test}_content_dir}" "$/folly/${cur_dir_name}${test_${cur_test}_content_dir}" COMMENT "Copying test content for ${cur_test_name}" VERBATIM ) endif() # Strip the tailing test directory name for the folder name. string(REPLACE "test/" "" test_dir_name "${cur_dir_name}") set_property(TARGET ${cur_test_name} PROPERTY FOLDER "Tests/${test_dir_name}") target_link_libraries(${cur_test_name} PRIVATE folly_test_support) apply_folly_compile_options_to_target(${cur_test_name}) endif() math(EXPR cur_test "${cur_test} + 1") endwhile() endfunction() # Initialize global property to track all granular component targets define_property(GLOBAL PROPERTY FOLLY_COMPONENT_TARGETS BRIEF_DOCS "List of all folly component OBJECT targets" FULL_DOCS "Used to aggregate all component targets into the monolithic folly library" ) set_property(GLOBAL PROPERTY FOLLY_COMPONENT_TARGETS "") # Track deferred dependencies to be linked after all targets are created # Each entry is: "target|visibility|dep1,dep2,dep3" define_property(GLOBAL PROPERTY FOLLY_DEFERRED_DEPS BRIEF_DOCS "List of deferred dependency specifications" FULL_DOCS "Format: target|PUBLIC|PRIVATE|dep1,dep2,..." ) set_property(GLOBAL PROPERTY FOLLY_DEFERRED_DEPS "") # Track INTERFACE targets that need to link to monolithic folly (for shared builds) define_property(GLOBAL PROPERTY FOLLY_GRANULAR_INTERFACE_TARGETS BRIEF_DOCS "List of granular INTERFACE targets for shared builds" FULL_DOCS "These targets will be linked to the monolithic folly library" ) set_property(GLOBAL PROPERTY FOLLY_GRANULAR_INTERFACE_TARGETS "") # Helper to add a folly library target # Creates: # 1. OBJECT library (${name}_obj) - for composition into monolithic target # 2. STATIC library (${name}) - individual .a file for granular linking # Usage: # folly_add_library( # NAME iobuf # Target suffix (full name: folly_io_iobuf) # TARGET_NAME follybenchmark # Optional: override computed target name # SRCS Cursor.cpp IOBuf.cpp # Source files (optional if using AUTO_SOURCES) # HEADERS Cursor.h IOBuf.h # Header files # AUTO_SOURCES # Use auto_sources() instead of explicit SRCS # DEPS folly_lang_bits # Private dependencies (internal folly targets) # EXPORTED_DEPS folly_range # Public dependencies (propagated to users) # EXTERNAL_DEPS ${LIBSODIUM_LIBRARIES} # External library dependencies # EXTERNAL_INCLUDE_DIRS ${LIBSODIUM_INCLUDE_DIRS} # External include directories # COMPILE_OPTIONS -mpclmul # Optional compile options for source files # EXCLUDE_FROM_MONOLITH # Don't include in monolithic folly library # ) function(folly_add_library) cmake_parse_arguments( FOLLY_LIB "AUTO_SOURCES;EXCLUDE_FROM_MONOLITH" # Options "NAME;TARGET_NAME" # Single-value args "SRCS;HEADERS;DEPS;EXPORTED_DEPS;EXTERNAL_DEPS;EXTERNAL_INCLUDE_DIRS;COMPILE_OPTIONS" # Multi-value args ${ARGN} ) # Use explicit TARGET_NAME if provided, otherwise compute from directory if(FOLLY_LIB_TARGET_NAME) set(_target_name "${FOLLY_LIB_TARGET_NAME}") else() # Compute target name from current directory relative to FOLLY_DIR # e.g., folly/io → folly_io, folly/io/async → folly_io_async file(RELATIVE_PATH _rel_path "${FOLLY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}") if(_rel_path STREQUAL "") set(_target_name "folly_${FOLLY_LIB_NAME}") else() string(REPLACE "/" "_" _prefix "${_rel_path}") set(_target_name "folly_${_prefix}_${FOLLY_LIB_NAME}") endif() endif() # Handle source collection if(FOLLY_LIB_AUTO_SOURCES) auto_sources(_srcs "*.cpp" "" "${CMAKE_CURRENT_SOURCE_DIR}") REMOVE_MATCHES_FROM_LISTS(_srcs MATCHES "test/" "Test.cpp$" "Benchmark.cpp$") else() set(_srcs ${FOLLY_LIB_SRCS}) endif() # Skip if no sources (header-only library) list(LENGTH _srcs _src_count) if(_src_count EQUAL 0) # Header-only: create INTERFACE library add_library(${_target_name} INTERFACE) target_include_directories(${_target_name} INTERFACE $ $ $ ) target_link_libraries(${_target_name} INTERFACE folly_deps ${FOLLY_LIB_EXPORTED_DEPS} ${FOLLY_LIB_EXTERNAL_DEPS} ) # Track external deps for monolithic library (header-only deps are used transitively) if(FOLLY_LIB_EXTERNAL_DEPS) set_property(GLOBAL APPEND PROPERTY FOLLY_MONOLITHIC_EXTERNAL_DEPS ${FOLLY_LIB_EXTERNAL_DEPS}) endif() install( TARGETS ${_target_name} EXPORT folly ) add_library(Folly::${_target_name} ALIAS ${_target_name}) return() endif() # 1. Create OBJECT library (for composition into monolithic target) set(_obj_target "${_target_name}_obj") add_library(${_obj_target} OBJECT ${_srcs} ${FOLLY_LIB_HEADERS}) set_property(TARGET ${_obj_target} PROPERTY VERSION ${PACKAGE_VERSION}) if(BUILD_SHARED_LIBS) set_property(TARGET ${_obj_target} PROPERTY POSITION_INDEPENDENT_CODE ON) endif() target_include_directories(${_obj_target} PUBLIC $ $ $ ) apply_folly_compile_options_to_target(${_obj_target}) # Apply optional compile options if(FOLLY_LIB_COMPILE_OPTIONS) target_compile_options(${_obj_target} PRIVATE ${FOLLY_LIB_COMPILE_OPTIONS}) endif() # Link dependencies on OBJECT library # External deps via folly_deps are always available # Internal folly deps are deferred until all targets exist target_link_libraries(${_obj_target} PUBLIC folly_deps ) # Link external dependencies (e.g., libsodium, openssl) directly if(FOLLY_LIB_EXTERNAL_DEPS) target_link_libraries(${_obj_target} PUBLIC ${FOLLY_LIB_EXTERNAL_DEPS} ) endif() # Add external include directories (e.g., libsodium include dirs) if(FOLLY_LIB_EXTERNAL_INCLUDE_DIRS) target_include_directories(${_obj_target} PUBLIC ${FOLLY_LIB_EXTERNAL_INCLUDE_DIRS} ) endif() # Defer internal folly dependencies until all targets are created # (Only for static builds - for shared builds, OBJECT targets are bundled into monolithic folly) if(NOT BUILD_SHARED_LIBS) if(FOLLY_LIB_EXPORTED_DEPS) # Join deps with comma, store as "target|PUBLIC|dep1,dep2,..." list(JOIN FOLLY_LIB_EXPORTED_DEPS "," _deps_str) set_property(GLOBAL APPEND PROPERTY FOLLY_DEFERRED_DEPS "${_obj_target}|PUBLIC|${_deps_str}" ) endif() if(FOLLY_LIB_DEPS) list(JOIN FOLLY_LIB_DEPS "," _deps_str) set_property(GLOBAL APPEND PROPERTY FOLLY_DEFERRED_DEPS "${_obj_target}|PRIVATE|${_deps_str}" ) endif() endif() # Track OBJECT target for monolithic aggregation (unless excluded) if(NOT FOLLY_LIB_EXCLUDE_FROM_MONOLITH) set_property(GLOBAL APPEND PROPERTY FOLLY_COMPONENT_TARGETS ${_obj_target}) # Track external deps for the monolithic library if(FOLLY_LIB_EXTERNAL_DEPS) set_property(GLOBAL APPEND PROPERTY FOLLY_MONOLITHIC_EXTERNAL_DEPS ${FOLLY_LIB_EXTERNAL_DEPS}) endif() endif() # 2. Create the granular library target if(BUILD_SHARED_LIBS AND NOT FOLLY_LIB_EXCLUDE_FROM_MONOLITH) # For shared builds: create INTERFACE library that will link to monolithic folly # This avoids duplicating symbols between granular and monolithic libraries add_library(${_target_name} INTERFACE) target_include_directories(${_target_name} INTERFACE $ $ $ ) # Add external include directories for INTERFACE target if(FOLLY_LIB_EXTERNAL_INCLUDE_DIRS) target_include_directories(${_target_name} INTERFACE ${FOLLY_LIB_EXTERNAL_INCLUDE_DIRS} ) endif() # Track this target to link to folly after monolithic library is created set_property(GLOBAL APPEND PROPERTY FOLLY_GRANULAR_INTERFACE_TARGETS ${_target_name}) # Install the INTERFACE library install( TARGETS ${_target_name} EXPORT folly ) elseif(BUILD_SHARED_LIBS AND FOLLY_LIB_EXCLUDE_FROM_MONOLITH) # For excluded targets in shared builds: create SHARED library with actual code # These are NOT in the monolithic folly, so they need their own implementation add_library(${_target_name} SHARED $) set_property(TARGET ${_target_name} PROPERTY VERSION ${PACKAGE_VERSION}) target_include_directories(${_target_name} PUBLIC $ $ $ ) # Link to folly_deps (external dependencies) target_link_libraries(${_target_name} PUBLIC folly_deps ) # Link external dependencies if(FOLLY_LIB_EXTERNAL_DEPS) target_link_libraries(${_target_name} PUBLIC ${FOLLY_LIB_EXTERNAL_DEPS} ) endif() # Add external include directories if(FOLLY_LIB_EXTERNAL_INCLUDE_DIRS) target_include_directories(${_target_name} PUBLIC ${FOLLY_LIB_EXTERNAL_INCLUDE_DIRS} ) endif() # Defer linking to folly (created later by folly_create_monolithic_library) # Also defer internal folly dependencies set(_all_deps "folly") if(FOLLY_LIB_EXPORTED_DEPS) list(APPEND _all_deps ${FOLLY_LIB_EXPORTED_DEPS}) endif() list(JOIN _all_deps "," _deps_str) set_property(GLOBAL APPEND PROPERTY FOLLY_DEFERRED_DEPS "${_target_name}|PUBLIC|${_deps_str}" ) if(FOLLY_LIB_DEPS) list(JOIN FOLLY_LIB_DEPS "," _deps_str) set_property(GLOBAL APPEND PROPERTY FOLLY_DEFERRED_DEPS "${_target_name}|PRIVATE|${_deps_str}" ) endif() # Install the SHARED library install( TARGETS ${_target_name} EXPORT folly LIBRARY DESTINATION ${LIB_INSTALL_DIR} ARCHIVE DESTINATION ${LIB_INSTALL_DIR} ) else() # For static builds: create STATIC library (individual .a file for granular linking) add_library(${_target_name} STATIC $) set_property(TARGET ${_target_name} PROPERTY VERSION ${PACKAGE_VERSION}) target_include_directories(${_target_name} PUBLIC $ $ $ ) # Link external dependencies on STATIC library # Internal folly deps are deferred (see above) target_link_libraries(${_target_name} PUBLIC folly_deps ) # Link external dependencies (e.g., libsodium, openssl) directly if(FOLLY_LIB_EXTERNAL_DEPS) target_link_libraries(${_target_name} PUBLIC ${FOLLY_LIB_EXTERNAL_DEPS} ) endif() # Add external include directories for STATIC target if(FOLLY_LIB_EXTERNAL_INCLUDE_DIRS) target_include_directories(${_target_name} PUBLIC ${FOLLY_LIB_EXTERNAL_INCLUDE_DIRS} ) endif() # Defer internal folly dependencies for STATIC library too if(FOLLY_LIB_EXPORTED_DEPS) list(JOIN FOLLY_LIB_EXPORTED_DEPS "," _deps_str) set_property(GLOBAL APPEND PROPERTY FOLLY_DEFERRED_DEPS "${_target_name}|PUBLIC|${_deps_str}" ) endif() if(FOLLY_LIB_DEPS) list(JOIN FOLLY_LIB_DEPS "," _deps_str) set_property(GLOBAL APPEND PROPERTY FOLLY_DEFERRED_DEPS "${_target_name}|PRIVATE|${_deps_str}" ) endif() # Install the STATIC library install( TARGETS ${_target_name} EXPORT folly LIBRARY DESTINATION ${LIB_INSTALL_DIR} ARCHIVE DESTINATION ${LIB_INSTALL_DIR} ) endif() # Create alias for the library add_library(Folly::${_target_name} ALIAS ${_target_name}) endfunction() # Resolve all deferred dependencies after all targets have been created # Call this after all add_subdirectory() calls function(folly_resolve_deferred_dependencies) # Allow linking targets defined in other directories cmake_policy(SET CMP0079 NEW) get_property(_deferred_deps GLOBAL PROPERTY FOLLY_DEFERRED_DEPS) foreach(_spec IN LISTS _deferred_deps) # Parse the spec: "target|visibility|dep1,dep2,..." string(REPLACE "|" ";" _parts "${_spec}") list(LENGTH _parts _len) if(_len LESS 3) continue() endif() list(GET _parts 0 _target) list(GET _parts 1 _visibility) list(GET _parts 2 _deps_str) # Split deps by comma string(REPLACE "," ";" _deps "${_deps_str}") # Filter to only existing targets (skip deps that weren't generated) set(_valid_deps "") foreach(_dep IN LISTS _deps) if(TARGET ${_dep}) list(APPEND _valid_deps ${_dep}) endif() endforeach() if(_valid_deps) target_link_libraries(${_target} ${_visibility} ${_valid_deps}) endif() endforeach() endfunction() # Create the monolithic folly library from all component OBJECT libraries # Call this after all add_subdirectory() calls and folly_resolve_deferred_dependencies() function(folly_create_monolithic_library) get_property(_component_targets GLOBAL PROPERTY FOLLY_COMPONENT_TARGETS) # Collect all object files from component targets set(_all_objects) foreach(_target IN LISTS _component_targets) list(APPEND _all_objects $) endforeach() # Create the monolithic library add_library(folly ${_all_objects}) if(BUILD_SHARED_LIBS) set_property(TARGET folly PROPERTY POSITION_INDEPENDENT_CODE ON) endif() set_property(TARGET folly PROPERTY VERSION ${PACKAGE_VERSION}) apply_folly_compile_options_to_target(folly) target_compile_features(folly INTERFACE cxx_generic_lambdas) target_include_directories(folly PUBLIC $ $ $ ) target_link_libraries(folly PUBLIC folly_deps) # Link all external dependencies that were tracked from component targets get_property(_external_deps GLOBAL PROPERTY FOLLY_MONOLITHIC_EXTERNAL_DEPS) if(_external_deps) list(REMOVE_DUPLICATES _external_deps) target_link_libraries(folly PUBLIC ${_external_deps}) endif() # Create alias for consistency add_library(Folly::folly ALIAS folly) # For shared builds: link all granular INTERFACE targets to the monolithic library if(BUILD_SHARED_LIBS) # CMP0079: target_link_libraries allows use with targets in other directories cmake_policy(SET CMP0079 NEW) get_property(_interface_targets GLOBAL PROPERTY FOLLY_GRANULAR_INTERFACE_TARGETS) foreach(_target IN LISTS _interface_targets) target_link_libraries(${_target} INTERFACE folly) endforeach() endif() endfunction() ================================================ FILE: CMake/GenPkgConfig.cmake ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # # 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. # Generate variables that can be used to help emit a pkg-config file # using configure_file(). # # Usage: gen_pkgconfig_vars(VAR_PREFIX target) # # This will set two variables in the caller scope: # ${VAR_PREFIX}_CFLAGS: set to the compile flags computed from the specified # target # ${VAR_PREFIX}_PRIVATE_LIBS: set to the linker flags needed for static # linking computed from the specified target function(gen_pkgconfig_vars) if (NOT ${ARGC} EQUAL 2) message(FATAL_ERROR "gen_pkgconfig_vars() requires exactly 2 arguments") endif() set(var_prefix "${ARGV0}") set(target "${ARGV1}") get_target_property(target_cflags "${target}" INTERFACE_COMPILE_OPTIONS) if(target_cflags) list(APPEND cflags "${target_cflags}") endif() get_target_property( target_inc_dirs "${target}" INTERFACE_INCLUDE_DIRECTORIES) if(target_inc_dirs) list(APPEND include_dirs "${target_inc_dirs}") endif() get_target_property(target_defns "${target}" INTERFACE_COMPILE_DEFINITIONS) if(target_defns) list(APPEND definitions "${target_defns}") endif() # The INTERFACE_LINK_LIBRARIES list is unfortunately somewhat awkward to # process. Entries in this list may be any of # - target names # - absolute paths to a library file # - plain library names that need "-l" prepended # - other linker flags starting with "-" # # Walk through each entry and transform it into the desired arguments get_target_property(link_libs "${target}" INTERFACE_LINK_LIBRARIES) if(link_libs) foreach(lib_arg IN LISTS link_libs) if(TARGET "${lib_arg}") # Add any compile options specified in the targets # INTERFACE_COMPILE_OPTIONS. We don't need to process its # INTERFACE_LINK_LIBRARIES property, since our INTERFACE_LINK_LIBRARIES # will already include its entries transitively. get_target_property(lib_cflags "${lib_arg}" INTERFACE_COMPILE_OPTIONS) if(lib_cflags) list(APPEND cflags "${lib_cflags}") endif() get_target_property(lib_defs "${lib_arg}" INTERFACE_COMPILE_DEFINITIONS) if(lib_defs) list(APPEND definitions "${lib_defs}") endif() elseif(lib_arg MATCHES "^[-/]") list(APPEND private_libs "${lib_arg}") else() list(APPEND private_libs "-l${lib_arg}") endif() endforeach() endif() list(APPEND cflags "${CMAKE_REQUIRED_FLAGS}") if(definitions) list(REMOVE_DUPLICATES definitions) foreach(def_arg IN LISTS definitions) list(APPEND cflags "-D${def_arg}") endforeach() endif() if(include_dirs) list(REMOVE_DUPLICATES include_dirs) foreach(inc_dir IN LISTS include_dirs) list(APPEND cflags "-I${inc_dir}") endforeach() endif() # Set the output variables string(REPLACE ";" " " cflags "${cflags}") string(REPLACE ";" " " private_libs "${private_libs}") # Since CMake 3.18 FindThreads may include a generator expression requiring # a target, which gets propagated to us through INTERFACE_COMPILE_OPTIONS. # Before CMake 3.19 there's no way to solve this in a general way, so we # work around the specific case. See #1414 and CMake bug #21074. if(CMAKE_VERSION VERSION_LESS 3.19) string(REPLACE "" "" cflags "${cflags}" ) endif() # patch for fmt's generator expression if (MSVC) # fmt 11.0.3 and above string(REPLACE "$<$,$>:/utf-8>" "/utf-8" cflags "${cflags}") # fmt 11.0.2 and below string(REPLACE "$<$:/utf-8>" "/utf-8" cflags "${cflags}") endif() set("${var_prefix}_CFLAGS" "${cflags}" PARENT_SCOPE) set("${var_prefix}_PRIVATE_LIBS" "${private_libs}" PARENT_SCOPE) endfunction() ================================================ FILE: CMake/folly-config.cmake.in ================================================ # CMake configuration file for folly # # This provides the Folly::folly target, which you can depend on by adding it # to your target_link_libraries(). # # It also defines the following variables, although using these directly is not # necessary if you use the Folly::folly target instead. # FOLLY_INCLUDE_DIR # FOLLY_LIBRARIES @PACKAGE_INIT@ include(CMakeFindDependencyMacro) set_and_check(FOLLY_INCLUDE_DIRS "@PACKAGE_INCLUDE_INSTALL_DIR@") set_and_check(FOLLY_INCLUDE_DIR "@PACKAGE_INCLUDE_INSTALL_DIR@") set_and_check(FOLLY_CMAKE_DIR "@PACKAGE_CMAKE_INSTALL_DIR@") # find_dependency() ends up changing PACKAGE_PREFIX_DIR, so save # folly's prefix directory in the FOLLY_PREFIX_DIR variable set(FOLLY_PREFIX_DIR "${PACKAGE_PREFIX_DIR}") # Find glog before loading targets, since targets reference the glog::glog target if(NOT TARGET glog::glog) find_package(Glog QUIET) endif() # Include the folly-targets.cmake file, which is generated from our CMake rules if (NOT TARGET Folly::folly) include("${FOLLY_CMAKE_DIR}/folly-targets.cmake") endif() # Set FOLLY_LIBRARIES from our Folly::folly target set(FOLLY_LIBRARIES Folly::folly) # Find folly's dependencies find_dependency(fmt) set(Boost_USE_STATIC_LIBS "@FOLLY_BOOST_LINK_STATIC@") find_package(Boost 1.69.0 REQUIRED COMPONENTS context filesystem program_options regex thread ) if (NOT folly_FIND_QUIETLY) message(STATUS "Found folly: ${FOLLY_PREFIX_DIR}") endif() ================================================ FILE: CMake/folly-config.h.cmake ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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 #ifdef __APPLE__ #include // @manual #endif #if !defined(FOLLY_MOBILE) #if defined(__ANDROID__) || \ (defined(__APPLE__) && \ (TARGET_IPHONE_SIMULATOR || TARGET_OS_SIMULATOR || TARGET_OS_IPHONE)) #define FOLLY_MOBILE 1 #else #define FOLLY_MOBILE 0 #endif #endif // FOLLY_MOBILE #cmakedefine01 FOLLY_HAVE_PTHREAD #cmakedefine FOLLY_HAVE_PTHREAD_ATFORK 1 #cmakedefine FOLLY_HAVE_LIBGFLAGS 1 #cmakedefine FOLLY_HAVE_LIBGLOG 1 #cmakedefine FOLLY_USE_JEMALLOC 1 #if __has_include() #include #endif #cmakedefine FOLLY_HAVE_ACCEPT4 1 #cmakedefine01 FOLLY_HAVE_GETRANDOM #cmakedefine FOLLY_HAVE_PREADV 1 #cmakedefine FOLLY_HAVE_PWRITEV 1 #cmakedefine01 FOLLY_HAVE_CLOCK_GETTIME #cmakedefine FOLLY_HAVE_PIPE2 1 #cmakedefine FOLLY_HAVE_IFUNC 1 #cmakedefine FOLLY_HAVE_UNALIGNED_ACCESS 1 #cmakedefine FOLLY_HAVE_VLA 1 #cmakedefine01 FOLLY_HAVE_WEAK_SYMBOLS #cmakedefine FOLLY_HAVE_LINUX_VDSO 1 #cmakedefine FOLLY_HAVE_MALLOC_USABLE_SIZE 1 #cmakedefine FOLLY_HAVE_INT128_T 1 #cmakedefine FOLLY_HAVE_WCHAR_SUPPORT 1 #cmakedefine FOLLY_HAVE_EXTRANDOM_SFMT19937 1 #cmakedefine HAVE_VSNPRINTF_ERRORS 1 #cmakedefine FOLLY_HAVE_LIBUNWIND 1 #cmakedefine01 FOLLY_HAVE_DWARF #cmakedefine01 FOLLY_HAVE_ELF #cmakedefine FOLLY_HAVE_SWAPCONTEXT 1 #cmakedefine FOLLY_HAVE_BACKTRACE 1 #cmakedefine FOLLY_USE_SYMBOLIZER 1 #define FOLLY_DEMANGLE_MAX_SYMBOL_SIZE 1024 #cmakedefine FOLLY_HAVE_SHADOW_LOCAL_WARNINGS 1 #cmakedefine01 FOLLY_HAVE_LIBLZ4 #cmakedefine01 FOLLY_HAVE_LIBLZMA #cmakedefine01 FOLLY_HAVE_LIBSNAPPY #cmakedefine01 FOLLY_HAVE_LIBZ #cmakedefine01 FOLLY_HAVE_LIBZSTD #cmakedefine01 FOLLY_HAVE_LIBBZ2 #cmakedefine01 FOLLY_LIBRARY_SANITIZE_ADDRESS #cmakedefine FOLLY_SUPPORT_SHARED_LIBRARY 1 #cmakedefine01 FOLLY_HAVE_LIBRT #cmakedefine01 FOLLY_HAVE_VSOCK ================================================ FILE: CMake/folly-deps.cmake ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # # 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(CheckCXXSourceCompiles) include(CheckCXXSymbolExists) include(CheckIncludeFileCXX) include(CheckFunctionExists) include(CMakePushCheckState) set( BOOST_LINK_STATIC "auto" CACHE STRING "Whether to link against boost statically or dynamically." ) if("${BOOST_LINK_STATIC}" STREQUAL "auto") # Default to linking boost statically on Windows with MSVC if(MSVC) set(FOLLY_BOOST_LINK_STATIC ON) else() set(FOLLY_BOOST_LINK_STATIC OFF) endif() else() set(FOLLY_BOOST_LINK_STATIC "${BOOST_LINK_STATIC}") endif() set(Boost_USE_STATIC_LIBS "${FOLLY_BOOST_LINK_STATIC}") # Note: We find these components so the CMake targets exist, but we don't # link them globally. Targets that need specific Boost libraries should # add them to their EXTERNAL_DEPS (e.g., Boost::regex, Boost::context). # Boost::thread is needed by Windows pthread compatibility layer. set(FOLLY_BOOST_COMPONENTS context filesystem program_options regex ) if(WIN32) list(APPEND FOLLY_BOOST_COMPONENTS thread) endif() find_package(Boost 1.69.0 REQUIRED COMPONENTS ${FOLLY_BOOST_COMPONENTS} ) # Only add include directories globally, not libraries # Per-target Boost dependencies are specified via EXTERNAL_DEPS list(APPEND FOLLY_INCLUDE_DIRECTORIES ${Boost_INCLUDE_DIRS}) find_package(DoubleConversion MODULE REQUIRED) list(APPEND FOLLY_LINK_LIBRARIES ${DOUBLE_CONVERSION_LIBRARY}) list(APPEND FOLLY_INCLUDE_DIRECTORIES ${DOUBLE_CONVERSION_INCLUDE_DIR}) find_package(FastFloat MODULE REQUIRED) list(APPEND FOLLY_INCLUDE_DIRECTORIES ${FASTFLOAT_INCLUDE_DIR}) find_package(Gflags MODULE) set(FOLLY_HAVE_LIBGFLAGS ${LIBGFLAGS_FOUND}) if(LIBGFLAGS_FOUND) list(APPEND FOLLY_LINK_LIBRARIES ${LIBGFLAGS_LIBRARY}) list(APPEND FOLLY_INCLUDE_DIRECTORIES ${LIBGFLAGS_INCLUDE_DIR}) set(FOLLY_LIBGFLAGS_LIBRARY ${LIBGFLAGS_LIBRARY}) set(FOLLY_LIBGFLAGS_INCLUDE ${LIBGFLAGS_INCLUDE_DIR}) endif() find_package(Glog MODULE) set(FOLLY_HAVE_LIBGLOG ${GLOG_FOUND}) list(APPEND FOLLY_LINK_LIBRARIES glog::glog) list(APPEND FOLLY_INCLUDE_DIRECTORIES ${GLOG_INCLUDE_DIR}) # Glog 0.7+ requires GLOG_USE_GLOG_EXPORT to be defined so that headers # include glog/export.h which defines GLOG_EXPORT. if (EXISTS "${GLOG_INCLUDE_DIR}/glog/export.h") list(APPEND FOLLY_CXX_FLAGS -DGLOG_USE_GLOG_EXPORT) endif() find_package(LibEvent MODULE REQUIRED) list(APPEND FOLLY_LINK_LIBRARIES ${LIBEVENT_LIB}) list(APPEND FOLLY_INCLUDE_DIRECTORIES ${LIBEVENT_INCLUDE_DIR}) find_package(ZLIB MODULE) set(FOLLY_HAVE_LIBZ ${ZLIB_FOUND}) if (ZLIB_FOUND) list(APPEND FOLLY_INCLUDE_DIRECTORIES ${ZLIB_INCLUDE_DIRS}) list(APPEND FOLLY_LINK_LIBRARIES ${ZLIB_LIBRARIES}) list(APPEND CMAKE_REQUIRED_LIBRARIES ${ZLIB_LIBRARIES}) endif() find_package(OpenSSL 1.1.1 MODULE REQUIRED) list(APPEND FOLLY_LINK_LIBRARIES ${OPENSSL_LIBRARIES}) list(APPEND FOLLY_INCLUDE_DIRECTORIES ${OPENSSL_INCLUDE_DIR}) list(APPEND CMAKE_REQUIRED_LIBRARIES ${OPENSSL_LIBRARIES}) list(APPEND CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) list(REMOVE_ITEM CMAKE_REQUIRED_LIBRARIES ${OPENSSL_LIBRARIES}) list(REMOVE_ITEM CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) if (ZLIB_FOUND) list(REMOVE_ITEM CMAKE_REQUIRED_LIBRARIES ${ZLIB_LIBRARIES}) endif() find_package(BZip2 MODULE) set(FOLLY_HAVE_LIBBZ2 ${BZIP2_FOUND}) if (BZIP2_FOUND) list(APPEND FOLLY_INCLUDE_DIRECTORIES ${BZIP2_INCLUDE_DIRS}) list(APPEND FOLLY_LINK_LIBRARIES ${BZIP2_LIBRARIES}) endif() find_package(LibLZMA MODULE) set(FOLLY_HAVE_LIBLZMA ${LIBLZMA_FOUND}) if (LIBLZMA_FOUND) list(APPEND FOLLY_INCLUDE_DIRECTORIES ${LIBLZMA_INCLUDE_DIRS}) list(APPEND FOLLY_LINK_LIBRARIES ${LIBLZMA_LIBRARIES}) endif() find_package(LZ4 MODULE) set(FOLLY_HAVE_LIBLZ4 ${LZ4_FOUND}) if (LZ4_FOUND) list(APPEND FOLLY_INCLUDE_DIRECTORIES ${LZ4_INCLUDE_DIR}) list(APPEND FOLLY_LINK_LIBRARIES ${LZ4_LIBRARY}) endif() find_package(Zstd MODULE) set(FOLLY_HAVE_LIBZSTD ${ZSTD_FOUND}) if(ZSTD_FOUND) list(APPEND FOLLY_INCLUDE_DIRECTORIES ${ZSTD_INCLUDE_DIR}) list(APPEND FOLLY_LINK_LIBRARIES ${ZSTD_LIBRARY}) endif() find_package(Snappy MODULE) set(FOLLY_HAVE_LIBSNAPPY ${SNAPPY_FOUND}) if (SNAPPY_FOUND) list(APPEND FOLLY_INCLUDE_DIRECTORIES ${SNAPPY_INCLUDE_DIR}) list(APPEND FOLLY_LINK_LIBRARIES ${SNAPPY_LIBRARY}) endif() find_package(LibDwarf) list(APPEND FOLLY_LINK_LIBRARIES ${LIBDWARF_LIBRARIES}) list(APPEND FOLLY_INCLUDE_DIRECTORIES ${LIBDWARF_INCLUDE_DIRS}) find_package(Libiberty) list(APPEND FOLLY_LINK_LIBRARIES ${LIBIBERTY_LIBRARIES}) list(APPEND FOLLY_INCLUDE_DIRECTORIES ${LIBIBERTY_INCLUDE_DIRS}) find_package(LibAIO) list(APPEND FOLLY_LINK_LIBRARIES ${LIBAIO_LIBRARIES}) list(APPEND FOLLY_INCLUDE_DIRECTORIES ${LIBAIO_INCLUDE_DIRS}) find_package(LibUring) list(APPEND FOLLY_LINK_LIBRARIES ${LIBURING_LIBRARIES}) list(APPEND FOLLY_INCLUDE_DIRECTORIES ${LIBURING_INCLUDE_DIRS}) find_package(Libsodium) list(APPEND FOLLY_LINK_LIBRARIES ${LIBSODIUM_LIBRARIES}) list(APPEND FOLLY_INCLUDE_DIRECTORIES ${LIBSODIUM_INCLUDE_DIRS}) list(APPEND FOLLY_LINK_LIBRARIES ${CMAKE_DL_LIBS}) list(APPEND CMAKE_REQUIRED_LIBRARIES ${CMAKE_DL_LIBS}) if (PYTHON_EXTENSIONS) find_package(Python3 COMPONENTS Interpreter Development REQUIRED) find_package(Cython 0.26 REQUIRED) endif () find_package(LibUnwind) list(APPEND FOLLY_LINK_LIBRARIES ${LIBUNWIND_LIBRARIES}) list(APPEND FOLLY_INCLUDE_DIRECTORIES ${LIBUNWIND_INCLUDE_DIRS}) if (LIBUNWIND_FOUND) set(FOLLY_HAVE_LIBUNWIND ON) endif() if (CMAKE_SYSTEM_NAME MATCHES "FreeBSD") list(APPEND FOLLY_LINK_LIBRARIES "execinfo") endif () cmake_push_check_state() set(CMAKE_REQUIRED_DEFINITIONS -D_XOPEN_SOURCE) check_cxx_symbol_exists(swapcontext ucontext.h FOLLY_HAVE_SWAPCONTEXT) cmake_pop_check_state() set(FOLLY_USE_SYMBOLIZER OFF) CHECK_INCLUDE_FILE_CXX(elf.h FOLLY_HAVE_ELF) find_package(Backtrace) set(FOLLY_HAVE_BACKTRACE ${Backtrace_FOUND}) set(FOLLY_HAVE_DWARF ${LIBDWARF_FOUND}) if (NOT WIN32 AND NOT APPLE) set(FOLLY_USE_SYMBOLIZER ON) endif() message(STATUS "Setting FOLLY_USE_SYMBOLIZER: ${FOLLY_USE_SYMBOLIZER}") message(STATUS "Setting FOLLY_HAVE_ELF: ${FOLLY_HAVE_ELF}") message(STATUS "Setting FOLLY_HAVE_DWARF: ${FOLLY_HAVE_DWARF}") # Using clang with libstdc++ requires explicitly linking against libatomic check_cxx_source_compiles(" #include int main(int argc, char** argv) { std::atomic a1; std::atomic a2; std::atomic a4; std::atomic a8; struct Test { bool val; }; std::atomic s; return a1++ + a2++ + a4++ + a8++ + unsigned(s.is_lock_free()); }" FOLLY_CPP_ATOMIC_BUILTIN ) if(NOT FOLLY_CPP_ATOMIC_BUILTIN) list(APPEND CMAKE_REQUIRED_LIBRARIES atomic) list(APPEND FOLLY_LINK_LIBRARIES atomic) set(ATOMIC_LIBRARY "atomic") check_cxx_source_compiles(" #include int main(int argc, char** argv) { std::atomic a1; std::atomic a2; std::atomic a4; std::atomic a8; struct Test { bool val; }; std::atomic s; return a1++ + a2++ + a4++ + a8++ + unsigned(s.is_lock_free()); }" FOLLY_CPP_ATOMIC_WITH_LIBATOMIC ) if (NOT FOLLY_CPP_ATOMIC_WITH_LIBATOMIC) message( FATAL_ERROR "unable to link C++ std::atomic code: you may need \ to install GNU libatomic" ) endif() endif() check_cxx_source_compiles(" #include #if _GLIBCXX_RELEASE int main() {} #endif" FOLLY_STDLIB_LIBSTDCXX ) check_cxx_source_compiles(" #include #if _GLIBCXX_RELEASE >= 9 int main() {} #endif" FOLLY_STDLIB_LIBSTDCXX_GE_9 ) check_cxx_source_compiles(" #include #if _LIBCPP_VERSION int main() {} #endif" FOLLY_STDLIB_LIBCXX ) check_cxx_source_compiles(" #include #if _LIBCPP_VERSION >= 9000 int main() {} #endif" FOLLY_STDLIB_LIBCXX_GE_9 ) check_cxx_source_compiles(" #include #if _CPPLIB_VER int main() {} #endif" FOLLY_STDLIB_LIBCPP ) if (APPLE) list (APPEND CMAKE_REQUIRED_LIBRARIES c++abi) list (APPEND FOLLY_LINK_LIBRARIES c++abi) endif () if (FOLLY_STDLIB_LIBSTDCXX AND NOT FOLLY_STDLIB_LIBSTDCXX_GE_9) list (APPEND CMAKE_REQUIRED_LIBRARIES stdc++fs) list (APPEND FOLLY_LINK_LIBRARIES stdc++fs) endif() if (FOLLY_STDLIB_LIBCXX AND NOT FOLLY_STDLIB_LIBCXX_GE_9) list (APPEND CMAKE_REQUIRED_LIBRARIES c++fs) list (APPEND FOLLY_LINK_LIBRARIES c++fs) endif () option( FOLLY_LIBRARY_SANITIZE_ADDRESS "Build folly with Address Sanitizer enabled." OFF ) if ($ENV{WITH_ASAN}) message(STATUS "ENV WITH_ASAN is set") set (FOLLY_LIBRARY_SANITIZE_ADDRESS ON) endif() if (FOLLY_LIBRARY_SANITIZE_ADDRESS) if ("${CMAKE_CXX_COMPILER_ID}" MATCHES GNU) set(FOLLY_LIBRARY_SANITIZE_ADDRESS ON) set(FOLLY_ASAN_FLAGS -fsanitize=address,undefined) list(APPEND FOLLY_CXX_FLAGS ${FOLLY_ASAN_FLAGS}) # All of the functions in folly/detail/Sse.cpp are intended to be compiled # with ASAN disabled. They are marked with attributes to disable the # sanitizer, but even so, gcc fails to compile them for some reason when # sanitization is enabled on the compile line. set_source_files_properties( "${PROJECT_SOURCE_DIR}/folly/detail/Sse.cpp" PROPERTIES COMPILE_FLAGS -fno-sanitize=address,undefined ) elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES Clang) set(FOLLY_LIBRARY_SANITIZE_ADDRESS ON) set( FOLLY_ASAN_FLAGS -fno-common -fsanitize=address,undefined,integer,nullability -fno-sanitize=unsigned-integer-overflow ) list(APPEND FOLLY_CXX_FLAGS ${FOLLY_ASAN_FLAGS}) endif() endif() add_library(folly_deps INTERFACE) find_package(fmt CONFIG) if (NOT DEFINED fmt_CONFIG) # Fallback on a normal search on the current system find_package(Fmt MODULE REQUIRED) endif() target_link_libraries(folly_deps INTERFACE fmt::fmt) list(REMOVE_DUPLICATES FOLLY_INCLUDE_DIRECTORIES) if(NOT "${CMAKE_SOURCE_DIR}" STREQUAL "${PROJECT_SOURCE_DIR}") # When consumed via add_subdirectory/FetchContent, wrap each include # directory in BUILD_INTERFACE so absolute build-tree paths don't leak # into the parent project's install-time INTERFACE_INCLUDE_DIRECTORIES. foreach(_dir IN LISTS FOLLY_INCLUDE_DIRECTORIES) target_include_directories(folly_deps INTERFACE $) endforeach() else() target_include_directories(folly_deps INTERFACE ${FOLLY_INCLUDE_DIRECTORIES}) endif() target_link_libraries(folly_deps INTERFACE ${FOLLY_LINK_LIBRARIES} ${FOLLY_SHINY_DEPENDENCIES} ${FOLLY_ASAN_FLAGS} ) ================================================ FILE: CMake/libfolly.pc.in ================================================ prefix=@CMAKE_INSTALL_PREFIX@ exec_prefix=@CMAKE_INSTALL_PREFIX@ libdir=${exec_prefix}/@LIB_INSTALL_DIR@ includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ Name: libfolly Description: Facebook (Folly) C++ library Version: @PACKAGE_VERSION@ Cflags: -I${includedir} @FOLLY_PKGCONFIG_CFLAGS@ Libs: -L${libdir} -lfolly Libs.private: @FOLLY_PKGCONFIG_PRIVATE_LIBS@ ================================================ FILE: CMakeLists.txt ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # # 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. cmake_minimum_required(VERSION 3.5 FATAL_ERROR) # We use the GoogleTest module if it is available (only in CMake 3.9+) # It requires CMP0054 and CMP0057 to be enabled. if (POLICY CMP0054) cmake_policy(SET CMP0054 NEW) endif() if (POLICY CMP0057) cmake_policy(SET CMP0057 NEW) endif() # CMP0075 Include file check macros honor CMAKE_REQUIRED_LIBRARIES if(POLICY CMP0075) cmake_policy(SET CMP0075 NEW) endif() # CMP0167 The FindBoost module is removed if(POLICY CMP0167) cmake_policy(SET CMP0167 NEW) endif() # includes set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake" # for in-fbsource builds "${CMAKE_CURRENT_SOURCE_DIR}/../opensource/fbcode_builder/CMake" # For shipit-transformed builds "${CMAKE_CURRENT_SOURCE_DIR}/build/fbcode_builder/CMake" ${CMAKE_MODULE_PATH}) # package information set(PACKAGE_NAME "folly") if (NOT DEFINED PACKAGE_VERSION) set(PACKAGE_VERSION "0.58.0-dev") endif() set(PACKAGE_STRING "${PACKAGE_NAME} ${PACKAGE_VERSION}") set(PACKAGE_TARNAME "${PACKAGE_NAME}-${PACKAGE_VERSION}") set(PACKAGE_BUGREPORT "https://github.com/facebook/folly/issues") # 150+ tests in the root folder anyone? No? I didn't think so. set_property(GLOBAL PROPERTY USE_FOLDERS ON) project(${PACKAGE_NAME} CXX C ASM) # Set for FetchContent to skip find_package(folly) set(folly_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}" CACHE INTERNAL "folly source directory") set(INCLUDE_INSTALL_DIR include CACHE STRING "The subdirectory where header files should be installed") set(LIB_INSTALL_DIR lib CACHE STRING "The subdirectory where libraries should be installed") set(BIN_INSTALL_DIR bin CACHE STRING "The subdirectory where binaries should be installed") set(CMAKE_INSTALL_DIR lib/cmake/folly CACHE STRING "The subdirectory where CMake package config files should be installed") option(BUILD_SHARED_LIBS "If enabled, build folly as a shared library. \ This is generally discouraged, since folly does not commit to having \ a stable ABI." OFF ) option(PYTHON_EXTENSIONS "Build Python Bindings for Folly, requires Cython and (BUILD_SHARED_LIBS=ON)" OFF ) # Python extensions require shared libraries for proper symbol resolution if (PYTHON_EXTENSIONS) set(BUILD_SHARED_LIBS ON CACHE BOOL "Forced ON because PYTHON_EXTENSIONS requires shared libraries" FORCE) endif() set(PYTHON_PACKAGE_INSTALL_DIR "" CACHE STRING "Installation directory for folly Python packages. \ If empty, CMAKE_INSTALL_PREFIX will be used. \ No effect if PYTHON_EXTENSIONS=OFF." ) # Mark BUILD_SHARED_LIBS as an "advanced" option, since enabling it # is generally discouraged. mark_as_advanced(BUILD_SHARED_LIBS) set(FOLLY_SUPPORT_SHARED_LIBRARY "${BUILD_SHARED_LIBS}") include(FBBuildOptions) fb_activate_static_library_option() if(NOT CMAKE_CXX_STANDARD) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) message(STATUS "setting C++ standard to C++${CMAKE_CXX_STANDARD}") endif() if(NOT DEFINED IS_X86_64_ARCH AND ${CMAKE_SYSTEM_PROCESSOR} MATCHES "x86_64|AMD64") set(IS_X86_64_ARCH TRUE) else() set(IS_X86_64_ARCH FALSE) endif() if(NOT DEFINED IS_AARCH64_ARCH AND ${CMAKE_SYSTEM_PROCESSOR} MATCHES "aarch64") set(IS_AARCH64_ARCH TRUE) else() set(IS_AARCH64_ARCH FALSE) endif() if(CMAKE_SYSTEM_NAME STREQUAL "Windows") # Check target architecture if (NOT CMAKE_SIZEOF_VOID_P EQUAL 8) message(FATAL_ERROR "Folly requires a 64bit target architecture.") endif() if (MSVC_VERSION LESS 1900) message( FATAL_ERROR "This build script only supports building Folly on 64-bit Windows with " "at least Visual Studio 2017. " "MSVC version '${MSVC_VERSION}' is not supported." ) endif() endif() if(NOT DEFINED FOLLY_HAVE_INT128_T) include(CheckTypeSize) check_type_size("__int128_t" FOLLY_INT128_T_SIZE) if(HAVE_FOLLY_INT128_T_SIZE) set(FOLLY_HAVE_INT128_T TRUE) endif() endif() set(TOP_DIR "${CMAKE_CURRENT_SOURCE_DIR}") set(FOLLY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/folly") set( FOLLY_DIR_PREFIXES "${CMAKE_CURRENT_SOURCE_DIR}:${CMAKE_CURRENT_BINARY_DIR}" ) # https://gitlab.kitware.com/cmake/cmake/-/issues/18580#note_1405108 string(REGEX REPLACE "(.)" "\\\\\\1" FOLLY_DIR_REGEX_ESCAPED "${FOLLY_DIR}") include(GNUInstallDirs) set(CMAKE_THREAD_PREFER_PTHREAD ON) set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) set(FOLLY_HAVE_PTHREAD "${CMAKE_USE_PTHREADS_INIT}") list(APPEND CMAKE_REQUIRED_LIBRARIES Threads::Threads) list(APPEND FOLLY_LINK_LIBRARIES Threads::Threads) if(MSVC) include(FollyCompilerMSVC) else() include(FollyCompilerUnix) endif() include(FollyFunctions) include(folly-deps) # Find the required packages include(FollyConfigChecks) configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/CMake/folly-config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/folly/folly-config.h ) # Define FOLLY_XLOG_STRIP_PREFIXES when compiling our sources so that # folly/logging will automatically choose the correct log category names, # using only the relative portion of the source file name inside the # folly repository. set_property( DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} APPEND PROPERTY COMPILE_DEFINITIONS "FOLLY_XLOG_STRIP_PREFIXES=\"${CMAKE_SOURCE_DIR}:${CMAKE_BINARY_DIR}\"" ) # Collect headers for installation (still needed for install step) auto_sources(hfiles "*.h" "RECURSE" "${FOLLY_DIR}") REMOVE_MATCHES_FROM_LISTS(hfiles MATCHES "^${FOLLY_DIR_REGEX_ESCAPED}/build/" "^${FOLLY_DIR_REGEX_ESCAPED}/docs/examples/" "^${FOLLY_DIR_REGEX_ESCAPED}/logging/example/" "^${FOLLY_DIR_REGEX_ESCAPED}/(.*/)?test/" "^${FOLLY_DIR_REGEX_ESCAPED}/(.*/)?tool/" "^${FOLLY_DIR_REGEX_ESCAPED}/facebook/" "^${FOLLY_DIR_REGEX_ESCAPED}/rust/" "^${FOLLY_DIR_REGEX_ESCAPED}/ext/buck2/" ) # Add test utility headers that are part of public API list(APPEND hfiles ${FOLLY_DIR}/container/test/F14TestUtil.h ${FOLLY_DIR}/container/test/TrackingTypes.h ${FOLLY_DIR}/io/async/test/AsyncSSLSocketTest.h ${FOLLY_DIR}/io/async/test/AsyncSocketTest.h ${FOLLY_DIR}/io/async/test/AsyncSocketTest2.h ${FOLLY_DIR}/io/async/test/BlockingSocket.h ${FOLLY_DIR}/io/async/test/CallbackStateEnum.h ${FOLLY_DIR}/io/async/test/ConnCallback.h ${FOLLY_DIR}/io/async/test/MockAsyncSocket.h ${FOLLY_DIR}/io/async/test/MockAsyncServerSocket.h ${FOLLY_DIR}/io/async/test/MockAsyncSSLSocket.h ${FOLLY_DIR}/io/async/test/MockAsyncTransport.h ${FOLLY_DIR}/io/async/test/MockAsyncUDPSocket.h ${FOLLY_DIR}/io/async/test/MockTimeoutManager.h ${FOLLY_DIR}/io/async/test/ScopedBoundPort.h ${FOLLY_DIR}/io/async/test/SocketPair.h ${FOLLY_DIR}/io/async/test/TFOUtil.h ${FOLLY_DIR}/io/async/test/TestSSLServer.h ${FOLLY_DIR}/io/async/test/TimeUtil.h ${FOLLY_DIR}/io/async/test/UndelayedDestruction.h ${FOLLY_DIR}/io/async/test/Util.h ${FOLLY_DIR}/synchronization/test/Semaphore.h ${FOLLY_DIR}/test/DeterministicSchedule.h ${FOLLY_DIR}/test/TestUtils.h ) # Generate pkg-config variables from folly_deps before we add our own # build/install-time include directory generator expressions include(GenPkgConfig) gen_pkgconfig_vars(FOLLY_PKGCONFIG folly_deps) target_include_directories(folly_deps BEFORE INTERFACE $ $ ) target_include_directories(folly_deps INTERFACE $ ) # ============================================================================= # Build granular libraries from subdirectory CMakeLists.txt files # Sources are compiled ONCE via OBJECT libraries, then reused for both # granular .a files and the monolithic libfolly.a # ============================================================================= # Check for architecture-specific compiler flags needed by subdirectories include(CheckCXXCompilerFlag) check_cxx_compiler_flag(-mpclmul COMPILER_HAS_M_PCLMUL) # GCC coroutines support if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") check_cxx_compiler_flag(-fcoroutines COMPILER_HAS_F_COROUTINES) if (COMPILER_HAS_F_COROUTINES) message(STATUS "GCC has C++ coroutines support, enabling for Folly.") add_compile_options($<$:-fcoroutines>) else() message(STATUS "GCC lacks C++ coroutines, disabling Folly coroutines.") endif() endif() add_subdirectory(folly) # Create monolithic folly library from all component OBJECT libraries folly_create_monolithic_library() # Resolve deferred dependencies (must be after monolithic library is created) folly_resolve_deferred_dependencies() # Define FOLLY_CERTS_DIR for the granular test_util target # This points to the SSL test certificates used by TestSSLServer and related tests if(TARGET folly_testing_test_util) target_compile_definitions(folly_testing_test_util INTERFACE FOLLY_CERTS_DIR="${FOLLY_DIR}/io/async/test/certs" ) endif() set(FOLLY_INSTALL_TARGETS folly folly_deps) # Test utilities exported for use by downstream projects add_library(folly_test_util ${FOLLY_DIR}/test/DeterministicSchedule.cpp ${FOLLY_DIR}/json/JsonTestUtil.cpp ) target_compile_definitions(folly_test_util PUBLIC FOLLY_CERTS_DIR="${FOLLY_DIR}/io/async/test/certs" ) set_property(TARGET folly_test_util PROPERTY VERSION ${PACKAGE_VERSION}) target_link_libraries(folly_test_util PUBLIC folly ${LIBGMOCK_LIBRARIES} ) apply_folly_compile_options_to_target(folly_test_util) list(APPEND FOLLY_INSTALL_TARGETS folly_test_util) install(TARGETS ${FOLLY_INSTALL_TARGETS} EXPORT folly RUNTIME DESTINATION bin LIBRARY DESTINATION ${LIB_INSTALL_DIR} ARCHIVE DESTINATION ${LIB_INSTALL_DIR}) auto_install_files(folly ${FOLLY_DIR} ${hfiles} ) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/folly/folly-config.h DESTINATION ${INCLUDE_INSTALL_DIR}/folly COMPONENT dev ) # Generate the folly-config.cmake file for installation so that # downstream projects that use on folly can easily depend on it in their CMake # files using "find_package(folly CONFIG)" include(CMakePackageConfigHelpers) configure_package_config_file( CMake/folly-config.cmake.in folly-config.cmake INSTALL_DESTINATION ${CMAKE_INSTALL_DIR} PATH_VARS INCLUDE_INSTALL_DIR CMAKE_INSTALL_DIR ) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/folly-config.cmake DESTINATION ${CMAKE_INSTALL_DIR} COMPONENT dev ) install( EXPORT folly DESTINATION ${CMAKE_INSTALL_DIR} NAMESPACE Folly:: FILE folly-targets.cmake COMPONENT dev ) # Generate a pkg-config file so that downstream projects that don't use # CMake can depend on folly using pkg-config. configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/CMake/libfolly.pc.in ${CMAKE_CURRENT_BINARY_DIR}/libfolly.pc.gen @ONLY ) # Specify target to allow resolving generator expressions requiring # a target for CMake >=3.19. See #1414. # VERSION_GREATER_EQUAL isn't available before CMake 3.7. if(NOT CMAKE_VERSION VERSION_LESS 3.19) set(target_arg TARGET folly_deps) endif() file( GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/libfolly.pc INPUT ${CMAKE_CURRENT_BINARY_DIR}/libfolly.pc.gen ${target_arg} ) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/libfolly.pc DESTINATION ${LIB_INSTALL_DIR}/pkgconfig COMPONENT dev ) option(BUILD_TESTS "If enabled, compile the tests." OFF) option(BUILD_BENCHMARKS "If enabled, compile the benchmarks." OFF) option(BUILD_BROKEN_TESTS "If enabled, compile tests that are known to be broken." OFF) option(BUILD_HANGING_TESTS "If enabled, compile tests that are known to hang." OFF) option(BUILD_SLOW_TESTS "If enabled, compile tests that take a while to run in debug mode." OFF) if (BUILD_TESTS OR BUILD_BENCHMARKS) option(USE_CMAKE_GOOGLE_TEST_INTEGRATION "If enabled, use the google test integration included in CMake." ON) find_package(GMock MODULE REQUIRED) find_package(GTest MODULE REQUIRED) if (USE_CMAKE_GOOGLE_TEST_INTEGRATION) include(GoogleTest OPTIONAL RESULT_VARIABLE HAVE_CMAKE_GTEST) enable_testing() else() set(HAVE_CMAKE_GTEST OFF) endif() # The ThreadLocalTest code uses a helper shared library for one of its tests. # This can only be built if folly itself was built as a shared library. if (BUILD_SHARED_LIBS) add_library(thread_local_test_lib MODULE ${FOLLY_DIR}/test/ThreadLocalTestLib.cpp ) set_target_properties(thread_local_test_lib PROPERTIES PREFIX "") apply_folly_compile_options_to_target(thread_local_test_lib) target_link_libraries(thread_local_test_lib PUBLIC folly) target_include_directories( thread_local_test_lib PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) endif() add_library(folly_test_support ${FOLLY_DIR}/test/common/TestMain.cpp ${FOLLY_DIR}/test/FBVectorTestUtil.cpp ${FOLLY_DIR}/test/DeterministicSchedule.cpp ${FOLLY_DIR}/test/DeterministicSchedule.h ${FOLLY_DIR}/test/SingletonTestStructs.cpp ${FOLLY_DIR}/test/SocketAddressTestHelper.cpp ${FOLLY_DIR}/test/SocketAddressTestHelper.h ${FOLLY_DIR}/compression/test/CodingTestUtils.cpp ${FOLLY_DIR}/container/test/F14TestUtil.h ${FOLLY_DIR}/container/test/TrackingTypes.h ${FOLLY_DIR}/futures/test/TestExecutor.cpp ${FOLLY_DIR}/futures/test/TestExecutor.h ${FOLLY_DIR}/io/async/test/BlockingSocket.h ${FOLLY_DIR}/io/async/test/CallbackStateEnum.h ${FOLLY_DIR}/io/async/test/ConnCallback.h ${FOLLY_DIR}/io/async/test/MockAsyncServerSocket.h ${FOLLY_DIR}/io/async/test/MockAsyncSocket.h ${FOLLY_DIR}/io/async/test/MockAsyncSSLSocket.h ${FOLLY_DIR}/io/async/test/MockAsyncTransport.h ${FOLLY_DIR}/io/async/test/MockAsyncUDPSocket.h ${FOLLY_DIR}/io/async/test/MockTimeoutManager.h ${FOLLY_DIR}/io/async/test/ScopedBoundPort.cpp ${FOLLY_DIR}/io/async/test/ScopedBoundPort.h ${FOLLY_DIR}/io/async/test/SocketPair.cpp ${FOLLY_DIR}/io/async/test/SocketPair.h ${FOLLY_DIR}/io/async/test/SSLUtil.cpp ${FOLLY_DIR}/io/async/test/SSLUtil.h ${FOLLY_DIR}/io/async/test/TFOUtil.cpp ${FOLLY_DIR}/io/async/test/TFOUtil.h ${FOLLY_DIR}/io/async/test/TestSSLServer.cpp ${FOLLY_DIR}/io/async/test/TestSSLServer.h ${FOLLY_DIR}/io/async/test/TimeUtil.cpp ${FOLLY_DIR}/io/async/test/TimeUtil.h ${FOLLY_DIR}/io/async/test/UndelayedDestruction.h ${FOLLY_DIR}/io/async/test/Util.h ${FOLLY_DIR}/logging/test/ConfigHelpers.cpp ${FOLLY_DIR}/logging/test/ConfigHelpers.h ${FOLLY_DIR}/logging/test/TestLogHandler.cpp ${FOLLY_DIR}/logging/test/TestLogHandler.h ) target_compile_definitions(folly_test_support PUBLIC ${LIBGMOCK_DEFINES} ) target_include_directories(folly_test_support SYSTEM PUBLIC ${LIBGMOCK_INCLUDE_DIR} ${GTEST_INCLUDE_DIRS} ) target_link_libraries(folly_test_support PUBLIC follybenchmark folly ${LIBGMOCK_LIBRARIES} glog::glog ) apply_folly_compile_options_to_target(folly_test_support) folly_define_tests( DIRECTORY algorithm/simd/detail/test/ TEST algorithm_simd_detail_simd_any_of_test SOURCES SimdAnyOfTest.cpp TEST algorithm_simd_detail_simd_for_each_test SOURCES SimdForEachTest.cpp TEST algorithm_simd_detail_simd_traits_test SOURCES TraitsTest.cpp TEST algorithm_simd_detail_unroll_utils_test SOURCES UnrollUtilsTest.cpp DIRECTORY algorithm/simd/test/ TEST algorithm_simd_contains_test SOURCES ContainsTest.cpp TEST algorithm_simd_find_first_of_test SOURCES find_first_of_test.cpp TEST algorithm_simd_find_fixed_test SOURCES FindFixedTest.cpp TEST algorithm_simd_movemask_test SOURCES MovemaskTest.cpp DIRECTORY chrono/test/ TEST chrono_conv_test WINDOWS_DISABLED SOURCES ConvTest.cpp DIRECTORY cli/test/ TEST cli_args_test SOURCES ArgsTest.cpp DIRECTORY codec/test/ TEST codec_hex_test SOURCES hex_test.cpp TEST codec_uuid_test SOURCES UuidTest.cpp DIRECTORY compression/test/ TEST compression_compression_test SLOW SOURCES CompressionTest.cpp TEST compression_quotient_multiset_test SOURCES QuotientMultiSetTest.cpp TEST compression_select64_test SOURCES Select64Test.cpp DIRECTORY compression/elias_fano/test/ TEST compression_alias_fano_bit_vector_coding_test SOURCES BitVectorCodingTest.cpp TEST compression_alias_fano_elias_fano_test SOURCES EliasFanoCodingTest.cpp DIRECTORY container/test/ TEST container_access_test SOURCES AccessTest.cpp TEST container_array_test SOURCES ArrayTest.cpp BENCHMARK container_bit_iterator_bench SOURCES BitIteratorBench.cpp TEST container_bit_iterator_test SOURCES BitIteratorTest.cpp TEST container_enumerate_test SOURCES EnumerateTest.cpp BENCHMARK container_evicting_cache_map_bench SOURCES EvictingCacheMapBench.cpp TEST container_evicting_cache_map_test SOURCES EvictingCacheMapTest.cpp TEST container_f14_fwd_test SOURCES F14FwdTest.cpp TEST container_f14_map_test SOURCES F14MapTest.cpp TEST container_f14_set_test SOURCES F14SetTest.cpp BENCHMARK container_fbvector_benchmark SOURCES FBVectorBenchmark.cpp HEADERS FBVectorBenchmarks.cpp.h TEST container_fbvector_test SOURCES FBVectorTest.cpp BENCHMARK container_foreach_benchmark SOURCES ForeachBenchmark.cpp TEST container_foreach_test SOURCES ForeachTest.cpp BENCHMARK container_hash_maps_bench SOURCES HashMapsBench.cpp TEST container_heap_vector_types_test SOURCES heap_vector_types_test.cpp TEST container_map_util_test WINDOWS_DISABLED SOURCES MapUtilTest.cpp TEST container_merge_test SOURCES MergeTest.cpp TEST container_regex_match_cache_test SOURCES RegexMatchCacheTest.cpp TEST container_small_vector_test WINDOWS_DISABLED SOURCES small_vector_test.cpp TEST container_sorted_vector_types_test SOURCES sorted_vector_test.cpp TEST container_span_test SOURCES span_test.cpp TEST container_std_bitset_test SOURCES StdBitsetTest.cpp BENCHMARK container_sparse_byte_set_benchmark SOURCES SparseByteSetBenchmark.cpp TEST container_sparse_byte_set_test SOURCES SparseByteSetTest.cpp TEST container_util_test SOURCES UtilTest.cpp DIRECTORY concurrency/container/test/ TEST concurrency_container_lock_free_ring_buffer_test SOURCES LockFreeRingBufferTest.cpp DIRECTORY concurrency/test/ TEST concurrency_atomic_shared_ptr_test SOURCES AtomicSharedPtrTest.cpp TEST concurrency_cache_locality_test WINDOWS_DISABLED SOURCES CacheLocalityTest.cpp TEST concurrency_core_cached_shared_ptr_test SOURCES CoreCachedSharedPtrTest.cpp BENCHMARK concurrency_concurrent_hash_map_bench WINDOWS_DISABLED SOURCES ConcurrentHashMapBench.cpp TEST concurrency_concurrent_hash_map_stress_test SLOW WINDOWS_DISABLED SOURCES ConcurrentHashMapStressTest.cpp TEST concurrency_concurrent_hash_map_test WINDOWS_DISABLED SOURCES ConcurrentHashMapTest.cpp TEST concurrency_dynamic_bounded_queue_test WINDOWS_DISABLED SOURCES DynamicBoundedQueueTest.cpp TEST concurrency_priority_unbounded_queue_set_test SOURCES PriorityUnboundedQueueSetTest.cpp BENCHMARK concurrency_thread_cached_synchronized_bench SOURCES ThreadCachedSynchronizedBench.cpp TEST concurrency_thread_cached_synchronized_test SOURCES ThreadCachedSynchronizedTest.cpp TEST concurrency_unbounded_queue_test SOURCES UnboundedQueueTest.cpp DIRECTORY detail/test/ TEST detail_simple_simd_string_utils_test SOURCES SimpleSimdStringUtilsTest.cpp TEST detail_split_string_simd_test WINDOWS_DISABLED SOURCES SplitStringSimdTest.cpp TEST detail_static_singleton_manager_test SOURCES StaticSingletonManagerTest.cpp TEST detail_type_list_test SOURCES TypeListTest.cpp DIRECTORY detail/base64_detail/test/ TEST detail_base64_detail_test WINDOWS_DISABLED SOURCES Base64AgainstScalarTest.cpp Base64PlatformTest.cpp Base64SpecialCasesTest.cpp DIRECTORY executors/test/ TEST executors_async_helpers_test SOURCES AsyncTest.cpp TEST executors_codel_test WINDOWS_DISABLED SOURCES CodelTest.cpp BENCHMARK executors_edf_thread_pool_executor_benchmark SOURCES EDFThreadPoolExecutorBenchmark.cpp TEST executors_executor_test SOURCES ExecutorTest.cpp TEST executors_fiber_io_executor_test SOURCES FiberIOExecutorTest.cpp # FunctionSchedulerTest has a lot of timing-dependent checks, # and tends to fail on heavily loaded systems. TEST executors_function_scheduler_test BROKEN SOURCES FunctionSchedulerTest.cpp TEST executors_global_executor_test SOURCES GlobalExecutorTest.cpp TEST executors_serial_executor_test SOURCES SerialExecutorTest.cpp # Fails in ThreadPoolExecutorTest.RequestContext:719 data2 != nullptr TEST executors_thread_pool_executor_test BROKEN WINDOWS_DISABLED SOURCES ThreadPoolExecutorTest.cpp TEST executors_threaded_executor_test SOURCES ThreadedExecutorTest.cpp TEST executors_timed_drivable_executor_test SOURCES TimedDrivableExecutorTest.cpp DIRECTORY executors/task_queue/test/ TEST executors_task_queue_priority_unbounded_blocking_queue_test SOURCES PriorityUnboundedBlockingQueueTest.cpp BENCHMARK executors_task_queue_unbounded_blocking_queue_bench SOURCES UnboundedBlockingQueueBench.cpp TEST executors_task_queue_unbounded_blocking_queue_test SOURCES UnboundedBlockingQueueTest.cpp #DIRECTORY experimental/test/ #TEST nested_command_line_app_test SOURCES NestedCommandLineAppTest.cpp #TEST program_options_test SOURCES ProgramOptionsTest.cpp # Depends on liburcu #TEST read_mostly_shared_ptr_test SOURCES ReadMostlySharedPtrTest.cpp #TEST ref_count_test SOURCES RefCountTest.cpp #DIRECTORY experimental/io/test/ DIRECTORY external/farmhash/test/ TEST external_farmhash_farmhash_test SOURCES farmhash_test.cpp DIRECTORY logging/test/ TEST logging_async_file_writer_test WINDOWS_DISABLED SOURCES AsyncFileWriterTest.cpp TEST logging_autotimer_test SOURCES AutoTimerTest.cpp TEST logging_config_parser_test SOURCES ConfigParserTest.cpp TEST logging_config_update_test SOURCES ConfigUpdateTest.cpp TEST logging_file_handler_factory_test WINDOWS_DISABLED SOURCES FileHandlerFactoryTest.cpp TEST logging_glog_formatter_test SOURCES GlogFormatterTest.cpp TEST logging_immediate_file_writer_test SOURCES ImmediateFileWriterTest.cpp TEST logging_log_category_test SOURCES LogCategoryTest.cpp TEST logging_logger_db_test SOURCES LoggerDBTest.cpp TEST logging_logger_test WINDOWS_DISABLED SOURCES LoggerTest.cpp TEST logging_log_level_test SOURCES LogLevelTest.cpp TEST logging_log_message_test SOURCES LogMessageTest.cpp TEST logging_log_name_test SOURCES LogNameTest.cpp TEST logging_log_stream_test SOURCES LogStreamTest.cpp TEST logging_rate_limiter_test SOURCES RateLimiterTest.cpp TEST logging_standard_log_handler_test SOURCES StandardLogHandlerTest.cpp TEST logging_xlog_test WINDOWS_DISABLED HEADERS XlogHeader1.h XlogHeader2.h SOURCES XlogFile1.cpp XlogFile2.cpp XlogTest.cpp DIRECTORY fibers/test/ # FiberManager swapWithException fails with segfault BENCHMARK fibers_fibers_benchmark WINDOWS_DISABLED SOURCES FibersBenchmark.cpp TEST fibers_fibers_test BROKEN SOURCES FibersTest.cpp DIRECTORY functional/test/ TEST functional_apply_tuple_test WINDOWS_DISABLED SOURCES ApplyTupleTest.cpp TEST functional_invoke_test WINDOWS_DISABLED SOURCES InvokeTest.cpp TEST functional_partial_test SOURCES PartialTest.cpp TEST functional_protocol_test SOURCES protocol_test.cpp TEST functional_traits_test SOURCES traits_test.cpp DIRECTORY futures/test/ TEST futures_barrier_test SOURCES BarrierTest.cpp TEST futures_callback_lifetime_test SOURCES CallbackLifetimeTest.cpp TEST futures_collect_test SOURCES CollectTest.cpp TEST futures_context_test SOURCES ContextTest.cpp TEST futures_core_test SOURCES CoreTest.cpp TEST futures_ensure_test SOURCES EnsureTest.cpp TEST futures_filter_test SOURCES FilterTest.cpp TEST futures_future_splitter_test SOURCES FutureSplitterTest.cpp BENCHMARK futures_benchmark WINDOWS_DISABLED SOURCES Benchmark.cpp TEST futures_future_test WINDOWS_DISABLED SOURCES FutureTest.cpp TEST futures_header_compile_test SOURCES HeaderCompileTest.cpp TEST futures_interrupt_test SOURCES InterruptTest.cpp TEST futures_map_test SOURCES MapTest.cpp TEST futures_non_copyable_lambda_test SOURCES NonCopyableLambdaTest.cpp TEST futures_poll_test SOURCES PollTest.cpp TEST futures_promise_test SOURCES PromiseTest.cpp TEST futures_reduce_test SOURCES ReduceTest.cpp TEST futures_retrying_test SOURCES RetryingTest.cpp TEST futures_self_destruct_test SOURCES SelfDestructTest.cpp TEST futures_shared_promise_test SOURCES SharedPromiseTest.cpp TEST futures_test_executor_test SOURCES TestExecutorTest.cpp TEST futures_then_compile_test HEADERS ThenCompileTest.h SOURCES ThenCompileTest.cpp TEST futures_then_test SOURCES ThenTest.cpp TEST futures_timekeeper_test WINDOWS_DISABLED SOURCES TimekeeperTest.cpp TEST futures_times_test SOURCES TimesTest.cpp TEST futures_unwrap_test SOURCES UnwrapTest.cpp TEST futures_via_test SOURCES ViaTest.cpp TEST futures_wait_test SOURCES WaitTest.cpp TEST futures_when_test SOURCES WhenTest.cpp TEST futures_while_do_test SOURCES WhileDoTest.cpp TEST futures_will_equal_test SOURCES WillEqualTest.cpp TEST futures_window_test WINDOWS_DISABLED SOURCES WindowTest.cpp DIRECTORY gen/test/ # MSVC bug can't resolve initializer_list constructor properly TEST gen_base_test WINDOWS_DISABLED SOURCES BaseTest.cpp TEST gen_combine_test SOURCES CombineTest.cpp BENCHMARK gen_parallel_map_benchmark SOURCES ParallelMapBenchmark.cpp TEST gen_parallel_map_test SOURCES ParallelMapTest.cpp BENCHMARK gen_parallel_benchmark SOURCES ParallelBenchmark.cpp TEST gen_parallel_test SOURCES ParallelTest.cpp DIRECTORY hash/test/ BENCHMARK hash_checksum_benchmark SOURCES ChecksumBenchmark.cpp # builds, but tests fail on MSVC cmake build TEST hash_checksum_test WINDOWS_DISABLED SOURCES ChecksumTest.cpp TEST hash_farm_hash_test SOURCES FarmHashTest.cpp BENCHMARK hash_hash_benchmark WINDOWS_DISABLED SOURCES HashBenchmark.cpp TEST hash_hash_test WINDOWS_DISABLED SOURCES HashTest.cpp TEST hash_spooky_hash_v1_test SOURCES SpookyHashV1Test.cpp TEST hash_spooky_hash_v2_test SOURCES SpookyHashV2Test.cpp TEST hash_traits_test SOURCES traits_test.cpp TEST hash_unique_hash_key_test SOURCES UniqueHashKeyTest.cpp DIRECTORY io/test/ TEST io_fs_util_test SOURCES FsUtilTest.cpp BENCHMARK io_iobuf_benchmark WINDOWS_DISABLED SOURCES IOBufBenchmark.cpp TEST io_iobuf_test WINDOWS_DISABLED SOURCES IOBufTest.cpp TEST io_iobuf_cursor_test SOURCES IOBufCursorTest.cpp TEST io_iobuf_queue_test SOURCES IOBufQueueTest.cpp TEST io_record_io_test WINDOWS_DISABLED SOURCES RecordIOTest.cpp TEST io_shutdown_socket_set_test HANGING SOURCES ShutdownSocketSetTest.cpp TEST io_socket_option_value_test HANGING SOURCES SocketOptionValueTest.cpp DIRECTORY io/async/test/ # A number of tests in the async_test binary are unfortunately flaky. # When run under Travis CI a number of the tests also hang (it looks # like they do not get expected socket accept events, causing them # to never break out of their event loops). TEST io_async_async_test BROKEN CONTENT_DIR certs/ HEADERS AsyncSocketTest.h AsyncSSLSocketTest.h SOURCES AsyncPipeTest.cpp AsyncSocketExceptionTest.cpp AsyncSocketTest.cpp AsyncSocketTest2.cpp AsyncSSLSocketTest.cpp AsyncSSLSocketTest2.cpp AsyncSSLSocketWriteTest.cpp AsyncTransportTest.cpp # This is disabled because it depends on things that don't exist # on Windows. # TODO: Refactor EventHandlerTest to not use eventfd so it can work on Mac OS X. #TEST io_async_event_handler_test WINDOWS_DISABLED SOURCES EventHandlerTest.cpp TEST io_async_async_timeout_test SOURCES AsyncTimeoutTest.cpp TEST io_async_async_udp_socket_test APPLE_DISABLED WINDOWS_DISABLED SOURCES AsyncUDPSocketTest.cpp TEST io_async_delayed_destruction_test SOURCES DelayedDestructionTest.cpp TEST io_async_delayed_destruction_base_test SOURCES DelayedDestructionBaseTest.cpp TEST io_async_destructor_check_test SOURCES DestructorCheckTest.cpp # Fails with gtest macro error BENCHMARK io_async_event_base_benchmark SOURCES EventBaseBenchmark.cpp TEST io_async_event_base_test BROKEN SOURCES EventBaseTest.cpp TEST io_async_event_base_local_test WINDOWS_DISABLED SOURCES EventBaseLocalTest.cpp TEST io_async_hh_wheel_timer_test SOURCES HHWheelTimerTest.cpp TEST io_async_hh_wheel_timer_slow_tests SLOW SOURCES HHWheelTimerSlowTests.cpp TEST io_async_notification_queue_test WINDOWS_DISABLED SOURCES NotificationQueueTest.cpp BENCHMARK io_async_request_context_benchmark WINDOWS_DISABLED SOURCES RequestContextBenchmark.cpp HEADERS RequestContextHelper.h TEST io_async_request_context_test WINDOWS_DISABLED SOURCES RequestContextTest.cpp TEST io_async_scoped_event_base_thread_test WINDOWS_DISABLED SOURCES ScopedEventBaseThreadTest.cpp TEST io_async_ssl_session_test CONTENT_DIR certs/ SOURCES SSLSessionTest.cpp TEST io_async_write_chain_async_transport_wrapper_test SOURCES WriteChainAsyncTransportWrapperTest.cpp DIRECTORY io/async/ssl/test/ TEST io_async_ssl_ssl_errors_test SOURCES SSLErrorsTest.cpp DIRECTORY lang/test/ TEST lang_align_test SOURCES AlignTest.cpp TEST lang_aligned_test SOURCES AlignedTest.cpp TEST lang_badge_test SOURCES BadgeTest.cpp TEST lang_bits_class_test SOURCES BitsClassTest.cpp TEST lang_bits_test SOURCES BitsTest.cpp TEST lang_c_string_test SOURCES CStringTest.cpp TEST lang_cast_test SOURCES CastTest.cpp TEST lang_checked_math_test SOURCES CheckedMathTest.cpp TEST lang_exception_test SOURCES ExceptionTest.cpp TEST lang_extern_test SOURCES ExternTest.cpp TEST lang_ordering_test SOURCES OrderingTest.cpp TEST lang_pretty_test SOURCES PrettyTest.cpp TEST lang_propagate_const_test SOURCES PropagateConstTest.cpp TEST lang_r_value_reference_wrapper_test WINDOWS_DISABLED SOURCES RValueReferenceWrapperTest.cpp TEST lang_safe_assert_test SOURCES SafeAssertTest.cpp TEST lang_switch_test SOURCES SwitchTest.cpp BENCHMARK lang_to_ascii_benchmark SOURCES ToAsciiBench.cpp TEST lang_to_ascii_test SOURCES ToAsciiTest.cpp TEST lang_type_info_test SOURCES TypeInfoTest.cpp DIRECTORY memory/test/ TEST memory_arena_test WINDOWS_DISABLED SOURCES ArenaTest.cpp TEST memory_reentrant_allocator_test WINDOWS_DISABLED SOURCES ReentrantAllocatorTest.cpp TEST memory_shared_from_this_ptr_test SOURCES shared_from_this_ptr_test.cpp TEST memory_thread_cached_arena_test WINDOWS_DISABLED SOURCES ThreadCachedArenaTest.cpp TEST memory_mallctl_helper_test SOURCES MallctlHelperTest.cpp TEST memory_uninitialized_memory_hacks_test SOURCES UninitializedMemoryHacksTest.cpp DIRECTORY net/detail/test/ TEST net_detail_socket_file_descriptor_map_test SOURCES SocketFileDescriptorMapTest.cpp DIRECTORY portability/test/ TEST portability_constexpr_test SOURCES ConstexprTest.cpp TEST portability_filesystem_test SOURCES FilesystemTest.cpp TEST portability_libgen_test SOURCES LibgenTest.cpp TEST portability_openssl_portability_test SOURCES OpenSSLPortabilityTest.cpp TEST portability_pthread_test SOURCES PThreadTest.cpp TEST portability_time_test WINDOWS_DISABLED SOURCES TimeTest.cpp DIRECTORY ssl/test/ TEST ssl_openssl_hash_test SOURCES OpenSSLHashTest.cpp DIRECTORY stats/test/ TEST stats_buffered_stat_test SOURCES BufferedStatTest.cpp BENCHMARK stats_digest_builder_benchmark SOURCES DigestBuilderBenchmark.cpp TEST stats_digest_builder_test SOURCES DigestBuilderTest.cpp BENCHMARK stats_histogram_benchmark SOURCES HistogramBenchmark.cpp TEST stats_histogram_test SOURCES HistogramTest.cpp BENCHMARK stats_quantile_histogram_benchmark SOURCES QuantileHistogramBenchmark.cpp TEST stats_quantile_estimator_test SOURCES QuantileEstimatorTest.cpp TEST stats_sliding_window_test SOURCES SlidingWindowTest.cpp BENCHMARK stats_tdigest_benchmark SOURCES TDigestBenchmark.cpp TEST stats_tdigest_test SOURCES TDigestTest.cpp TEST stats_timeseries_histogram_test SOURCES TimeseriesHistogramTest.cpp TEST stats_timeseries_test SOURCES TimeSeriesTest.cpp DIRECTORY synchronization/test/ TEST synchronization_atomic_util_test SOURCES AtomicUtilTest.cpp TEST synchronization_atomic_struct_test SOURCES AtomicStructTest.cpp BENCHMARK synchronization_baton_benchmark SOURCES BatonBenchmark.cpp TEST synchronization_baton_test SOURCES BatonTest.cpp TEST synchronization_call_once_test SOURCES CallOnceTest.cpp TEST synchronization_event_count_test SOURCES EventCountTest.cpp BENCHMARK synchronization_hazptr_bench SOURCES HazptrBench.cpp TEST synchronization_hazptr_test SOURCES HazptrTest.cpp BENCHMARK synchronization_lifo_sem_bench WINDOWS_DISABLED SOURCES LifoSemBench.cpp TEST synchronization_lifo_sem_test WINDOWS_DISABLED SOURCES LifoSemTests.cpp TEST synchronization_relaxed_atomic_test WINDOWS_DISABLED SOURCES RelaxedAtomicTest.cpp TEST synchronization_rw_spin_lock_test SOURCES RWSpinLockTest.cpp TEST synchronization_semaphore_test WINDOWS_DISABLED SOURCES SemaphoreTest.cpp BENCHMARK synchronization_small_locks_benchmark SOURCES SmallLocksBenchmark.cpp TEST synchronization_small_locks_test SOURCES SmallLocksTest.cpp DIRECTORY synchronization/detail/test/ TEST synchronization_detail_inline_function_ref_test SOURCES InlineFunctionRefTest.cpp DIRECTORY system/test/ TEST system_at_fork_test WINDOWS_DISABLED SOURCES AtForkTest.cpp TEST system_memory_mapping_test SOURCES MemoryMappingTest.cpp TEST system_shell_test SOURCES ShellTest.cpp #TEST system_subprocess_test SOURCES SubprocessTest.cpp TEST system_thread_id_test SOURCES ThreadIdTest.cpp TEST system_thread_name_test WINDOWS_DISABLED SOURCES ThreadNameTest.cpp DIRECTORY test/ TEST ahm_int_stress_test SOURCES AHMIntStressTest.cpp TEST arena_smartptr_test SOURCES ArenaSmartPtrTest.cpp BENCHMARK ascii_case_insensitive_benchmark SOURCES AsciiCaseInsensitiveBenchmark.cpp TEST ascii_check_test SOURCES AsciiCaseInsensitiveTest.cpp TEST atomic_hash_array_test SOURCES AtomicHashArrayTest.cpp TEST atomic_hash_map_test HANGING SOURCES AtomicHashMapTest.cpp TEST atomic_linked_list_test SOURCES AtomicLinkedListTest.cpp TEST atomic_unordered_map_test SOURCES AtomicUnorderedMapTest.cpp TEST base64_test SOURCES base64_test.cpp TEST buffered_atomic_test SOURCES BufferedAtomicTest.cpp TEST cancellation_token_test SOURCES CancellationTokenTest.cpp TEST chrono_test SOURCES ChronoTest.cpp TEST clock_gettime_wrappers_test SOURCES ClockGettimeWrappersTest.cpp TEST concurrent_bit_set_test SOURCES ConcurrentBitSetTest.cpp BENCHMARK concurrent_skip_list_benchmark SOURCES ConcurrentSkipListBenchmark.cpp TEST concurrent_skip_list_test SOURCES ConcurrentSkipListTest.cpp # Builds but fails test on constexpr_exp_floating std::exp(471.L) TEST constexpr_math_test WINDOWS_DISABLED SOURCES ConstexprMathTest.cpp TEST conv_test SOURCES ConvTest.cpp TEST cpu_id_test SOURCES CpuIdTest.cpp TEST demangle_test SOURCES DemangleTest.cpp TEST deterministic_schedule_test SOURCES DeterministicScheduleTest.cpp TEST discriminated_ptr_test SOURCES DiscriminatedPtrTest.cpp TEST endian_test SOURCES EndianTest.cpp TEST exception_string_test BROKEN SOURCES ExceptionStringTest.cpp TEST exception_test SOURCES ExceptionTest.cpp BENCHMARK exception_wrapper_benchmark WINDOWS_DISABLED SOURCES ExceptionWrapperBenchmark.cpp TEST exception_wrapper_test WINDOWS_DISABLED SOURCES ExceptionWrapperTest.cpp TEST expected_coroutines_test SOURCES ExpectedCoroutinesTest.cpp TEST expected_test WINDOWS_DISABLED SOURCES ExpectedTest.cpp BENCHMARK fbstring_benchmark WINDOWS_DISABLED SOURCES FBStringBenchmark.cpp HEADERS FBStringTestBenchmarks.cpp.h TEST fbstring_test WINDOWS_DISABLED SOURCES FBStringTest.cpp TEST file_test SOURCES FileTest.cpp # Open-source linux build can't handle running this. #TEST file_lock_test SOURCES FileLockTest.cpp TEST file_util_test SOURCES FileUtilTest.cpp BENCHMARK fingerprint_benchmark SOURCES FingerprintBenchmark.cpp TEST fingerprint_test SOURCES FingerprintTest.cpp TEST fixed_string_test SOURCES FixedStringTest.cpp TEST fmt_utility_test SOURCES FmtUtilityTest.cpp TEST format_other_test SOURCES FormatOtherTest.cpp BENCHMARK format_benchmark SOURCES FormatBenchmark.cpp TEST format_test SOURCES FormatTest.cpp TEST function_test BROKEN SOURCES FunctionTest.cpp BENCHMARK function_ref_benchmark SOURCES FunctionRefBenchmark.cpp TEST function_ref_test SOURCES FunctionRefTest.cpp TEST futex_test SOURCES FutexTest.cpp TEST glog_test SOURCES GLogTest.cpp TEST group_varint_test SOURCES GroupVarintTest.cpp TEST group_varint_test_ssse3 SOURCES GroupVarintTest.cpp TEST indestructible_test SOURCES IndestructibleTest.cpp TEST indexed_mem_pool_test BROKEN SOURCES IndexedMemPoolTest.cpp TEST iterators_test SOURCES IteratorsTest.cpp TEST lazy_test SOURCES LazyTest.cpp TEST locks_test SOURCES SpinLockTest.cpp TEST math_test SOURCES MathTest.cpp BENCHMARK memcpy_benchmark WINDOWS_DISABLED SOURCES MemcpyBenchmark.cpp TEST memcpy_test SOURCES MemcpyTest.cpp TEST memory_idler_test SOURCES MemoryIdlerTest.cpp TEST memory_test WINDOWS_DISABLED SOURCES MemoryTest.cpp BENCHMARK memset_benchmark WINDOWS_DISABLED SOURCES MemsetBenchmark.cpp TEST move_wrapper_test SOURCES MoveWrapperTest.cpp TEST mpmc_pipeline_test SOURCES MPMCPipelineTest.cpp TEST mpmc_queue_test SLOW SOURCES MPMCQueueTest.cpp TEST network_address_test HANGING SOURCES IPAddressTest.cpp MacAddressTest.cpp SocketAddressTest.cpp TEST optional_coroutines_test SOURCES OptionalCoroutinesTest.cpp TEST optional_test SOURCES OptionalTest.cpp TEST packed_sync_ptr_test HANGING SOURCES PackedSyncPtrTest.cpp TEST padded_test SOURCES PaddedTest.cpp #TEST poly_test SOURCES PolyTest.cpp TEST portability_test SOURCES PortabilityTest.cpp # Turns out this benchmark uses Linux only API for setting CPU affinity i.e. cpu_set_t. BENCHMARK producer_consumer_queue_benchmark APPLE_DISABLED WINDOWS_DISABLED SOURCES ProducerConsumerQueueBenchmark.cpp TEST producer_consumer_queue_test SLOW SOURCES ProducerConsumerQueueTest.cpp BENCHMARK range_find_benchmark SOURCES RangeFindBenchmark.cpp BENCHMARK random_benchmark SOURCES RandomBenchmark.cpp TEST random_test SOURCES RandomTest.cpp TEST range_test SOURCES RangeTest.cpp TEST replaceable_test WINDOWS_DISABLED SOURCES ReplaceableTest.cpp TEST scope_guard_test WINDOWS_DISABLED SOURCES ScopeGuardTest.cpp # Heavily dependent on drand and srand48 #TEST shared_mutex_test SOURCES SharedMutexTest.cpp # SingletonTest requires Subprocess #TEST singleton_test SOURCES SingletonTest.cpp TEST singleton_double_registration_test BROKEN SOURCES SingletonDoubleRegistration.cpp TEST singleton_test_global SOURCES SingletonTestGlobal.cpp TEST singleton_thread_local_test BROKEN SOURCES SingletonThreadLocalTest.cpp SingletonThreadLocalTestOverload.cpp TEST spin_lock_test SOURCES SpinLockTest.cpp BENCHMARK string_benchmark WINDOWS_DISABLED SOURCES StringBenchmark.cpp TEST string_test WINDOWS_DISABLED SOURCES StringTest.cpp BENCHMARK string_to_float_benchmark SOURCES StringToFloatBenchmark.cpp BENCHMARK synchronized_benchmark WINDOWS_DISABLED SOURCES SynchronizedBenchmark.cpp TEST synchronized_test WINDOWS_DISABLED SOURCES SynchronizedTest.cpp TEST thread_cached_int_test WINDOWS_DISABLED SOURCES ThreadCachedIntTest.cpp TEST thread_local_test WINDOWS_DISABLED SOURCES ThreadLocalTest.cpp TEST timeout_queue_test SOURCES TimeoutQueueTest.cpp TEST token_bucket_test SOURCES TokenBucketTest.cpp TEST traits_test SOURCES TraitsTest.cpp TEST try_test WINDOWS_DISABLED SOURCES TryTest.cpp TEST unicode_test SOURCES UnicodeTest.cpp TEST unit_test SOURCES UnitTest.cpp BENCHMARK uri_benchmark SOURCES UriBenchmark.cpp TEST uri_test SOURCES UriTest.cpp TEST utf8_string_test SOURCES UTF8StringTest.cpp TEST utility_test SOURCES UtilityTest.cpp TEST varint_test SOURCES VarintTest.cpp DIRECTORY test/function_benchmark/ BENCHMARK function_benchmark SOURCES main.cpp benchmark_impl.cpp test_functions.cpp DIRECTORY testing/test/ TEST testing_test_util_test SOURCES TestUtilTest.cpp DIRECTORY tracing/test/ TEST static_tracepoint_section_test SOURCES StaticTracepointSectionTest.cpp DIRECTORY json/test/ TEST json_dynamic_converter_test SOURCES DynamicConverterTest.cpp TEST json_dynamic_other_test SOURCES DynamicOtherTest.cpp TEST json_dynamic_parser_test SOURCES DynamicParserTest.cpp TEST json_dynamic_test SOURCES DynamicTest.cpp # MSVC Preprocessor stringizing raw string literals bug TEST json_json_test WINDOWS_DISABLED SOURCES JsonTest.cpp BENCHMARK json_json_benchmark SOURCES JsonBenchmark.cpp TEST json_json_other_test SOURCES JsonOtherTest.cpp TEST json_json_patch_test SOURCES json_patch_test.cpp TEST json_json_pointer_test SOURCES json_pointer_test.cpp TEST json_json_schema_test SOURCES JSONSchemaTest.cpp ) if (${LIBSODIUM_FOUND}) folly_define_tests( DIRECTORY crypto/test/ TEST crypto_blake2xb_test SOURCES Blake2xbTest.cpp BENCHMARK crypto_lt_hash_benchmark SOURCES LtHashBenchmark.cpp TEST crypto_lt_hash_test SOURCES LtHashTest.cpp ) endif() if (${LIBAIO_FOUND}) folly_define_tests( DIRECTORY io/async/test/ TEST io_async_async_io_test SOURCES AsyncIOTest.cpp AsyncBaseTestLib.cpp IoTestTempFileUtil.cpp ) endif() get_target_property(pic folly POSITION_INDEPENDENT_CODE) if (pic) add_library(singleton_thread_local_overload SHARED ${FOLLY_DIR}/test/SingletonThreadLocalTestOverload.cpp) apply_folly_compile_options_to_target(singleton_thread_local_overload) set_target_properties(singleton_thread_local_overload PROPERTIES PREFIX "" LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/folly/test ) target_link_libraries(singleton_thread_local_overload PRIVATE folly) folly_define_tests( DIRECTORY test/ TEST singleton_thread_local_test SOURCES SingletonThreadLocalTest.cpp ) endif() endif() ================================================ FILE: CMakeListsForBuck2.txt ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # # 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. cmake_minimum_required(VERSION 3.5 FATAL_ERROR) # We use the GoogleTest module if it is available (only in CMake 3.9+) # It requires CMP0054 and CMP0057 to be enabled. if (POLICY CMP0054) cmake_policy(SET CMP0054 NEW) endif() if (POLICY CMP0057) cmake_policy(SET CMP0057 NEW) endif() # CMP0075 Include file check macros honor CMAKE_REQUIRED_LIBRARIES if(POLICY CMP0075) cmake_policy(SET CMP0075 NEW) endif() # includes set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake" # for in-fbsource builds "${CMAKE_CURRENT_SOURCE_DIR}/../opensource/fbcode_builder/CMake" # For shipit-transformed builds "${CMAKE_CURRENT_SOURCE_DIR}/build/fbcode_builder/CMake" ${CMAKE_MODULE_PATH}) # package information set(PACKAGE_NAME "folly") if (NOT DEFINED PACKAGE_VERSION) set(PACKAGE_VERSION "0.58.0-dev") endif() set(PACKAGE_STRING "${PACKAGE_NAME} ${PACKAGE_VERSION}") set(PACKAGE_TARNAME "${PACKAGE_NAME}-${PACKAGE_VERSION}") set(PACKAGE_BUGREPORT "https://github.com/facebook/folly/issues") # 150+ tests in the root folder anyone? No? I didn't think so. set_property(GLOBAL PROPERTY USE_FOLDERS ON) project(${PACKAGE_NAME} CXX C ASM) set(INCLUDE_INSTALL_DIR include CACHE STRING "The subdirectory where header files should be installed") set(LIB_INSTALL_DIR lib CACHE STRING "The subdirectory where libraries should be installed") set(BIN_INSTALL_DIR bin CACHE STRING "The subdirectory where binaries should be installed") set(CMAKE_INSTALL_DIR lib/cmake/folly CACHE STRING "The subdirectory where CMake package config files should be installed") option(BUILD_SHARED_LIBS "If enabled, build folly as a shared library. \ This is generally discouraged, since folly does not commit to having \ a stable ABI." OFF ) # Mark BUILD_SHARED_LIBS as an "advanced" option, since enabling it # is generally discouraged. mark_as_advanced(BUILD_SHARED_LIBS) set(FOLLY_SUPPORT_SHARED_LIBRARY "${BUILD_SHARED_LIBS}") include(FBBuildOptions) fb_activate_static_library_option() if(NOT CMAKE_CXX_STANDARD) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) message(STATUS "setting C++ standard to C++${CMAKE_CXX_STANDARD}") endif() if(NOT DEFINED IS_X86_64_ARCH AND ${CMAKE_SYSTEM_PROCESSOR} MATCHES "x86_64|AMD64") set(IS_X86_64_ARCH TRUE) else() set(IS_X86_64_ARCH FALSE) endif() if(CMAKE_SYSTEM_NAME STREQUAL "Windows") # Check target architecture if (NOT CMAKE_SIZEOF_VOID_P EQUAL 8) message(FATAL_ERROR "Folly requires a 64bit target architecture.") endif() if (MSVC_VERSION LESS 1900) message( FATAL_ERROR "This build script only supports building Folly on 64-bit Windows with " "at least Visual Studio 2017. " "MSVC version '${MSVC_VERSION}' is not supported." ) endif() endif() set(TOP_DIR "${CMAKE_CURRENT_SOURCE_DIR}") set(FOLLY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/folly") set( FOLLY_DIR_PREFIXES "${CMAKE_CURRENT_SOURCE_DIR}:${CMAKE_CURRENT_BINARY_DIR}" ) include(GNUInstallDirs) set(CMAKE_THREAD_PREFER_PTHREAD ON) set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) set(FOLLY_HAVE_PTHREAD "${CMAKE_USE_PTHREADS_INIT}") list(APPEND CMAKE_REQUIRED_LIBRARIES Threads::Threads) list(APPEND FOLLY_LINK_LIBRARIES Threads::Threads) if(MSVC) include(FollyCompilerMSVC) else() include(FollyCompilerUnix) endif() include(FollyFunctions) include(folly-deps) # Find the required packages include(FollyConfigChecks) configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/CMake/folly-config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/folly/folly-config.h ) ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies within all project spaces, and it also applies when an individual is representing the project or its community in public spaces. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at . All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to Folly We want to make contributing to this project as easy and transparent as possible. ## Code of Conduct The code of conduct is described in [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md). ## Pull Requests We actively welcome your pull requests. 1. Fork the repo and create your branch from `master`. 2. If you've added code that should be tested, add tests. 3. If you've changed APIs, update the documentation. 4. Ensure the test suite passes. 5. If you haven't already, complete the Contributor License Agreement ("CLA"). ## Contributor License Agreement ("CLA") In order to accept your pull request, we need you to submit a CLA. You only need to do this once to work on any of Facebook's open source projects. Complete your CLA here: ## Issues We use GitHub issues to track public bugs. Please ensure your description is clear and has sufficient instructions to be able to reproduce the issue. Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe disclosure of security bugs. In those cases, please go through the process outlined on that page and do not file a public issue. ## License By contributing to folly, you agree that your contributions will be licensed under the LICENSE file in the root directory of this source tree. ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS Files in folly/external/farmhash licensed as follows Copyright (c) 2014 Google, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: PACKAGE ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # # 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. load("@shim//:cfg.bzl", "SHIM_ALIASES", "set_cfg_constructor", "get_shim_modifiers") load("@prelude//cfg/modifier:set_cfg_modifiers.bzl", "set_cfg_modifiers") # Activate cfg modifiers from CLI / PACKAGE / targets set_cfg_constructor(SHIM_ALIASES) modifiers = get_shim_modifiers() set_cfg_modifiers(modifiers) ================================================ FILE: README.md ================================================ Folly: Facebook Open-source Library =================================== # What is `folly`? Logo Folly Folly (an acronym loosely after Facebook Open Source Library) is a library of C++20 components designed with practicality and efficiency in mind. **Folly contains a variety of core library components used extensively at Facebook**. In particular, it's often a dependency of Facebook's other open source C++ efforts and place where those projects can share code. It complements (as opposed to competing against) offerings such as Boost and of course `std`. In fact, we embark on defining our own component only when something we need is either not available, or does not meet the needed performance profile. We endeavor to remove things from folly if or when `std` or Boost obsoletes them. Performance concerns permeate much of Folly, sometimes leading to designs that are more idiosyncratic than they would otherwise be (see e.g. `PackedSyncPtr.h`, `SmallLocks.h`). Good performance at large scale is a unifying theme in all of Folly. ## Check it out in the intro video [![Explain Like I’m 5: Folly](https://img.youtube.com/vi/Wr_IfOICYSs/0.jpg)](https://www.youtube.com/watch?v=Wr_IfOICYSs) # Logical Design Folly is a collection of relatively independent components, some as simple as a few symbols. There is no restriction on internal dependencies, meaning that a given folly module may use any other folly components. All symbols are defined in the top-level namespace `folly`, except of course macros. Macro names are ALL_UPPERCASE and should be prefixed with `FOLLY_`. Namespace `folly` defines other internal namespaces such as `internal` or `detail`. User code should not depend on symbols in those namespaces. # Physical Design At the top level Folly uses the classic "stuttering" scheme `folly/folly` used by Boost and others. The first directory serves as an installation root of the library (with possible versioning a la `folly-1.0/`), and the second is to distinguish the library when including files, e.g. `#include `. The directory structure is flat (mimicking the namespace structure), i.e. we don't have an elaborate directory hierarchy (it is possible this will change in future versions). The subdirectory `experimental` contains files that are used inside folly and possibly at Facebook but not considered stable enough for client use. Your code should not use files in `folly/experimental` lest it may break when you update Folly. The `folly/folly/test` subdirectory includes the unittests for all components, usually named `ComponentXyzTest.cpp` for each `ComponentXyz.*`. The `folly/folly/docs` directory contains documentation. # What's in it? Because of folly's fairly flat structure, the best way to see what's in it is to look at the headers in [top level `folly/` directory](https://github.com/facebook/folly/tree/main/folly). You can also check the [`docs` folder](folly/docs) for documentation, starting with the [overview](folly/docs/Overview.md). Folly is published on GitHub at https://github.com/facebook/folly. # Build Notes Because folly does not provide any ABI compatibility guarantees from commit to commit, we generally recommend building folly as a static library. folly supports gcc (5.1+), clang, or MSVC. It should run on Linux (x86-32, x86-64, and ARM), iOS, macOS, and Windows (x86-64). The CMake build is only tested on some of these platforms; at a minimum, we aim to support macOS and Linux (on the latest Ubuntu LTS release or newer.) ## `getdeps.py` This script is used by many of Meta's OSS tools. It will download and build all of the necessary dependencies first, and will then invoke cmake etc to build folly. This will help ensure that you build with relevant versions of all of the dependent libraries, taking into account what versions are installed locally on your system. It's written in python so you'll need python3.6 or later on your PATH. It works on Linux, macOS and Windows. The settings for folly's cmake build are held in its getdeps manifest `build/fbcode_builder/manifests/folly`, which you can edit locally if desired. ### Dependencies If on Linux or MacOS (with homebrew installed) you can install system dependencies to save building them: # Clone the repo git clone https://github.com/facebook/folly # Install dependencies cd folly sudo ./build/fbcode_builder/getdeps.py install-system-deps --recursive If you'd like to see the packages before installing them: ./build/fbcode_builder/getdeps.py install-system-deps --dry-run --recursive On other platforms or if on Linux and without system dependencies `getdeps.py` will mostly download and build them for you during the build step. Some of the dependencies `getdeps.py` uses and installs are: * a version of boost compiled with C++14 support. * googletest is required to build and run folly's tests. ### Build This script will download and build all of the necessary dependencies first, and will then invoke cmake etc to build folly. This will help ensure that you build with relevant versions of all of the dependent libraries, taking into account what versions are installed locally on your system. `getdeps.py` currently requires python 3.6+ to be on your path. `getdeps.py` will invoke cmake etc. # Clone the repo git clone https://github.com/facebook/folly cd folly # Build, using system dependencies if available python3 ./build/fbcode_builder/getdeps.py --allow-system-packages build It puts output in its scratch area: * `installed/folly/lib/libfolly.a`: Library You can also specify a `--scratch-path` argument to control the location of the scratch directory used for the build. You can find the default scratch install location from logs or with `python3 ./build/fbcode_builder/getdeps.py show-inst-dir`. There are also `--install-dir` and `--install-prefix` arguments to provide some more fine-grained control of the installation directories. However, given that folly provides no compatibility guarantees between commits we generally recommend building and installing the libraries to a temporary location, and then pointing your project's build at this temporary location, rather than installing folly in the traditional system installation directories. e.g., if you are building with CMake you can use the `CMAKE_PREFIX_PATH` variable to allow CMake to find folly in this temporary installation directory when building your project. If you want to invoke `cmake` again to iterate, there is a helpful `run_cmake.py` script output in the scratch build directory. You can find the scratch build directory from logs or with `python3 ./build/fbcode_builder/getdeps.py show-build-dir`. ### Run tests By default `getdeps.py` will build the tests for folly. To run them: cd folly python3 ./build/fbcode_builder/getdeps.py --allow-system-packages test ### `build.sh`/`build.bat` wrapper `build.sh` can be used on Linux and MacOS, on Windows use the `build.bat` script instead. Its a wrapper around `getdeps.py`. ## Build with cmake directly If you don't want to let getdeps invoke cmake for you then by default, building the tests is disabled as part of the CMake `all` target. To build the tests, specify `-DBUILD_TESTS=ON` to CMake at configure time. NB if you want to invoke `cmake` again to iterate on a `getdeps.py` build, there is a helpful `run_cmake.py` script output in the scratch-path build directory. You can find the scratch build directory from logs or with `python3 ./build/fbcode_builder/getdeps.py show-build-dir`. Running tests with ctests also works if you cd to the build dir, e.g. `(cd $(python3 ./build/fbcode_builder/getdeps.py show-build-dir) && ctest)` ### Finding dependencies in non-default locations If you have boost, gtest, or other dependencies installed in a non-default location, you can use the `CMAKE_INCLUDE_PATH` and `CMAKE_LIBRARY_PATH` variables to make CMAKE look also look for header files and libraries in non-standard locations. For example, to also search the directories `/alt/include/path1` and `/alt/include/path2` for header files and the directories `/alt/lib/path1` and `/alt/lib/path2` for libraries, you can invoke `cmake` as follows: ``` cmake \ -DCMAKE_INCLUDE_PATH=/alt/include/path1:/alt/include/path2 \ -DCMAKE_LIBRARY_PATH=/alt/lib/path1:/alt/lib/path2 ... ``` ## Ubuntu LTS, CentOS Stream, Fedora Use the `getdeps.py` approach above. We test in CI on Ubuntu LTS, and occasionally on other distros. If you find the set of system packages is not quite right for your chosen distro, you can specify distro version specific overrides in the dependency manifests (e.g. https://github.com/facebook/folly/blob/main/build/fbcode_builder/manifests/boost ). You could probably make it work on most recent Ubuntu/Debian or Fedora/Redhat derived distributions. At time of writing (Dec 2021) there is a build break on GCC 11.x based systems in lang_badge_test. If you don't need badge functionality you can work around by commenting it out from CMakeLists.txt (unfortunately fbthrift does need it) ## Windows (Vcpkg) Note that many tests are disabled for folly Windows builds, you can see them in the log from the cmake configure step, or by looking for WINDOWS_DISABLED in `CMakeLists.txt` That said, `getdeps.py` builds work on Windows and are tested in CI. If you prefer, you can try Vcpkg. folly is available in [Vcpkg](https://github.com/Microsoft/vcpkg#vcpkg) and releases may be built via `vcpkg install folly:x64-windows`. You may also use `vcpkg install folly:x64-windows --head` to build against `main`. ## macOS `getdeps.py` builds work on macOS and are tested in CI, however if you prefer, you can try one of the macOS package managers ### Homebrew folly is available as a Formula and releases may be built via `brew install folly`. You may also use `folly/build/bootstrap-osx-homebrew.sh` to build against `main`: ``` ./folly/build/bootstrap-osx-homebrew.sh ``` This will create a build directory `_build` in the top-level. ### MacPorts Install the required packages from MacPorts: ``` sudo port install \ boost \ cmake \ gflags \ git \ google-glog \ libevent \ libtool \ lz4 \ lzma \ openssl \ snappy \ xz \ zlib ``` Download and install double-conversion: ``` git clone https://github.com/google/double-conversion.git cd double-conversion cmake -DBUILD_SHARED_LIBS=ON . make sudo make install ``` Download and install folly with the parameters listed below: ``` git clone https://github.com/facebook/folly.git cd folly mkdir _build cd _build cmake .. make sudo make install ``` ================================================ FILE: buck2 ================================================ #!/usr/bin/env dotslash { "name": "buck2", "platforms": { "macos-aarch64": { "size": 31815315, "hash": "blake3", "digest": "65a9c2296163b9d0a3e0f6ae36923444c79d5abcc7160e88cdce56d1646e12ca", "format": "zst", "path": "buck2-aarch64-apple-darwin", "providers": [ { "url": "https://github.com/facebook/buck2/releases/download/2026-03-15/buck2-aarch64-apple-darwin.zst" } ] }, "windows-aarch64": { "size": 27939642, "hash": "blake3", "digest": "64bc1eeecdb17aca4cd4cc3e2e878ee5f86d95993c891232add0374bb575304e", "format": "zst", "path": "buck2-aarch64-pc-windows-msvc.exe", "providers": [ { "url": "https://github.com/facebook/buck2/releases/download/2026-03-15/buck2-aarch64-pc-windows-msvc.exe.zst" } ] }, "linux-aarch64": { "size": 34861909, "hash": "blake3", "digest": "c94bc9096171d9c06e478646ce2fc21ab64cf0d9028aa9341de1fec075f54402", "format": "zst", "path": "buck2-aarch64-unknown-linux-musl", "providers": [ { "url": "https://github.com/facebook/buck2/releases/download/2026-03-15/buck2-aarch64-unknown-linux-musl.zst" } ] }, "linux-riscv64": { "size": 37112472, "hash": "blake3", "digest": "0f64fbe707fd43504c353bb633b64e8f6b64884c50ebbde116f197a465d9d04a", "format": "zst", "path": "buck2-riscv64gc-unknown-linux-gnu", "providers": [ { "url": "https://github.com/facebook/buck2/releases/download/2026-03-15/buck2-riscv64gc-unknown-linux-gnu.zst" } ] }, "macos-x86_64": { "size": 34156976, "hash": "blake3", "digest": "a158bd24817ece46ce43e700818704182421ad95a0b2031c2e2a9e6d0fcf29a7", "format": "zst", "path": "buck2-x86_64-apple-darwin", "providers": [ { "url": "https://github.com/facebook/buck2/releases/download/2026-03-15/buck2-x86_64-apple-darwin.zst" } ] }, "windows-x86_64": { "size": 29670925, "hash": "blake3", "digest": "a6522f77c3c9680539df265fc85c8ac98888fa4bb8971e0c43e4b08cc46f3c4a", "format": "zst", "path": "buck2-x86_64-pc-windows-msvc.exe", "providers": [ { "url": "https://github.com/facebook/buck2/releases/download/2026-03-15/buck2-x86_64-pc-windows-msvc.exe.zst" } ] }, "linux-x86_64": { "size": 36453720, "hash": "blake3", "digest": "61bc5de2f2847fef8a4aa80c6d0b29352e1cce667adbe7d07b79c5ced44d7561", "format": "zst", "path": "buck2-x86_64-unknown-linux-musl", "providers": [ { "url": "https://github.com/facebook/buck2/releases/download/2026-03-15/buck2-x86_64-unknown-linux-musl.zst" } ] } } } ================================================ FILE: build/buck2/README.md ================================================ # Easy buck2 builds for Facebook projects This directory contains buck2 targets designed to simplify buck2 builds of Meta open source projects. The most notable target is `//build/buck2/install_deps`, which will attempt to discover and install necessary third party packages from apt / dnf / etc. See the "repos" directory for the currently supported platforms. ## Deployment This directory is copied literally into a number of different Facebook open source repositories. Any change made to code in this directory will be automatically be replicated by our open source tooling into all GitHub hosted repositories that use `buck2`. Typically this directory is copied into the open source repositories as `build/buck2/`. ================================================ FILE: build/buck2/install_deps/BUCK ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # # This source code is licensed under the LICENSE file found in the root # directory of this source tree. load("@fbcode_macros//build_defs:native_rules.bzl", "buck_sh_binary") oncall("open_source") buck_sh_binary( name = "install_deps", main = "install_deps.sh", resources = glob(["repos/*"]), ) ================================================ FILE: build/buck2/install_deps/install_deps.sh ================================================ #!/bin/sh # Copyright (c) Meta Platforms, Inc. and affiliates. # # This source code is licensed under the LICENSE file found in the root # directory of this source tree. if [ -z "$INSTALL_COMMAND" ]; then if [ -f /etc/os-release ]; then . /etc/os-release; fi if command -v brew >/dev/null; then ID="homebrew"; fi if [ -f "$BUCK_DEFAULT_RUNTIME_RESOURCES/repos/$ID" ]; then # shellcheck disable=SC1090 . "$BUCK_DEFAULT_RUNTIME_RESOURCES/repos/$ID"; else echo "Unable to determine platform id / install commands"; return 1; fi fi if [ -z "${BUCK2_COMMAND}" ]; then if command -v buck2 >/dev/null; then BUCK2_COMMAND="buck2" elif command -v dotslash >/dev/null && [ -f ./buck2 ]; then BUCK2_COMMAND="dotslash ./buck2" else echo "Unable to determine buck2 command"; return 1; fi fi __confirm() { echo "Press \"y\" to continue" read -r REPLY expr "X$REPLY" : '^X[Yy]$' >/dev/null } PKG_FILE=$(mktemp /tmp/buck2-install-pkgs.XXXXXX) if ! command -v jq >/dev/null; then echo "Failed to find jq command, attempting to install with" echo echo "$INSTALL_COMMAND" jq echo if __confirm; then eval "$INSTALL_COMMAND jq" else echo "Not confirmed, exiting"; exit 1 fi fi eval "$BUCK2_COMMAND cquery 'kind(system_packages, deps(//...))' \\ --output-attribute=packages --modifier $ID --json 2>/dev/null \\ | jq -r '.[].packages[]' \\ | sort \\ | uniq \\ > $PKG_FILE" echo "About to install the project dependencies with the following command:" echo eval "cat $PKG_FILE | xargs echo $INSTALL_COMMAND" echo if __confirm; then eval "cat $PKG_FILE | xargs -r $INSTALL_COMMAND" else echo "Not installing dependencies" fi rm "$PKG_FILE" ================================================ FILE: build/buck2/install_deps/repos/fedora ================================================ INSTALL_COMMAND="sudo -E dnf install -y" ================================================ FILE: build/buck2/install_deps/repos/homebrew ================================================ INSTALL_COMMAND="brew install" ================================================ FILE: build/buck2/install_deps/repos/ubuntu ================================================ INSTALL_COMMAND="sudo -E apt-get install -y" ================================================ FILE: build/fbcode_builder/.gitignore ================================================ # Facebook-internal CI builds don't have write permission outside of the # source tree, so we install all projects into this directory. /facebook_ci __pycache__/ *.pyc ================================================ FILE: build/fbcode_builder/CMake/FBBuildOptions.cmake ================================================ # Copyright (c) Facebook, Inc. and its affiliates. function (fb_activate_static_library_option) option(USE_STATIC_DEPS_ON_UNIX "If enabled, use static dependencies on unix systems. This is generally discouraged." OFF ) # Mark USE_STATIC_DEPS_ON_UNIX as an "advanced" option, since enabling it # is generally discouraged. mark_as_advanced(USE_STATIC_DEPS_ON_UNIX) if(UNIX AND USE_STATIC_DEPS_ON_UNIX) SET(CMAKE_FIND_LIBRARY_SUFFIXES ".a" PARENT_SCOPE) endif() option(PREFER_STATIC_DEPS_ON_UNIX "If enabled, use static dependencies on unix systems as possible as we can. This is generally discouraged." OFF ) # Mark PREFER_STATIC_DEPS_ON_UNIX as an "advanced" option, since enabling it # is generally discouraged. mark_as_advanced(PREFER_STATIC_DEPS_ON_UNIX) if(UNIX AND PREFER_STATIC_DEPS_ON_UNIX) SET(CMAKE_FIND_LIBRARY_SUFFIXES ".a" ".so" PARENT_SCOPE) endif() endfunction() ================================================ FILE: build/fbcode_builder/CMake/FBCMakeParseArgs.cmake ================================================ # # Copyright (c) Facebook, Inc. and its affiliates. # # Helper function for parsing arguments to a CMake function. # # This function is very similar to CMake's built-in cmake_parse_arguments() # function, with some improvements: # - This function correctly handles empty arguments. (cmake_parse_arguments() # ignores empty arguments.) # - If a multi-value argument is specified more than once, the subsequent # arguments are appended to the original list rather than replacing it. e.g. # if "SOURCES" is a multi-value argument, and the argument list contains # "SOURCES a b c SOURCES x y z" then the resulting value for SOURCES will be # "a;b;c;x;y;z" rather than "x;y;z" # - This function errors out by default on unrecognized arguments. You can # pass in an extra "ALLOW_UNPARSED_ARGS" argument to make it behave like # cmake_parse_arguments(), and return the unparsed arguments in a # _UNPARSED_ARGUMENTS variable instead. # # It does look like cmake_parse_arguments() handled empty arguments correctly # from CMake 3.0 through 3.3, but it seems like this was probably broken when # it was turned into a built-in function in CMake 3.4. Here is discussion and # patches that fixed this behavior prior to CMake 3.0: # https://cmake.org/pipermail/cmake-developers/2013-November/020607.html # # The one downside to this function over the built-in cmake_parse_arguments() # is that I don't think we can achieve the PARSE_ARGV behavior in a non-builtin # function, so we can't properly handle arguments that contain ";". CMake will # treat the ";" characters as list element separators, and treat it as multiple # separate arguments. # function(fb_cmake_parse_args PREFIX OPTIONS ONE_VALUE_ARGS MULTI_VALUE_ARGS ARGS) foreach(option IN LISTS ARGN) if ("${option}" STREQUAL "ALLOW_UNPARSED_ARGS") set(ALLOW_UNPARSED_ARGS TRUE) else() message( FATAL_ERROR "unknown optional argument for fb_cmake_parse_args(): ${option}" ) endif() endforeach() # Define all options as FALSE in the parent scope to start with foreach(var_name IN LISTS OPTIONS) set("${PREFIX}_${var_name}" "FALSE" PARENT_SCOPE) endforeach() # TODO: We aren't extremely strict about error checking for one-value # arguments here. e.g., we don't complain if a one-value argument is # followed by another option/one-value/multi-value name rather than an # argument. We also don't complain if a one-value argument is the last # argument and isn't followed by a value. list(APPEND all_args ${ONE_VALUE_ARGS}) list(APPEND all_args ${MULTI_VALUE_ARGS}) set(current_variable) set(unparsed_args) foreach(arg IN LISTS ARGS) list(FIND OPTIONS "${arg}" opt_index) if("${opt_index}" EQUAL -1) list(FIND all_args "${arg}" arg_index) if("${arg_index}" EQUAL -1) # This argument does not match an argument name, # must be an argument value if("${current_variable}" STREQUAL "") list(APPEND unparsed_args "${arg}") else() # Ugh, CMake lists have a pretty fundamental flaw: they cannot # distinguish between an empty list and a list with a single empty # element. We track our own SEEN_VALUES_arg setting to help # distinguish this and behave properly here. if ("${SEEN_${current_variable}}" AND "${${current_variable}}" STREQUAL "") set("${current_variable}" ";${arg}") else() list(APPEND "${current_variable}" "${arg}") endif() set("SEEN_${current_variable}" TRUE) endif() else() # We found a single- or multi-value argument name set(current_variable "VALUES_${arg}") set("SEEN_${arg}" TRUE) endif() else() # We found an option variable set("${PREFIX}_${arg}" "TRUE" PARENT_SCOPE) set(current_variable) endif() endforeach() foreach(arg_name IN LISTS ONE_VALUE_ARGS) if(NOT "${SEEN_${arg_name}}") unset("${PREFIX}_${arg_name}" PARENT_SCOPE) elseif(NOT "${SEEN_VALUES_${arg_name}}") # If the argument was seen but a value wasn't specified, error out. # We require exactly one value to be specified. message( FATAL_ERROR "argument ${arg_name} was specified without a value" ) else() list(LENGTH "VALUES_${arg_name}" num_args) if("${num_args}" EQUAL 0) # We know an argument was specified and that we called list(APPEND). # If CMake thinks the list is empty that means there is really a single # empty element in the list. set("${PREFIX}_${arg_name}" "" PARENT_SCOPE) elseif("${num_args}" EQUAL 1) list(GET "VALUES_${arg_name}" 0 arg_value) set("${PREFIX}_${arg_name}" "${arg_value}" PARENT_SCOPE) else() message( FATAL_ERROR "too many arguments specified for ${arg_name}: " "${VALUES_${arg_name}}" ) endif() endif() endforeach() foreach(arg_name IN LISTS MULTI_VALUE_ARGS) # If this argument name was never seen, then unset the parent scope if (NOT "${SEEN_${arg_name}}") unset("${PREFIX}_${arg_name}" PARENT_SCOPE) else() # TODO: Our caller still won't be able to distinguish between an empty # list and a list with a single empty element. We can tell which is # which, but CMake lists don't make it easy to show this to our caller. set("${PREFIX}_${arg_name}" "${VALUES_${arg_name}}" PARENT_SCOPE) endif() endforeach() # By default we fatal out on unparsed arguments, but return them to the # caller if ALLOW_UNPARSED_ARGS was specified. if (DEFINED unparsed_args) if ("${ALLOW_UNPARSED_ARGS}") set("${PREFIX}_UNPARSED_ARGUMENTS" "${unparsed_args}" PARENT_SCOPE) else() message(FATAL_ERROR "unrecognized arguments: ${unparsed_args}") endif() endif() endfunction() ================================================ FILE: build/fbcode_builder/CMake/FBCompilerSettings.cmake ================================================ # Copyright (c) Facebook, Inc. and its affiliates. # This file applies common compiler settings that are shared across # a number of Facebook opensource projects. # Please use caution and your best judgement before making changes # to these shared compiler settings in order to avoid accidentally # breaking a build in another project! if (WIN32) include(FBCompilerSettingsMSVC) else() include(FBCompilerSettingsUnix) endif() ================================================ FILE: build/fbcode_builder/CMake/FBCompilerSettingsMSVC.cmake ================================================ # Copyright (c) Facebook, Inc. and its affiliates. # This file applies common compiler settings that are shared across # a number of Facebook opensource projects. # Please use caution and your best judgement before making changes # to these shared compiler settings in order to avoid accidentally # breaking a build in another project! add_compile_options( /wd4250 # 'class1' : inherits 'class2::member' via dominance /Zc:preprocessor # Enable conforming preprocessor for __VA_OPT__ support ) ================================================ FILE: build/fbcode_builder/CMake/FBCompilerSettingsUnix.cmake ================================================ # Copyright (c) Facebook, Inc. and its affiliates. # This file applies common compiler settings that are shared across # a number of Facebook opensource projects. # Please use caution and your best judgement before making changes # to these shared compiler settings in order to avoid accidentally # breaking a build in another project! set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -Wall -Wextra -Wno-deprecated -Wno-deprecated-declarations") ================================================ FILE: build/fbcode_builder/CMake/FBPythonBinary.cmake ================================================ # Copyright (c) Facebook, Inc. and its affiliates. include(FBCMakeParseArgs) # # This file contains helper functions for building self-executing Python # binaries. # # This is somewhat different than typical python installation with # distutils/pip/virtualenv/etc. We primarily want to build a standalone # executable, isolated from other Python packages on the system. We don't want # to install files into the standard library python paths. This is more # similar to PEX (https://github.com/pantsbuild/pex) and XAR # (https://github.com/facebookincubator/xar). (In the future it would be nice # to update this code to also support directly generating XAR files if XAR is # available.) # # We also want to be able to easily define "libraries" of python files that can # be shared and re-used between these standalone python executables, and can be # shared across projects in different repositories. This means that we do need # a way to "install" libraries so that they are visible to CMake builds in # other repositories, without actually installing them in the standard python # library paths. # # If the caller has not already found Python, do so now. # If we fail to find python now we won't fail immediately, but # add_fb_python_executable() or add_fb_python_library() will fatal out if they # are used. if(NOT TARGET Python3::Interpreter) # CMake 3.12+ ships with a FindPython3.cmake module. Try using it first. # We find with QUIET here, since otherwise this generates some noisy warnings # on versions of CMake before 3.12 if (WIN32) # On Windows we need both the Interpreter as well as the Development # libraries. find_package(Python3 COMPONENTS Interpreter Development QUIET) else() find_package(Python3 COMPONENTS Interpreter QUIET) endif() if(Python3_Interpreter_FOUND) message(STATUS "Found Python 3: ${Python3_EXECUTABLE}") else() # Try with the FindPythonInterp.cmake module available in older CMake # versions. Check to see if the caller has already searched for this # themselves first. if(NOT PYTHONINTERP_FOUND) set(Python_ADDITIONAL_VERSIONS 3 3.6 3.5 3.4 3.3 3.2 3.1) find_package(PythonInterp) # TODO: On Windows we require the Python libraries as well. # We currently do not search for them on this code path. # For now we require building with CMake 3.12+ on Windows, so that the # FindPython3 code path above is available. endif() if(PYTHONINTERP_FOUND) if("${PYTHON_VERSION_MAJOR}" GREATER_EQUAL 3) set(Python3_EXECUTABLE "${PYTHON_EXECUTABLE}") add_custom_target(Python3::Interpreter) else() string( CONCAT FBPY_FIND_PYTHON_ERR "found Python ${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}, " "but need Python 3" ) endif() endif() endif() endif() # Find our helper program. # We typically install this in the same directory as this .cmake file. find_program( FB_MAKE_PYTHON_ARCHIVE "make_fbpy_archive.py" PATHS ${CMAKE_MODULE_PATH} ) set(FB_PY_TEST_MAIN "${CMAKE_CURRENT_LIST_DIR}/fb_py_test_main.py") set( FB_PY_TEST_DISCOVER_SCRIPT "${CMAKE_CURRENT_LIST_DIR}/FBPythonTestAddTests.cmake" ) set( FB_PY_WIN_MAIN_C "${CMAKE_CURRENT_LIST_DIR}/fb_py_win_main.c" ) # An option to control the default installation location for # install_fb_python_library(). This is relative to ${CMAKE_INSTALL_PREFIX} set( FBPY_LIB_INSTALL_DIR "lib/fb-py-libs" CACHE STRING "The subdirectory where FB python libraries should be installed" ) # # Build a self-executing python binary. # # This accepts the same arguments as add_fb_python_library(). # # In addition, a MAIN_MODULE argument is accepted. This argument specifies # which module should be started as the __main__ module when the executable is # run. If left unspecified, a __main__.py script must be present in the # manifest. # function(add_fb_python_executable TARGET) fb_py_check_available() # Parse the arguments set(one_value_args BASE_DIR NAMESPACE MAIN_MODULE TYPE) set(multi_value_args SOURCES DEPENDS) fb_cmake_parse_args( ARG "" "${one_value_args}" "${multi_value_args}" "${ARGN}" ) fb_py_process_default_args(ARG_NAMESPACE ARG_BASE_DIR) # Use add_fb_python_library() to perform most of our source handling add_fb_python_library( "${TARGET}.main_lib" BASE_DIR "${ARG_BASE_DIR}" NAMESPACE "${ARG_NAMESPACE}" SOURCES ${ARG_SOURCES} DEPENDS ${ARG_DEPENDS} ) set( manifest_files "$" ) set( source_files "$" ) # The command to build the executable archive. # # If we are using CMake 3.8+ we can use COMMAND_EXPAND_LISTS. # CMP0067 isn't really the policy we care about, but seems like the best way # to check if we are running 3.8+. if (POLICY CMP0067) set(extra_cmd_params COMMAND_EXPAND_LISTS) set(make_py_args "${manifest_files}") else() set(extra_cmd_params) set(make_py_args --manifest-separator "::" "$") endif() set(output_file "${TARGET}${CMAKE_EXECUTABLE_SUFFIX}") if(WIN32) set(zipapp_output "${TARGET}.py_zipapp") else() set(zipapp_output "${output_file}") endif() set(zipapp_output_file "${zipapp_output}") set(is_dir_output FALSE) if(DEFINED ARG_TYPE) list(APPEND make_py_args "--type" "${ARG_TYPE}") if ("${ARG_TYPE}" STREQUAL "dir") set(is_dir_output TRUE) # CMake doesn't really seem to like having a directory specified as an # output; specify the __main__.py file as the output instead. set(zipapp_output_file "${zipapp_output}/__main__.py") # Update output_file to match zipapp_output_file for dir type set(output_file "${zipapp_output_file}") list(APPEND extra_cmd_params COMMAND "${CMAKE_COMMAND}" -E remove_directory "${zipapp_output}" ) endif() endif() if(DEFINED ARG_MAIN_MODULE) list(APPEND make_py_args "--main" "${ARG_MAIN_MODULE}") endif() add_custom_command( OUTPUT "${zipapp_output_file}" ${extra_cmd_params} COMMAND "${Python3_EXECUTABLE}" "${FB_MAKE_PYTHON_ARCHIVE}" -o "${zipapp_output}" ${make_py_args} DEPENDS ${source_files} "${TARGET}.main_lib.py_sources_built" "${FB_MAKE_PYTHON_ARCHIVE}" ) if(WIN32) if(is_dir_output) # TODO: generate a main executable that will invoke Python3 # with the correct main module inside the output directory else() add_executable("${TARGET}.winmain" "${FB_PY_WIN_MAIN_C}") target_link_libraries("${TARGET}.winmain" Python3::Python) # The Python3::Python target doesn't seem to be set up completely # correctly on Windows for some reason, and we have to explicitly add # ${Python3_LIBRARY_DIRS} to the target link directories. target_link_directories( "${TARGET}.winmain" PUBLIC ${Python3_LIBRARY_DIRS} ) add_custom_command( OUTPUT "${output_file}" DEPENDS "${TARGET}.winmain" "${zipapp_output_file}" COMMAND "cmd.exe" "/c" "copy" "/b" "${TARGET}.winmain${CMAKE_EXECUTABLE_SUFFIX}+${zipapp_output}" "${output_file}" ) endif() endif() # Add an "ALL" target that depends on force ${TARGET}, # so that ${TARGET} will be included in the default list of build targets. add_custom_target("${TARGET}.GEN_PY_EXE" ALL DEPENDS "${output_file}") # Allow resolving the executable path for the target that we generate # via a generator expression like: # "WATCHMAN_WAIT_PATH=$" set_property(TARGET "${TARGET}.GEN_PY_EXE" PROPERTY EXECUTABLE "${CMAKE_CURRENT_BINARY_DIR}/${output_file}") endfunction() # Define a python unittest executable. # The executable is built using add_fb_python_executable and has the # following differences: # # Each of the source files specified in SOURCES will be imported # and have unittest discovery performed upon them. # Those sources will be imported in the top level namespace. # # The ENV argument allows specifying a list of "KEY=VALUE" # pairs that will be used by the test runner to set up the environment # in the child process prior to running the test. This is useful for # passing additional configuration to the test. function(add_fb_python_unittest TARGET) # Parse the arguments set(multi_value_args SOURCES DEPENDS ENV PROPERTIES) set( one_value_args WORKING_DIRECTORY BASE_DIR NAMESPACE TEST_LIST DISCOVERY_TIMEOUT TYPE ) fb_cmake_parse_args( ARG "" "${one_value_args}" "${multi_value_args}" "${ARGN}" ) fb_py_process_default_args(ARG_NAMESPACE ARG_BASE_DIR) if(NOT ARG_WORKING_DIRECTORY) # Default the working directory to the current binary directory. # This matches the default behavior of add_test() and other standard # test functions like gtest_discover_tests() set(ARG_WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}") endif() if(NOT ARG_TEST_LIST) set(ARG_TEST_LIST "${TARGET}_TESTS") endif() if(NOT ARG_DISCOVERY_TIMEOUT) set(ARG_DISCOVERY_TIMEOUT 5) endif() # Tell our test program the list of modules to scan for tests. # We scan all modules directly listed in our SOURCES argument, and skip # modules that came from dependencies in the DEPENDS list. # # This is written into a __test_modules__.py module that the test runner # will look at. set( test_modules_path "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_test_modules.py" ) file(WRITE "${test_modules_path}" "TEST_MODULES = [\n") string(REPLACE "." "/" namespace_dir "${ARG_NAMESPACE}") if (NOT "${namespace_dir}" STREQUAL "") set(namespace_dir "${namespace_dir}/") endif() set(test_modules) foreach(src_path IN LISTS ARG_SOURCES) fb_py_compute_dest_path( abs_source dest_path "${src_path}" "${namespace_dir}" "${ARG_BASE_DIR}" ) string(REPLACE "/" "." module_name "${dest_path}") string(REGEX REPLACE "\\.py$" "" module_name "${module_name}") list(APPEND test_modules "${module_name}") file(APPEND "${test_modules_path}" " '${module_name}',\n") endforeach() file(APPEND "${test_modules_path}" "]\n") # The __main__ is provided by our runner wrapper/bootstrap list(APPEND ARG_SOURCES "${FB_PY_TEST_MAIN}=__main__.py") list(APPEND ARG_SOURCES "${test_modules_path}=__test_modules__.py") if(NOT DEFINED ARG_TYPE) set(ARG_TYPE "zipapp") endif() add_fb_python_executable( "${TARGET}" TYPE "${ARG_TYPE}" NAMESPACE "${ARG_NAMESPACE}" BASE_DIR "${ARG_BASE_DIR}" SOURCES ${ARG_SOURCES} DEPENDS ${ARG_DEPENDS} ) # Run test discovery after the test executable is built. # This logic is based on the code for gtest_discover_tests() set(ctest_file_base "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}") set(ctest_include_file "${ctest_file_base}_include.cmake") set(ctest_tests_file "${ctest_file_base}_tests.cmake") add_custom_command( TARGET "${TARGET}.GEN_PY_EXE" POST_BUILD BYPRODUCTS "${ctest_tests_file}" COMMAND "${CMAKE_COMMAND}" -D "TEST_TARGET=${TARGET}" -D "TEST_INTERPRETER=${Python3_EXECUTABLE}" -D "TEST_ENV=${ARG_ENV}" -D "TEST_EXECUTABLE=$" -D "TEST_WORKING_DIR=${ARG_WORKING_DIRECTORY}" -D "TEST_LIST=${ARG_TEST_LIST}" -D "TEST_PREFIX=${TARGET}::" -D "TEST_PROPERTIES=${ARG_PROPERTIES}" -D "CTEST_FILE=${ctest_tests_file}" -P "${FB_PY_TEST_DISCOVER_SCRIPT}" VERBATIM ) file( WRITE "${ctest_include_file}" "if(EXISTS \"${ctest_tests_file}\")\n" " include(\"${ctest_tests_file}\")\n" "else()\n" " add_test(\"${TARGET}_NOT_BUILT\" \"${TARGET}_NOT_BUILT\")\n" "endif()\n" ) set_property( DIRECTORY APPEND PROPERTY TEST_INCLUDE_FILES "${ctest_include_file}" ) endfunction() # # Define a python library. # # If you want to install a python library generated from this rule note that # you need to use install_fb_python_library() rather than CMake's built-in # install() function. This will make it available for other downstream # projects to use in their add_fb_python_executable() and # add_fb_python_library() calls. (You do still need to use `install(EXPORT)` # later to install the CMake exports.) # # Parameters: # - BASE_DIR : # The base directory path to strip off from each source path. All source # files must be inside this directory. If not specified it defaults to # ${CMAKE_CURRENT_SOURCE_DIR}. # - NAMESPACE : # The destination namespace where these files should be installed in python # binaries. If not specified, this defaults to the current relative path of # ${CMAKE_CURRENT_SOURCE_DIR} inside ${CMAKE_SOURCE_DIR}. e.g., a python # library defined in the directory repo_root/foo/bar will use a default # namespace of "foo.bar" # - SOURCES <...>: # The python source files. # You may optionally specify as source using the form: PATH=ALIAS where # PATH is a relative path in the source tree and ALIAS is the relative # path into which PATH should be rewritten. This is useful for mapping # an executable script to the main module in a python executable. # e.g.: `python/bin/watchman-wait=__main__.py` # - DEPENDS <...>: # Other python libraries that this one depends on. # - INSTALL_DIR : # The directory where this library should be installed. # install_fb_python_library() must still be called later to perform the # installation. If a relative path is given it will be treated relative to # ${CMAKE_INSTALL_PREFIX} # # CMake is unfortunately pretty crappy at being able to define custom build # rules & behaviors. It doesn't support transitive property propagation # between custom targets; only the built-in add_executable() and add_library() # targets support transitive properties. # # We hack around this janky CMake behavior by (ab)using interface libraries to # propagate some of the data we want between targets, without actually # generating a C library. # # add_fb_python_library(SOMELIB) generates the following things: # - An INTERFACE library rule named SOMELIB.py_lib which tracks some # information about transitive dependencies: # - the transitive set of source files in the INTERFACE_SOURCES property # - the transitive set of manifest files that this library depends on in # the INTERFACE_INCLUDE_DIRECTORIES property. # - A custom command that generates a SOMELIB.manifest file. # This file contains the mapping of source files to desired destination # locations in executables that depend on this library. This manifest file # will then be read at build-time in order to build executables. # function(add_fb_python_library LIB_NAME) fb_py_check_available() # Parse the arguments # We use fb_cmake_parse_args() rather than cmake_parse_arguments() since # cmake_parse_arguments() does not handle empty arguments, and it is common # for callers to want to specify an empty NAMESPACE parameter. set(one_value_args BASE_DIR NAMESPACE INSTALL_DIR) set(multi_value_args SOURCES DEPENDS) fb_cmake_parse_args( ARG "" "${one_value_args}" "${multi_value_args}" "${ARGN}" ) fb_py_process_default_args(ARG_NAMESPACE ARG_BASE_DIR) string(REPLACE "." "/" namespace_dir "${ARG_NAMESPACE}") if (NOT "${namespace_dir}" STREQUAL "") set(namespace_dir "${namespace_dir}/") endif() if(NOT DEFINED ARG_INSTALL_DIR) set(install_dir "${FBPY_LIB_INSTALL_DIR}/") elseif("${ARG_INSTALL_DIR}" STREQUAL "") set(install_dir "") else() set(install_dir "${ARG_INSTALL_DIR}/") endif() # message(STATUS "fb py library ${LIB_NAME}: " # "NS=${namespace_dir} BASE=${ARG_BASE_DIR}") # TODO: In the future it would be nice to support pre-compiling the source # files. We could emit a rule to compile each source file and emit a # .pyc/.pyo file here, and then have the manifest reference the pyc/pyo # files. # Define a library target to help pass around information about the library, # and propagate dependency information. # # CMake make a lot of assumptions that libraries are C++ libraries. To help # avoid confusion we name our target "${LIB_NAME}.py_lib" rather than just # "${LIB_NAME}". This helps avoid confusion if callers try to use # "${LIB_NAME}" on their own as a target name. (e.g., attempting to install # it directly with install(TARGETS) won't work. Callers must use # install_fb_python_library() instead.) add_library("${LIB_NAME}.py_lib" INTERFACE) # Emit the manifest file. # # We write the manifest file to a temporary path first, then copy it with # configure_file(COPYONLY). This is necessary to get CMake to understand # that "${manifest_path}" is generated by the CMake configure phase, # and allow using it as a dependency for add_custom_command(). # (https://gitlab.kitware.com/cmake/cmake/issues/16367) set(manifest_path "${CMAKE_CURRENT_BINARY_DIR}/${LIB_NAME}.manifest") set(tmp_manifest "${manifest_path}.tmp") file(WRITE "${tmp_manifest}" "FBPY_MANIFEST 1\n") set(abs_sources) foreach(src_path IN LISTS ARG_SOURCES) fb_py_compute_dest_path( abs_source dest_path "${src_path}" "${namespace_dir}" "${ARG_BASE_DIR}" ) list(APPEND abs_sources "${abs_source}") target_sources( "${LIB_NAME}.py_lib" INTERFACE "$" "$" ) file( APPEND "${tmp_manifest}" "${abs_source} :: ${dest_path}\n" ) endforeach() configure_file("${tmp_manifest}" "${manifest_path}" COPYONLY) target_include_directories( "${LIB_NAME}.py_lib" INTERFACE "$" "$" ) # Add a target that depends on all of the source files. # This is needed in case some of the source files are generated. This will # ensure that these source files are brought up-to-date before we build # any python binaries that depend on this library. add_custom_target("${LIB_NAME}.py_sources_built" DEPENDS ${abs_sources}) add_dependencies("${LIB_NAME}.py_lib" "${LIB_NAME}.py_sources_built") # Hook up library dependencies, and also make the *.py_sources_built target # depend on the sources for all of our dependencies also being up-to-date. foreach(dep IN LISTS ARG_DEPENDS) target_link_libraries("${LIB_NAME}.py_lib" INTERFACE "${dep}.py_lib") # Mark that our .py_sources_built target depends on each our our dependent # libraries. This serves two functions: # - This causes CMake to generate an error message if one of the # dependencies is never defined. The target_link_libraries() call above # won't complain if one of the dependencies doesn't exist (since it is # intended to allow passing in file names for plain library files rather # than just targets). # - It ensures that sources for our dependencies are built before any # executable that depends on us. Note that we depend on "${dep}.py_lib" # rather than "${dep}.py_sources_built" for this purpose because the # ".py_sources_built" target won't be available for imported targets. add_dependencies("${LIB_NAME}.py_sources_built" "${dep}.py_lib") endforeach() # Add a custom command to help with library installation, in case # install_fb_python_library() is called later for this library. # add_custom_command() only works with file dependencies defined in the same # CMakeLists.txt file, so we want to make sure this is defined here, rather # then where install_fb_python_library() is called. # This command won't be run by default, but will only be run if it is needed # by a subsequent install_fb_python_library() call. # # This command copies the library contents into the build directory. # It would be nicer if we could skip this intermediate copy, and just run # make_fbpy_archive.py at install time to copy them directly to the desired # installation directory. Unfortunately this is difficult to do, and seems # to interfere with some of the CMake code that wants to generate a manifest # of installed files. set(build_install_dir "${CMAKE_CURRENT_BINARY_DIR}/${LIB_NAME}.lib_install") add_custom_command( OUTPUT "${build_install_dir}/${LIB_NAME}.manifest" COMMAND "${CMAKE_COMMAND}" -E remove_directory "${build_install_dir}" COMMAND "${Python3_EXECUTABLE}" "${FB_MAKE_PYTHON_ARCHIVE}" --type lib-install --install-dir "${LIB_NAME}" -o "${build_install_dir}/${LIB_NAME}" "${manifest_path}" DEPENDS "${abs_sources}" "${manifest_path}" "${FB_MAKE_PYTHON_ARCHIVE}" ) add_custom_target( "${LIB_NAME}.py_lib_install" DEPENDS "${build_install_dir}/${LIB_NAME}.manifest" ) # Set some properties to pass through the install paths to # install_fb_python_library() # # Passing through ${build_install_dir} allows install_fb_python_library() # to work even if used from a different CMakeLists.txt file than where # add_fb_python_library() was called (i.e. such that # ${CMAKE_CURRENT_BINARY_DIR} is different between the two calls). set(abs_install_dir "${install_dir}") if(NOT IS_ABSOLUTE "${abs_install_dir}") set(abs_install_dir "${CMAKE_INSTALL_PREFIX}/${abs_install_dir}") endif() string(REGEX REPLACE "/$" "" abs_install_dir "${abs_install_dir}") set_target_properties( "${LIB_NAME}.py_lib_install" PROPERTIES INSTALL_DIR "${abs_install_dir}" BUILD_INSTALL_DIR "${build_install_dir}" ) endfunction() # # Install an FB-style packaged python binary. # # - DESTINATION : # Associate the installed target files with the given export-name. # function(install_fb_python_executable TARGET) # Parse the arguments set(one_value_args DESTINATION) set(multi_value_args) fb_cmake_parse_args( ARG "" "${one_value_args}" "${multi_value_args}" "${ARGN}" ) if(NOT DEFINED ARG_DESTINATION) set(ARG_DESTINATION bin) endif() install( PROGRAMS "$" DESTINATION "${ARG_DESTINATION}" ) endfunction() # # Install a python library. # # - EXPORT : # Associate the installed target files with the given export-name. # # Note that unlike the built-in CMake install() function we do not accept a # DESTINATION parameter. Instead, use the INSTALL_DIR parameter to # add_fb_python_library() to set the installation location. # function(install_fb_python_library LIB_NAME) set(one_value_args EXPORT) fb_cmake_parse_args(ARG "" "${one_value_args}" "" "${ARGN}") # Export our "${LIB_NAME}.py_lib" target so that it will be available to # downstream projects in our installed CMake config files. if(DEFINED ARG_EXPORT) install(TARGETS "${LIB_NAME}.py_lib" EXPORT "${ARG_EXPORT}") endif() # add_fb_python_library() emits a .py_lib_install target that will prepare # the installation directory. However, it isn't part of the "ALL" target and # therefore isn't built by default. # # Make sure the ALL target depends on it now. We have to do this by # introducing yet another custom target. # Add it as a dependency to the ALL target now. add_custom_target("${LIB_NAME}.py_lib_install_all" ALL) add_dependencies( "${LIB_NAME}.py_lib_install_all" "${LIB_NAME}.py_lib_install" ) # Copy the intermediate install directory generated at build time into # the desired install location. get_target_property(dest_dir "${LIB_NAME}.py_lib_install" "INSTALL_DIR") get_target_property( build_install_dir "${LIB_NAME}.py_lib_install" "BUILD_INSTALL_DIR" ) install( DIRECTORY "${build_install_dir}/${LIB_NAME}" DESTINATION "${dest_dir}" ) install( FILES "${build_install_dir}/${LIB_NAME}.manifest" DESTINATION "${dest_dir}" ) endfunction() # Helper macro to process the BASE_DIR and NAMESPACE arguments for # add_fb_python_executable() and add_fb_python_executable() macro(fb_py_process_default_args NAMESPACE_VAR BASE_DIR_VAR) # If the namespace was not specified, default to the relative path to the # current directory (starting from the repository root). if(NOT DEFINED "${NAMESPACE_VAR}") file( RELATIVE_PATH "${NAMESPACE_VAR}" "${CMAKE_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}" ) endif() if(NOT DEFINED "${BASE_DIR_VAR}") # If the base directory was not specified, default to the current directory set("${BASE_DIR_VAR}" "${CMAKE_CURRENT_SOURCE_DIR}") else() # If the base directory was specified, always convert it to an # absolute path. get_filename_component("${BASE_DIR_VAR}" "${${BASE_DIR_VAR}}" ABSOLUTE) endif() endmacro() function(fb_py_check_available) # Make sure that Python 3 and our make_fbpy_archive.py helper script are # available. if(NOT Python3_EXECUTABLE) if(FBPY_FIND_PYTHON_ERR) message(FATAL_ERROR "Unable to find Python 3: ${FBPY_FIND_PYTHON_ERR}") else() message(FATAL_ERROR "Unable to find Python 3") endif() endif() if (NOT FB_MAKE_PYTHON_ARCHIVE) message( FATAL_ERROR "unable to find make_fbpy_archive.py helper program (it " "should be located in the same directory as FBPythonBinary.cmake)" ) endif() endfunction() function( fb_py_compute_dest_path src_path_output dest_path_output src_path namespace_dir base_dir ) if("${src_path}" MATCHES "=") # We want to split the string on the `=` sign, but cmake doesn't # provide much in the way of helpers for this, so we rewrite the # `=` sign to `;` so that we can treat it as a cmake list and # then index into the components string(REPLACE "=" ";" src_path_list "${src_path}") list(GET src_path_list 0 src_path) # Note that we ignore the `namespace_dir` in the alias case # in order to allow aliasing a source to the top level `__main__.py` # filename. list(GET src_path_list 1 dest_path) else() unset(dest_path) endif() get_filename_component(abs_source "${src_path}" ABSOLUTE) if(NOT DEFINED dest_path) file(RELATIVE_PATH rel_src "${ARG_BASE_DIR}" "${abs_source}") if("${rel_src}" MATCHES "^../") message( FATAL_ERROR "${LIB_NAME}: source file \"${abs_source}\" is not inside " "the base directory ${ARG_BASE_DIR}" ) endif() set(dest_path "${namespace_dir}${rel_src}") endif() set("${src_path_output}" "${abs_source}" PARENT_SCOPE) set("${dest_path_output}" "${dest_path}" PARENT_SCOPE) endfunction() ================================================ FILE: build/fbcode_builder/CMake/FBPythonTestAddTests.cmake ================================================ # Copyright (c) Facebook, Inc. and its affiliates. # Add a command to be emitted to the CTest file set(ctest_script) function(add_command CMD) set(escaped_args "") foreach(arg ${ARGN}) # Escape all arguments using "Bracket Argument" syntax # We could skip this for argument that don't contain any special # characters if we wanted to make the output slightly more human-friendly. set(escaped_args "${escaped_args} [==[${arg}]==]") endforeach() set(ctest_script "${ctest_script}${CMD}(${escaped_args})\n" PARENT_SCOPE) endfunction() if(NOT EXISTS "${TEST_EXECUTABLE}") message(FATAL_ERROR "Test executable does not exist: ${TEST_EXECUTABLE}") endif() execute_process( COMMAND ${CMAKE_COMMAND} -E env ${TEST_ENV} "${TEST_INTERPRETER}" "${TEST_EXECUTABLE}" --list-tests WORKING_DIRECTORY "${TEST_WORKING_DIR}" OUTPUT_VARIABLE output RESULT_VARIABLE result ) if(NOT "${result}" EQUAL 0) string(REPLACE "\n" "\n " output "${output}") message( FATAL_ERROR "Error running test executable: ${TEST_EXECUTABLE}\n" "Output:\n" " ${output}\n" ) endif() # Parse output string(REPLACE "\n" ";" tests_list "${output}") foreach(test_name ${tests_list}) add_command( add_test "${TEST_PREFIX}${test_name}" ${CMAKE_COMMAND} -E env ${TEST_ENV} "${TEST_INTERPRETER}" "${TEST_EXECUTABLE}" "${test_name}" ) add_command( set_tests_properties "${TEST_PREFIX}${test_name}" PROPERTIES WORKING_DIRECTORY "${TEST_WORKING_DIR}" ${TEST_PROPERTIES} ) endforeach() # Set a list of discovered tests in the parent scope, in case users # want access to this list as a CMake variable if(TEST_LIST) add_command(set ${TEST_LIST} ${tests_list}) endif() file(WRITE "${CTEST_FILE}" "${ctest_script}") ================================================ FILE: build/fbcode_builder/CMake/FBThriftCppLibrary.cmake ================================================ # Copyright (c) Facebook, Inc. and its affiliates. include(FBCMakeParseArgs) # Generate a C++ library from a thrift file # # Parameters: # - SERVICES [ ...] # The names of the services defined in the thrift file. # - DEPENDS [ ...] # A list of other thrift C++ libraries that this library depends on. # - OPTIONS [ ...] # A list of options to pass to the thrift compiler. # - INCLUDE_DIR # The sub-directory where generated headers will be installed. # Defaults to "include" if not specified. The caller must still call # install() to install the thrift library if desired. # - THRIFT_INCLUDE_DIR # The sub-directory where generated headers will be installed. # Defaults to "${INCLUDE_DIR}/thrift-files" if not specified. # The caller must still call install() to install the thrift library if # desired. function(add_fbthrift_cpp_library LIB_NAME THRIFT_FILE) # Parse the arguments set(one_value_args INCLUDE_DIR THRIFT_INCLUDE_DIR) set(multi_value_args SERVICES DEPENDS OPTIONS) fb_cmake_parse_args( ARG "" "${one_value_args}" "${multi_value_args}" "${ARGN}" ) if(NOT DEFINED ARG_INCLUDE_DIR) set(ARG_INCLUDE_DIR "include") endif() if(NOT DEFINED ARG_THRIFT_INCLUDE_DIR) set(ARG_THRIFT_INCLUDE_DIR "${ARG_INCLUDE_DIR}/thrift-files") endif() get_filename_component(base ${THRIFT_FILE} NAME_WE) get_filename_component( output_dir ${CMAKE_CURRENT_BINARY_DIR}/${THRIFT_FILE} DIRECTORY ) # Generate relative paths in #includes file( RELATIVE_PATH include_prefix "${CMAKE_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/${THRIFT_FILE}" ) get_filename_component(include_prefix ${include_prefix} DIRECTORY) if (NOT "${include_prefix}" STREQUAL "") list(APPEND ARG_OPTIONS "include_prefix=${include_prefix}") endif() # CMake 3.12 is finally getting a list(JOIN) function, but until then # treating the list as a string and replacing the semicolons is good enough. string(REPLACE ";" "," GEN_ARG_STR "${ARG_OPTIONS}") # Compute the list of generated files list(APPEND generated_headers "${output_dir}/gen-cpp2/${base}_constants.h" "${output_dir}/gen-cpp2/${base}_types.h" "${output_dir}/gen-cpp2/${base}_types.tcc" "${output_dir}/gen-cpp2/${base}_types_custom_protocol.h" "${output_dir}/gen-cpp2/${base}_metadata.h" ) list(APPEND generated_sources "${output_dir}/gen-cpp2/${base}_constants.cpp" "${output_dir}/gen-cpp2/${base}_data.h" "${output_dir}/gen-cpp2/${base}_data.cpp" "${output_dir}/gen-cpp2/${base}_types.cpp" "${output_dir}/gen-cpp2/${base}_types_binary.cpp" "${output_dir}/gen-cpp2/${base}_types_compact.cpp" "${output_dir}/gen-cpp2/${base}_types_serialization.cpp" "${output_dir}/gen-cpp2/${base}_metadata.cpp" ) foreach(service IN LISTS ARG_SERVICES) list(APPEND generated_headers "${output_dir}/gen-cpp2/${service}.h" "${output_dir}/gen-cpp2/${service}.tcc" "${output_dir}/gen-cpp2/${service}AsyncClient.h" "${output_dir}/gen-cpp2/${service}_custom_protocol.h" ) list(APPEND generated_sources "${output_dir}/gen-cpp2/${service}.cpp" "${output_dir}/gen-cpp2/${service}AsyncClient.cpp" "${output_dir}/gen-cpp2/${service}_processmap_binary.cpp" "${output_dir}/gen-cpp2/${service}_processmap_compact.cpp" ) endforeach() # This generator expression gets the list of include directories required # for all of our dependencies. # It requires using COMMAND_EXPAND_LISTS in the add_custom_command() call # below. COMMAND_EXPAND_LISTS is only available in CMake 3.8+ # If we really had to support older versions of CMake we would probably need # to use a wrapper script around the thrift compiler that could take the # include list as a single argument and split it up before invoking the # thrift compiler. if (NOT POLICY CMP0067) message(FATAL_ERROR "add_fbthrift_cpp_library() requires CMake 3.8+") endif() set( thrift_include_options "-I;$,;-I;>" ) # Emit the rule to run the thrift compiler add_custom_command( OUTPUT ${generated_headers} ${generated_sources} COMMAND_EXPAND_LISTS COMMAND "${CMAKE_COMMAND}" -E make_directory "${output_dir}" COMMAND "${FBTHRIFT_COMPILER}" --legacy-strict --gen "mstch_cpp2:${GEN_ARG_STR}" "${thrift_include_options}" -I "${FBTHRIFT_INCLUDE_DIR}" -o "${output_dir}" "${CMAKE_CURRENT_SOURCE_DIR}/${THRIFT_FILE}" WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" MAIN_DEPENDENCY "${THRIFT_FILE}" DEPENDS ${ARG_DEPENDS} "${FBTHRIFT_COMPILER}" ) # Now emit the library rule to compile the sources if (BUILD_SHARED_LIBS) set(LIB_TYPE SHARED) else () set(LIB_TYPE STATIC) endif () add_library( "${LIB_NAME}" ${LIB_TYPE} ${generated_sources} ) target_include_directories( "${LIB_NAME}" PUBLIC "$" "$" ${Xxhash_INCLUDE_DIR} ) target_link_libraries( "${LIB_NAME}" PUBLIC ${ARG_DEPENDS} FBThrift::thriftcpp2 Folly::folly mvfst::mvfst_server_async_tran mvfst::mvfst_server ${Xxhash_LIBRARY} ) # Add ${generated_headers} to the PUBLIC_HEADER property for ${LIB_NAME} # # This allows callers to install it using # "install(TARGETS ${LIB_NAME} PUBLIC_HEADER)" # However, note that CMake's PUBLIC_HEADER behavior is rather inflexible, # and does have any way to preserve header directory structure. Callers # must be careful to use the correct PUBLIC_HEADER DESTINATION parameter # when doing this, to put the files the correct directory themselves. # We define a HEADER_INSTALL_DIR property with the include directory prefix, # so typically callers should specify the PUBLIC_HEADER DESTINATION as # "$" set_property( TARGET "${LIB_NAME}" PROPERTY PUBLIC_HEADER ${generated_headers} ) # Define a dummy interface library to help propagate the thrift include # directories between dependencies. add_library("${LIB_NAME}.thrift_includes" INTERFACE) target_include_directories( "${LIB_NAME}.thrift_includes" INTERFACE "$" "$" ) foreach(dep IN LISTS ARG_DEPENDS) target_link_libraries( "${LIB_NAME}.thrift_includes" INTERFACE "${dep}.thrift_includes" ) endforeach() set_target_properties( "${LIB_NAME}" PROPERTIES EXPORT_PROPERTIES "THRIFT_INSTALL_DIR" THRIFT_INSTALL_DIR "${ARG_THRIFT_INCLUDE_DIR}/${include_prefix}" HEADER_INSTALL_DIR "${ARG_INCLUDE_DIR}/${include_prefix}/gen-cpp2" ) endfunction() ================================================ FILE: build/fbcode_builder/CMake/FBThriftLibrary.cmake ================================================ # Copyright (c) Facebook, Inc. and its affiliates. include(FBCMakeParseArgs) include(FBThriftPyLibrary) include(FBThriftCppLibrary) # # add_fbthrift_library() # # This is a convenience function that generates thrift libraries for multiple # languages. # # For example: # add_fbthrift_library( # foo foo.thrift # LANGUAGES cpp py # SERVICES Foo # DEPENDS bar) # # will be expanded into two separate calls: # # add_fbthrift_cpp_library(foo_cpp foo.thrift SERVICES Foo DEPENDS bar_cpp) # add_fbthrift_py_library(foo_py foo.thrift SERVICES Foo DEPENDS bar_py) # function(add_fbthrift_library LIB_NAME THRIFT_FILE) # Parse the arguments set(one_value_args PY_NAMESPACE INCLUDE_DIR THRIFT_INCLUDE_DIR) set(multi_value_args SERVICES DEPENDS LANGUAGES CPP_OPTIONS PY_OPTIONS) fb_cmake_parse_args( ARG "" "${one_value_args}" "${multi_value_args}" "${ARGN}" ) if(NOT DEFINED ARG_INCLUDE_DIR) set(ARG_INCLUDE_DIR "include") endif() if(NOT DEFINED ARG_THRIFT_INCLUDE_DIR) set(ARG_THRIFT_INCLUDE_DIR "${ARG_INCLUDE_DIR}/thrift-files") endif() # CMake 3.12+ adds list(TRANSFORM) which would be nice to use here, but for # now we still want to support older versions of CMake. set(CPP_DEPENDS) set(PY_DEPENDS) foreach(dep IN LISTS ARG_DEPENDS) list(APPEND CPP_DEPENDS "${dep}_cpp") list(APPEND PY_DEPENDS "${dep}_py") endforeach() foreach(lang IN LISTS ARG_LANGUAGES) if ("${lang}" STREQUAL "cpp") add_fbthrift_cpp_library( "${LIB_NAME}_cpp" "${THRIFT_FILE}" SERVICES ${ARG_SERVICES} DEPENDS ${CPP_DEPENDS} OPTIONS ${ARG_CPP_OPTIONS} INCLUDE_DIR "${ARG_INCLUDE_DIR}" THRIFT_INCLUDE_DIR "${ARG_THRIFT_INCLUDE_DIR}" ) elseif ("${lang}" STREQUAL "py" OR "${lang}" STREQUAL "python") if (DEFINED ARG_PY_NAMESPACE) set(namespace_args NAMESPACE "${ARG_PY_NAMESPACE}") endif() add_fbthrift_py_library( "${LIB_NAME}_py" "${THRIFT_FILE}" SERVICES ${ARG_SERVICES} ${namespace_args} DEPENDS ${PY_DEPENDS} OPTIONS ${ARG_PY_OPTIONS} THRIFT_INCLUDE_DIR "${ARG_THRIFT_INCLUDE_DIR}" ) else() message( FATAL_ERROR "unknown language for thrift library ${LIB_NAME}: ${lang}" ) endif() endforeach() endfunction() ================================================ FILE: build/fbcode_builder/CMake/FBThriftPyLibrary.cmake ================================================ # Copyright (c) Facebook, Inc. and its affiliates. include(FBCMakeParseArgs) include(FBPythonBinary) # Generate a Python library from a thrift file function(add_fbthrift_py_library LIB_NAME THRIFT_FILE) # Parse the arguments set(one_value_args NAMESPACE THRIFT_INCLUDE_DIR) set(multi_value_args SERVICES DEPENDS OPTIONS) fb_cmake_parse_args( ARG "" "${one_value_args}" "${multi_value_args}" "${ARGN}" ) if(NOT DEFINED ARG_THRIFT_INCLUDE_DIR) set(ARG_THRIFT_INCLUDE_DIR "include/thrift-files") endif() get_filename_component(base ${THRIFT_FILE} NAME_WE) set(output_dir "${CMAKE_CURRENT_BINARY_DIR}/${THRIFT_FILE}-py") # Parse the namespace value if (NOT DEFINED ARG_NAMESPACE) set(ARG_NAMESPACE "${base}") endif() string(REPLACE "." "/" namespace_dir "${ARG_NAMESPACE}") set(py_output_dir "${output_dir}/gen-py/${namespace_dir}") list(APPEND generated_sources "${py_output_dir}/__init__.py" "${py_output_dir}/ttypes.py" "${py_output_dir}/constants.py" ) foreach(service IN LISTS ARG_SERVICES) list(APPEND generated_sources ${py_output_dir}/${service}.py ) endforeach() # Define a dummy interface library to help propagate the thrift include # directories between dependencies. add_library("${LIB_NAME}.thrift_includes" INTERFACE) target_include_directories( "${LIB_NAME}.thrift_includes" INTERFACE "$" "$" ) foreach(dep IN LISTS ARG_DEPENDS) target_link_libraries( "${LIB_NAME}.thrift_includes" INTERFACE "${dep}.thrift_includes" ) endforeach() # This generator expression gets the list of include directories required # for all of our dependencies. # It requires using COMMAND_EXPAND_LISTS in the add_custom_command() call # below. COMMAND_EXPAND_LISTS is only available in CMake 3.8+ # If we really had to support older versions of CMake we would probably need # to use a wrapper script around the thrift compiler that could take the # include list as a single argument and split it up before invoking the # thrift compiler. if (NOT POLICY CMP0067) message(FATAL_ERROR "add_fbthrift_py_library() requires CMake 3.8+") endif() set( thrift_include_options "-I;$,;-I;>" ) # Always force generation of "new-style" python classes for Python 2 list(APPEND ARG_OPTIONS "new_style") # CMake 3.12 is finally getting a list(JOIN) function, but until then # treating the list as a string and replacing the semicolons is good enough. string(REPLACE ";" "," GEN_ARG_STR "${ARG_OPTIONS}") # Emit the rule to run the thrift compiler add_custom_command( OUTPUT ${generated_sources} COMMAND_EXPAND_LISTS COMMAND "${CMAKE_COMMAND}" -E make_directory "${output_dir}" COMMAND "${FBTHRIFT_COMPILER}" --legacy-strict --gen "py:${GEN_ARG_STR}" "${thrift_include_options}" -o "${output_dir}" "${CMAKE_CURRENT_SOURCE_DIR}/${THRIFT_FILE}" WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" MAIN_DEPENDENCY "${THRIFT_FILE}" DEPENDS "${FBTHRIFT_COMPILER}" ) # We always want to pass the namespace as "" to this call: # thrift will already emit the files with the desired namespace prefix under # gen-py. We don't want add_fb_python_library() to prepend the namespace a # second time. add_fb_python_library( "${LIB_NAME}" BASE_DIR "${output_dir}/gen-py" NAMESPACE "" SOURCES ${generated_sources} DEPENDS ${ARG_DEPENDS} FBThrift::thrift_py ) endfunction() ================================================ FILE: build/fbcode_builder/CMake/FindCares.cmake ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # All rights reserved. # # This source code is licensed under the BSD-style license found in the # LICENSE file in the root directory of this source tree. find_path(CARES_INCLUDE_DIR NAMES ares.h) find_library(CARES_LIBRARIES NAMES cares) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Cares DEFAULT_MSG CARES_LIBRARIES CARES_INCLUDE_DIR) mark_as_advanced( CARES_LIBRARIES CARES_INCLUDE_DIR ) if(NOT TARGET cares) if("${CARES_LIBRARIES}" MATCHES ".*.a$") add_library(cares STATIC IMPORTED) else() add_library(cares SHARED IMPORTED) endif() set_target_properties( cares PROPERTIES IMPORTED_LOCATION ${CARES_LIBRARIES} INTERFACE_INCLUDE_DIRECTORIES ${CARES_INCLUDE_DIR} ) endif() ================================================ FILE: build/fbcode_builder/CMake/FindDoubleConversion.cmake ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # Finds libdouble-conversion. # # This module defines: # DOUBLE_CONVERSION_INCLUDE_DIR # DOUBLE_CONVERSION_LIBRARY # find_path(DOUBLE_CONVERSION_INCLUDE_DIR double-conversion/double-conversion.h) find_library(DOUBLE_CONVERSION_LIBRARY NAMES double-conversion) include(FindPackageHandleStandardArgs) find_package_handle_standard_args( DoubleConversion DEFAULT_MSG DOUBLE_CONVERSION_LIBRARY DOUBLE_CONVERSION_INCLUDE_DIR) mark_as_advanced(DOUBLE_CONVERSION_INCLUDE_DIR DOUBLE_CONVERSION_LIBRARY) ================================================ FILE: build/fbcode_builder/CMake/FindGMock.cmake ================================================ # Copyright (c) Facebook, Inc. and its affiliates. # Find libgmock # # LIBGMOCK_DEFINES - List of defines when using libgmock. # LIBGMOCK_INCLUDE_DIR - where to find gmock/gmock.h, etc. # LIBGMOCK_LIBRARIES - List of libraries when using libgmock. # LIBGMOCK_FOUND - True if libgmock found. IF (LIBGMOCK_INCLUDE_DIR) # Already in cache, be silent SET(LIBGMOCK_FIND_QUIETLY TRUE) ENDIF () find_package(GTest CONFIG QUIET) if (TARGET GTest::gmock) get_target_property(LIBGMOCK_DEFINES GTest::gtest INTERFACE_COMPILE_DEFINITIONS) if (NOT ${LIBGMOCK_DEFINES}) # Explicitly set to empty string if not found to avoid it being # set to NOTFOUND and breaking compilation set(LIBGMOCK_DEFINES "") endif() get_target_property(LIBGMOCK_INCLUDE_DIR GTest::gtest INTERFACE_INCLUDE_DIRECTORIES) set(LIBGMOCK_LIBRARIES GTest::gmock_main GTest::gmock GTest::gtest) set(LIBGMOCK_FOUND ON) message(STATUS "Found gmock via config, defines=${LIBGMOCK_DEFINES}, include=${LIBGMOCK_INCLUDE_DIR}, libs=${LIBGMOCK_LIBRARIES}") else() FIND_PATH(LIBGMOCK_INCLUDE_DIR gmock/gmock.h) FIND_LIBRARY(LIBGMOCK_MAIN_LIBRARY_DEBUG NAMES gmock_maind) FIND_LIBRARY(LIBGMOCK_MAIN_LIBRARY_RELEASE NAMES gmock_main) FIND_LIBRARY(LIBGMOCK_LIBRARY_DEBUG NAMES gmockd) FIND_LIBRARY(LIBGMOCK_LIBRARY_RELEASE NAMES gmock) FIND_LIBRARY(LIBGTEST_LIBRARY_DEBUG NAMES gtestd) FIND_LIBRARY(LIBGTEST_LIBRARY_RELEASE NAMES gtest) find_package(Threads REQUIRED) INCLUDE(SelectLibraryConfigurations) SELECT_LIBRARY_CONFIGURATIONS(LIBGMOCK_MAIN) SELECT_LIBRARY_CONFIGURATIONS(LIBGMOCK) SELECT_LIBRARY_CONFIGURATIONS(LIBGTEST) set(LIBGMOCK_LIBRARIES ${LIBGMOCK_MAIN_LIBRARY} ${LIBGMOCK_LIBRARY} ${LIBGTEST_LIBRARY} Threads::Threads ) if(CMAKE_SYSTEM_NAME STREQUAL "Windows") # The GTEST_LINKED_AS_SHARED_LIBRARY macro must be set properly on Windows. # # There isn't currently an easy way to determine if a library was compiled as # a shared library on Windows, so just assume we've been built against a # shared build of gmock for now. SET(LIBGMOCK_DEFINES "GTEST_LINKED_AS_SHARED_LIBRARY=1" CACHE STRING "") endif() # handle the QUIETLY and REQUIRED arguments and set LIBGMOCK_FOUND to TRUE if # all listed variables are TRUE INCLUDE(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS( GMock DEFAULT_MSG LIBGMOCK_MAIN_LIBRARY LIBGMOCK_LIBRARY LIBGTEST_LIBRARY LIBGMOCK_LIBRARIES LIBGMOCK_INCLUDE_DIR ) MARK_AS_ADVANCED( LIBGMOCK_DEFINES LIBGMOCK_MAIN_LIBRARY LIBGMOCK_LIBRARY LIBGTEST_LIBRARY LIBGMOCK_LIBRARIES LIBGMOCK_INCLUDE_DIR ) endif() ================================================ FILE: build/fbcode_builder/CMake/FindGflags.cmake ================================================ # Copyright (c) Facebook, Inc. and its affiliates. # Find libgflags. # There's a lot of compatibility cruft going on in here, both # to deal with changes across the FB consumers of this and also # to deal with variances in behavior of cmake itself. # # Since this file is named FindGflags.cmake the cmake convention # is for the module to export both GFLAGS_FOUND and Gflags_FOUND. # The convention expected by consumers is that we export the # following variables, even though these do not match the cmake # conventions: # # LIBGFLAGS_INCLUDE_DIR - where to find gflags/gflags.h, etc. # LIBGFLAGS_LIBRARY - List of libraries when using libgflags. # LIBGFLAGS_FOUND - True if libgflags found. # # We need to be able to locate gflags both from an installed # cmake config file and just from the raw headers and libs, so # test for the former and then the latter, and then stick # the results together and export them into the variables # listed above. # # For forwards compatibility, we export the following variables: # # gflags_INCLUDE_DIR - where to find gflags/gflags.h, etc. # gflags_TARGET / GFLAGS_TARGET / gflags_LIBRARIES # - List of libraries when using libgflags. # gflags_FOUND - True if libgflags found. # IF (LIBGFLAGS_INCLUDE_DIR) # Already in cache, be silent SET(Gflags_FIND_QUIETLY TRUE) ENDIF () find_package(gflags CONFIG QUIET) if (gflags_FOUND) if (NOT Gflags_FIND_QUIETLY) message(STATUS "Found gflags from package config ${gflags_CONFIG}") endif() # Re-export the config-specified libs with our local names set(LIBGFLAGS_LIBRARY ${gflags_LIBRARIES}) set(LIBGFLAGS_INCLUDE_DIR ${gflags_INCLUDE_DIR}) if(NOT EXISTS "${gflags_INCLUDE_DIR}") # The gflags-devel RPM on recent RedHat-based systems is somewhat broken. # RedHat symlinks /lib64 to /usr/lib64, and this breaks some of the # relative path computation performed in gflags-config.cmake. The package # config file ends up being found via /lib64, but the relative path # computation it does only works if it was found in /usr/lib64. # If gflags_INCLUDE_DIR does not actually exist, simply default it to # /usr/include on these systems. set(LIBGFLAGS_INCLUDE_DIR "/usr/include") set(GFLAGS_INCLUDE_DIR "/usr/include") endif() set(LIBGFLAGS_FOUND ${gflags_FOUND}) # cmake module compat set(GFLAGS_FOUND ${gflags_FOUND}) set(Gflags_FOUND ${gflags_FOUND}) else() FIND_PATH(LIBGFLAGS_INCLUDE_DIR gflags/gflags.h) FIND_LIBRARY(LIBGFLAGS_LIBRARY_DEBUG NAMES gflagsd gflags_staticd) FIND_LIBRARY(LIBGFLAGS_LIBRARY_RELEASE NAMES gflags gflags_static) INCLUDE(SelectLibraryConfigurations) SELECT_LIBRARY_CONFIGURATIONS(LIBGFLAGS) # handle the QUIETLY and REQUIRED arguments and set LIBGFLAGS_FOUND to TRUE if # all listed variables are TRUE INCLUDE(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(gflags DEFAULT_MSG LIBGFLAGS_LIBRARY LIBGFLAGS_INCLUDE_DIR) # cmake module compat set(Gflags_FOUND ${GFLAGS_FOUND}) # compat with some existing FindGflags consumers set(LIBGFLAGS_FOUND ${GFLAGS_FOUND}) # Compat with the gflags CONFIG based detection set(gflags_FOUND ${GFLAGS_FOUND}) set(gflags_INCLUDE_DIR ${LIBGFLAGS_INCLUDE_DIR}) set(gflags_LIBRARIES ${LIBGFLAGS_LIBRARY}) set(GFLAGS_TARGET ${LIBGFLAGS_LIBRARY}) set(gflags_TARGET ${LIBGFLAGS_LIBRARY}) MARK_AS_ADVANCED(LIBGFLAGS_LIBRARY LIBGFLAGS_INCLUDE_DIR) endif() # Compat with the gflags CONFIG based detection if (LIBGFLAGS_FOUND AND NOT TARGET gflags) add_library(gflags UNKNOWN IMPORTED) if(TARGET gflags-shared) # If the installed gflags CMake package config defines a gflags-shared # target but not gflags, just make the gflags target that we define # depend on the gflags-shared target. target_link_libraries(gflags INTERFACE gflags-shared) # Export LIBGFLAGS_LIBRARY as the gflags-shared target in this case. set(LIBGFLAGS_LIBRARY gflags-shared) else() set_target_properties( gflags PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES "C" IMPORTED_LOCATION "${LIBGFLAGS_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${LIBGFLAGS_INCLUDE_DIR}" ) endif() endif() ================================================ FILE: build/fbcode_builder/CMake/FindGlog.cmake ================================================ # Copyright (c) Facebook, Inc. and its affiliates. # - Try to find Glog # Once done, this will define # # GLOG_FOUND - system has Glog # GLOG_INCLUDE_DIRS - the Glog include directories # GLOG_LIBRARIES - link these to use Glog include(FindPackageHandleStandardArgs) include(SelectLibraryConfigurations) find_library(GLOG_LIBRARY_RELEASE glog PATHS ${GLOG_LIBRARYDIR}) find_library(GLOG_LIBRARY_DEBUG glogd PATHS ${GLOG_LIBRARYDIR}) find_path(GLOG_INCLUDE_DIR glog/logging.h PATHS ${GLOG_INCLUDEDIR}) select_library_configurations(GLOG) find_package_handle_standard_args(Glog DEFAULT_MSG GLOG_LIBRARY GLOG_INCLUDE_DIR) mark_as_advanced( GLOG_LIBRARY GLOG_INCLUDE_DIR) set(GLOG_LIBRARIES ${GLOG_LIBRARY}) set(GLOG_INCLUDE_DIRS ${GLOG_INCLUDE_DIR}) if (NOT TARGET glog::glog) add_library(glog::glog UNKNOWN IMPORTED) set_target_properties(glog::glog PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${GLOG_INCLUDE_DIRS}") set_target_properties(glog::glog PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES "C" IMPORTED_LOCATION "${GLOG_LIBRARIES}") set_target_properties(glog::glog PROPERTIES INTERFACE_COMPILE_DEFINITIONS "GLOG_USE_GLOG_EXPORT") find_package(Gflags) if(GFLAGS_FOUND) message(STATUS "Found gflags as a dependency of glog::glog, include=${LIBGFLAGS_INCLUDE_DIR}, libs=${LIBGFLAGS_LIBRARY}") set_property(TARGET glog::glog APPEND PROPERTY IMPORTED_LINK_INTERFACE_LIBRARIES ${LIBGFLAGS_LIBRARY}) endif() find_package(LibUnwind) if(LIBUNWIND_FOUND) message(STATUS "Found LibUnwind as a dependency of glog::glog, include=${LIBUNWIND_INCLUDE_DIR}, libs=${LIBUNWIND_LIBRARY}") set_property(TARGET glog::glog APPEND PROPERTY IMPORTED_LINK_INTERFACE_LIBRARIES ${LIBUNWIND_LIBRARY}) endif() endif() ================================================ FILE: build/fbcode_builder/CMake/FindLMDB.cmake ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # # This software may be used and distributed according to the terms of the # GNU General Public License version 2. find_library(LMDB_LIBRARIES NAMES lmdb liblmdb) mark_as_advanced(LMDB_LIBRARIES) find_path(LMDB_INCLUDE_DIR NAMES lmdb.h) mark_as_advanced(LMDB_INCLUDE_DIR) find_package_handle_standard_args( LMDB REQUIRED_VARS LMDB_LIBRARIES LMDB_INCLUDE_DIR) if(LMDB_FOUND) set(LMDB_LIBRARIES ${LMDB_LIBRARIES}) set(LMDB_INCLUDE_DIR, ${LMDB_INCLUDE_DIR}) endif() ================================================ FILE: build/fbcode_builder/CMake/FindLibEvent.cmake ================================================ # Copyright (c) Facebook, Inc. and its affiliates. # - Find LibEvent (a cross event library) # This module defines # LIBEVENT_INCLUDE_DIR, where to find LibEvent headers # LIBEVENT_LIB, LibEvent libraries # LibEvent_FOUND, If false, do not try to use libevent set(LibEvent_EXTRA_PREFIXES /usr/local /opt/local "$ENV{HOME}") foreach(prefix ${LibEvent_EXTRA_PREFIXES}) list(APPEND LibEvent_INCLUDE_PATHS "${prefix}/include") list(APPEND LibEvent_LIB_PATHS "${prefix}/lib") endforeach() find_package(Libevent CONFIG QUIET) if (TARGET event) # Re-export the config under our own names # Somewhat gross, but some vcpkg installed libevents have a relative # `include` path exported into LIBEVENT_INCLUDE_DIRS, which triggers # a cmake error because it resolves to the `include` dir within the # folly repo, which is not something cmake allows to be in the # INTERFACE_INCLUDE_DIRECTORIES. Thankfully on such a system the # actual include directory is already part of the global include # directories, so we can just skip it. if (NOT "${LIBEVENT_INCLUDE_DIRS}" STREQUAL "include") set(LIBEVENT_INCLUDE_DIR ${LIBEVENT_INCLUDE_DIRS}) else() set(LIBEVENT_INCLUDE_DIR) endif() # Unfortunately, with a bare target name `event`, downstream consumers # of the package that depends on `Libevent` located via CONFIG end # up exporting just a bare `event` in their libraries. This is problematic # because this in interpreted as just `-levent` with no library path. # When libevent is not installed in the default installation prefix # this results in linker errors. # To resolve this, we ask cmake to lookup the full path to the library # and use that instead. cmake_policy(PUSH) if(POLICY CMP0026) # Allow reading the LOCATION property cmake_policy(SET CMP0026 OLD) endif() get_target_property(LIBEVENT_LIB event LOCATION) cmake_policy(POP) set(LibEvent_FOUND ${Libevent_FOUND}) if (NOT LibEvent_FIND_QUIETLY) message(STATUS "Found libevent from package config include=${LIBEVENT_INCLUDE_DIRS} lib=${LIBEVENT_LIB}") endif() else() find_path(LIBEVENT_INCLUDE_DIR event.h PATHS ${LibEvent_INCLUDE_PATHS}) find_library(LIBEVENT_LIB NAMES event PATHS ${LibEvent_LIB_PATHS}) if (LIBEVENT_LIB AND LIBEVENT_INCLUDE_DIR) set(LibEvent_FOUND TRUE) set(LIBEVENT_LIB ${LIBEVENT_LIB}) else () set(LibEvent_FOUND FALSE) endif () if (LibEvent_FOUND) if (NOT LibEvent_FIND_QUIETLY) message(STATUS "Found libevent: ${LIBEVENT_LIB}") endif () else () if (LibEvent_FIND_REQUIRED) message(FATAL_ERROR "Could NOT find libevent.") endif () message(STATUS "libevent NOT found.") endif () mark_as_advanced( LIBEVENT_LIB LIBEVENT_INCLUDE_DIR ) endif() ================================================ FILE: build/fbcode_builder/CMake/FindLibUnwind.cmake ================================================ # Copyright (c) Facebook, Inc. and its affiliates. # # 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(FindPackageHandleStandardArgs) # Prefer pkg-config: picks up transitive deps (e.g. lzma, zlib) that # static libunwind needs but a bare find_library would miss. find_package(PkgConfig QUIET) if(PKG_CONFIG_FOUND) pkg_check_modules(PC_LIBUNWIND QUIET libunwind) endif() if(PC_LIBUNWIND_FOUND) find_path(LIBUNWIND_INCLUDE_DIR NAMES libunwind.h HINTS ${PC_LIBUNWIND_INCLUDE_DIRS} PATH_SUFFIXES libunwind) mark_as_advanced(LIBUNWIND_INCLUDE_DIR) # Resolve each library from the static set (Libs + Libs.private) to a # full path. This gives the linker everything it needs for a fully-static # link without leaking imported targets through cmake exports. set(LIBUNWIND_LIBRARIES "") foreach(_lib IN LISTS PC_LIBUNWIND_STATIC_LIBRARIES) find_library(_libunwind_dep_${_lib} NAMES ${_lib} HINTS ${PC_LIBUNWIND_STATIC_LIBRARY_DIRS}) if(_libunwind_dep_${_lib}) list(APPEND LIBUNWIND_LIBRARIES ${_libunwind_dep_${_lib}}) else() # Fall back to bare name; the linker will resolve -l. list(APPEND LIBUNWIND_LIBRARIES ${_lib}) endif() unset(_libunwind_dep_${_lib} CACHE) endforeach() set(LIBUNWIND_LIBRARY "${LIBUNWIND_LIBRARIES}") FIND_PACKAGE_HANDLE_STANDARD_ARGS(LibUnwind REQUIRED_VARS LIBUNWIND_LIBRARIES LIBUNWIND_INCLUDE_DIR) else() # Fallback for systems without pkg-config. # When using prepackaged LLVM libunwind on Ubuntu, its includes are # installed in a subdirectory. find_path(LIBUNWIND_INCLUDE_DIR NAMES libunwind.h PATH_SUFFIXES libunwind) mark_as_advanced(LIBUNWIND_INCLUDE_DIR) find_library(LIBUNWIND_LIBRARY NAMES unwind) mark_as_advanced(LIBUNWIND_LIBRARY) FIND_PACKAGE_HANDLE_STANDARD_ARGS(LibUnwind REQUIRED_VARS LIBUNWIND_LIBRARY LIBUNWIND_INCLUDE_DIR) endif() if(LibUnwind_FOUND) if(NOT LIBUNWIND_LIBRARIES) set(LIBUNWIND_LIBRARIES ${LIBUNWIND_LIBRARY}) endif() set(LIBUNWIND_INCLUDE_DIRS ${LIBUNWIND_INCLUDE_DIR}) endif() ================================================ FILE: build/fbcode_builder/CMake/FindLibiberty.cmake ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # # 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. find_path(LIBIBERTY_INCLUDE_DIR NAMES libiberty.h PATH_SUFFIXES libiberty) mark_as_advanced(LIBIBERTY_INCLUDE_DIR) find_library(LIBIBERTY_LIBRARY NAMES iberty) mark_as_advanced(LIBIBERTY_LIBRARY) include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS( LIBIBERTY REQUIRED_VARS LIBIBERTY_LIBRARY LIBIBERTY_INCLUDE_DIR) if(LIBIBERTY_FOUND) set(LIBIBERTY_LIBRARIES ${LIBIBERTY_LIBRARY}) set(LIBIBERTY_INCLUDE_DIRS ${LIBIBERTY_INCLUDE_DIR}) endif() ================================================ FILE: build/fbcode_builder/CMake/FindPCRE.cmake ================================================ # Copyright (c) Facebook, Inc. and its affiliates. include(FindPackageHandleStandardArgs) find_path(PCRE_INCLUDE_DIR NAMES pcre.h) find_library(PCRE_LIBRARY NAMES pcre) find_package_handle_standard_args( PCRE DEFAULT_MSG PCRE_LIBRARY PCRE_INCLUDE_DIR ) mark_as_advanced(PCRE_INCLUDE_DIR PCRE_LIBRARY) ================================================ FILE: build/fbcode_builder/CMake/FindPCRE2.cmake ================================================ # Copyright (c) Facebook, Inc. and its affiliates. include(FindPackageHandleStandardArgs) find_path(PCRE2_INCLUDE_DIR NAMES pcre2.h) find_library(PCRE2_LIBRARY NAMES pcre2-8) find_package_handle_standard_args( PCRE2 DEFAULT_MSG PCRE2_LIBRARY PCRE2_INCLUDE_DIR ) set(PCRE2_DEFINES "PCRE2_CODE_UNIT_WIDTH=8") mark_as_advanced(PCRE2_INCLUDE_DIR PCRE2_LIBRARY PCRE2_DEFINES) ================================================ FILE: build/fbcode_builder/CMake/FindRe2.cmake ================================================ # Copyright (c) Facebook, Inc. and its affiliates. # # This software may be used and distributed according to the terms of the # GNU General Public License version 2. find_library(RE2_LIBRARY re2) mark_as_advanced(RE2_LIBRARY) find_path(RE2_INCLUDE_DIR NAMES re2/re2.h) mark_as_advanced(RE2_INCLUDE_DIR) include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS( RE2 REQUIRED_VARS RE2_LIBRARY RE2_INCLUDE_DIR) if(RE2_FOUND) set(RE2_LIBRARY ${RE2_LIBRARY}) set(RE2_INCLUDE_DIR, ${RE2_INCLUDE_DIR}) endif() ================================================ FILE: build/fbcode_builder/CMake/FindSodium.cmake ================================================ # Written in 2016 by Henrik Steffen Gaßmann # # To the extent possible under law, the author(s) have dedicated all # copyright and related and neighboring rights to this software to the # public domain worldwide. This software is distributed without any warranty. # # You should have received a copy of the CC0 Public Domain Dedication # along with this software. If not, see # # http://creativecommons.org/publicdomain/zero/1.0/ # ######################################################################## # Tries to find the local libsodium installation. # # On Windows the sodium_DIR environment variable is used as a default # hint which can be overridden by setting the corresponding cmake variable. # # Once done the following variables will be defined: # # sodium_FOUND # sodium_INCLUDE_DIR # sodium_LIBRARY_DEBUG # sodium_LIBRARY_RELEASE # # # Furthermore an imported "sodium" target is created. # if (CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "Clang") set(_GCC_COMPATIBLE 1) endif() # static library option if (NOT DEFINED sodium_USE_STATIC_LIBS) option(sodium_USE_STATIC_LIBS "enable to statically link against sodium" OFF) endif() if(NOT (sodium_USE_STATIC_LIBS EQUAL sodium_USE_STATIC_LIBS_LAST)) unset(sodium_LIBRARY CACHE) unset(sodium_LIBRARY_DEBUG CACHE) unset(sodium_LIBRARY_RELEASE CACHE) unset(sodium_DLL_DEBUG CACHE) unset(sodium_DLL_RELEASE CACHE) set(sodium_USE_STATIC_LIBS_LAST ${sodium_USE_STATIC_LIBS} CACHE INTERNAL "internal change tracking variable") endif() ######################################################################## # UNIX if (UNIX) # import pkg-config find_package(PkgConfig QUIET) if (PKG_CONFIG_FOUND) pkg_check_modules(sodium_PKG QUIET libsodium) endif() if(sodium_USE_STATIC_LIBS) foreach(_libname ${sodium_PKG_STATIC_LIBRARIES}) if (NOT _libname MATCHES "^lib.*\\.a$") # ignore strings already ending with .a list(INSERT sodium_PKG_STATIC_LIBRARIES 0 "lib${_libname}.a") endif() endforeach() list(REMOVE_DUPLICATES sodium_PKG_STATIC_LIBRARIES) # if pkgconfig for libsodium doesn't provide # static lib info, then override PKG_STATIC here.. if (NOT sodium_PKG_STATIC_FOUND) set(sodium_PKG_STATIC_LIBRARIES libsodium.a) endif() set(XPREFIX sodium_PKG_STATIC) else() if (NOT sodium_PKG_FOUND) set(sodium_PKG_LIBRARIES sodium) endif() set(XPREFIX sodium_PKG) endif() find_path(sodium_INCLUDE_DIR sodium.h HINTS ${${XPREFIX}_INCLUDE_DIRS} ) find_library(sodium_LIBRARY_DEBUG NAMES ${${XPREFIX}_LIBRARIES} HINTS ${${XPREFIX}_LIBRARY_DIRS} ) find_library(sodium_LIBRARY_RELEASE NAMES ${${XPREFIX}_LIBRARIES} HINTS ${${XPREFIX}_LIBRARY_DIRS} ) ######################################################################## # Windows elseif (WIN32) set(sodium_DIR "$ENV{sodium_DIR}" CACHE FILEPATH "sodium install directory") mark_as_advanced(sodium_DIR) find_path(sodium_INCLUDE_DIR sodium.h HINTS ${sodium_DIR} PATH_SUFFIXES include ) if (MSVC) # detect target architecture file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/arch.cpp" [=[ #if defined _M_IX86 #error ARCH_VALUE x86_32 #elif defined _M_X64 #error ARCH_VALUE x86_64 #endif #error ARCH_VALUE unknown ]=]) try_compile(_UNUSED_VAR "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/arch.cpp" OUTPUT_VARIABLE _COMPILATION_LOG ) string(REGEX REPLACE ".*ARCH_VALUE ([a-zA-Z0-9_]+).*" "\\1" _TARGET_ARCH "${_COMPILATION_LOG}") # construct library path if (_TARGET_ARCH STREQUAL "x86_32") string(APPEND _PLATFORM_PATH "Win32") elseif(_TARGET_ARCH STREQUAL "x86_64") string(APPEND _PLATFORM_PATH "x64") else() message(FATAL_ERROR "the ${_TARGET_ARCH} architecture is not supported by Findsodium.cmake.") endif() string(APPEND _PLATFORM_PATH "/$$CONFIG$$") if (MSVC_VERSION LESS 1900) math(EXPR _VS_VERSION "${MSVC_VERSION} / 10 - 60") else() math(EXPR _VS_VERSION "${MSVC_VERSION} / 10 - 50") endif() string(APPEND _PLATFORM_PATH "/v${_VS_VERSION}") if (sodium_USE_STATIC_LIBS) string(APPEND _PLATFORM_PATH "/static") else() string(APPEND _PLATFORM_PATH "/dynamic") endif() string(REPLACE "$$CONFIG$$" "Debug" _DEBUG_PATH_SUFFIX "${_PLATFORM_PATH}") string(REPLACE "$$CONFIG$$" "Release" _RELEASE_PATH_SUFFIX "${_PLATFORM_PATH}") find_library(sodium_LIBRARY_DEBUG libsodium.lib HINTS ${sodium_DIR} PATH_SUFFIXES ${_DEBUG_PATH_SUFFIX} ) find_library(sodium_LIBRARY_RELEASE libsodium.lib HINTS ${sodium_DIR} PATH_SUFFIXES ${_RELEASE_PATH_SUFFIX} ) if (NOT sodium_USE_STATIC_LIBS) set(CMAKE_FIND_LIBRARY_SUFFIXES_BCK ${CMAKE_FIND_LIBRARY_SUFFIXES}) set(CMAKE_FIND_LIBRARY_SUFFIXES ".dll") find_library(sodium_DLL_DEBUG libsodium HINTS ${sodium_DIR} PATH_SUFFIXES ${_DEBUG_PATH_SUFFIX} ) find_library(sodium_DLL_RELEASE libsodium HINTS ${sodium_DIR} PATH_SUFFIXES ${_RELEASE_PATH_SUFFIX} ) set(CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES_BCK}) endif() elseif(_GCC_COMPATIBLE) if (sodium_USE_STATIC_LIBS) find_library(sodium_LIBRARY_DEBUG libsodium.a HINTS ${sodium_DIR} PATH_SUFFIXES lib ) find_library(sodium_LIBRARY_RELEASE libsodium.a HINTS ${sodium_DIR} PATH_SUFFIXES lib ) else() find_library(sodium_LIBRARY_DEBUG libsodium.dll.a HINTS ${sodium_DIR} PATH_SUFFIXES lib ) find_library(sodium_LIBRARY_RELEASE libsodium.dll.a HINTS ${sodium_DIR} PATH_SUFFIXES lib ) file(GLOB _DLL LIST_DIRECTORIES false RELATIVE "${sodium_DIR}/bin" "${sodium_DIR}/bin/libsodium*.dll" ) find_library(sodium_DLL_DEBUG ${_DLL} libsodium HINTS ${sodium_DIR} PATH_SUFFIXES bin ) find_library(sodium_DLL_RELEASE ${_DLL} libsodium HINTS ${sodium_DIR} PATH_SUFFIXES bin ) endif() else() message(FATAL_ERROR "this platform is not supported by FindSodium.cmake") endif() ######################################################################## # unsupported else() message(FATAL_ERROR "this platform is not supported by FindSodium.cmake") endif() ######################################################################## # common stuff # extract sodium version if (sodium_INCLUDE_DIR) set(_VERSION_HEADER "${_INCLUDE_DIR}/sodium/version.h") if (EXISTS _VERSION_HEADER) file(READ "${_VERSION_HEADER}" _VERSION_HEADER_CONTENT) string(REGEX REPLACE ".*#[ \t]*define[ \t]*SODIUM_VERSION_STRING[ \t]*\"([^\n]*)\".*" "\\1" sodium_VERSION "${_VERSION_HEADER_CONTENT}") set(sodium_VERSION "${sodium_VERSION}" PARENT_SCOPE) endif() endif() # communicate results include(FindPackageHandleStandardArgs) find_package_handle_standard_args( Sodium # The name must be either uppercase or match the filename case. REQUIRED_VARS sodium_LIBRARY_RELEASE sodium_LIBRARY_DEBUG sodium_INCLUDE_DIR VERSION_VAR sodium_VERSION ) if(Sodium_FOUND) set(sodium_LIBRARIES optimized ${sodium_LIBRARY_RELEASE} debug ${sodium_LIBRARY_DEBUG}) endif() # mark file paths as advanced mark_as_advanced(sodium_INCLUDE_DIR) mark_as_advanced(sodium_LIBRARY_DEBUG) mark_as_advanced(sodium_LIBRARY_RELEASE) if (WIN32) mark_as_advanced(sodium_DLL_DEBUG) mark_as_advanced(sodium_DLL_RELEASE) endif() # create imported target if(sodium_USE_STATIC_LIBS) set(_LIB_TYPE STATIC) else() set(_LIB_TYPE SHARED) endif() if(NOT TARGET sodium) add_library(sodium ${_LIB_TYPE} IMPORTED) endif() set_target_properties(sodium PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${sodium_INCLUDE_DIR}" IMPORTED_LINK_INTERFACE_LANGUAGES "C" ) if (sodium_USE_STATIC_LIBS) set_target_properties(sodium PROPERTIES INTERFACE_COMPILE_DEFINITIONS "SODIUM_STATIC" IMPORTED_LOCATION "${sodium_LIBRARY_RELEASE}" IMPORTED_LOCATION_DEBUG "${sodium_LIBRARY_DEBUG}" ) else() if (UNIX) set_target_properties(sodium PROPERTIES IMPORTED_LOCATION "${sodium_LIBRARY_RELEASE}" IMPORTED_LOCATION_DEBUG "${sodium_LIBRARY_DEBUG}" ) elseif (WIN32) set_target_properties(sodium PROPERTIES IMPORTED_IMPLIB "${sodium_LIBRARY_RELEASE}" IMPORTED_IMPLIB_DEBUG "${sodium_LIBRARY_DEBUG}" ) if (NOT (sodium_DLL_DEBUG MATCHES ".*-NOTFOUND")) set_target_properties(sodium PROPERTIES IMPORTED_LOCATION_DEBUG "${sodium_DLL_DEBUG}" ) endif() if (NOT (sodium_DLL_RELEASE MATCHES ".*-NOTFOUND")) set_target_properties(sodium PROPERTIES IMPORTED_LOCATION_RELWITHDEBINFO "${sodium_DLL_RELEASE}" IMPORTED_LOCATION_MINSIZEREL "${sodium_DLL_RELEASE}" IMPORTED_LOCATION_RELEASE "${sodium_DLL_RELEASE}" ) endif() endif() endif() ================================================ FILE: build/fbcode_builder/CMake/FindXxhash.cmake ================================================ # Copyright (c) Facebook, Inc. and its affiliates. # # 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. # # - Try to find Facebook xxhash library # This will define # Xxhash_FOUND # Xxhash_INCLUDE_DIR # Xxhash_LIBRARY # find_path(Xxhash_INCLUDE_DIR NAMES xxhash.h) find_library(Xxhash_LIBRARY_RELEASE NAMES xxhash) include(SelectLibraryConfigurations) SELECT_LIBRARY_CONFIGURATIONS(Xxhash) include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS( Xxhash DEFAULT_MSG Xxhash_LIBRARY Xxhash_INCLUDE_DIR ) if (Xxhash_FOUND) message(STATUS "Found xxhash: ${Xxhash_LIBRARY}") endif() mark_as_advanced(Xxhash_INCLUDE_DIR Xxhash_LIBRARY) ================================================ FILE: build/fbcode_builder/CMake/FindZstd.cmake ================================================ # Copyright (c) Facebook, Inc. and its affiliates. # # 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. # # - Try to find Facebook zstd library # This will define # ZSTD_FOUND # ZSTD_INCLUDE_DIR # ZSTD_LIBRARY # find_path(ZSTD_INCLUDE_DIR NAMES zstd.h) find_library(ZSTD_LIBRARY_DEBUG NAMES zstdd zstd_staticd) find_library(ZSTD_LIBRARY_RELEASE NAMES zstd zstd_static) include(SelectLibraryConfigurations) SELECT_LIBRARY_CONFIGURATIONS(ZSTD) include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS( Zstd DEFAULT_MSG ZSTD_LIBRARY ZSTD_INCLUDE_DIR ) if (ZSTD_FOUND) message(STATUS "Found Zstd: ${ZSTD_LIBRARY}") endif() mark_as_advanced(ZSTD_INCLUDE_DIR ZSTD_LIBRARY) ================================================ FILE: build/fbcode_builder/CMake/Findibverbs.cmake ================================================ # Copyright (c) Facebook, Inc. and its affiliates. # # 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. # Find the ibverbs libraries # # The following variables are optionally searched for defaults # IBVERBS_ROOT_DIR: Base directory where all ibverbs components are found # IBVERBS_INCLUDE_DIR: Directory where ibverbs headers are found # IBVERBS_LIB_DIR: Directory where ibverbs libraries are found # The following are set after configuration is done: # IBVERBS_FOUND # IBVERBS_INCLUDE_DIRS # IBVERBS_LIBRARIES # IBVERBS_VERSION find_path(IBVERBS_INCLUDE_DIRS NAMES infiniband/verbs.h HINTS ${IBVERBS_INCLUDE_DIR} ${IBVERBS_ROOT_DIR} ${IBVERBS_ROOT_DIR}/include) find_library(IBVERBS_LIBRARIES NAMES ibverbs HINTS ${IBVERBS_LIB_DIR} ${IBVERBS_ROOT_DIR} ${IBVERBS_ROOT_DIR}/lib) # Try to determine the rdma-core version if(IBVERBS_INCLUDE_DIRS AND IBVERBS_LIBRARIES) # First try using pkg-config if available find_package(PkgConfig QUIET) if(PKG_CONFIG_FOUND) pkg_check_modules(PC_RDMA_CORE QUIET rdma-core) if(PC_RDMA_CORE_VERSION) set(IBVERBS_VERSION ${PC_RDMA_CORE_VERSION}) endif() endif() # If pkg-config didn't work, try to extract version from library filename # According to rdma-core Documentation/versioning.md: # Library filename format: # libibverbs.so.SONAME.ABI.PACKAGE_VERSION_MAIN[.PACKAGE_VERSION_BRANCH] # Where: # - SONAME: Major version (1st field) # - ABI: ABI version number (2nd field) # - PACKAGE_VERSION_MAIN: Main package version (3rd field) # - PACKAGE_VERSION_BRANCH: Optional counter for branched stable # releases (4th field, part of PACKAGE_VERSION) # Example: libibverbs.so.1.14.57.0 → SONAME=1, ABI=14, # PACKAGE_VERSION=57.0 if(NOT IBVERBS_VERSION) # Get the real path of the library (follows symlinks) get_filename_component(IBVERBS_REAL_PATH "${IBVERBS_LIBRARIES}" REALPATH) get_filename_component(IBVERBS_LIB_NAME "${IBVERBS_REAL_PATH}" NAME) # Extract version from filename if(IBVERBS_LIB_NAME MATCHES "libibverbs\\.so\\.([0-9]+)\\.([0-9]+)\\.([0-9]+)\\.([0-9]+)") # Four-component version: PACKAGE_VERSION_MAIN.PACKAGE_VERSION_BRANCH set(IBVERBS_VERSION_MAJOR ${CMAKE_MATCH_3}) set(IBVERBS_VERSION_MINOR ${CMAKE_MATCH_4}) set(IBVERBS_VERSION "${IBVERBS_VERSION_MAJOR}.${IBVERBS_VERSION_MINOR}") elseif(IBVERBS_LIB_NAME MATCHES "libibverbs\\.so\\.([0-9]+)\\.([0-9]+)\\.([0-9]+)") # Three-component version: PACKAGE_VERSION_MAIN only set(IBVERBS_VERSION_MAJOR ${CMAKE_MATCH_3}) set(IBVERBS_VERSION "${IBVERBS_VERSION_MAJOR}.0") else() # If we can't parse the filename, set to empty string # Feature detection will be done in CMakeLists.txt set(IBVERBS_VERSION "") endif() endif() endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(ibverbs REQUIRED_VARS IBVERBS_INCLUDE_DIRS IBVERBS_LIBRARIES VERSION_VAR IBVERBS_VERSION) mark_as_advanced(IBVERBS_INCLUDE_DIRS IBVERBS_LIBRARIES IBVERBS_VERSION) ================================================ FILE: build/fbcode_builder/CMake/RustStaticLibrary.cmake ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. include(FBCMakeParseArgs) set( USE_CARGO_VENDOR AUTO CACHE STRING "Download Rust Crates from an internally vendored location" ) set_property(CACHE USE_CARGO_VENDOR PROPERTY STRINGS AUTO ON OFF) set( GENERATE_CARGO_VENDOR_CONFIG AUTO CACHE STRING "Whether to generate Rust cargo vendor config or use existing" ) set_property(CACHE GENERATE_CARGO_VENDOR_CONFIG PROPERTY STRINGS AUTO ON OFF) set(RUST_VENDORED_CRATES_DIR "$ENV{RUST_VENDORED_CRATES_DIR}") if("${USE_CARGO_VENDOR}" STREQUAL "AUTO") if(EXISTS "${RUST_VENDORED_CRATES_DIR}") set(USE_CARGO_VENDOR ON) else() set(USE_CARGO_VENDOR OFF) endif() endif() if("${GENERATE_CARGO_VENDOR_CONFIG}" STREQUAL "AUTO") set(GENERATE_CARGO_VENDOR_CONFIG "${USE_CARGO_VENDOR}") endif() if(GENERATE_CARGO_VENDOR_CONFIG) if(NOT EXISTS "${RUST_VENDORED_CRATES_DIR}") message( FATAL "vendored rust crates not present: " "${RUST_VENDORED_CRATES_DIR}" ) endif() set(RUST_CARGO_HOME "${CMAKE_BINARY_DIR}/_cargo_home") file(MAKE_DIRECTORY "${RUST_CARGO_HOME}") file( TO_NATIVE_PATH "${RUST_VENDORED_CRATES_DIR}" ESCAPED_RUST_VENDORED_CRATES_DIR ) string( REPLACE "\\" "\\\\" ESCAPED_RUST_VENDORED_CRATES_DIR "${ESCAPED_RUST_VENDORED_CRATES_DIR}" ) file( WRITE "${RUST_CARGO_HOME}/config" "[source.crates-io]\n" "replace-with = \"vendored-sources\"\n" "\n" "[source.vendored-sources]\n" "directory = \"${ESCAPED_RUST_VENDORED_CRATES_DIR}\"\n" ) endif() find_program(CARGO_COMMAND cargo REQUIRED) # Cargo is a build system in itself, and thus will try to take advantage of all # the cores on the system. Unfortunately, this conflicts with Ninja, since it # also tries to utilize all the cores. This can lead to a system that is # completely overloaded with compile jobs to the point where nothing else can # be achieved on the system. # # Let's inform Ninja of this fact so it won't try to spawn other jobs while # Rust being compiled. set_property(GLOBAL APPEND PROPERTY JOB_POOLS rust_job_pool=1) # This function creates an interface library target based on the static library # built by Cargo. It will call Cargo to build a staticlib and generate a CMake # interface library with it. # # This function requires `find_package(Python COMPONENTS Interpreter)`. # # You need to set `lib:crate-type = ["staticlib"]` in your Cargo.toml to make # Cargo build static library. # # ```cmake # rust_static_library( [CRATE ] [FEATURES ] [USE_CXX_INCLUDE]) # ``` # # Parameters: # - TARGET: # Name of the target name. This function will create an interface library # target with this name. # - CRATE_NAME: # Name of the crate. This parameter is optional. If unspecified, it will # fallback to `${TARGET}`. # - FEATURE_NAME: # Name of the Rust feature to enable. # - USE_CXX_INCLUDE: # Include cxx.rs include path in `${TARGET}` INTERFACE. # # This function creates two targets: # - "${TARGET}": an interface library target contains the static library built # from Cargo. # - "${TARGET}.cargo": an internal custom target that invokes Cargo. # # If you are going to use this static library from C/C++, you will need to # write header files for the library (or generate with cbindgen) and bind these # headers with the interface library. # function(rust_static_library TARGET) fb_cmake_parse_args(ARG "USE_CXX_INCLUDE" "CRATE;FEATURES" "" "${ARGN}") if(DEFINED ARG_CRATE) set(crate_name "${ARG_CRATE}") else() set(crate_name "${TARGET}") endif() if(DEFINED ARG_FEATURES) set(features --features ${ARG_FEATURES}) else() set(features ) endif() set(cargo_target "${TARGET}.cargo") set(target_dir $,debug,release>) set(staticlib_name "${CMAKE_STATIC_LIBRARY_PREFIX}${crate_name}${CMAKE_STATIC_LIBRARY_SUFFIX}") set(rust_staticlib "${CMAKE_CURRENT_BINARY_DIR}/${target_dir}/${staticlib_name}") if(DEFINED ARG_FEATURES) set(cargo_flags build $,,--release> -p ${crate_name} --features ${ARG_FEATURES} --config fbcode_build=false) else() set(cargo_flags build $,,--release> -p ${crate_name} --config fbcode_build=false) endif() if(USE_CARGO_VENDOR) set(extra_cargo_env "CARGO_HOME=${RUST_CARGO_HOME}") set(cargo_flags ${cargo_flags}) endif() add_custom_target( ${cargo_target} COMMAND "${CMAKE_COMMAND}" -E remove -f "${CMAKE_CURRENT_SOURCE_DIR}/Cargo.lock" COMMAND "${CMAKE_COMMAND}" -E env "CARGO_TARGET_DIR=${CMAKE_CURRENT_BINARY_DIR}" ${extra_cargo_env} ${CARGO_COMMAND} ${cargo_flags} COMMENT "Building Rust crate '${crate_name}'..." JOB_POOL rust_job_pool WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} BYPRODUCTS "${CMAKE_CURRENT_BINARY_DIR}/debug/${staticlib_name}" "${CMAKE_CURRENT_BINARY_DIR}/release/${staticlib_name}" ) add_library(${TARGET} INTERFACE) add_dependencies(${TARGET} ${cargo_target}) set_target_properties( ${TARGET} PROPERTIES INTERFACE_STATICLIB_OUTPUT_PATH "${rust_staticlib}" INTERFACE_INSTALL_LIBNAME "${CMAKE_STATIC_LIBRARY_PREFIX}${crate_name}_rs${CMAKE_STATIC_LIBRARY_SUFFIX}" ) if(DEFINED ARG_USE_CXX_INCLUDE) target_include_directories( ${TARGET} INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/cxxbridge/ ) endif() target_link_libraries( ${TARGET} INTERFACE "$" ) endfunction() # This function instructs CMake to define a target that will use `cargo build` # to build a bin crate referenced by the Cargo.toml file in the current source # directory. # It accepts a single `TARGET` parameter which will be passed as the package # name to `cargo build -p TARGET`. If binary has different name as package, # use optional flag BINARY_NAME to override it. # It also accepts a `FEATURES` parameter if you want to enable certain features # in your Rust binary. # The CMake target will be registered to build by default as part of the # ALL target. function(rust_executable TARGET) fb_cmake_parse_args(ARG "" "BINARY_NAME;FEATURES" "" "${ARGN}") set(crate_name "${TARGET}") set(cargo_target "${TARGET}.cargo") set(target_dir $,debug,release>) if(DEFINED ARG_BINARY_NAME) set(executable_name "${ARG_BINARY_NAME}${CMAKE_EXECUTABLE_SUFFIX}") else() set(executable_name "${crate_name}${CMAKE_EXECUTABLE_SUFFIX}") endif() if(DEFINED ARG_FEATURES) set(features --features ${ARG_FEATURES}) else() set(features ) endif() if(DEFINED ARG_FEATURES) set(cargo_flags build $,,--release> -p ${crate_name} --features ${ARG_FEATURES}) else() set(cargo_flags build $,,--release> -p ${crate_name}) endif() if(USE_CARGO_VENDOR) set(extra_cargo_env "CARGO_HOME=${RUST_CARGO_HOME}") set(cargo_flags ${cargo_flags}) endif() add_custom_target( ${cargo_target} ALL COMMAND "${CMAKE_COMMAND}" -E remove -f "${CMAKE_CURRENT_SOURCE_DIR}/Cargo.lock" COMMAND "${CMAKE_COMMAND}" -E env "CARGO_TARGET_DIR=${CMAKE_CURRENT_BINARY_DIR}" ${extra_cargo_env} ${CARGO_COMMAND} ${cargo_flags} COMMENT "Building Rust executable '${crate_name}'..." JOB_POOL rust_job_pool WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} BYPRODUCTS "${CMAKE_CURRENT_BINARY_DIR}/debug/${executable_name}" "${CMAKE_CURRENT_BINARY_DIR}/release/${executable_name}" ) set_property(TARGET "${cargo_target}" PROPERTY EXECUTABLE "${CMAKE_CURRENT_BINARY_DIR}/${target_dir}/${executable_name}") endfunction() # This function can be used to install the executable generated by a prior # call to the `rust_executable` function. # It requires a `TARGET` parameter to identify the target to be installed, # and an optional `DESTINATION` parameter to specify the installation # directory. If DESTINATION is not specified then the `bin` directory # will be assumed. function(install_rust_executable TARGET) # Parse the arguments set(one_value_args DESTINATION) set(multi_value_args) fb_cmake_parse_args( ARG "" "${one_value_args}" "${multi_value_args}" "${ARGN}" ) if(NOT DEFINED ARG_DESTINATION) set(ARG_DESTINATION bin) endif() get_target_property(foo "${TARGET}.cargo" EXECUTABLE) install( PROGRAMS "${foo}" DESTINATION "${ARG_DESTINATION}" ) endfunction() # This function installs the interface target generated from the function # `rust_static_library`. Use this function if you want to export your Rust # target to external CMake targets. # # ```cmake # install_rust_static_library( # # INSTALL_DIR # [EXPORT ] # ) # ``` # # Parameters: # - TARGET: Name of the Rust static library target. # - EXPORT_NAME: Name of the exported target. # - INSTALL_DIR: Path to the directory where this library will be installed. # function(install_rust_static_library TARGET) fb_cmake_parse_args(ARG "" "EXPORT;INSTALL_DIR" "" "${ARGN}") get_property( staticlib_output_path TARGET "${TARGET}" PROPERTY INTERFACE_STATICLIB_OUTPUT_PATH ) get_property( staticlib_output_name TARGET "${TARGET}" PROPERTY INTERFACE_INSTALL_LIBNAME ) if(NOT DEFINED staticlib_output_path) message(FATAL_ERROR "Not a rust_static_library target.") endif() if(NOT DEFINED ARG_INSTALL_DIR) message(FATAL_ERROR "Missing required argument.") endif() if(DEFINED ARG_EXPORT) set(install_export_args EXPORT "${ARG_EXPORT}") endif() set(install_interface_dir "${ARG_INSTALL_DIR}") if(NOT IS_ABSOLUTE "${install_interface_dir}") set(install_interface_dir "\${_IMPORT_PREFIX}/${install_interface_dir}") endif() target_link_libraries( ${TARGET} INTERFACE "$" ) install( TARGETS ${TARGET} ${install_export_args} LIBRARY DESTINATION ${ARG_INSTALL_DIR} ) install( FILES ${staticlib_output_path} RENAME ${staticlib_output_name} DESTINATION ${ARG_INSTALL_DIR} ) endfunction() # This function creates C++ bindings using the [cxx] crate. # # Original function found here: https://github.com/corrosion-rs/corrosion/blob/master/cmake/Corrosion.cmake#L1390 # Simplified for use as part of RustStaticLibrary module. License below. # # MIT License # # Copyright (c) 2018 Andrew Gaspar # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # # The rules approximately do the following: # - Check which version of `cxx` the Rust crate depends on. # - Check if the exact same version of `cxxbridge-cmd` is installed # - If not, create a rule to build the exact same version of `cxxbridge-cmd`. # - Create rules to run `cxxbridge` and generate # - The `rust/cxx.h` header # - A header and source file for the specified CXX_BRIDGE_FILE. # - The generated sources (and header include directories) are added to the # `${TARGET}` CMake library target. # # ```cmake # rust_cxx_bridge( [CRATE ] [LIBS ]) # ``` # # Parameters: # - TARGET: # Name of the target name. The target that the bridge will be included with. # - CXX_BRIDGE_FILE: # Name of the file that include the cxxbridge (e.g., "src/ffi.rs"). # - CRATE_NAME: # Name of the crate. This parameter is optional. If unspecified, it will # fallback to `${TARGET}`. # - LIBS [ ...]: # A list of libraries that this library depends on. # function(rust_cxx_bridge TARGET CXX_BRIDGE_FILE) fb_cmake_parse_args(ARG "" "CRATE" "LIBS" "${ARGN}") if(DEFINED ARG_CRATE) set(crate_name "${ARG_CRATE}") else() set(crate_name "${TARGET}") endif() if(USE_CARGO_VENDOR) set(extra_cargo_env "CARGO_HOME=${RUST_CARGO_HOME}") endif() execute_process( COMMAND "${CMAKE_COMMAND}" -E env ${extra_cargo_env} "${CARGO_COMMAND}" tree -i cxx --depth=0 WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} RESULT_VARIABLE cxx_version_result OUTPUT_VARIABLE cxx_version_output ) if(NOT "${cxx_version_result}" EQUAL "0") message(FATAL_ERROR "Crate ${crate_name} does not depend on cxx.") endif() if(cxx_version_output MATCHES "cxx v([0-9]+.[0-9]+.[0-9]+)") set(cxx_required_version "${CMAKE_MATCH_1}") else() message( FATAL_ERROR "Failed to parse cxx version from cargo tree output: `cxx_version_output`") endif() # First check if a suitable version of cxxbridge is installed find_program(INSTALLED_CXXBRIDGE cxxbridge PATHS "$ENV{HOME}/.cargo/bin/") mark_as_advanced(INSTALLED_CXXBRIDGE) if(INSTALLED_CXXBRIDGE) execute_process( COMMAND "${INSTALLED_CXXBRIDGE}" --version OUTPUT_VARIABLE cxxbridge_version_output ) if(cxxbridge_version_output MATCHES "cxxbridge ([0-9]+.[0-9]+.[0-9]+)") set(cxxbridge_version "${CMAKE_MATCH_1}") else() set(cxxbridge_version "") endif() endif() set(cxxbridge "") if(cxxbridge_version) if(cxxbridge_version VERSION_EQUAL cxx_required_version) set(cxxbridge "${INSTALLED_CXXBRIDGE}") if(NOT TARGET "cxxbridge_v${cxx_required_version}") # Add an empty target. add_custom_target("cxxbridge_v${cxx_required_version}") endif() endif() endif() # No suitable version of cxxbridge was installed, # so use custom target to install correct version. if(NOT cxxbridge) if(NOT TARGET "cxxbridge_v${cxx_required_version}") add_custom_command( OUTPUT "${CMAKE_BINARY_DIR}/cxxbridge_v${cxx_required_version}/bin/cxxbridge" COMMAND "${CMAKE_COMMAND}" -E make_directory "${CMAKE_BINARY_DIR}/cxxbridge_v${cxx_required_version}" COMMAND "${CMAKE_COMMAND}" -E remove -f "${CMAKE_CURRENT_SOURCE_DIR}/Cargo.lock" COMMAND "${CMAKE_COMMAND}" -E env ${extra_cargo_env} "${CARGO_COMMAND}" install cxxbridge-cmd --version "${cxx_required_version}" --root "${CMAKE_BINARY_DIR}/cxxbridge_v${cxx_required_version}" --quiet COMMAND "${CMAKE_COMMAND}" -E remove -f "${CMAKE_CURRENT_SOURCE_DIR}/Cargo.lock" COMMENT "Installing cxxbridge (version ${cxx_required_version})" ) add_custom_target( "cxxbridge_v${cxx_required_version}" DEPENDS "${CMAKE_BINARY_DIR}/cxxbridge_v${cxx_required_version}/bin/cxxbridge" ) endif() set( cxxbridge "${CMAKE_BINARY_DIR}/cxxbridge_v${cxx_required_version}/bin/cxxbridge" ) endif() add_library(${crate_name} STATIC) target_include_directories( ${crate_name} PUBLIC $ $ ) target_link_libraries( ${crate_name} PUBLIC ${ARG_LIBS} ) file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/rust") add_custom_command( OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/rust/cxx.h" COMMAND "${cxxbridge}" --header --output "${CMAKE_CURRENT_BINARY_DIR}/rust/cxx.h" DEPENDS "cxxbridge_v${cxx_required_version}" COMMENT "Generating rust/cxx.h header" ) get_filename_component(filename_component ${CXX_BRIDGE_FILE} NAME) get_filename_component(directory_component ${CXX_BRIDGE_FILE} DIRECTORY) set(directory "") if(directory_component) set(directory "${directory_component}") endif() set(cxx_header ${directory}/${filename_component}.h) set(cxx_source ${directory}/${filename_component}.cc) set(rust_source_path "${CMAKE_CURRENT_SOURCE_DIR}/${CXX_BRIDGE_FILE}") file( MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${directory_component}" ) add_custom_command( OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${cxx_header}" "${CMAKE_CURRENT_BINARY_DIR}/${cxx_source}" COMMAND ${cxxbridge} ${rust_source_path} --cfg fbcode_build=false --header --output "${CMAKE_CURRENT_BINARY_DIR}/${cxx_header}" COMMAND ${cxxbridge} ${rust_source_path} --cfg fbcode_build=false --output "${CMAKE_CURRENT_BINARY_DIR}/${cxx_source}" --include "${cxx_header}" DEPENDS "cxxbridge_v${cxx_required_version}" "${rust_source_path}" COMMENT "Generating cxx bindings for crate ${crate_name}" ) target_sources( ${crate_name} PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/${cxx_header}" "${CMAKE_CURRENT_BINARY_DIR}/rust/cxx.h" "${CMAKE_CURRENT_BINARY_DIR}/${cxx_source}" ) endfunction() ================================================ FILE: build/fbcode_builder/CMake/fb_py_test_main.py ================================================ #!/usr/bin/env python # # Copyright (c) Facebook, Inc. and its affiliates. # """ This file contains the main module code for Python test programs. """ import contextlib import ctypes import fnmatch import json import logging import optparse import os import platform import re import sys import tempfile import time import traceback import unittest import warnings from importlib.machinery import PathFinder try: from StringIO import StringIO except ImportError: from io import StringIO try: import coverage except ImportError: coverage = None # type: ignore try: from importlib.machinery import SourceFileLoader except ImportError: SourceFileLoader = None # type: ignore class get_cpu_instr_counter: def read(self): # TODO return 0 EXIT_CODE_SUCCESS = 0 EXIT_CODE_TEST_FAILURE = 70 class TestStatus: ABORTED = "FAILURE" PASSED = "SUCCESS" FAILED = "FAILURE" EXPECTED_FAILURE = "SUCCESS" UNEXPECTED_SUCCESS = "FAILURE" SKIPPED = "ASSUMPTION_VIOLATION" class PathMatcher: def __init__(self, include_patterns, omit_patterns): self.include_patterns = include_patterns self.omit_patterns = omit_patterns def omit(self, path): """ Omit iff matches any of the omit_patterns or the include patterns are not empty and none is matched """ path = os.path.realpath(path) return any(fnmatch.fnmatch(path, p) for p in self.omit_patterns) or ( self.include_patterns and not any(fnmatch.fnmatch(path, p) for p in self.include_patterns) ) def include(self, path): return not self.omit(path) class DebugWipeFinder(PathFinder): """ PEP 302 finder that uses a DebugWipeLoader for all files which do not need coverage """ def __init__(self, matcher): self.matcher = matcher def find_spec(self, fullname, path=None, target=None): spec = super().find_spec(fullname, path=path, target=target) if spec is None or spec.origin is None: return None if not spec.origin.endswith(".py"): return None if self.matcher.include(spec.origin): return None class PyVarObject(ctypes.Structure): _fields_ = [ ("ob_refcnt", ctypes.c_long), ("ob_type", ctypes.c_void_p), ("ob_size", ctypes.c_ulong), ] class DebugWipeLoader(SourceFileLoader): """ PEP302 loader that zeros out debug information before execution """ def get_code(self, fullname): code = super().get_code(fullname) if code: # Ideally we'd do # code.co_lnotab = b'' # But code objects are READONLY. Not to worry though; we'll # directly modify CPython's object code_impl = PyVarObject.from_address(id(code.co_lnotab)) code_impl.ob_size = 0 return code if isinstance(spec.loader, SourceFileLoader): spec.loader = DebugWipeLoader(fullname, spec.origin) return spec def optimize_for_coverage(cov, include_patterns, omit_patterns): """ We get better performance if we zero out debug information for files which we're not interested in. Only available in CPython 3.3+ """ matcher = PathMatcher(include_patterns, omit_patterns) if SourceFileLoader and platform.python_implementation() == "CPython": sys.meta_path.insert(0, DebugWipeFinder(matcher)) class TeeStream: def __init__(self, *streams): self._streams = streams def write(self, data): for stream in self._streams: stream.write(data) def flush(self): for stream in self._streams: stream.flush() def isatty(self): return False class CallbackStream: def __init__(self, callback, bytes_callback=None, orig=None): self._callback = callback self._fileno = orig.fileno() if orig else None # Python 3 APIs: # - `encoding` is a string holding the encoding name # - `errors` is a string holding the error-handling mode for encoding # - `buffer` should look like an io.BufferedIOBase object self.errors = orig.errors if orig else None if bytes_callback: # those members are only on the io.TextIOWrapper self.encoding = orig.encoding if orig else "UTF-8" self.buffer = CallbackStream(bytes_callback, orig=orig) def write(self, data): self._callback(data) def flush(self): pass def isatty(self): return False def fileno(self): return self._fileno class BuckTestResult(unittest.TextTestResult): """ Our own TestResult class that outputs data in a format that can be easily parsed by buck's test runner. """ _instr_counter = get_cpu_instr_counter() def __init__( self, stream, descriptions, verbosity, show_output, main_program, suite ): super(BuckTestResult, self).__init__(stream, descriptions, verbosity) self._main_program = main_program self._suite = suite self._results = [] self._current_test = None self._saved_stdout = sys.stdout self._saved_stderr = sys.stderr self._show_output = show_output def getResults(self): return self._results def startTest(self, test): super(BuckTestResult, self).startTest(test) # Pass in the real stdout and stderr filenos. We can't really do much # here to intercept callers who directly operate on these fileno # objects. sys.stdout = CallbackStream( self.addStdout, self.addStdoutBytes, orig=sys.stdout ) sys.stderr = CallbackStream( self.addStderr, self.addStderrBytes, orig=sys.stderr ) self._current_test = test self._test_start_time = time.time() self._current_status = TestStatus.ABORTED self._messages = [] self._stacktrace = None self._stdout = "" self._stderr = "" self._start_instr_count = self._instr_counter.read() def _find_next_test(self, suite): """ Find the next test that has not been run. """ for test in suite: # We identify test suites by test that are iterable (as is done in # the builtin python test harness). If we see one, recurse on it. if hasattr(test, "__iter__"): test = self._find_next_test(test) # The builtin python test harness sets test references to `None` # after they have run, so we know we've found the next test up # if it's not `None`. if test is not None: return test def stopTest(self, test): sys.stdout = self._saved_stdout sys.stderr = self._saved_stderr super(BuckTestResult, self).stopTest(test) # If a failure occurred during module/class setup, then this "test" may # actually be a `_ErrorHolder`, which doesn't contain explicit info # about the upcoming test. Since we really only care about the test # name field (i.e. `_testMethodName`), we use that to detect an actual # test cases, and fall back to looking the test up from the suite # otherwise. if not hasattr(test, "_testMethodName"): test = self._find_next_test(self._suite) result = { "testCaseName": "{0}.{1}".format( test.__class__.__module__, test.__class__.__name__ ), "testCase": test._testMethodName, "type": self._current_status, "time": int((time.time() - self._test_start_time) * 1000), "message": os.linesep.join(self._messages), "stacktrace": self._stacktrace, "stdOut": self._stdout, "stdErr": self._stderr, } # TestPilot supports an instruction count field. if "TEST_PILOT" in os.environ: result["instrCount"] = ( int(self._instr_counter.read() - self._start_instr_count), ) self._results.append(result) self._current_test = None def stopTestRun(self): cov = self._main_program.get_coverage() if cov is not None: self._results.append({"coverage": cov}) @contextlib.contextmanager def _withTest(self, test): self.startTest(test) yield self.stopTest(test) def _setStatus(self, test, status, message=None, stacktrace=None): assert test == self._current_test self._current_status = status self._stacktrace = stacktrace if message is not None: if message.endswith(os.linesep): message = message[:-1] self._messages.append(message) def setStatus(self, test, status, message=None, stacktrace=None): # addError() may be called outside of a test if one of the shared # fixtures (setUpClass/tearDownClass/setUpModule/tearDownModule) # throws an error. # # In this case, create a fake test result to record the error. if self._current_test is None: with self._withTest(test): self._setStatus(test, status, message, stacktrace) else: self._setStatus(test, status, message, stacktrace) def setException(self, test, status, excinfo): exctype, value, tb = excinfo self.setStatus( test, status, "{0}: {1}".format(exctype.__name__, value), "".join(traceback.format_tb(tb)), ) def addSuccess(self, test): super(BuckTestResult, self).addSuccess(test) self.setStatus(test, TestStatus.PASSED) def addError(self, test, err): super(BuckTestResult, self).addError(test, err) self.setException(test, TestStatus.ABORTED, err) def addFailure(self, test, err): super(BuckTestResult, self).addFailure(test, err) self.setException(test, TestStatus.FAILED, err) def addSkip(self, test, reason): super(BuckTestResult, self).addSkip(test, reason) self.setStatus(test, TestStatus.SKIPPED, "Skipped: %s" % (reason,)) def addExpectedFailure(self, test, err): super(BuckTestResult, self).addExpectedFailure(test, err) self.setException(test, TestStatus.EXPECTED_FAILURE, err) def addUnexpectedSuccess(self, test): super(BuckTestResult, self).addUnexpectedSuccess(test) self.setStatus(test, TestStatus.UNEXPECTED_SUCCESS, "Unexpected success") def addStdout(self, val): self._stdout += val if self._show_output: self._saved_stdout.write(val) self._saved_stdout.flush() def addStdoutBytes(self, val): string = val.decode("utf-8", errors="backslashreplace") self.addStdout(string) def addStderr(self, val): self._stderr += val if self._show_output: self._saved_stderr.write(val) self._saved_stderr.flush() def addStderrBytes(self, val): string = val.decode("utf-8", errors="backslashreplace") self.addStderr(string) class BuckTestRunner(unittest.TextTestRunner): def __init__(self, main_program, suite, show_output=True, **kwargs): super(BuckTestRunner, self).__init__(**kwargs) self.show_output = show_output self._main_program = main_program self._suite = suite def _makeResult(self): return BuckTestResult( self.stream, self.descriptions, self.verbosity, self.show_output, self._main_program, self._suite, ) def _format_test_name(test_class, attrname): return "{0}.{1}.{2}".format(test_class.__module__, test_class.__name__, attrname) class StderrLogHandler(logging.StreamHandler): """ This class is very similar to logging.StreamHandler, except that it always uses the current sys.stderr object. StreamHandler caches the current sys.stderr object when it is constructed. This makes it behave poorly in unit tests, which may replace sys.stderr with a StringIO buffer during tests. The StreamHandler will continue using the old sys.stderr object instead of the desired StringIO buffer. """ def __init__(self): logging.Handler.__init__(self) @property def stream(self): return sys.stderr class RegexTestLoader(unittest.TestLoader): def __init__(self, regex=None): self.regex = regex super(RegexTestLoader, self).__init__() def getTestCaseNames(self, testCaseClass): """ Return a sorted sequence of method names found within testCaseClass """ testFnNames = super(RegexTestLoader, self).getTestCaseNames(testCaseClass) if self.regex is None: return testFnNames robj = re.compile(self.regex) matched = [] for attrname in testFnNames: fullname = _format_test_name(testCaseClass, attrname) if robj.search(fullname): matched.append(attrname) return matched class Loader: suiteClass = unittest.TestSuite def __init__(self, modules, regex=None): self.modules = modules self.regex = regex def load_all(self): loader = RegexTestLoader(self.regex) test_suite = self.suiteClass() for module_name in self.modules: __import__(module_name, level=0) module = sys.modules[module_name] module_suite = loader.loadTestsFromModule(module) test_suite.addTest(module_suite) return test_suite def load_args(self, args): loader = RegexTestLoader(self.regex) suites = [] for arg in args: suite = loader.loadTestsFromName(arg) # loadTestsFromName() can only process names that refer to # individual test functions or modules. It can't process package # names. If there were no module/function matches, check to see if # this looks like a package name. if suite.countTestCases() != 0: suites.append(suite) continue # Load all modules whose name is . prefix = arg + "." for module in self.modules: if module.startswith(prefix): suite = loader.loadTestsFromName(module) suites.append(suite) return loader.suiteClass(suites) _COVERAGE_INI = """\ [report] exclude_lines = pragma: no cover pragma: nocover pragma:.*no${PLATFORM} pragma:.*no${PY_IMPL}${PY_MAJOR}${PY_MINOR} pragma:.*no${PY_IMPL}${PY_MAJOR} pragma:.*nopy${PY_MAJOR} pragma:.*nopy${PY_MAJOR}${PY_MINOR} """ class MainProgram: """ This class implements the main program. It can be subclassed by users who wish to customize some parts of the main program. (Adding additional command line options, customizing test loading, etc.) """ DEFAULT_VERBOSITY = 2 def __init__(self, argv): self.init_option_parser() self.parse_options(argv) self.setup_logging() def init_option_parser(self): usage = "%prog [options] [TEST] ..." op = optparse.OptionParser(usage=usage, add_help_option=False) self.option_parser = op op.add_option( "--hide-output", dest="show_output", action="store_false", default=True, help="Suppress data that tests print to stdout/stderr, and only " "show it if the test fails.", ) op.add_option( "-o", "--output", help="Write results to a file in a JSON format to be read by Buck", ) op.add_option( "-f", "--failfast", action="store_true", default=False, help="Stop after the first failure", ) op.add_option( "-l", "--list-tests", action="store_true", dest="list", default=False, help="List tests and exit", ) op.add_option( "-r", "--regex", default=None, help="Regex to apply to tests, to only run those tests", ) op.add_option( "--collect-coverage", action="store_true", default=False, help="Collect test coverage information", ) op.add_option( "--coverage-include", default="*", help='File globs to include in converage (split by ",")', ) op.add_option( "--coverage-omit", default="", help='File globs to omit from converage (split by ",")', ) op.add_option( "--logger", action="append", metavar="=", default=[], help="Configure log levels for specific logger categories", ) op.add_option( "-q", "--quiet", action="count", default=0, help="Decrease the verbosity (may be specified multiple times)", ) op.add_option( "-v", "--verbosity", action="count", default=self.DEFAULT_VERBOSITY, help="Increase the verbosity (may be specified multiple times)", ) op.add_option( "-?", "--help", action="help", help="Show this help message and exit" ) def parse_options(self, argv): self.options, self.test_args = self.option_parser.parse_args(argv[1:]) self.options.verbosity -= self.options.quiet if self.options.collect_coverage and coverage is None: self.option_parser.error("coverage module is not available") self.options.coverage_include = self.options.coverage_include.split(",") if self.options.coverage_omit == "": self.options.coverage_omit = [] else: self.options.coverage_omit = self.options.coverage_omit.split(",") def setup_logging(self): # Configure the root logger to log at INFO level. # This is similar to logging.basicConfig(), but uses our # StderrLogHandler instead of a StreamHandler. fmt = logging.Formatter("%(pathname)s:%(lineno)s: %(message)s") log_handler = StderrLogHandler() log_handler.setFormatter(fmt) root_logger = logging.getLogger() root_logger.addHandler(log_handler) root_logger.setLevel(logging.INFO) level_names = { "debug": logging.DEBUG, "info": logging.INFO, "warn": logging.WARNING, "warning": logging.WARNING, "error": logging.ERROR, "critical": logging.CRITICAL, "fatal": logging.FATAL, } for value in self.options.logger: parts = value.rsplit("=", 1) if len(parts) != 2: self.option_parser.error( "--logger argument must be of the " "form =: %s" % value ) name = parts[0] level_name = parts[1].lower() level = level_names.get(level_name) if level is None: self.option_parser.error( "invalid log level %r for log " "category %s" % (parts[1], name) ) logging.getLogger(name).setLevel(level) def create_loader(self): import __test_modules__ return Loader(__test_modules__.TEST_MODULES, self.options.regex) def load_tests(self): loader = self.create_loader() if self.options.collect_coverage: self.start_coverage() include = self.options.coverage_include omit = self.options.coverage_omit if include and "*" not in include: optimize_for_coverage(self.cov, include, omit) if self.test_args: suite = loader.load_args(self.test_args) else: suite = loader.load_all() if self.options.collect_coverage: self.cov.start() return suite def get_tests(self, test_suite): tests = [] for test in test_suite: if isinstance(test, unittest.TestSuite): tests.extend(self.get_tests(test)) else: tests.append(test) return tests def run(self): test_suite = self.load_tests() if self.options.list: for test in self.get_tests(test_suite): method_name = getattr(test, "_testMethodName", "") name = _format_test_name(test.__class__, method_name) print(name) return EXIT_CODE_SUCCESS else: result = self.run_tests(test_suite) if self.options.output is not None: with open(self.options.output, "w") as f: json.dump(result.getResults(), f, indent=4, sort_keys=True) if not result.wasSuccessful(): return EXIT_CODE_TEST_FAILURE return EXIT_CODE_SUCCESS def run_tests(self, test_suite): # Install a signal handler to catch Ctrl-C and display the results # (but only if running >2.6). if sys.version_info[0] > 2 or sys.version_info[1] > 6: unittest.installHandler() # Run the tests runner = BuckTestRunner( self, test_suite, verbosity=self.options.verbosity, show_output=self.options.show_output, ) result = runner.run(test_suite) if self.options.collect_coverage and self.options.show_output: self.cov.stop() try: self.cov.report(file=sys.stdout) except coverage.misc.CoverageException: print("No lines were covered, potentially restricted by file filters") return result def get_abbr_impl(self): """Return abbreviated implementation name.""" impl = platform.python_implementation() if impl == "PyPy": return "pp" elif impl == "Jython": return "jy" elif impl == "IronPython": return "ip" elif impl == "CPython": return "cp" else: raise RuntimeError("unknown python runtime") def start_coverage(self): if not self.options.collect_coverage: return with tempfile.NamedTemporaryFile("w", delete=False) as coverage_ini: coverage_ini.write(_COVERAGE_INI) self._coverage_ini_path = coverage_ini.name # Keep the original working dir in case tests use os.chdir self._original_working_dir = os.getcwd() # for coverage config ignores by platform/python version os.environ["PLATFORM"] = sys.platform os.environ["PY_IMPL"] = self.get_abbr_impl() os.environ["PY_MAJOR"] = str(sys.version_info.major) os.environ["PY_MINOR"] = str(sys.version_info.minor) self.cov = coverage.Coverage( include=self.options.coverage_include, omit=self.options.coverage_omit, config_file=coverage_ini.name, ) self.cov.erase() self.cov.start() def get_coverage(self): if not self.options.collect_coverage: return None try: os.remove(self._coverage_ini_path) except OSError: pass # Better to litter than to fail the test # Switch back to the original working directory. os.chdir(self._original_working_dir) result = {} self.cov.stop() try: f = StringIO() self.cov.report(file=f) lines = f.getvalue().split("\n") except coverage.misc.CoverageException: # Nothing was covered. That's fine by us return result # N.B.: the format of the coverage library's output differs # depending on whether one or more files are in the results for line in lines[2:]: if line.strip("-") == "": break r = line.split()[0] analysis = self.cov.analysis2(r) covString = self.convert_to_diff_cov_str(analysis) if covString: result[r] = covString return result def convert_to_diff_cov_str(self, analysis): # Info on the format of analysis: # http://nedbatchelder.com/code/coverage/api.html if not analysis: return None numLines = max( analysis[1][-1] if len(analysis[1]) else 0, analysis[2][-1] if len(analysis[2]) else 0, analysis[3][-1] if len(analysis[3]) else 0, ) lines = ["N"] * numLines for l in analysis[1]: lines[l - 1] = "C" for l in analysis[2]: lines[l - 1] = "X" for l in analysis[3]: lines[l - 1] = "U" return "".join(lines) def main(argv): return MainProgram(sys.argv).run() if __name__ == "__main__": sys.exit(main(sys.argv)) ================================================ FILE: build/fbcode_builder/CMake/fb_py_win_main.c ================================================ // Copyright (c) Facebook, Inc. and its affiliates. #define WIN32_LEAN_AND_MEAN #include #include #include #define PATH_SIZE 32768 typedef int (*Py_Main)(int, wchar_t**); int locate_py_main(int argc, wchar_t** argv) { /* * We have to dynamically locate Python3.dll because we may be loading a * Python native module while running. If that module is built with a * different Python version, we will end up a DLL import error. To resolve * this, we can either ship an embedded version of Python with us or * dynamically look up existing Python distribution installed on user's * machine. This way, we should be able to get a consistent version of * Python3.dll and .pyd modules. */ HINSTANCE python_dll; Py_Main pymain; python_dll = LoadLibraryExW(L"python3.dll", NULL, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS); int returncode = 0; if (python_dll != NULL) { pymain = (Py_Main)GetProcAddress(python_dll, "Py_Main"); if (pymain != NULL) { returncode = (pymain)(argc, argv); } else { fprintf(stderr, "error: %d unable to load Py_Main\n", GetLastError()); } FreeLibrary(python_dll); } else { fprintf(stderr, "error: %d unable to locate python3.dll\n", GetLastError()); return 1; } return returncode; } int wmain() { /* * This executable will be prepended to the start of a Python ZIP archive. * Python will be able to directly execute the ZIP archive, so we simply * need to tell Py_Main() to run our own file. Duplicate the argument list * and add our file name to the beginning to tell Python what file to invoke. */ wchar_t** pyargv = malloc(sizeof(wchar_t*) * (__argc + 1)); if (!pyargv) { fprintf(stderr, "error: failed to allocate argument vector\n"); return 1; } /* Py_Main wants the wide character version of the argv so we pull those * values from the global __wargv array that has been prepared by MSVCRT. * * In order for the zipapp to run we need to insert an extra argument in * the front of the argument vector that points to ourselves. * * An additional complication is that, depending on who prepared the argument * string used to start our process, the computed __wargv[0] can be a simple * shell word like `watchman-wait` which is normally resolved together with * the PATH by the shell. * That unresolved path isn't sufficient to start the zipapp on windows; * we need the fully qualified path. * * Given: * __wargv == {"watchman-wait", "-h"} * * we want to pass the following to Py_Main: * * { * "z:\build\watchman\python\watchman-wait.exe", * "z:\build\watchman\python\watchman-wait.exe", * "-h" * } */ wchar_t full_path_to_argv0[PATH_SIZE]; DWORD len = GetModuleFileNameW(NULL, full_path_to_argv0, PATH_SIZE); if (len == 0 || len == PATH_SIZE && GetLastError() == ERROR_INSUFFICIENT_BUFFER) { fprintf( stderr, "error: %d while retrieving full path to this executable\n", GetLastError()); return 1; } for (int n = 1; n < __argc; ++n) { pyargv[n + 1] = __wargv[n]; } pyargv[0] = full_path_to_argv0; pyargv[1] = full_path_to_argv0; return locate_py_main(__argc + 1, pyargv); } ================================================ FILE: build/fbcode_builder/CMake/make_fbpy_archive.py ================================================ #!/usr/bin/env python3 # # Copyright (c) Facebook, Inc. and its affiliates. # import argparse import collections import errno import os import shutil import subprocess import sys import tempfile import zipapp MANIFEST_SEPARATOR = " :: " MANIFEST_HEADER_V1 = "FBPY_MANIFEST 1\n" class UsageError(Exception): def __init__(self, message): self.message = message def __str__(self): return self.message class BadManifestError(UsageError): def __init__(self, path, line_num, message): full_msg = "%s:%s: %s" % (path, line_num, message) super().__init__(full_msg) self.path = path self.line_num = line_num self.raw_message = message PathInfo = collections.namedtuple( "PathInfo", ("src", "dest", "manifest_path", "manifest_line") ) def parse_manifest(manifest, path_map): bad_prefix = ".." + os.path.sep manifest_dir = os.path.dirname(manifest) with open(manifest, "r") as f: line_num = 1 line = f.readline() if line != MANIFEST_HEADER_V1: raise BadManifestError( manifest, line_num, "Unexpected manifest file header" ) for line in f: line_num += 1 if line.startswith("#"): continue line = line.rstrip("\n") parts = line.split(MANIFEST_SEPARATOR) if len(parts) != 2: msg = "line must be of the form SRC %s DEST" % MANIFEST_SEPARATOR raise BadManifestError(manifest, line_num, msg) src, dest = parts dest = os.path.normpath(dest) if dest.startswith(bad_prefix): msg = "destination path starts with %s: %s" % (bad_prefix, dest) raise BadManifestError(manifest, line_num, msg) if not os.path.isabs(src): src = os.path.normpath(os.path.join(manifest_dir, src)) if dest in path_map: prev_info = path_map[dest] msg = ( "multiple source paths specified for destination " "path %s. Previous source was %s from %s:%s" % ( dest, prev_info.src, prev_info.manifest_path, prev_info.manifest_line, ) ) raise BadManifestError(manifest, line_num, msg) info = PathInfo( src=src, dest=dest, manifest_path=manifest, manifest_line=line_num, ) path_map[dest] = info def populate_install_tree(inst_dir, path_map): os.mkdir(inst_dir) dest_dirs = {"": False} def make_dest_dir(path): if path in dest_dirs: return parent = os.path.dirname(path) make_dest_dir(parent) abs_path = os.path.join(inst_dir, path) os.mkdir(abs_path) dest_dirs[path] = False def install_file(info): dir_name, base_name = os.path.split(info.dest) make_dest_dir(dir_name) if base_name == "__init__.py": dest_dirs[dir_name] = True abs_dest = os.path.join(inst_dir, info.dest) shutil.copy2(info.src, abs_dest) # Copy all of the destination files for info in path_map.values(): install_file(info) # Create __init__ files in any directories that don't have them. for dir_path, has_init in dest_dirs.items(): if has_init: continue init_path = os.path.join(inst_dir, dir_path, "__init__.py") with open(init_path, "w"): pass def build_pex(args, path_map): """Create a self executing python binary using the PEX tool This type of Python binary is more complex as it requires a third-party tool, but it does support native language extensions (.so/.dll files). """ dest_dir = os.path.dirname(args.output) with tempfile.TemporaryDirectory(prefix="make_fbpy.", dir=dest_dir) as tmpdir: inst_dir = os.path.join(tmpdir, "tree") populate_install_tree(inst_dir, path_map) if os.path.exists(os.path.join(inst_dir, "__main__.py")): os.rename( os.path.join(inst_dir, "__main__.py"), os.path.join(inst_dir, "main.py"), ) args.main = "main" tmp_output = os.path.abspath(os.path.join(tmpdir, "output.exe")) subprocess.check_call( ["pex"] + ["--output-file", tmp_output] + ["--python", args.python] + ["--sources-directory", inst_dir] + ["-e", args.main] ) os.replace(tmp_output, args.output) def build_zipapp(args, path_map): """Create a self executing python binary using Python 3's built-in zipapp module. This type of Python binary is relatively simple, as zipapp is part of the standard library, but it does not support native language extensions (.so/.dll files). """ dest_dir = os.path.dirname(args.output) with tempfile.TemporaryDirectory(prefix="make_fbpy.", dir=dest_dir) as tmpdir: inst_dir = os.path.join(tmpdir, "tree") populate_install_tree(inst_dir, path_map) tmp_output = os.path.join(tmpdir, "output.exe") zipapp.create_archive( inst_dir, target=tmp_output, interpreter=args.python, main=args.main ) os.replace(tmp_output, args.output) def create_main_module(args, inst_dir, path_map): if not args.main: assert "__main__.py" in path_map return dest_path = os.path.join(inst_dir, "__main__.py") main_module, main_fn = args.main.split(":") main_contents = """\ #!{python} if __name__ == "__main__": import {main_module} {main_module}.{main_fn}() """.format( python=args.python, main_module=main_module, main_fn=main_fn ) with open(dest_path, "w") as f: f.write(main_contents) os.chmod(dest_path, 0o755) def build_install_dir(args, path_map): """Create a directory that contains all of the sources, with a __main__ module to run the program. """ # Populate a temporary directory first, then rename to the destination # location. This ensures that we don't ever leave a halfway-built # directory behind at the output path if something goes wrong. dest_dir = os.path.dirname(args.output) with tempfile.TemporaryDirectory(prefix="make_fbpy.", dir=dest_dir) as tmpdir: inst_dir = os.path.join(tmpdir, "tree") populate_install_tree(inst_dir, path_map) create_main_module(args, inst_dir, path_map) os.rename(inst_dir, args.output) def ensure_directory(path): try: os.makedirs(path) except OSError as ex: if ex.errno != errno.EEXIST: raise def install_library(args, path_map): """Create an installation directory a python library.""" out_dir = args.output out_manifest = args.output + ".manifest" install_dir = args.install_dir if not install_dir: install_dir = out_dir os.makedirs(out_dir) with open(out_manifest, "w") as manifest: manifest.write(MANIFEST_HEADER_V1) for info in path_map.values(): abs_dest = os.path.join(out_dir, info.dest) ensure_directory(os.path.dirname(abs_dest)) print("copy %r --> %r" % (info.src, abs_dest)) shutil.copy2(info.src, abs_dest) installed_dest = os.path.join(install_dir, info.dest) manifest.write("%s%s%s\n" % (installed_dest, MANIFEST_SEPARATOR, info.dest)) def parse_manifests(args): # Process args.manifest_separator to help support older versions of CMake if args.manifest_separator: manifests = [] for manifest_arg in args.manifests: split_arg = manifest_arg.split(args.manifest_separator) manifests.extend(split_arg) args.manifests = manifests path_map = {} for manifest in args.manifests: parse_manifest(manifest, path_map) return path_map def check_main_module(args, path_map): # Translate an empty string in the --main argument to None, # just to allow the CMake logic to be slightly simpler and pass in an # empty string when it really wants the default __main__.py module to be # used. if args.main == "": args.main = None if args.type == "lib-install": if args.main is not None: raise UsageError("cannot specify a --main argument with --type=lib-install") return main_info = path_map.get("__main__.py") if args.main: if main_info is not None: msg = ( "specified an explicit main module with --main, " "but the file listing already includes __main__.py" ) raise BadManifestError( main_info.manifest_path, main_info.manifest_line, msg ) parts = args.main.split(":") if len(parts) != 2: raise UsageError( "argument to --main must be of the form MODULE:CALLABLE " "(received %s)" % (args.main,) ) else: if main_info is None: raise UsageError( "no main module specified with --main, " "and no __main__.py module present" ) BUILD_TYPES = { "pex": build_pex, "zipapp": build_zipapp, "dir": build_install_dir, "lib-install": install_library, } def main(): ap = argparse.ArgumentParser() ap.add_argument("-o", "--output", required=True, help="The output file path") ap.add_argument( "--install-dir", help="When used with --type=lib-install, this parameter specifies the " "final location where the library where be installed. This can be " "used to generate the library in one directory first, when you plan " "to move or copy it to another final location later.", ) ap.add_argument( "--manifest-separator", help="Split manifest arguments around this separator. This is used " "to support older versions of CMake that cannot supply the manifests " "as separate arguments.", ) ap.add_argument( "--main", help="The main module to run, specified as :. " "This must be specified if and only if the archive does not contain " "a __main__.py file.", ) ap.add_argument( "--python", help="Explicitly specify the python interpreter to use for the " "executable.", ) ap.add_argument( "--type", choices=BUILD_TYPES.keys(), help="The type of output to build." ) ap.add_argument( "manifests", nargs="+", help="The manifest files specifying how to construct the archive", ) args = ap.parse_args() if args.python is None: args.python = sys.executable if args.type is None: # In the future we might want different default output types # for different platforms. args.type = "zipapp" build_fn = BUILD_TYPES[args.type] try: path_map = parse_manifests(args) check_main_module(args, path_map) except UsageError as ex: print("error: %s" % (ex,), file=sys.stderr) sys.exit(1) build_fn(args, path_map) if __name__ == "__main__": main() ================================================ FILE: build/fbcode_builder/LICENSE ================================================ MIT License Copyright (c) Facebook, Inc. and its affiliates. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: build/fbcode_builder/README.md ================================================ # Easy builds for Facebook projects This directory contains tools designed to simplify continuous-integration (and other builds) of Facebook open source projects. In particular, this helps manage builds for cross-project dependencies. The main entry point is the `getdeps.py` script. This script has several subcommands, but the most notable is the `build` command. This will download and build all dependencies for a project, and then build the project itself. ## Deployment This directory is copied literally into a number of different Facebook open source repositories. Any change made to code in this directory will be automatically be replicated by our open source tooling into all GitHub hosted repositories that use `fbcode_builder`. Typically this directory is copied into the open source repositories as `build/fbcode_builder/`. # Project Configuration Files The `manifests` subdirectory contains configuration files for many different projects, describing how to build each project. These files also list dependencies between projects, enabling `getdeps.py` to build all dependencies for a project before building the project itself. # Shared CMake utilities Since this directory is copied into many Facebook open source repositories, it is also used to help share some CMake utility files across projects. The `CMake/` subdirectory contains a number of `.cmake` files that are shared by the CMake-based build systems across several different projects. # Older Build Scripts This directory also still contains a handful of older build scripts that pre-date the current `getdeps.py` build system. Most of the other `.py` files in this top directory, apart from `getdeps.py` itself, are from this older build system. This older system is only used by a few remaining projects, and new projects should generally use the newer `getdeps.py` script, by adding a new configuration file in the `manifests/` subdirectory. ================================================ FILE: build/fbcode_builder/getdeps/__init__.py ================================================ # pyre-strict ================================================ FILE: build/fbcode_builder/getdeps/builder.py ================================================ #!/usr/bin/env python3 # Copyright (c) Meta Platforms, Inc. and affiliates. # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. # pyre-strict from __future__ import annotations import glob import json import os import os.path import pathlib import re import shutil import stat import subprocess import sys import typing from collections.abc import Callable, Sequence from shlex import quote as shellquote from .copytree import rmtree_more, simple_copytree from .dyndeps import create_dyn_dep_munger from .envfuncs import add_path_entry, Env, path_search from .fetcher import copy_if_different, is_public_commit from .runcmd import make_memory_limit_preexec_fn, run_cmd if typing.TYPE_CHECKING: from .buildopts import BuildOptions from .dyndeps import DepBase from .load import ManifestLoader from .manifest import ManifestContext, ManifestParser class BuilderBase: def __init__( self, loader: ManifestLoader, dep_manifests: list[ManifestParser], build_opts: BuildOptions, ctx: ManifestContext, manifest: ManifestParser, src_dir: str, build_dir: str | None, inst_dir: str, env: Env | None = None, final_install_prefix: str | None = None, ) -> None: self.env: Env = Env() if env: # pyre-fixme[6]: For 1st argument expected `Mapping[str, str]` but got # `Env`. self.env.update(env) subdir: str | None = manifest.get("build", "subdir", ctx=ctx) if subdir: src_dir = os.path.join(src_dir, subdir) self.patchfile: str | None = manifest.get("build", "patchfile", ctx=ctx) self.patchfile_opts: str = ( manifest.get("build", "patchfile_opts", ctx=ctx) or "" ) self.ctx: ManifestContext = ctx self.src_dir: str = src_dir self.build_dir: str = build_dir or src_dir self.inst_dir: str = inst_dir self.build_opts: BuildOptions = build_opts self.manifest: ManifestParser = manifest self.final_install_prefix: str | None = final_install_prefix self.loader: ManifestLoader = loader self.dep_manifests: list[ManifestParser] = dep_manifests self.install_dirs: list[str] = [ loader.get_project_install_dir(m) for m in dep_manifests ] def _get_cmd_prefix(self) -> list[str]: if self.build_opts.is_windows(): vcvarsall = self.build_opts.get_vcvars_path() if vcvarsall is not None: # Since it sets rather a large number of variables we mildly abuse # the cmd quoting rules to assemble a command that calls the script # to prep the environment and then triggers the actual command that # we wanted to run. # Due to changes in vscrsall.bat, it now reports an ERRORLEVEL of 1 # even when succeeding. This occurs when an extension is not present. # To continue, we must ignore the ERRORLEVEL returned. We do this by # wrapping the call in a batch file that always succeeds. wrapper = os.path.join(self.build_dir, "succeed.bat") with open(wrapper, "w") as f: f.write("@echo off\n") f.write(f'call "{vcvarsall}" amd64\n') f.write("set ERRORLEVEL=0\n") f.write("exit /b 0\n") return [wrapper, "&&"] return [] def _check_cmd(self, cmd: list[str], **kwargs: object) -> None: """Run the command and abort on failure""" # pyre-fixme[6]: For 2nd argument expected `Optional[Env]` but got `object`. # pyre-fixme[6]: For 2nd argument expected `Optional[str]` but got `object`. # pyre-fixme[6]: For 2nd argument expected `bool` but got `object`. rc = self._run_cmd(cmd, **kwargs) if rc != 0: raise RuntimeError(f"Failure exit code {rc} for command {cmd}") def _run_cmd( self, cmd: list[str], cwd: str | None = None, env: Env | None = None, use_cmd_prefix: bool = True, allow_fail: bool = False, preexec_fn: Callable[[], None] | None = None, ) -> int: if env: e = self.env.copy() # pyre-fixme[6]: For 1st argument expected `Mapping[str, str]` but got # `Env`. e.update(env) env = e else: env = self.env if use_cmd_prefix: cmd_prefix = self._get_cmd_prefix() if cmd_prefix: cmd = cmd_prefix + cmd log_file = os.path.join(self.build_dir, "getdeps_build.log") return run_cmd( cmd=cmd, env=env, cwd=cwd or self.build_dir, log_file=log_file, allow_fail=allow_fail, preexec_fn=preexec_fn, ) def _reconfigure(self, reconfigure: bool) -> bool: if self.build_dir is not None: if not os.path.isdir(self.build_dir): os.makedirs(self.build_dir) reconfigure = True return reconfigure def _apply_patchfile(self) -> None: if self.patchfile is None: return patched_sentinel_file = pathlib.Path(self.src_dir + "/.getdeps_patched") if patched_sentinel_file.exists(): return old_wd = os.getcwd() os.chdir(self.src_dir) # Apply patches from the git repo root so paths resolve correctly # even when src_dir is a subdirectory of the repo. try: git_root = subprocess.check_output( ["git", "rev-parse", "--show-toplevel"], text=True ).strip() os.chdir(git_root) except subprocess.CalledProcessError: pass # not a git repo, stay in src_dir print(f"Patching {self.manifest.name} with {self.patchfile} in {os.getcwd()}") patchfile = os.path.join( self.build_opts.fbcode_builder_dir, "patches", # pyre-fixme[6]: For 3rd argument expected `Union[PathLike[str], str]` # but got `Optional[str]`. self.patchfile, ) patchcmd = ["git", "apply", "--ignore-space-change"] if self.patchfile_opts: patchcmd.append(self.patchfile_opts) try: subprocess.check_call(patchcmd + [patchfile]) except subprocess.CalledProcessError: raise ValueError(f"Failed to apply patch to {self.manifest.name}") os.chdir(old_wd) patched_sentinel_file.touch() def prepare(self, reconfigure: bool) -> None: print("Preparing %s..." % self.manifest.name) reconfigure = self._reconfigure(reconfigure) self._apply_patchfile() self._prepare(reconfigure=reconfigure) def debug(self, reconfigure: bool) -> None: reconfigure = self._reconfigure(reconfigure) self._apply_patchfile() self._prepare(reconfigure=reconfigure) env = self._compute_env() print("Starting a shell in %s, ^D to exit..." % self.build_dir) # TODO: print the command to run the build shell = ["powershell.exe"] if sys.platform == "win32" else ["/bin/sh", "-i"] self._run_cmd(shell, cwd=self.build_dir, env=env) def printenv(self, reconfigure: bool) -> None: """print the environment in a shell sourcable format""" reconfigure = self._reconfigure(reconfigure) self._apply_patchfile() self._prepare(reconfigure=reconfigure) env = self._compute_env(env=Env(src={})) prefix = "export " sep = ":" expand = "$" expandpost = "" if self.build_opts.is_windows(): prefix = "SET " sep = ";" expand = "%" expandpost = "%" for k, v in sorted(env.items()): existing = os.environ.get(k, None) if k.endswith("PATH") and existing: v = shellquote(v) + sep + f"{expand}{k}{expandpost}" else: v = shellquote(v) print("%s%s=%s" % (prefix, k, v)) def build(self, reconfigure: bool) -> None: print("Building %s..." % self.manifest.name) reconfigure = self._reconfigure(reconfigure) self._apply_patchfile() self._prepare(reconfigure=reconfigure) self._build(reconfigure=reconfigure) if self.build_opts.free_up_disk: # don't clean --src-dir=. case as user may want to build again or run tests on the build if self.src_dir.startswith(self.build_opts.scratch_dir) and os.path.isdir( self.build_dir ): if os.path.islink(self.build_dir): os.remove(self.build_dir) else: rmtree_more(self.build_dir) elif self.build_opts.is_windows(): # On Windows, emit a wrapper script that can be used to run build artifacts # directly from the build directory, without installing them. On Windows $PATH # needs to be updated to include all of the directories containing the runtime # library dependencies in order to run the binaries. script_path = self.get_dev_run_script_path() dep_munger = create_dyn_dep_munger( self.build_opts, self._compute_env(), self.install_dirs ) dep_dirs = self.get_dev_run_extra_path_dirs(dep_munger) # pyre-fixme[16]: Optional type has no attribute `emit_dev_run_script`. dep_munger.emit_dev_run_script(script_path, dep_dirs) @property def _job_weight_mib(self) -> int: # This is a hack, but we don't have a "defaults manifest" that we can # customize per platform. # TODO: Introduce some sort of defaults config that can select by # platform, just like manifest contexts. if sys.platform.startswith("freebsd"): # clang on FreeBSD is quite memory-efficient. default_job_weight = 512 else: # 1.5 GiB is a lot to assume, but it's typical of Facebook-style C++. # Some manifests are even heavier and should override. default_job_weight = 1536 return int( self.manifest.get( "build", "job_weight_mib", str(default_job_weight), ctx=self.ctx ) ) @property def num_jobs(self) -> int: return self.build_opts.get_num_jobs(self._job_weight_mib) @property def memory_limit_preexec_fn(self) -> Callable[[], None] | None: """Return a preexec_fn that caps per-process virtual memory. Uses the same job_weight_mib that controls parallelism, so the memory limit is consistent with the parallelism budget. """ return make_memory_limit_preexec_fn(self._job_weight_mib) def run_tests( self, schedule_type: str, owner: str | None, test_filter: str | None, test_exclude: str | None, retry: int, no_testpilot: bool, timeout: int | None = None, ) -> None: """Execute any tests that we know how to run. If they fail, raise an exception.""" pass def _prepare(self, reconfigure: bool) -> None: """Prepare the build. Useful when need to generate config, but builder is not the primary build system. e.g. cargo when called from cmake""" pass def _build(self, reconfigure: bool) -> None: """Perform the build. reconfigure will be set to true if the fetcher determined that the sources have changed in such a way that the build system needs to regenerate its rules.""" pass def _compute_env(self, env: Env | None = None) -> Env: if env is None: env = self.env # CMAKE_PREFIX_PATH is only respected when passed through the # environment, so we construct an appropriate path to pass down return self.build_opts.compute_env_for_install_dirs( self.loader, self.dep_manifests, self.ctx, env=env, manifest=self.manifest, ) def get_dev_run_script_path(self) -> str: assert self.build_opts.is_windows() return os.path.join(self.build_dir, "run.ps1") def get_dev_run_extra_path_dirs( self, dep_munger: DepBase | None = None ) -> list[str]: assert self.build_opts.is_windows() if dep_munger is None: dep_munger = create_dyn_dep_munger( self.build_opts, self._compute_env(), self.install_dirs ) # pyre-fixme[16]: Optional type has no attribute `compute_dependency_paths`. return dep_munger.compute_dependency_paths(self.build_dir) class MakeBuilder(BuilderBase): def __init__( self, loader: ManifestLoader, dep_manifests: list[ManifestParser], build_opts: BuildOptions, ctx: ManifestContext, manifest: ManifestParser, src_dir: str, build_dir: str, inst_dir: str, build_args: list[str] | None, install_args: list[str] | None, test_args: list[str] | None, ) -> None: super(MakeBuilder, self).__init__( loader, dep_manifests, build_opts, ctx, manifest, src_dir, build_dir, inst_dir, ) self.build_args: list[str] = build_args or [] self.install_args: list[str] = install_args or [] self.test_args: list[str] | None = test_args @property def _make_binary(self) -> str | None: return self.manifest.get("build", "make_binary", "make", ctx=self.ctx) def _get_prefix(self) -> list[str]: return ["PREFIX=" + self.inst_dir, "prefix=" + self.inst_dir] def _build(self, reconfigure: bool) -> None: env = self._compute_env() # Need to ensure that PREFIX is set prior to install because # libbpf uses it when generating its pkg-config file. # The lowercase prefix is used by some projects. cmd = ( [self._make_binary, "-j%s" % self.num_jobs] + self.build_args + self._get_prefix() ) # pyre-fixme[6]: For 1st argument expected `List[str]` but got # `List[Optional[str]]`. self._check_cmd(cmd, env=env) install_cmd = [self._make_binary] + self.install_args + self._get_prefix() # pyre-fixme[6]: For 1st argument expected `List[str]` but got # `List[Optional[str]]`. self._check_cmd(install_cmd, env=env) # bz2's Makefile doesn't install its .so properly if self.manifest and self.manifest.name == "bz2": libdir = os.path.join(self.inst_dir, "lib") srcpattern = os.path.join(self.src_dir, "lib*.so.*") print(f"copying to {libdir} from {srcpattern}") for file in glob.glob(srcpattern): shutil.copy(file, libdir) def run_tests( self, schedule_type: str, owner: str | None, test_filter: str | None, test_exclude: str | None, retry: int, no_testpilot: bool, timeout: int | None = None, ) -> None: if not self.test_args: return env = self._compute_env() if test_filter: env["GETDEPS_TEST_FILTER"] = test_filter else: env["GETDEPS_TEST_FILTER"] = "" if retry: # pyre-fixme[6]: Expected `str` but got `int`. env["GETDEPS_TEST_RETRY"] = retry else: # pyre-fixme[6]: Expected `str` but got `int`. env["GETDEPS_TEST_RETRY"] = 0 if timeout is not None: env["GETDEPS_TEST_TIMEOUT"] = str(timeout) cmd = ( [self._make_binary, "-j%s" % self.num_jobs] # pyre-fixme[58]: `+` is not supported for operand types # `list[Optional[str]]` and `Optional[list[str]]`. + self.test_args + self._get_prefix() ) # pyre-fixme[6]: For 1st argument expected `List[str]` but got # `List[Optional[str]]`. self._check_cmd(cmd, allow_fail=False, env=env) class CMakeBootStrapBuilder(MakeBuilder): def _build(self, reconfigure: bool) -> None: self._check_cmd( [ "./bootstrap", "--prefix=" + self.inst_dir, f"--parallel={self.num_jobs}", ] ) super(CMakeBootStrapBuilder, self)._build(reconfigure) class AutoconfBuilder(BuilderBase): def __init__( self, loader: ManifestLoader, dep_manifests: list[ManifestParser], build_opts: BuildOptions, ctx: ManifestContext, manifest: ManifestParser, src_dir: str, build_dir: str, inst_dir: str, args: list[str] | None, conf_env_args: dict[str, list[str]] | None, ) -> None: super(AutoconfBuilder, self).__init__( loader, dep_manifests, build_opts, ctx, manifest, src_dir, build_dir, inst_dir, ) self.args: list[str] = args or [] if ( not build_opts.shared_libs and "--disable-shared" not in self.args and "--enable-shared" not in self.args ): self.args.append("--disable-shared") self.conf_env_args: dict[str, list[str]] = conf_env_args or {} @property def _make_binary(self) -> str | None: return self.manifest.get("build", "make_binary", "make", ctx=self.ctx) def _build(self, reconfigure: bool) -> None: configure_path = os.path.join(self.src_dir, "configure") autogen_path = os.path.join(self.src_dir, "autogen.sh") env = self._compute_env() # Some configure scripts need additional env values passed derived from cmds for k, cmd_args in self.conf_env_args.items(): out = ( subprocess.check_output(cmd_args, env=dict(env.items())) .decode("utf-8") .strip() ) if out: env.set(k, out) if not os.path.exists(configure_path): print("%s doesn't exist, so reconfiguring" % configure_path) # This libtoolize call is a bit gross; the issue is that # `autoreconf` as invoked by libsodium's `autogen.sh` doesn't # seem to realize that it should invoke libtoolize and then # error out when the configure script references a libtool # related symbol. self._check_cmd(["libtoolize"], cwd=self.src_dir, env=env) # We generally prefer to call the `autogen.sh` script provided # by the project on the basis that it may know more than plain # autoreconf does. if os.path.exists(autogen_path): self._check_cmd(["bash", autogen_path], cwd=self.src_dir, env=env) else: self._check_cmd(["autoreconf", "-ivf"], cwd=self.src_dir, env=env) configure_cmd = [configure_path, "--prefix=" + self.inst_dir] + self.args self._check_cmd(configure_cmd, env=env) only_install = self.manifest.get("build", "only_install", ctx=self.ctx) if not only_install or only_install.lower() == "false": # pyre-fixme[6]: For 1st argument expected `List[str]` but got # `List[Optional[str]]`. self._check_cmd([self._make_binary, "-j%s" % self.num_jobs], env=env) # pyre-fixme[6]: For 1st argument expected `List[str]` but got # `List[Union[str, None, str]]`. self._check_cmd([self._make_binary, "install"], env=env) class Iproute2Builder(BuilderBase): # ./configure --prefix does not work for iproute2. # Thus, explicitly copy sources from src_dir to build_dir, build, # and then install to inst_dir using DESTDIR # lastly, also copy include from build_dir to inst_dir def __init__( self, loader: ManifestLoader, dep_manifests: list[ManifestParser], build_opts: BuildOptions, ctx: ManifestContext, manifest: ManifestParser, src_dir: str, build_dir: str, inst_dir: str, ) -> None: super(Iproute2Builder, self).__init__( loader, dep_manifests, build_opts, ctx, manifest, src_dir, build_dir, inst_dir, ) def _build(self, reconfigure: bool) -> None: configure_path = os.path.join(self.src_dir, "configure") env = self.env.copy() self._check_cmd([configure_path], env=env) shutil.rmtree(self.build_dir) shutil.copytree(self.src_dir, self.build_dir) self._check_cmd(["make", "-j%s" % self.num_jobs], env=env) install_cmd = ["make", "install", "DESTDIR=" + self.inst_dir] for d in ["include", "lib"]: if not os.path.isdir(os.path.join(self.inst_dir, d)): shutil.copytree( os.path.join(self.build_dir, d), os.path.join(self.inst_dir, d) ) self._check_cmd(install_cmd, env=env) class MesonBuilder(BuilderBase): # MesonBuilder assumes that meson build tool has already been installed on # the machine. def __init__( self, loader: ManifestLoader, dep_manifests: list[ManifestParser], build_opts: BuildOptions, ctx: ManifestContext, manifest: ManifestParser, src_dir: str, build_dir: str, inst_dir: str, ) -> None: super(MesonBuilder, self).__init__( loader, dep_manifests, build_opts, ctx, manifest, src_dir, build_dir, inst_dir, ) def _build(self, reconfigure: bool) -> None: env = self._compute_env() # pyre-fixme[6]: For 1st argument expected `Mapping[str, str]` but got `Env`. meson: str | None = path_search(env, "meson") if meson is None: raise Exception("Failed to find Meson") setup_args = self.manifest.get_section_as_args("meson.setup_args", self.ctx) # Meson builds typically require setup, compile, and install steps. # During this setup step we ensure that the static library is built and # the prefix is empty. self._check_cmd( [ meson, "setup", ] + setup_args + [ self.build_dir, self.src_dir, ] ) # Compile step needs to satisfy the build directory that was previously # prepared during setup. self._check_cmd([meson, "compile", "-C", self.build_dir]) # Install step self._check_cmd( [meson, "install", "-C", self.build_dir, "--destdir", self.inst_dir] ) class CMakeBuilder(BuilderBase): MANUAL_BUILD_SCRIPT = """\ #!{sys.executable} import argparse import subprocess import sys CMAKE = {cmake!r} CTEST = {ctest!r} SRC_DIR = {src_dir!r} BUILD_DIR = {build_dir!r} INSTALL_DIR = {install_dir!r} CMD_PREFIX = {cmd_prefix!r} CMAKE_ENV = {env_str} CMAKE_DEFINE_ARGS = {define_args_str} def get_jobs_argument(num_jobs_arg: int) -> str: if num_jobs_arg > 0: return "-j" + str(num_jobs_arg) import multiprocessing num_jobs = multiprocessing.cpu_count() // 2 return "-j" + str(num_jobs) def main(): ap = argparse.ArgumentParser() ap.add_argument( "cmake_args", nargs=argparse.REMAINDER, help='Any extra arguments after an "--" argument will be passed ' "directly to CMake." ) ap.add_argument( "--mode", choices=["configure", "build", "install", "test"], default="configure", help="The mode to run: configure, build, or install. " "Defaults to configure", ) ap.add_argument( "--build", action="store_const", const="build", dest="mode", help="An alias for --mode=build", ) ap.add_argument( "-j", "--num-jobs", action="store", type=int, default=0, help="Run the build or tests with the specified number of parallel jobs", ) ap.add_argument( "--install", action="store_const", const="install", dest="mode", help="An alias for --mode=install", ) ap.add_argument( "--test", action="store_const", const="test", dest="mode", help="An alias for --mode=test", ) args = ap.parse_args() # Strip off a leading "--" from the additional CMake arguments if args.cmake_args and args.cmake_args[0] == "--": args.cmake_args = args.cmake_args[1:] env = CMAKE_ENV if args.mode == "configure": full_cmd = CMD_PREFIX + [CMAKE, SRC_DIR] + CMAKE_DEFINE_ARGS + args.cmake_args elif args.mode in ("build", "install"): target = "all" if args.mode == "build" else "install" full_cmd = CMD_PREFIX + [ CMAKE, "--build", BUILD_DIR, "--target", target, "--config", "{build_type}", get_jobs_argument(args.num_jobs), ] + args.cmake_args elif args.mode == "test": full_cmd = CMD_PREFIX + [ {dev_run_script}CTEST, "--output-on-failure", get_jobs_argument(args.num_jobs), ] + args.cmake_args else: ap.error("unknown invocation mode: %s" % (args.mode,)) cmd_str = " ".join(full_cmd) print("Running: %r" % (cmd_str,)) proc = subprocess.run(full_cmd, env=env, cwd=BUILD_DIR) sys.exit(proc.returncode) if __name__ == "__main__": main() """ def __init__( self, loader: ManifestLoader, dep_manifests: list[ManifestParser], build_opts: BuildOptions, ctx: ManifestContext, manifest: ManifestParser, src_dir: str, build_dir: str, inst_dir: str, defines: dict[str, str] | None, final_install_prefix: str | None = None, extra_cmake_defines: dict[str, str] | None = None, cmake_targets: list[str] | None = None, ) -> None: super(CMakeBuilder, self).__init__( loader, dep_manifests, build_opts, ctx, manifest, src_dir, build_dir, inst_dir, final_install_prefix=final_install_prefix, ) self.defines: dict[str, str] = defines or {} if extra_cmake_defines: self.defines.update(extra_cmake_defines) self.cmake_targets: list[str] = cmake_targets or ["install"] if build_opts.is_windows(): try: from .facebook.vcvarsall import extra_vc_cmake_defines except ImportError: pass else: self.defines.update(extra_vc_cmake_defines) self.loader = loader if build_opts.shared_libs: self.defines["BUILD_SHARED_LIBS"] = "ON" self.defines["BOOST_LINK_STATIC"] = "OFF" def _invalidate_cache(self) -> None: for name in [ "CMakeCache.txt", "CMakeFiles/CMakeError.log", "CMakeFiles/CMakeOutput.log", ]: name = os.path.join(self.build_dir, name) if os.path.isdir(name): shutil.rmtree(name) elif os.path.exists(name): os.unlink(name) def _needs_reconfigure(self) -> bool: for name in ["CMakeCache.txt", "build.ninja"]: name = os.path.join(self.build_dir, name) if not os.path.exists(name): return True return False def _write_build_script(self, **kwargs: object) -> None: # pyre-fixme[16]: `object` has no attribute `items`. env_lines = [" {!r}: {!r},".format(k, v) for k, v in kwargs["env"].items()] kwargs["env_str"] = "\n".join(["{"] + env_lines + ["}"]) if self.build_opts.is_windows(): kwargs["dev_run_script"] = '"powershell.exe", {!r}, '.format( self.get_dev_run_script_path() ) else: kwargs["dev_run_script"] = "" define_arg_lines = ["["] # pyre-fixme[16]: `object` has no attribute `__iter__`. for arg in kwargs["define_args"]: # Replace the CMAKE_INSTALL_PREFIX argument to use the INSTALL_DIR # variable that we define in the MANUAL_BUILD_SCRIPT code. if arg.startswith("-DCMAKE_INSTALL_PREFIX="): value = " {!r}.format(INSTALL_DIR),".format( "-DCMAKE_INSTALL_PREFIX={}" ) else: value = " {!r},".format(arg) define_arg_lines.append(value) define_arg_lines.append("]") kwargs["define_args_str"] = "\n".join(define_arg_lines) # In order to make it easier for developers to manually run builds for # CMake-based projects, write out some build scripts that can be used to invoke # CMake manually. build_script_path = os.path.join(self.build_dir, "run_cmake.py") script_contents = self.MANUAL_BUILD_SCRIPT.format(**kwargs) with open(build_script_path, "wb") as f: f.write(script_contents.encode()) os.chmod(build_script_path, 0o755) def _compute_cmake_define_args(self, env: Env) -> list[str]: defines = { "CMAKE_INSTALL_PREFIX": self.final_install_prefix or self.inst_dir, "BUILD_SHARED_LIBS": "OFF", # Some of the deps (rsocket) default to UBSAN enabled if left # unspecified. Some of the deps fail to compile in release mode # due to warning->error promotion. RelWithDebInfo is the happy # medium. "CMAKE_BUILD_TYPE": self.build_opts.build_type, } if "SANDCASTLE" not in os.environ: # We sometimes see intermittent ccache related breakages on some # of the FB internal CI hosts, so we prefer to disable ccache # when running in that environment. # pyre-fixme[6]: For 1st argument expected `Mapping[str, str]` but got # `Env`. ccache = path_search(env, "ccache") if ccache: defines["CMAKE_CXX_COMPILER_LAUNCHER"] = ccache else: # rocksdb does its own probing for ccache. # Ensure that it is disabled on sandcastle env["CCACHE_DISABLE"] = "1" # Some sandcastle hosts have broken ccache related dirs, and # even though we've asked for it to be disabled ccache is # still invoked by rocksdb's cmake. # Redirect its config directory to somewhere that is guaranteed # fresh to us, and that won't have any ccache data inside. env["CCACHE_DIR"] = f"{self.build_opts.scratch_dir}/ccache" if "GITHUB_ACTIONS" in os.environ and self.build_opts.is_windows(): # GitHub actions: the host has both gcc and msvc installed, and # the default behavior of cmake is to prefer gcc. # Instruct cmake that we want it to use cl.exe; this is important # because Boost prefers cl.exe and the mismatch results in cmake # with gcc not being able to find boost built with cl.exe. defines["CMAKE_C_COMPILER"] = "cl.exe" defines["CMAKE_CXX_COMPILER"] = "cl.exe" if self.build_opts.is_darwin(): # Try to persuade cmake to set the rpath to match the lib # dirs of the dependencies. This isn't automatic, and to # make things more interesting, cmake uses `;` as the path # separator, so translate the runtime path to something # that cmake will parse defines["CMAKE_INSTALL_RPATH"] = ";".join( # pyre-fixme[16]: Optional type has no attribute `split`. env.get("DYLD_LIBRARY_PATH", "").split(":") ) # Tell cmake that we want to set the rpath in the tree # at build time. Without this the rpath is only set # at the moment that the binaries are installed. That # default is problematic for example when using the # gtest integration in cmake which runs the built test # executables during the build to discover the set of # tests. defines["CMAKE_BUILD_WITH_INSTALL_RPATH"] = "ON" defines.update(self.defines) define_args = ["-D%s=%s" % (k, v) for (k, v) in defines.items()] # if self.build_opts.is_windows(): # define_args += ["-G", "Visual Studio 15 2017 Win64"] define_args += ["-G", "Ninja"] return define_args def _run_include_rewriter(self) -> None: """Run include path rewriting on source files before building.""" from .include_rewriter import rewrite_includes_from_manifest print(f"Rewriting include paths for {self.manifest.name}...") try: modified_count = rewrite_includes_from_manifest( self.manifest, self.ctx, self.src_dir, verbose=True ) if modified_count > 0: print(f"Successfully modified {modified_count} files") else: print("No files needed modification") except Exception as e: print(f"Warning: Include path rewriting failed: {e}") # Don't fail the build for include rewriting issues def _build(self, reconfigure: bool) -> None: # Check if include rewriting is enabled rewrite_includes: str | None = self.manifest.get( "build", "rewrite_includes", "false", ctx=self.ctx ) # pyre-fixme[16]: Optional type has no attribute `lower`. if rewrite_includes.lower() == "true": self._run_include_rewriter() reconfigure = reconfigure or self._needs_reconfigure() env = self._compute_env() if not self.build_opts.is_windows() and self.final_install_prefix: env["DESTDIR"] = self.inst_dir # Resolve the cmake that we installed # pyre-fixme[6]: For 1st argument expected `Mapping[str, str]` but got `Env`. cmake = path_search(env, "cmake") if cmake is None: raise Exception("Failed to find CMake") if self.build_opts.is_windows(): checkdir = self.src_dir if os.path.exists(checkdir): children = os.listdir(checkdir) print(f"Building from source {checkdir} contents: {children}") else: print(f"Source {checkdir} not found") if reconfigure: define_args = self._compute_cmake_define_args(env) self._write_build_script( cmd_prefix=self._get_cmd_prefix(), cmake=cmake, # pyre-fixme[6]: For 1st argument expected `Mapping[str, str]` but # got `Env`. ctest=path_search(env, "ctest"), env=env, define_args=define_args, src_dir=self.src_dir, build_dir=self.build_dir, install_dir=self.inst_dir, sys=sys, build_type=self.build_opts.build_type, ) self._invalidate_cache() self._check_cmd([cmake, self.src_dir] + define_args, env=env) self._check_cmd( # pyre-fixme[6]: For 1st argument expected `List[str]` but got # `List[Optional[str]]`. [cmake, "--build", self.build_dir, "--target"] + self.cmake_targets + [ "--config", self.build_opts.build_type, "-j", str(self.num_jobs), ], env=env, preexec_fn=self.memory_limit_preexec_fn, ) def _build_targets(self, targets: Sequence[str]) -> None: """Build one or more cmake targets in parallel. Args: targets: Sequence of target names (strings) to build """ if not targets: return env = self._compute_env() # pyre-fixme[6]: For 1st argument expected `Mapping[str, str]` but got `Env`. cmake = path_search(env, "cmake") if cmake is None: raise RuntimeError("unable to find cmake") # Build all targets in a single cmake invocation for better parallelism cmd = [ cmake, "--build", self.build_dir, ] # Add all targets for target in targets: cmd.extend(["--target", target]) cmd.extend( # pyre-fixme[6]: For 1st argument expected `Iterable[str]` but got # `Iterable[Union[str, str, None, str]]`. [ "--config", self.build_opts.build_type, "-j", str(self.num_jobs), ] ) self._check_cmd(cmd, env=env, preexec_fn=self.memory_limit_preexec_fn) def _get_missing_test_executables( self, test_filter: str | None, env: Env, ctest: str | None ) -> set[str]: """Discover which test executables are missing for the given filter. Returns a set of missing executable basenames (without path).""" if ctest is None: return set() # Run ctest -N (show tests without running) with the filter to see which tests match cmd = [ctest, "-N"] if test_filter: cmd += ["-R", test_filter] try: output = subprocess.check_output( cmd, env=dict(env.items()), cwd=self.build_dir, stderr=subprocess.STDOUT, text=True, ) except subprocess.CalledProcessError as e: # If ctest fails, it might be because executables don't exist yet # Parse the error output to find the missing executables output = e.output # Parse output to find missing executable paths # Look for lines like "Could not find executable /path/to/test_binary" missing_executables = set() for line in output.split("\n"): match = re.search(r"Could not find executable (.+)", line) if match: exe_path = match.group(1) exe_name = os.path.basename(exe_path) missing_executables.add(exe_name) return missing_executables def run_tests( self, schedule_type: str, owner: str | None, test_filter: str | None, test_exclude: str | None, retry: int, no_testpilot: bool, timeout: int | None = None, ) -> None: env = self._compute_env() # pyre-fixme[6]: For 1st argument expected `Mapping[str, str]` but got `Env`. ctest: str | None = path_search(env, "ctest") # pyre-fixme[6]: For 1st argument expected `Mapping[str, str]` but got `Env`. cmake = path_search(env, "cmake") # Build only the missing test executables needed for the given filter. # This is especially important for LocalDirFetcher projects (like fboss) # where the build marker gets removed when building specific cmake targets. missing_test_executables = self._get_missing_test_executables( test_filter, env, ctest ) if missing_test_executables: sorted_executables = sorted(missing_test_executables) print(f"Building missing test executables: {', '.join(sorted_executables)}") # Build all missing executables in one cmake invocation for better parallelism self._build_targets(sorted_executables) def require_command(path: str | None, name: str) -> str: if path is None: raise RuntimeError("unable to find command `{}`".format(name)) return path # On Windows, we also need to update $PATH to include the directories that # contain runtime library dependencies. This is not needed on other platforms # since CMake will emit RPATH properly in the binary so they can find these # dependencies. if self.build_opts.is_windows(): path_entries = self.get_dev_run_extra_path_dirs() path = env.get("PATH") if path: path_entries.insert(0, path) env["PATH"] = ";".join(path_entries) # Don't use the cmd_prefix when running tests. This is vcvarsall.bat on # Windows. vcvarsall.bat is only needed for the build, not tests. It # unfortunately fails if invoked with a long PATH environment variable when # running the tests. use_cmd_prefix = False def get_property( test: dict[str, object], propname: str, defval: object = None ) -> object: """extracts a named property from a cmake test info json blob. The properties look like: [{"name": "WORKING_DIRECTORY"}, {"value": "something"}] We assume that it is invalid for the same named property to be listed more than once. """ props = test.get("properties", []) # pyre-fixme[16]: `object` has no attribute `__iter__`. for p in props: if p.get("name", None) == propname: return p.get("value", defval) return defval # pyre-fixme[53]: Captured variable `cmake` is not annotated. # pyre-fixme[53]: Captured variable `env` is not annotated. def list_tests() -> list[dict[str, object]]: output = subprocess.check_output( [require_command(ctest, "ctest"), "--show-only=json-v1"], env=env, cwd=self.build_dir, ) try: data = json.loads(output.decode("utf-8")) except ValueError as exc: raise Exception( "Failed to decode cmake test info using %s: %s. Output was: %r" % (ctest, str(exc), output) ) tests = [] machine_suffix = self.build_opts.host_type.as_tuple_string() for test in data["tests"]: working_dir = get_property(test, "WORKING_DIRECTORY") labels = [] machine_suffix = self.build_opts.host_type.as_tuple_string() labels.append("tpx-fb-test-type=3") labels.append("tpx_test_config::buildsystem=getdeps") labels.append("tpx_test_config::platform={}".format(machine_suffix)) if get_property(test, "DISABLED"): labels.append("disabled") command = test["command"] if working_dir: command = [ require_command(cmake, "cmake"), "-E", "chdir", working_dir, ] + command tests.append( { "type": "custom", "target": "%s-%s-getdeps-%s" % (self.manifest.name, test["name"], machine_suffix), "command": command, "labels": labels, "env": {}, "required_paths": [], "contacts": [], "cwd": os.getcwd(), } ) return tests discover_like_continuous = False if schedule_type == "continuous" or ( schedule_type == "base_retry" and is_public_commit(self.build_opts) ): discover_like_continuous = True if discover_like_continuous or schedule_type == "testwarden": # for continuous and testwarden runs, disabling retry can give up # better signals for flaky tests. retry = 0 tpx = None try: from .facebook.testinfra import start_run # pyre-fixme[6]: For 1st argument expected `Mapping[str, str]` but got # `Env`. tpx = path_search(env, "tpx") except ImportError: # internal testinfra not available pass if tpx and not no_testpilot: import os buck_test_info = list_tests() buck_test_info_name = os.path.join(self.build_dir, ".buck-test-info.json") with open(buck_test_info_name, "w") as f: json.dump(buck_test_info, f) env.set("http_proxy", "") env.set("https_proxy", "") runs = [] with start_run(env["FBSOURCE_HASH"]) as run_id: testpilot_args = [ tpx, "--force-local-execution", "--buck-test-info", buck_test_info_name, "--retry=%d" % retry, "-j=%s" % str(self.num_jobs), "--print-long-results", ] if owner: testpilot_args += ["--contacts", owner] if env: testpilot_args.append("--env") testpilot_args.extend(f"{key}={val}" for key, val in env.items()) if run_id is not None: testpilot_args += ["--run-id", run_id] if timeout is not None: testpilot_args += ["--timeout", str(timeout)] if test_filter: testpilot_args += ["--", test_filter] if schedule_type == "diff": runs.append(["--collection", "oss-diff", "--purpose", "diff"]) elif discover_like_continuous: runs.append( [ "--tag-new-tests", "--collection", "oss-continuous", "--purpose", "continuous", ] ) elif schedule_type == "testwarden": # One run to assess new tests runs.append( [ "--tag-new-tests", "--collection", "oss-new-test-stress", "--stress-runs", "10", "--purpose", "stress-run-new-test", ] ) # And another for existing tests runs.append( [ "--tag-new-tests", "--collection", "oss-existing-test-stress", "--stress-runs", "10", "--purpose", "stress-run", ] ) else: runs.append([]) for run in runs: # FIXME: What is this trying to accomplish? Should it fail on first or >=1 errors? self._run_cmd( testpilot_args + run, cwd=self.build_opts.fbcode_builder_dir, env=env, use_cmd_prefix=use_cmd_prefix, ) else: args = [ require_command(ctest, "ctest"), "--output-on-failure", "-j", str(self.num_jobs), ] if test_filter: args += ["-R", test_filter] if test_exclude: args += ["--exclude-regex", test_exclude] if timeout is not None: args += ["--timeout", str(timeout)] count: int = 0 retcode: int | None = -1 while count <= retry: # FIXME: What is this trying to accomplish? Should it fail on first or >=1 errors? retcode = self._check_cmd( args, env=env, use_cmd_prefix=use_cmd_prefix, allow_fail=True ) if retcode == 0: break if count == 0: # Only add this option in the second run. args += ["--rerun-failed"] count += 1 if retcode is not None and retcode != 0: # Allow except clause in getdeps.main to catch and exit gracefully # This allows non-testpilot runs to fail through the same logic as failed testpilot runs, which may become handy in case if post test processing is needed in the future raise subprocess.CalledProcessError(retcode, args) class NinjaBootstrap(BuilderBase): def __init__( self, loader: ManifestLoader, dep_manifests: list[ManifestParser], build_opts: BuildOptions, ctx: ManifestContext, manifest: ManifestParser, build_dir: str, src_dir: str, inst_dir: str, ) -> None: super(NinjaBootstrap, self).__init__( loader, dep_manifests, build_opts, ctx, manifest, src_dir, build_dir, inst_dir, ) def _build(self, reconfigure: bool) -> None: self._check_cmd( [sys.executable, "configure.py", "--bootstrap"], cwd=self.src_dir ) src_ninja = os.path.join(self.src_dir, "ninja") dest_ninja = os.path.join(self.inst_dir, "bin/ninja") bin_dir = os.path.dirname(dest_ninja) if not os.path.exists(bin_dir): os.makedirs(bin_dir) shutil.copyfile(src_ninja, dest_ninja) shutil.copymode(src_ninja, dest_ninja) class OpenSSLBuilder(BuilderBase): def __init__( self, loader: ManifestLoader, dep_manifests: list[ManifestParser], build_opts: BuildOptions, ctx: ManifestContext, manifest: ManifestParser, build_dir: str, src_dir: str, inst_dir: str, ) -> None: super(OpenSSLBuilder, self).__init__( loader, dep_manifests, build_opts, ctx, manifest, src_dir, build_dir, inst_dir, ) def _build(self, reconfigure: bool) -> None: configure = os.path.join(self.src_dir, "Configure") # prefer to resolve the perl that we installed from # our manifest on windows, but fall back to the system # path on eg: darwin env = self.env.copy() for m in self.dep_manifests: bindir = os.path.join(self.loader.get_project_install_dir(m), "bin") add_path_entry(env, "PATH", bindir, append=False) # pyre-fixme[6]: For 1st argument expected `Mapping[str, str]` but got `Env`. perl = typing.cast(str, path_search(env, "perl", "perl")) make_j_args = [] extra_args = [] if self.build_opts.is_windows(): # jom is compatible with nmake, adds the /j argument for parallel build make = "jom.exe" make_j_args = ["/j%s" % self.num_jobs] args = ["VC-WIN64A-masm", "-utf-8"] # fixes "if multiple CL.EXE write to the same .PDB file, please use /FS" extra_args = ["/FS"] elif self.build_opts.is_darwin(): make = "make" make_j_args = ["-j%s" % self.num_jobs] args = ( ["darwin64-x86_64-cc"] if not self.build_opts.is_arm() else ["darwin64-arm64-cc"] ) elif self.build_opts.is_linux(): make = "make" make_j_args = ["-j%s" % self.num_jobs] args = ( ["linux-x86_64"] if not self.build_opts.is_arm() else ["linux-aarch64"] ) else: raise Exception("don't know how to build openssl for %r" % self.ctx) self._check_cmd( [ perl, configure, "--prefix=%s" % self.inst_dir, "--openssldir=%s" % self.inst_dir, ] + args + [ "enable-static-engine", "enable-capieng", "no-makedepend", "no-unit-test", "no-tests", ] + extra_args ) # show the config produced self._check_cmd([perl, "configdata.pm", "--dump"], env=env) make_build = [make] + make_j_args self._check_cmd(make_build, env=env) make_install = [make, "install_sw", "install_ssldirs"] self._check_cmd(make_install, env=env) class Boost(BuilderBase): def __init__( self, loader: ManifestLoader, dep_manifests: list[ManifestParser], build_opts: BuildOptions, ctx: ManifestContext, manifest: ManifestParser, src_dir: str, build_dir: str, inst_dir: str, b2_args: list[str], ) -> None: children = os.listdir(src_dir) assert len(children) == 1, "expected a single directory entry: %r" % (children,) boost_src = children[0] assert boost_src.startswith("boost") src_dir = os.path.join(src_dir, children[0]) super(Boost, self).__init__( loader, dep_manifests, build_opts, ctx, manifest, src_dir, build_dir, inst_dir, ) self.b2_args: list[str] = b2_args def _build(self, reconfigure: bool) -> None: env = self._compute_env() linkage: list[str] = ["static"] if self.build_opts.is_windows() or self.build_opts.shared_libs: linkage.append("shared") args = [] if self.build_opts.is_darwin(): clang = subprocess.check_output(["xcrun", "--find", "clang"]) user_config = os.path.join(self.build_dir, "project-config.jam") with open(user_config, "w") as jamfile: jamfile.write("using clang : : %s ;\n" % clang.decode().strip()) args.append("--user-config=%s" % user_config) for link in linkage: bootstrap_args = self.manifest.get_section_as_args( "bootstrap.args", self.ctx ) if self.build_opts.is_windows(): bootstrap = os.path.join(self.src_dir, "bootstrap.bat") self._check_cmd([bootstrap] + bootstrap_args, cwd=self.src_dir, env=env) args += ["address-model=64"] else: bootstrap = os.path.join(self.src_dir, "bootstrap.sh") self._check_cmd( [bootstrap, "--prefix=%s" % self.inst_dir] + bootstrap_args, cwd=self.src_dir, env=env, ) b2 = os.path.join(self.src_dir, "b2") self._check_cmd( [ b2, "-j%s" % self.num_jobs, "--prefix=%s" % self.inst_dir, "--builddir=%s" % self.build_dir, ] + args + self.b2_args + [ "link=%s" % link, "runtime-link=shared", "variant=release", "threading=multi", "debug-symbols=on", "visibility=global", "-d2", "install", ], cwd=self.src_dir, env=env, ) class NopBuilder(BuilderBase): def __init__( self, loader: ManifestLoader, dep_manifests: list[ManifestParser], build_opts: BuildOptions, ctx: ManifestContext, manifest: ManifestParser, src_dir: str, inst_dir: str, ) -> None: super(NopBuilder, self).__init__( loader, dep_manifests, build_opts, ctx, manifest, src_dir, None, inst_dir ) def build(self, reconfigure: bool) -> None: print("Installing %s -> %s" % (self.src_dir, self.inst_dir)) parent = os.path.dirname(self.inst_dir) if not os.path.exists(parent): os.makedirs(parent) install_files = self.manifest.get_section_as_ordered_pairs( "install.files", self.ctx ) if install_files: for src_name, dest_name in self.manifest.get_section_as_ordered_pairs( "install.files", self.ctx ): # pyre-fixme[6]: For 2nd argument expected `Union[PathLike[str], # str]` but got `Optional[str]`. full_dest = os.path.join(self.inst_dir, dest_name) full_src = os.path.join(self.src_dir, src_name) dest_parent = os.path.dirname(full_dest) if not os.path.exists(dest_parent): os.makedirs(dest_parent) if os.path.isdir(full_src): if not os.path.exists(full_dest): simple_copytree(full_src, full_dest) else: shutil.copyfile(full_src, full_dest) shutil.copymode(full_src, full_dest) # This is a bit gross, but the mac ninja.zip doesn't # give ninja execute permissions, so force them on # for things that look like they live in a bin dir # pyre-fixme[6]: For 1st argument expected `PathLike[AnyStr]` # but got `Optional[str]`. if os.path.dirname(dest_name) == "bin": st = os.lstat(full_dest) os.chmod(full_dest, st.st_mode | stat.S_IXUSR) else: if not os.path.exists(self.inst_dir): simple_copytree(self.src_dir, self.inst_dir) class SetupPyBuilder(BuilderBase): def _build(self, reconfigure: bool) -> None: env = self._compute_env() setup_env = self.manifest.get_section_as_dict("setup-py.env", self.ctx) for key, value in setup_env.items(): # pyre-fixme[6]: For 2nd argument expected `str` but got `Optional[str]`. env[key] = value setup_py_path = os.path.join(self.src_dir, "setup.py") if not os.path.exists(setup_py_path): raise RuntimeError(f"setup.py script not found at {setup_py_path}") self._check_cmd( # pyre-fixme[6]: For 1st argument expected `List[str]` but got # `List[Union[str, None, str]]`. # pyre-fixme[6]: For 1st argument expected `Mapping[str, str]` but got # `Env`. [path_search(env, "python3"), setup_py_path, "install"], cwd=self.src_dir, env=env, ) # Create the installation directory if it doesn't exist os.makedirs(self.inst_dir, exist_ok=True) # Mark the project as built with open(os.path.join(self.inst_dir, ".built-by-getdeps"), "w") as f: f.write("built") def run_tests( self, schedule_type: str, owner: str | None, test_filter: str | None, test_exclude: str | None, retry: int, no_testpilot: bool, timeout: int | None = None, ) -> None: # setup.py actually no longer has a standard command for running tests. # Instead we let manifest files specify an arbitrary Python file to run # as a test. # Get the test command from the manifest python_script = self.manifest.get( "setup-py.test", "python_script", ctx=self.ctx ) if not python_script: print(f"No test script specified for {self.manifest.name}") return # Run the command env = self._compute_env() self._check_cmd(["python3", python_script], cwd=self.src_dir, env=env) class SqliteBuilder(BuilderBase): def __init__( self, loader: ManifestLoader, dep_manifests: list[ManifestParser], build_opts: BuildOptions, ctx: ManifestContext, manifest: ManifestParser, src_dir: str, build_dir: str, inst_dir: str, ) -> None: super(SqliteBuilder, self).__init__( loader, dep_manifests, build_opts, ctx, manifest, src_dir, build_dir, inst_dir, ) def _build(self, reconfigure: bool) -> None: for f in ["sqlite3.c", "sqlite3.h", "sqlite3ext.h"]: src = os.path.join(self.src_dir, f) dest = os.path.join(self.build_dir, f) copy_if_different(src, dest) cmake_lists = """ cmake_minimum_required(VERSION 3.5 FATAL_ERROR) project(sqlite3 C) add_library(sqlite3 STATIC sqlite3.c) # These options are taken from the defaults in Makefile.msc in # the sqlite distribution target_compile_definitions(sqlite3 PRIVATE -DSQLITE_ENABLE_COLUMN_METADATA=1 -DSQLITE_ENABLE_FTS3=1 -DSQLITE_ENABLE_RTREE=1 -DSQLITE_ENABLE_GEOPOLY=1 -DSQLITE_ENABLE_JSON1=1 -DSQLITE_ENABLE_STMTVTAB=1 -DSQLITE_ENABLE_DBPAGE_VTAB=1 -DSQLITE_ENABLE_DBSTAT_VTAB=1 -DSQLITE_INTROSPECTION_PRAGMAS=1 -DSQLITE_ENABLE_DESERIALIZE=1 ) install(TARGETS sqlite3) install(FILES sqlite3.h sqlite3ext.h DESTINATION include) """ with open(os.path.join(self.build_dir, "CMakeLists.txt"), "w") as f: f.write(cmake_lists) defines = { "CMAKE_INSTALL_PREFIX": self.inst_dir, "BUILD_SHARED_LIBS": "ON" if self.build_opts.shared_libs else "OFF", "CMAKE_BUILD_TYPE": "RelWithDebInfo", } define_args = ["-D%s=%s" % (k, v) for (k, v) in defines.items()] define_args += ["-G", "Ninja"] env = self._compute_env() # Resolve the cmake that we installed # pyre-fixme[6]: For 1st argument expected `Mapping[str, str]` but got `Env`. cmake = path_search(env, "cmake") # pyre-fixme[6]: For 1st argument expected `List[str]` but got # `List[Optional[str]]`. self._check_cmd([cmake, self.build_dir] + define_args, env=env) self._check_cmd( # pyre-fixme[6]: For 1st argument expected `List[str]` but got # `List[Union[str, str, str, str, str, None, str]]`. [ cmake, "--build", self.build_dir, "--target", "install", "--config", self.build_opts.build_type, "-j", str(self.num_jobs), ], env=env, ) ================================================ FILE: build/fbcode_builder/getdeps/buildopts.py ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. # pyre-strict from __future__ import annotations import argparse import errno import glob import ntpath import os import subprocess import sys import tempfile import typing from collections.abc import Mapping from .copytree import containing_repo_type from .envfuncs import add_flag, add_path_entry, Env from .fetcher import get_fbsource_repo_data, homebrew_package_prefix from .manifest import ContextGenerator from .platform import get_available_ram, HostType, is_windows if typing.TYPE_CHECKING: from .load import ManifestLoader from .manifest import ManifestContext, ManifestParser GITBASH_TMP: str = "c:\\tools\\fb.gitbash\\tmp" def detect_project(path: str) -> tuple[str | None, str | None]: repo_type, repo_root = containing_repo_type(path) if repo_type is None: return None, None # Look for a .projectid file. If it exists, read the project name from it. # pyre-fixme[6]: For 1st argument expected `LiteralString` but got `Optional[str]`. project_id_path = os.path.join(repo_root, ".projectid") try: with open(project_id_path, "r") as f: project_name = f.read().strip() return repo_root, project_name except EnvironmentError as ex: if ex.errno != errno.ENOENT: raise return repo_root, None class BuildOptions: def __init__( self, fbcode_builder_dir: str, scratch_dir: str, host_type: HostType, install_dir: str | None = None, num_jobs: int = 0, use_shipit: bool = False, vcvars_path: str | None = None, allow_system_packages: bool = False, lfs_path: str | None = None, shared_libs: bool = False, facebook_internal: bool | None = None, free_up_disk: bool = False, build_type: str | None = None, ) -> None: """fbcode_builder_dir - the path to either the in-fbsource fbcode_builder dir, or for shipit-transformed repos, the build dir that has been mapped into that dir. scratch_dir - a place where we can store repos and build bits. This path should be stable across runs and ideally should not be in the repo of the project being built, but that is ultimately where we generally fall back for builds outside of FB install_dir - where the project will ultimately be installed num_jobs - the level of concurrency to use while building use_shipit - use real shipit instead of the simple shipit transformer vcvars_path - Path to external VS toolchain's vsvarsall.bat shared_libs - whether to build shared libraries free_up_disk - take extra actions to save runner disk space build_type - CMAKE_BUILD_TYPE, used by cmake and cargo builders """ if not install_dir: install_dir = os.path.join(scratch_dir, "installed") self.project_hashes: str | None = None for p in ["../deps/github_hashes", "../project_hashes"]: hashes = os.path.join(fbcode_builder_dir, p) if os.path.exists(hashes): self.project_hashes = hashes break # Detect what repository and project we are being run from. # pyre-fixme[4]: Attribute must be annotated. self.repo_root, self.repo_project = detect_project(os.getcwd()) # If we are running from an fbsource repository, set self.fbsource_dir # to allow the ShipIt-based fetchers to use it. if self.repo_project == "fbsource": self.fbsource_dir: str | None = self.repo_root else: self.fbsource_dir = None if facebook_internal is None: if self.fbsource_dir: facebook_internal = True else: facebook_internal = False self.facebook_internal: bool = facebook_internal self.specified_num_jobs: int = num_jobs self.scratch_dir: str = scratch_dir self.install_dir: str = install_dir self.fbcode_builder_dir: str = fbcode_builder_dir self.host_type: HostType = host_type self.use_shipit: bool = use_shipit self.allow_system_packages: bool = allow_system_packages self.lfs_path: str | None = lfs_path self.shared_libs: bool = shared_libs self.free_up_disk: bool = free_up_disk self.build_type: str | None = build_type lib_path: str | None = None if self.is_darwin(): lib_path = "DYLD_LIBRARY_PATH" elif self.is_linux(): lib_path = "LD_LIBRARY_PATH" elif self.is_windows(): lib_path = "PATH" else: lib_path = None self.lib_path: str | None = lib_path if vcvars_path is None and is_windows(): try: # Allow a site-specific vcvarsall path. from .facebook.vcvarsall import build_default_vcvarsall except ImportError: vcvarsall: list[str] = [] else: vcvarsall = ( build_default_vcvarsall(self.fbsource_dir) if self.fbsource_dir is not None else [] ) # On Windows, the compiler is not available in the PATH by # default so we need to run the vcvarsall script to populate the # environment. We use a glob to find some version of this script # as deployed with Visual Studio. if len(vcvarsall) == 0: # check the 64 bit installs for year in ["2022"]: vcvarsall += glob.glob( os.path.join( os.environ.get("ProgramFiles", "C:\\Program Files"), "Microsoft Visual Studio", year, "*", "VC", "Auxiliary", "Build", "vcvarsall.bat", ) ) # then the 32 bit ones for year in ["2022", "2019", "2017"]: vcvarsall += glob.glob( os.path.join( os.environ["ProgramFiles(x86)"], "Microsoft Visual Studio", year, "*", "VC", "Auxiliary", "Build", "vcvarsall.bat", ) ) if len(vcvarsall) == 0: raise Exception( "Could not find vcvarsall.bat. Please install Visual Studio." ) vcvars_path = vcvarsall[0] print(f"Using vcvarsall.bat from {vcvars_path}", file=sys.stderr) self.vcvars_path: str | None = vcvars_path @property def manifests_dir(self) -> str: return os.path.join(self.fbcode_builder_dir, "manifests") def is_darwin(self) -> bool: return self.host_type.is_darwin() def is_windows(self) -> bool: return self.host_type.is_windows() def is_arm(self) -> bool: return self.host_type.is_arm() def get_vcvars_path(self) -> str | None: return self.vcvars_path def is_linux(self) -> bool: return self.host_type.is_linux() def is_freebsd(self) -> bool: return self.host_type.is_freebsd() def get_num_jobs(self, job_weight: int) -> int: """Given an estimated job_weight in MiB, compute a reasonable concurrency limit.""" if self.specified_num_jobs: return self.specified_num_jobs available_ram = get_available_ram() import multiprocessing return max(1, min(multiprocessing.cpu_count(), available_ram // job_weight)) def get_context_generator( self, host_tuple: str | HostType | None = None ) -> ContextGenerator: """Create a manifest ContextGenerator for the specified target platform.""" if host_tuple is None: host_type = self.host_type elif isinstance(host_tuple, HostType): host_type = host_tuple else: host_type = HostType.from_tuple_string(host_tuple) return ContextGenerator( { "os": host_type.ostype, "distro": host_type.distro, "distro_vers": host_type.distrovers, "fb": "on" if self.facebook_internal else "off", "fbsource": "on" if self.fbsource_dir else "off", "test": "off", "shared_libs": "on" if self.shared_libs else "off", } ) def compute_env_for_install_dirs( self, loader: ManifestLoader, dep_manifests: list[ManifestParser], ctx: ManifestContext, env: Env | None = None, manifest: ManifestParser | None = None, ) -> Env: # noqa: C901 if env is not None: env = env.copy() else: env = Env() env["GETDEPS_BUILD_DIR"] = os.path.join(self.scratch_dir, "build") env["GETDEPS_INSTALL_DIR"] = self.install_dir # Python setuptools attempts to discover a local MSVC for # building Python extensions. On Windows, getdeps already # supports invoking a vcvarsall prior to compilation. # # Tell setuptools to bypass its own search. This fixes a bug # where setuptools would fail when run from CMake on GitHub # Actions with the inscrutable message 'error: Microsoft # Visual C++ 14.0 is required. Get it with "Build Tools for # Visual Studio"'. I suspect the actual error is that the # environment or PATH is overflowing. # # For extra credit, someone could patch setuptools to # propagate the actual error message from vcvarsall, because # often it does not mean Visual C++ is not available. # # Related discussions: # - https://github.com/pypa/setuptools/issues/2028 # - https://github.com/pypa/setuptools/issues/2307 # - https://developercommunity.visualstudio.com/t/error-microsoft-visual-c-140-is-required/409173 # - https://github.com/OpenMS/OpenMS/pull/4779 # - https://github.com/actions/virtual-environments/issues/1484 if self.is_windows() and self.get_vcvars_path(): env["DISTUTILS_USE_SDK"] = "1" # On macOS we need to set `SDKROOT` when we use clang for system # header files. if self.is_darwin() and "SDKROOT" not in env: sdkroot = subprocess.check_output(["xcrun", "--show-sdk-path"]) env["SDKROOT"] = sdkroot.decode().strip() if ( self.is_darwin() and self.allow_system_packages and self.host_type.get_package_manager() == "homebrew" and manifest and manifest.resolved_system_packages ): # Homebrew packages may not be on the default PATHs brew_packages = manifest.resolved_system_packages.get("homebrew", []) for p in brew_packages: found = self.add_homebrew_package_to_env(p, env) # Try extra hard to find openssl, needed with homebrew on macOS if found and p.startswith("openssl"): candidate = homebrew_package_prefix("openssl@1.1") # pyre-fixme[6]: For 1st argument expected # `Union[PathLike[bytes], PathLike[str], bytes, int, str]` but got # `Optional[str]`. if os.path.exists(candidate): # pyre-fixme[6]: For 2nd argument expected `str` but got # `Optional[str]`. os.environ["OPENSSL_ROOT_DIR"] = candidate env["OPENSSL_ROOT_DIR"] = os.environ["OPENSSL_ROOT_DIR"] if self.fbsource_dir: env["YARN_YARN_OFFLINE_MIRROR"] = os.path.join( self.fbsource_dir, "xplat/third-party/yarn/offline-mirror" ) yarn_exe = "yarn.bat" if self.is_windows() else "yarn" env["YARN_PATH"] = os.path.join( # pyre-fixme[6]: For 1st argument expected `LiteralString` but got # `Optional[str]`. self.fbsource_dir, "xplat/third-party/yarn/", yarn_exe, ) node_exe = "node-win-x64.exe" if self.is_windows() else "node" env["NODE_BIN"] = os.path.join( # pyre-fixme[6]: For 1st argument expected `LiteralString` but got # `Optional[str]`. self.fbsource_dir, "xplat/third-party/node/bin/", node_exe, ) env["RUST_VENDORED_CRATES_DIR"] = os.path.join( # pyre-fixme[6]: For 1st argument expected `LiteralString` but got # `Optional[str]`. self.fbsource_dir, "third-party/rust/vendor", ) hash_data = get_fbsource_repo_data(self) env["FBSOURCE_HASH"] = hash_data.hash env["FBSOURCE_DATE"] = hash_data.date # reverse as we are prepending to the PATHs for m in reversed(dep_manifests): is_direct_dep = ( manifest is not None and m.name in manifest.get_dependencies(ctx) ) d = loader.get_project_install_dir(m) if os.path.exists(d): self.add_prefix_to_env( d, env, append=False, is_direct_dep=is_direct_dep, ) # Linux is always system openssl system_openssl: bool = self.is_linux() # For other systems lets see if package is requested if not system_openssl and manifest and manifest.resolved_system_packages: for _pkg_type, pkgs in manifest.resolved_system_packages.items(): for p in pkgs: if p.startswith("openssl") or p.startswith("libssl"): system_openssl = True break # Let openssl know to pick up the system certs if present if system_openssl or "OPENSSL_DIR" in env: for system_ssl_cfg in ["/etc/pki/tls", "/etc/ssl"]: if os.path.isdir(system_ssl_cfg): cert_dir = system_ssl_cfg + "/certs" if os.path.isdir(cert_dir): env["SSL_CERT_DIR"] = cert_dir cert_file = system_ssl_cfg + "/cert.pem" if os.path.isfile(cert_file): env["SSL_CERT_FILE"] = cert_file return env def add_homebrew_package_to_env(self, package: str, env: Env) -> bool: prefix = homebrew_package_prefix(package) if prefix and os.path.exists(prefix): return self.add_prefix_to_env( prefix, env, append=False, add_library_path=True ) return False def add_prefix_to_env( self, d: str, env: Env, append: bool = True, add_library_path: bool = False, is_direct_dep: bool = False, ) -> bool: # noqa: C901 bindir: str = os.path.join(d, "bin") found: bool = False has_pkgconfig: bool = False pkgconfig: str = os.path.join(d, "lib", "pkgconfig") if os.path.exists(pkgconfig): found = True has_pkgconfig = True add_path_entry(env, "PKG_CONFIG_PATH", pkgconfig, append=append) pkgconfig = os.path.join(d, "lib64", "pkgconfig") if os.path.exists(pkgconfig): found = True has_pkgconfig = True add_path_entry(env, "PKG_CONFIG_PATH", pkgconfig, append=append) add_path_entry(env, "CMAKE_PREFIX_PATH", d, append=append) # Tell the thrift compiler about includes it needs to consider thriftdir: str = os.path.join(d, "include", "thrift-files") if os.path.exists(thriftdir): found = True add_path_entry(env, "THRIFT_INCLUDE_PATH", thriftdir, append=append) # module detection for python is old fashioned and needs flags includedir: str = os.path.join(d, "include") if os.path.exists(includedir): found = True ncursesincludedir: str = os.path.join(d, "include", "ncurses") if os.path.exists(ncursesincludedir): add_path_entry(env, "C_INCLUDE_PATH", ncursesincludedir, append=append) add_flag(env, "CPPFLAGS", f"-I{includedir}", append=append) add_flag(env, "CPPFLAGS", f"-I{ncursesincludedir}", append=append) elif "/bz2-" in d: add_flag(env, "CPPFLAGS", f"-I{includedir}", append=append) # For non-pkgconfig projects Cabal has no way to find the includes or # libraries, so we provide a set of extra Cabal flags in the env if not has_pkgconfig and is_direct_dep: add_flag( env, "GETDEPS_CABAL_FLAGS", f"--extra-include-dirs={includedir}", append=append, ) # The thrift compiler's built-in includes are installed directly to the include dir includethriftdir: str = os.path.join(d, "include", "thrift") if os.path.exists(includethriftdir): add_path_entry(env, "THRIFT_INCLUDE_PATH", includedir, append=append) # Map from FB python manifests to PYTHONPATH pydir: str = os.path.join(d, "lib", "fb-py-libs") if os.path.exists(pydir): found = True manifest_ext: str = ".manifest" pymanifestfiles: list[str] = [ f for f in os.listdir(pydir) if f.endswith(manifest_ext) and os.path.isfile(os.path.join(pydir, f)) ] for f in pymanifestfiles: subdir = f[: -len(manifest_ext)] add_path_entry( env, "PYTHONPATH", os.path.join(pydir, subdir), append=append ) # Allow resolving shared objects built earlier (eg: zstd # doesn't include the full path to the dylib in its linkage # so we need to give it an assist) if self.lib_path: for lib in ["lib", "lib64"]: libdir: str = os.path.join(d, lib) if os.path.exists(libdir): found = True # pyre-fixme[6]: For 2nd argument expected `str` but got # `Optional[str]`. add_path_entry(env, self.lib_path, libdir, append=append) # module detection for python is old fashioned and needs flags if "/ncurses-" in d: add_flag(env, "LDFLAGS", f"-L{libdir}", append=append) elif "/bz2-" in d: add_flag(env, "LDFLAGS", f"-L{libdir}", append=append) if add_library_path: add_path_entry(env, "LIBRARY_PATH", libdir, append=append) if not has_pkgconfig and is_direct_dep: add_flag( env, "GETDEPS_CABAL_FLAGS", f"--extra-lib-dirs={libdir}", append=append, ) # Allow resolving binaries (eg: cmake, ninja) and dlls # built by earlier steps if os.path.exists(bindir): found = True add_path_entry(env, "PATH", bindir, append=append) # If rustc is present in the `bin` directory, set RUSTC to prevent # cargo uses the rustc installed in the system. if self.is_windows(): cargo_path: str = os.path.join(bindir, "cargo.exe") rustc_path: str = os.path.join(bindir, "rustc.exe") rustdoc_path: str = os.path.join(bindir, "rustdoc.exe") else: cargo_path = os.path.join(bindir, "cargo") rustc_path = os.path.join(bindir, "rustc") rustdoc_path = os.path.join(bindir, "rustdoc") if os.path.isfile(rustc_path): env["CARGO_BIN"] = cargo_path env["RUSTC"] = rustc_path env["RUSTDOC"] = rustdoc_path openssl_include: str = os.path.join(d, "include", "openssl") if os.path.isdir(openssl_include) and any( os.path.isfile(os.path.join(d, "lib", libcrypto)) for libcrypto in ("libcrypto.lib", "libcrypto.so", "libcrypto.a") ): # This must be the openssl library, let Rust know about it env["OPENSSL_DIR"] = d return found def list_win32_subst_letters() -> dict[str, str]: output = subprocess.check_output(["subst"]).decode("utf-8") # The output is a set of lines like: `F:\: => C:\open\some\where` lines = output.strip().split("\r\n") mapping: dict[str, str] = {} for line in lines: fields = line.split(": => ") if len(fields) != 2: continue letter = fields[0] path = fields[1] mapping[letter] = path return mapping def find_existing_win32_subst_for_path( path: str, subst_mapping: Mapping[str, str], ) -> str | None: path = ntpath.normcase(ntpath.normpath(path)) for letter, target in subst_mapping.items(): if ntpath.normcase(target) == path: return letter return None def find_unused_drive_letter() -> str | None: import ctypes buffer_len = 256 blen = ctypes.c_uint(buffer_len) rv = ctypes.c_uint() bufs = ctypes.create_string_buffer(buffer_len) # pyre-fixme[16]: Module `ctypes` has no attribute `windll`. rv = ctypes.windll.kernel32.GetLogicalDriveStringsA(blen, bufs) if rv > buffer_len: raise Exception("GetLogicalDriveStringsA result too large for buffer") nul = "\x00".encode("ascii") used: list[str] = [ drive.decode("ascii")[0] for drive in bufs.raw.strip(nul).split(nul) ] possible: list[str] = [c for c in "ABCDEFGHIJKLMNOPQRSTUVWXYZ"] available: list[str] = sorted(list(set(possible) - set(used))) if len(available) == 0: return None # Prefer to assign later letters rather than earlier letters return available[-1] def map_subst_path(path: str) -> str: """find a short drive letter mapping for a path""" for _attempt in range(0, 24): drive = find_existing_win32_subst_for_path( path, subst_mapping=list_win32_subst_letters() ) if drive: return drive available = find_unused_drive_letter() if available is None: raise Exception( ( "unable to make shorter subst mapping for %s; " "no available drive letters" ) % path ) # Try to set up a subst mapping; note that we may be racing with # other processes on the same host, so this may not succeed. try: subprocess.check_call(["subst", "%s:" % available, path]) subst = "%s:\\" % available print("Mapped scratch dir %s -> %s" % (path, subst), file=sys.stderr) return subst except Exception: print("Failed to map %s -> %s" % (available, path), file=sys.stderr) raise Exception("failed to set up a subst path for %s" % path) def _check_host_type(args: argparse.Namespace, host_type: HostType | None) -> HostType: if host_type is None: host_tuple_string: str | None = getattr(args, "host_type", None) if host_tuple_string: host_type = HostType.from_tuple_string(host_tuple_string) else: host_type = HostType() assert isinstance(host_type, HostType) return host_type def setup_build_options( args: argparse.Namespace, host_type: HostType | None = None ) -> BuildOptions: """Create a BuildOptions object based on the arguments""" fbcode_builder_dir: str = os.path.dirname( os.path.dirname(os.path.abspath(__file__)) ) scratch_dir: str | None = args.scratch_path if not scratch_dir: # TODO: `mkscratch` doesn't currently know how best to place things on # sandcastle, so whip up something reasonable-ish if "SANDCASTLE" in os.environ: if "DISK_TEMP" not in os.environ: raise Exception( ( "I need DISK_TEMP to be set in the sandcastle environment " "so that I can store build products somewhere sane" ) ) disk_temp: str = os.environ["DISK_TEMP"] if is_windows(): # force use gitbash tmp dir for windows, as its less likely to have a tmp cleaner # that removes extracted prior dated source files os.makedirs(GITBASH_TMP, exist_ok=True) print( f"Using {GITBASH_TMP} instead of DISK_TEMP {disk_temp} for scratch dir", file=sys.stderr, ) disk_temp = GITBASH_TMP scratch_dir = os.path.join(disk_temp, "fbcode_builder_getdeps") if not scratch_dir: try: scratch_dir = ( subprocess.check_output( ["mkscratch", "path", "--subdir", "fbcode_builder_getdeps"] ) .strip() .decode("utf-8") ) except OSError as exc: if exc.errno != errno.ENOENT: # A legit failure; don't fall back, surface the error raise # This system doesn't have mkscratch so we fall back to # something local. munged: str = fbcode_builder_dir.replace("Z", "zZ") for s in ["/", "\\", ":"]: munged = munged.replace(s, "Z") if is_windows() and os.path.isdir("c:/open"): temp: str = "c:/open/scratch" else: temp = tempfile.gettempdir() scratch_dir = os.path.join(temp, "fbcode_builder_getdeps-%s" % munged) if not is_windows() and os.geteuid() == 0: # Running as root; in the case where someone runs # sudo getdeps.py install-system-deps # and then runs as build without privs, we want to avoid creating # a scratch dir that the second stage cannot write to. # So we generate a different path if we are root. scratch_dir += "-root" if not os.path.exists(scratch_dir): os.makedirs(scratch_dir) if is_windows(): subst = map_subst_path(scratch_dir) scratch_dir = subst else: if not os.path.exists(scratch_dir): os.makedirs(scratch_dir) # Make sure we normalize the scratch path. This path is used as part of the hash # computation for detecting if projects have been updated, so we need to always # use the exact same string to refer to a given directory. # But! realpath in some combinations of Windows/Python3 versions can expand the # drive substitutions on Windows, so avoid that! if not is_windows(): scratch_dir = os.path.realpath(scratch_dir) # Save these args passed by the user in an env variable, so it # can be used while hashing this build. os.environ["GETDEPS_CMAKE_DEFINES"] = getattr(args, "extra_cmake_defines", "") or "" host_type = _check_host_type(args, host_type) build_args: dict[str, object] = { k: v for (k, v) in vars(args).items() if k in { "num_jobs", "use_shipit", "vcvars_path", "allow_system_packages", "lfs_path", "shared_libs", "free_up_disk", "build_type", } } return BuildOptions( fbcode_builder_dir, scratch_dir, host_type, install_dir=args.install_prefix, facebook_internal=args.facebook_internal, # pyre-fixme[6]: For 6th argument expected `Optional[str]` but got `object`. # pyre-fixme[6]: For 6th argument expected `bool` but got `object`. # pyre-fixme[6]: For 6th argument expected `int` but got `object`. **build_args, ) ================================================ FILE: build/fbcode_builder/getdeps/cache.py ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. # pyre-strict from __future__ import annotations class ArtifactCache: """The ArtifactCache is a small abstraction that allows caching named things in some external storage mechanism. The primary use case is for storing the build products on CI systems to accelerate the build""" def download_to_file(self, name: str, dest_file_name: str) -> bool: """If `name` exists in the cache, download it and place it in the specified `dest_file_name` location on the filesystem. If a transient issue was encountered a TransientFailure shall be raised. If `name` doesn't exist in the cache `False` shall be returned. If `dest_file_name` was successfully updated `True` shall be returned. All other conditions shall raise an appropriate exception.""" return False def upload_from_file(self, name: str, source_file_name: str) -> None: """Causes `name` to be populated in the cache by uploading the contents of `source_file_name` to the storage system. If a transient issue was encountered a TransientFailure shall be raised. If the upload failed for some other reason, an appropriate exception shall be raised.""" pass def create_cache() -> ArtifactCache | None: """This function is monkey patchable to provide an actual implementation""" return None ================================================ FILE: build/fbcode_builder/getdeps/cargo.py ================================================ #!/usr/bin/env python3 # Copyright (c) Meta Platforms, Inc. and affiliates. # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. # pyre-strict from __future__ import annotations import os import re import shutil import sys import typing from .builder import BuilderBase from .copytree import rmtree_more, simple_copytree if typing.TYPE_CHECKING: from .buildopts import BuildOptions from .load import ManifestLoader from .manifest import ManifestContext, ManifestParser class CargoBuilder(BuilderBase): def __init__( self, loader: ManifestLoader, dep_manifests: list[ManifestParser], # manifests of dependencies build_opts: BuildOptions, ctx: ManifestContext, manifest: ManifestParser, src_dir: str, build_dir: str, inst_dir: str, build_doc: bool, workspace_dir: str | None, manifests_to_build: str | None, cargo_config_file: str | None, ) -> None: super(CargoBuilder, self).__init__( loader, dep_manifests, build_opts, ctx, manifest, src_dir, build_dir, inst_dir, ) self.build_doc = build_doc self.ws_dir: str | None = workspace_dir # pyre-fixme[8]: Attribute has type `Optional[List[str]]`; used as # `Union[None, List[str], str]`. self.manifests_to_build: list[str] | None = ( manifests_to_build and manifests_to_build.split(",") ) self.loader: ManifestLoader = loader self.cargo_config_file_subdir: str | None = cargo_config_file def run_cargo( self, install_dirs: list[str], operation: str, args: list[str] | None = None, ) -> None: args = args or [] env = self._compute_env() # Enable using nightly features with stable compiler env["RUSTC_BOOTSTRAP"] = "1" env["LIBZ_SYS_STATIC"] = "1" cmd = [ "cargo", operation, "--workspace", "-j%s" % self.num_jobs, ] + args self._check_cmd(cmd, cwd=self.workspace_dir(), env=env) def build_source_dir(self) -> str: return os.path.join(self.build_dir, "source") def workspace_dir(self) -> str: return os.path.join(self.build_source_dir(), self.ws_dir or "") def manifest_dir(self, manifest: str) -> str: return os.path.join(self.build_source_dir(), manifest) def recreate_dir(self, src: str, dst: str) -> None: if os.path.isdir(dst): if os.path.islink(dst): os.remove(dst) else: rmtree_more(dst) simple_copytree(src, dst) def recreate_linked_dir(self, src: str, dst: str) -> None: if os.path.isdir(dst): if os.path.islink(dst): os.remove(dst) elif os.path.isdir(dst): shutil.rmtree(dst) os.symlink(src, dst) def cargo_config_file(self) -> str: build_source_dir = self.build_dir if self.cargo_config_file_subdir: return os.path.join(build_source_dir, self.cargo_config_file_subdir) else: return os.path.join(build_source_dir, ".cargo", "config.toml") def _create_cargo_config(self) -> dict[str, dict[str, str]]: cargo_config_file = self.cargo_config_file() cargo_config_dir = os.path.dirname(cargo_config_file) if not os.path.isdir(cargo_config_dir): os.mkdir(cargo_config_dir) dep_to_git = self._resolve_dep_to_git() if os.path.isfile(cargo_config_file): with open(cargo_config_file, "r") as f: print(f"Reading {cargo_config_file}", file=sys.stderr) cargo_content = f.read() else: cargo_content = "" new_content = cargo_content if "# Generated by getdeps.py" not in cargo_content: new_content += """\ # Generated by getdeps.py [build] target-dir = '''{}''' [profile.dev] debug = false incremental = false [profile.release] opt-level = "{}" """.format( self.build_dir.replace("\\", "\\\\"), "z" if self.build_opts.build_type == "MinSizeRel" else "s", ) # Point to vendored sources from getdeps manifests for _dep, git_conf in dep_to_git.items(): if "cargo_vendored_sources" in git_conf: vendored_dir = git_conf["cargo_vendored_sources"].replace("\\", "\\\\") override = ( f'[source."{git_conf["repo_url"]}"]\ndirectory = "{vendored_dir}"\n' ) if override not in cargo_content: new_content += override if self.build_opts.fbsource_dir: # Point to vendored crates.io if possible try: from .facebook.rust import vendored_crates new_content = vendored_crates( self.build_opts.fbsource_dir, new_content ) except ImportError: # This FB internal module isn't shippped to github, # so just rely on cargo downloading crates on it's own pass if new_content != cargo_content: with open(cargo_config_file, "w") as f: print( f"Writing cargo config for {self.manifest.name} to {cargo_config_file}", file=sys.stderr, ) f.write(new_content) return dep_to_git def _prepare(self, reconfigure: bool) -> None: build_source_dir = self.build_source_dir() self.recreate_dir(self.src_dir, build_source_dir) dep_to_git = self._create_cargo_config() if self.ws_dir is not None: self._patchup_workspace(dep_to_git) def _build(self, reconfigure: bool) -> None: # _prepare has been run already. Actually do the build build_source_dir = self.build_source_dir() build_args = [ "--artifact-dir", os.path.join(self.inst_dir, "bin"), "-Zunstable-options", ] if self.build_opts.build_type != "Debug": build_args.append("--release") if self.manifests_to_build is None: self.run_cargo( self.install_dirs, "build", build_args, ) else: # pyre-fixme[16]: Optional type has no attribute `__iter__`. for manifest in self.manifests_to_build: self.run_cargo( self.install_dirs, "build", build_args + [ "--manifest-path", self.manifest_dir(manifest), ], ) self.recreate_linked_dir( build_source_dir, os.path.join(self.inst_dir, "source") ) def run_tests( self, schedule_type: str, owner: str | None, test_filter: str | None, test_exclude: str | None, retry: int, no_testpilot: bool, timeout: int | None = None, ) -> None: build_args: list[str] = [] if self.build_opts.build_type != "Debug": build_args.append("--release") if test_filter: filter_args = ["--", test_filter] else: filter_args = [] if self.manifests_to_build is None: self.run_cargo(self.install_dirs, "test", build_args + filter_args) if self.build_doc and not filter_args: self.run_cargo(self.install_dirs, "doc", ["--no-deps"]) else: # pyre-fixme[16]: Optional type has no attribute `__iter__`. for manifest in self.manifests_to_build: margs = ["--manifest-path", self.manifest_dir(manifest)] self.run_cargo( self.install_dirs, "test", build_args + filter_args + margs ) if self.build_doc and not filter_args: self.run_cargo(self.install_dirs, "doc", ["--no-deps"] + margs) def _patchup_workspace(self, dep_to_git: dict[str, dict[str, str]]) -> None: """ This method makes some assumptions about the state of the project and its cargo dependendies: 1. Crates from cargo dependencies can be extracted from Cargo.toml files using _extract_crates function. It is using a heuristic so check its code to understand how it is done. 2. The extracted cargo dependencies crates can be found in the dependency's install dir using _resolve_crate_to_path function which again is using a heuristic. Notice that many things might go wrong here. E.g. if someone depends on another getdeps crate by writing in their Cargo.toml file: my-rename-of-crate = { package = "crate", git = "..." } they can count themselves lucky because the code will raise an Exception. There might be more cases where the code will silently pass producing bad results. """ workspace_dir = self.workspace_dir() git_url_to_crates_and_paths = self._resolve_config(dep_to_git) if git_url_to_crates_and_paths: patch_cargo = os.path.join(workspace_dir, "Cargo.toml") if os.path.isfile(patch_cargo): with open(patch_cargo, "r") as f: manifest_content = f.read() else: manifest_content = "" new_content = manifest_content if "[package]" not in manifest_content: # A fake manifest has to be crated to change the virtual # manifest into a non-virtual. The virtual manifests are limited # in many ways and the inability to define patches on them is # one. Check https://github.com/rust-lang/cargo/issues/4934 to # see if it is resolved. null_file = "/dev/null" if self.build_opts.is_windows(): null_file = "nul" new_content += f""" [package] name = "fake_manifest_of_{self.manifest.name}" version = "0.0.0" [lib] path = "{null_file}" """ config: list[str] = [] for git_url, crates_to_patch_path in git_url_to_crates_and_paths.items(): crates_patches = [ '{} = {{ path = "{}" }}'.format( crate, crates_to_patch_path[crate].replace("\\", "\\\\"), ) for crate in sorted(crates_to_patch_path.keys()) ] patch_key = f'[patch."{git_url}"]' if patch_key not in manifest_content: config.append(f"\n{patch_key}\n" + "\n".join(crates_patches)) new_content += "\n".join(config) if new_content != manifest_content: with open(patch_cargo, "w") as f: print( f"writing patch to {patch_cargo}", file=sys.stderr, ) f.write(new_content) def _resolve_config( self, dep_to_git: dict[str, dict[str, str]] ) -> dict[str, dict[str, str]]: """ Returns a configuration to be put inside root Cargo.toml file which patches the dependencies git code with local getdeps versions. See https://doc.rust-lang.org/cargo/reference/manifest.html#the-patch-section """ dep_to_crates = self._resolve_dep_to_crates(self.build_source_dir(), dep_to_git) git_url_to_crates_and_paths: dict[str, dict[str, str]] = {} for dep_name in sorted(dep_to_git.keys()): git_conf = dep_to_git[dep_name] req_crates = sorted(dep_to_crates.get(dep_name, [])) if not req_crates: continue # nothing to patch, move along git_url = git_conf.get("repo_url", None) crate_source_map = git_conf["crate_source_map"] if git_url and crate_source_map: crates_to_patch_path = git_url_to_crates_and_paths.get(git_url, {}) for c in req_crates: if c in crate_source_map and c not in crates_to_patch_path: # pyre-fixme[6]: For 1st argument expected `Union[slice[Any, # Any, Any], SupportsIndex]` but got `str`. crates_to_patch_path[c] = crate_source_map[c] print( f"{self.manifest.name}: Patching crate {c} via virtual manifest in {self.workspace_dir()}", file=sys.stderr, ) if crates_to_patch_path: git_url_to_crates_and_paths[git_url] = crates_to_patch_path return git_url_to_crates_and_paths def _resolve_dep_to_git(self) -> dict[str, dict[str, str]]: """ For each direct dependency of the currently build manifest check if it is also cargo-builded and if yes then extract it's git configs and install dir """ dependencies = self.manifest.get_dependencies(self.ctx) if not dependencies: return {} dep_to_git: dict[str, dict[str, str]] = {} for dep in dependencies: dep_manifest = self.loader.load_manifest(dep) dep_builder = dep_manifest.get("build", "builder", ctx=self.ctx) dep_cargo_conf = dep_manifest.get_section_as_dict("cargo", self.ctx) dep_crate_map = dep_manifest.get_section_as_dict("crate.pathmap", self.ctx) if ( not (dep_crate_map or dep_cargo_conf) and dep_builder not in ["cargo"] or dep == "rust" ): # This dependency has no cargo rust content so ignore it. # The "rust" dependency is an exception since it contains the # toolchain. continue git_conf = dep_manifest.get_section_as_dict("git", self.ctx) if dep != "rust" and "repo_url" not in git_conf: raise Exception( f"{dep}: A cargo dependency requires git.repo_url to be defined." ) if dep_builder == "cargo": dep_source_dir = self.loader.get_project_install_dir(dep_manifest) dep_source_dir = os.path.join(dep_source_dir, "source") else: fetcher = self.loader.create_fetcher(dep_manifest) dep_source_dir = fetcher.get_src_dir() crate_source_map: dict[str, str] = {} if dep_crate_map: for crate, subpath in dep_crate_map.items(): if crate not in crate_source_map: if self.build_opts.is_windows(): # pyre-fixme[16]: Optional type has no attribute `replace`. subpath = subpath.replace("/", "\\") crate_path = os.path.join(dep_source_dir, subpath) print( f"{self.manifest.name}: Mapped crate {crate} to dep {dep} dir {crate_path}", file=sys.stderr, ) crate_source_map[crate] = crate_path elif dep_cargo_conf: # We don't know what crates are defined buy the dep, look for them search_pattern = re.compile('\\[package\\]\nname = "(.*)"') for crate_root, _, files in os.walk(dep_source_dir): if "Cargo.toml" in files: with open(os.path.join(crate_root, "Cargo.toml"), "r") as f: content = f.read() match = search_pattern.search(content) if match: crate = match.group(1) if crate: print( f"{self.manifest.name}: Discovered crate {crate} in dep {dep} dir {crate_root}", file=sys.stderr, ) crate_source_map[crate] = crate_root # pyre-fixme[6]: For 2nd argument expected `Optional[str]` but got # `Dict[str, str]`. git_conf["crate_source_map"] = crate_source_map if not dep_crate_map and dep_cargo_conf: dep_cargo_dir = self.loader.get_project_build_dir(dep_manifest) dep_cargo_dir = os.path.join(dep_cargo_dir, "source") dep_ws_dir = dep_cargo_conf.get("workspace_dir", None) if dep_ws_dir: dep_cargo_dir = os.path.join(dep_cargo_dir, dep_ws_dir) git_conf["cargo_vendored_sources"] = dep_cargo_dir # pyre-fixme[6]: For 2nd argument expected `Dict[str, str]` but got # `Dict[str, Optional[str]]`. dep_to_git[dep] = git_conf return dep_to_git def _resolve_dep_to_crates( self, build_source_dir: str, dep_to_git: dict[str, dict[str, str]], ) -> dict[str, set[str]]: """ This function traverse the build_source_dir in search of Cargo.toml files, extracts the crate names from them using _extract_crates function and returns a merged result containing crate names per dependency name from all Cargo.toml files in the project. """ if not dep_to_git: return {} # no deps, so don't waste time traversing files dep_to_crates: dict[str, set[str]] = {} # First populate explicit crate paths from dependencies for name, git_conf in dep_to_git.items(): # pyre-fixme[16]: `str` has no attribute `keys`. crates = git_conf["crate_source_map"].keys() if crates: dep_to_crates.setdefault(name, set()).update(crates) # Now find from Cargo.tomls for root, _, files in os.walk(build_source_dir): for f in files: if f == "Cargo.toml": more_dep_to_crates = CargoBuilder._extract_crates_used( os.path.join(root, f), dep_to_git ) for dep_name, crates in more_dep_to_crates.items(): existing_crates = dep_to_crates.get(dep_name, set()) for c in crates: if c not in existing_crates: print( f"Patch {self.manifest.name} uses {dep_name} crate {crates}", file=sys.stderr, ) existing_crates.add(c) # pyre-fixme[61]: `name` is undefined, or not always defined. dep_to_crates.setdefault(name, set()).update(existing_crates) return dep_to_crates @staticmethod def _extract_crates_used( cargo_toml_file: str, dep_to_git: dict[str, dict[str, str]], ) -> dict[str, set[str]]: """ This functions reads content of provided cargo toml file and extracts crate names per each dependency. The extraction is done by a heuristic so it might be incorrect. """ deps_to_crates: dict[str, set[str]] = {} with open(cargo_toml_file, "r") as f: for line in f.readlines(): if line.startswith("#") or "git = " not in line: continue # filter out commented lines and ones without git deps for dep_name, conf in dep_to_git.items(): # Only redirect deps that point to git URLS if 'git = "{}"'.format(conf["repo_url"]) in line: pkg_template = ' package = "' if pkg_template in line: crate_name, _, _ = line.partition(pkg_template)[ 2 ].partition('"') else: crate_name, _, _ = line.partition("=") deps_to_crates.setdefault(dep_name, set()).add( crate_name.strip() ) return deps_to_crates def _resolve_crate_to_path( self, crate: str, crate_source_map: dict[str, str], ) -> str: """ Tries to find in source_dir by searching a [package] keyword followed by name = "". """ search_pattern = '[package]\nname = "{}"'.format(crate) for _crate, crate_source_dir in crate_source_map.items(): for crate_root, _, files in os.walk(crate_source_dir): if "Cargo.toml" in files: with open(os.path.join(crate_root, "Cargo.toml"), "r") as f: content = f.read() if search_pattern in content: return crate_root raise Exception( f"{self.manifest.name}: Failed to find dep crate {crate} in paths {crate_source_map}" ) ================================================ FILE: build/fbcode_builder/getdeps/copytree.py ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. # pyre-strict from __future__ import annotations import os import shutil import stat import subprocess from collections.abc import Callable from .platform import is_windows from .runcmd import run_cmd PREFETCHED_DIRS: set[str] = set() def containing_repo_type(path: str) -> tuple[str | None, str | None]: while True: if os.path.exists(os.path.join(path, ".git")): return ("git", path) if os.path.exists(os.path.join(path, ".hg")): return ("hg", path) parent = os.path.dirname(path) if parent == path: return None, None path = parent def find_eden_root(dirpath: str) -> str | None: """If the specified directory is inside an EdenFS checkout, returns the canonical absolute path to the root of that checkout. Returns None if the specified directory is not in an EdenFS checkout. """ if is_windows(): repo_type, repo_root = containing_repo_type(dirpath) if repo_root is not None: if os.path.exists(os.path.join(repo_root, ".eden", "config")): return repo_root return None try: return os.readlink(os.path.join(dirpath, ".eden", "root")) except OSError: return None def prefetch_dir_if_eden(dirpath: str) -> None: """After an amend/rebase, Eden may need to fetch a large number of trees from the servers. The simplistic single threaded walk performed by copytree makes this more expensive than is desirable so we help accelerate things by performing a prefetch on the source directory""" global PREFETCHED_DIRS if dirpath in PREFETCHED_DIRS: return root = find_eden_root(dirpath) if root is None: return glob = f"{os.path.relpath(dirpath, root).replace(os.sep, '/')}/**" print(f"Prefetching {glob}") subprocess.call(["edenfsctl", "prefetch", "--repo", root, glob, "--background"]) PREFETCHED_DIRS.add(dirpath) def simple_copytree(src_dir: str, dest_dir: str, symlinks: bool = False) -> str: """A simple version of shutil.copytree() that can delegate to native tools if faster""" if is_windows(): os.makedirs(dest_dir, exist_ok=True) cmd = [ "robocopy.exe", src_dir, dest_dir, # copy directories, including empty ones "/E", # Ignore Extra files in destination "/XX", # enable parallel copy "/MT", # be quiet "/NFL", "/NDL", "/NJH", "/NJS", "/NP", ] if symlinks: cmd.append("/SL") # robocopy exits with code 1 if it copied ok, hence allow_fail # https://learn.microsoft.com/en-us/troubleshoot/windows-server/backup-and-storage/return-codes-used-robocopy-utility exit_code = run_cmd(cmd, allow_fail=True) if exit_code > 1: raise subprocess.CalledProcessError(exit_code, cmd) return dest_dir else: return shutil.copytree(src_dir, dest_dir, symlinks=symlinks) def _remove_readonly_and_try_again( func: Callable[..., object], path: str, # pyre-fixme[24]: Generic type `type` expects 1 type parameter, use # `typing.Type[]` to avoid runtime subscripting errors. exc_info: tuple[type, BaseException, object], ) -> None: """ Error handler for shutil.rmtree. If the error is due to an access error (read only file) it attempts to add write permission and then retries the operation. Any other failure propagates. """ # exc_info is a tuple (exc_type, exc_value, traceback) exc_type = exc_info[0] if exc_type is PermissionError: os.chmod(path, stat.S_IWRITE) # Retry the original function (os.remove or os.rmdir) try: func(path) except Exception: # If it still fails, the original exception from func() will propagate raise else: # If the error is not a PermissionError, re-raise the original exception raise exc_info[1] def rmtree_more(path: str) -> None: """Wrapper around shutil.rmtree() that makes it remove readonly files as well. Useful when git on windows decides to make some files readonly on checkout""" shutil.rmtree(path, onerror=_remove_readonly_and_try_again) ================================================ FILE: build/fbcode_builder/getdeps/dyndeps.py ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. # pyre-strict from __future__ import annotations import errno import glob import os import re import shlex import shutil import stat import subprocess import sys import typing from collections.abc import Generator from struct import unpack if typing.TYPE_CHECKING: from .buildopts import BuildOptions from .envfuncs import Env OBJECT_SUBDIRS: tuple[str, ...] = ("bin", "lib", "lib64") def copyfile(src: str, dest: str) -> None: shutil.copyfile(src, dest) shutil.copymode(src, dest) class DepBase: def __init__( self, buildopts: BuildOptions, env: Env, install_dirs: list[str], strip: bool, ) -> None: self.buildopts: BuildOptions = buildopts self.env: Env = env self.install_dirs: list[str] = install_dirs self.strip: bool = strip # Deduplicates dependency processing. Keyed on the library # destination path. self.processed_deps: set[str] = set() self.munged_lib_dir: str = "" def list_dynamic_deps(self, objfile: str) -> list[str]: raise RuntimeError("list_dynamic_deps not implemented") def interesting_dep(self, d: str) -> bool: return True # final_install_prefix must be the equivalent path to `destdir` on the # installed system. For example, if destdir is `/tmp/RANDOM/usr/local' which # is intended to map to `/usr/local` in the install image, then # final_install_prefix='/usr/local'. # If left unspecified, destdir will be used. def process_deps( self, destdir: str, final_install_prefix: str | None = None ) -> None: if self.buildopts.is_windows(): lib_dir = "bin" else: lib_dir = "lib" self.munged_lib_dir = os.path.join(destdir, lib_dir) final_lib_dir: str = os.path.join(final_install_prefix or destdir, lib_dir) if not os.path.isdir(self.munged_lib_dir): os.makedirs(self.munged_lib_dir) # Look only at the things that got installed in the leaf package, # which will be the last entry in the install dirs list inst_dir: str = self.install_dirs[-1] print("Process deps under %s" % inst_dir, file=sys.stderr) for dir in OBJECT_SUBDIRS: src_dir: str = os.path.join(inst_dir, dir) if not os.path.isdir(src_dir): continue dest_dir: str = os.path.join(destdir, dir) if not os.path.exists(dest_dir): os.makedirs(dest_dir) for objfile in self.list_objs_in_dir(src_dir): print("Consider %s/%s" % (dir, objfile)) dest_obj: str = os.path.join(dest_dir, objfile) copyfile(os.path.join(src_dir, objfile), dest_obj) self.munge_in_place(dest_obj, final_lib_dir) def find_all_dependencies(self, build_dir: str) -> list[str]: all_deps: set[str] = set() for objfile in self.list_objs_in_dir( build_dir, recurse=True, output_prefix=build_dir ): for d in self.list_dynamic_deps(objfile): all_deps.add(d) interesting_deps: set[str] = {d for d in all_deps if self.interesting_dep(d)} dep_paths: list[str] = [] for dep in interesting_deps: dep_path: str | None = self.resolve_loader_path(dep) if dep_path: dep_paths.append(dep_path) return dep_paths def munge_in_place(self, objfile: str, final_lib_dir: str) -> None: print("Munging %s" % objfile) for d in self.list_dynamic_deps(objfile): if not self.interesting_dep(d): continue # Resolve this dep: does it exist in any of our installation # directories? If so, then it is a candidate for processing dep: str | None = self.resolve_loader_path(d) if dep: dest_dep: str = os.path.join(self.munged_lib_dir, os.path.basename(dep)) print("dep: %s -> %s" % (d, dest_dep)) if dest_dep in self.processed_deps: # A previous dependency with the same name has already # been installed at dest_dep, so there is no need to copy # or munge the dependency again. # TODO: audit that both source paths have the same inode number pass else: self.processed_deps.add(dest_dep) copyfile(dep, dest_dep) self.munge_in_place(dest_dep, final_lib_dir) self.rewrite_dep(objfile, d, dep, dest_dep, final_lib_dir) if self.strip: self.strip_debug_info(objfile) def rewrite_dep( self, objfile: str, depname: str, old_dep: str, new_dep: str, final_lib_dir: str, ) -> None: raise RuntimeError("rewrite_dep not implemented") def resolve_loader_path(self, dep: str) -> str | None: if os.path.isabs(dep): return dep d: str = os.path.basename(dep) for inst_dir in self.install_dirs: for libdir in OBJECT_SUBDIRS: candidate: str = os.path.join(inst_dir, libdir, d) if os.path.exists(candidate): return candidate return None def list_objs_in_dir( self, dir: str, recurse: bool = False, output_prefix: str = "" ) -> Generator[str, None, None]: for entry in os.listdir(dir): entry_path: str = os.path.join(dir, entry) st: os.stat_result = os.lstat(entry_path) if stat.S_ISREG(st.st_mode): if self.is_objfile(entry_path): relative_result: str = os.path.join(output_prefix, entry) yield os.path.normcase(relative_result) elif recurse and stat.S_ISDIR(st.st_mode): child_prefix: str = os.path.join(output_prefix, entry) for result in self.list_objs_in_dir( entry_path, recurse=recurse, output_prefix=child_prefix ): yield result def is_objfile(self, objfile: str) -> bool: return True def strip_debug_info(self, objfile: str) -> None: """override this to define how to remove debug information from an object file""" pass def check_call_verbose(self, args: list[str]) -> None: print(" ".join(map(shlex.quote, args))) subprocess.check_call(args) class WinDeps(DepBase): def __init__( self, buildopts: BuildOptions, env: Env, install_dirs: list[str], strip: bool, ) -> None: super(WinDeps, self).__init__(buildopts, env, install_dirs, strip) self.dumpbin: str = self.find_dumpbin() def find_dumpbin(self) -> str: # Looking for dumpbin in the following hardcoded paths. # The registry option to find the install dir doesn't work anymore. globs: list[str] = [ ( "C:/Program Files/" "Microsoft Visual Studio/" "*/*/VC/Tools/" "MSVC/*/bin/Hostx64/x64/dumpbin.exe" ), ( "C:/Program Files (x86)/" "Microsoft Visual Studio/" "*/*/VC/Tools/" "MSVC/*/bin/Hostx64/x64/dumpbin.exe" ), ( "C:/Program Files (x86)/" "Common Files/" "Microsoft/Visual C++ for Python/*/" "VC/bin/dumpbin.exe" ), ("c:/Program Files (x86)/Microsoft Visual Studio */VC/bin/dumpbin.exe"), ( "C:/Program Files/Microsoft Visual Studio/*/Professional/VC/Tools/MSVC/*/bin/HostX64/x64/dumpbin.exe" ), ] for pattern in globs: for exe in glob.glob(pattern): return exe raise RuntimeError("could not find dumpbin.exe") # pyre-fixme[14]: `list_dynamic_deps` overrides method defined in `DepBase` # inconsistently. def list_dynamic_deps(self, exe: str) -> list[str]: deps: list[str] = [] print("Resolve deps for %s" % exe) output: str = subprocess.check_output( [self.dumpbin, "/nologo", "/dependents", exe] ).decode("utf-8") lines: list[str] = output.split("\n") for line in lines: m: re.Match[str] | None = re.match("\\s+(\\S+.dll)", line, re.IGNORECASE) if m: deps.append(m.group(1).lower()) return deps def rewrite_dep( self, objfile: str, depname: str, old_dep: str, new_dep: str, final_lib_dir: str, ) -> None: # We can't rewrite on windows, but we will # place the deps alongside the exe so that # they end up in the search path pass # These are the Windows system dll, which we don't want to copy while # packaging. SYSTEM_DLLS: set[str] = set( # noqa: C405 [ "advapi32.dll", "dbghelp.dll", "kernel32.dll", "msvcp140.dll", "vcruntime140.dll", "ws2_32.dll", "ntdll.dll", "shlwapi.dll", ] ) def interesting_dep(self, d: str) -> bool: if "api-ms-win-crt" in d: return False if d in self.SYSTEM_DLLS: return False return True def is_objfile(self, objfile: str) -> bool: if not os.path.isfile(objfile): return False if objfile.lower().endswith(".exe"): return True return False def emit_dev_run_script(self, script_path: str, dep_dirs: list[str]) -> None: """Emit a script that can be used to run build artifacts directly from the build directory, without installing them. The dep_dirs parameter should be a list of paths that need to be added to $PATH. This can be computed by calling compute_dependency_paths() or compute_dependency_paths_fast(). This is only necessary on Windows, which does not have RPATH, and instead requires the $PATH environment variable be updated in order to find the proper library dependencies. """ contents: str = self._get_dev_run_script_contents(dep_dirs) with open(script_path, "w") as f: f.write(contents) def compute_dependency_paths(self, build_dir: str) -> list[str]: """Return a list of all directories that need to be added to $PATH to ensure that library dependencies can be found correctly. This is computed by scanning binaries to determine exactly the right list of dependencies. The compute_dependency_paths_fast() is a alternative function that runs faster but may return additional extraneous paths. """ dep_dirs: set[str] = set() # Find paths by scanning the binaries. for dep in self.find_all_dependencies(build_dir): dep_dirs.add(os.path.dirname(dep)) dep_dirs.update(self.read_custom_dep_dirs(build_dir)) return sorted(dep_dirs) def compute_dependency_paths_fast(self, build_dir: str) -> list[str]: """Similar to compute_dependency_paths(), but rather than actually scanning binaries, just add all library paths from the specified installation directories. This is much faster than scanning the binaries, but may result in more paths being returned than actually necessary. """ dep_dirs: set[str] = set() for inst_dir in self.install_dirs: for subdir in OBJECT_SUBDIRS: path: str = os.path.join(inst_dir, subdir) if os.path.exists(path): dep_dirs.add(path) dep_dirs.update(self.read_custom_dep_dirs(build_dir)) return sorted(dep_dirs) def read_custom_dep_dirs(self, build_dir: str) -> set[str]: # The build system may also have included libraries from other locations that # we might not be able to find normally in find_all_dependencies(). # To handle this situation we support reading additional library paths # from a LIBRARY_DEP_DIRS.txt file that may have been generated in the build # output directory. dep_dirs: set[str] = set() try: explicit_dep_dirs_path: str = os.path.join( build_dir, "LIBRARY_DEP_DIRS.txt" ) with open(explicit_dep_dirs_path, "r") as f: for line in f.read().splitlines(): dep_dirs.add(line) except OSError as ex: if ex.errno != errno.ENOENT: raise return dep_dirs def _get_dev_run_script_contents(self, path_dirs: list[str]) -> str: path_entries: list[str] = ["$env:PATH"] + path_dirs path_str: str = ";".join(path_entries) return """\ $orig_env = $env:PATH $env:PATH = "{path_str}" try {{ $cmd_args = $args[1..$args.length] & $args[0] @cmd_args }} finally {{ $env:PATH = $orig_env }} """.format( path_str=path_str ) class ElfDeps(DepBase): def __init__( self, buildopts: BuildOptions, env: Env, install_dirs: list[str], strip: bool, ) -> None: super(ElfDeps, self).__init__(buildopts, env, install_dirs, strip) # We need patchelf to rewrite deps, so ensure that it is built... args: list[str] = [sys.executable, sys.argv[0]] if buildopts.allow_system_packages: args.append("--allow-system-packages") subprocess.check_call(args + ["build", "patchelf"]) # ... and that we know where it lives patchelf_install: str = os.fsdecode( subprocess.check_output(args + ["show-inst-dir", "patchelf"]).strip() ) if not patchelf_install: # its a system package, so we assume it is in the path patchelf_install = "patchelf" else: patchelf_install = os.path.join(patchelf_install, "bin", "patchelf") self.patchelf: str = patchelf_install def list_dynamic_deps(self, objfile: str) -> list[str]: out: str = ( subprocess.check_output( [self.patchelf, "--print-needed", objfile], env=dict(self.env.items()) ) .decode("utf-8") .strip() ) lines: list[str] = out.split("\n") return lines def rewrite_dep( self, objfile: str, depname: str, old_dep: str, new_dep: str, final_lib_dir: str, ) -> None: final_dep: str = os.path.join( final_lib_dir, os.path.relpath(new_dep, self.munged_lib_dir), ) self.check_call_verbose( [self.patchelf, "--replace-needed", depname, final_dep, objfile] ) def is_objfile(self, objfile: str) -> bool: if not os.path.isfile(objfile): return False with open(objfile, "rb") as f: # https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#File_header magic: bytes = f.read(4) return magic == b"\x7fELF" def strip_debug_info(self, objfile: str) -> None: self.check_call_verbose(["strip", objfile]) # MACH-O magic number MACH_MAGIC: int = 0xFEEDFACF class MachDeps(DepBase): def interesting_dep(self, d: str) -> bool: if d.startswith("/usr/lib/") or d.startswith("/System/"): return False return True def is_objfile(self, objfile: str) -> bool: if not os.path.isfile(objfile): return False with open(objfile, "rb") as f: # mach stores the magic number in native endianness, # so unpack as native here and compare header: bytes = f.read(4) if len(header) != 4: return False magic: int = unpack("I", header)[0] return magic == MACH_MAGIC def list_dynamic_deps(self, objfile: str) -> list[str]: if not self.interesting_dep(objfile): return [] out: str = ( subprocess.check_output( ["otool", "-L", objfile], env=dict(self.env.items()) ) .decode("utf-8") .strip() ) lines: list[str] = out.split("\n") deps: list[str] = [] for line in lines: m: re.Match[str] | None = re.match("\t(\\S+)\\s", line) if m: if os.path.basename(m.group(1)) != os.path.basename(objfile): deps.append(os.path.normcase(m.group(1))) return deps def rewrite_dep( self, objfile: str, depname: str, old_dep: str, new_dep: str, final_lib_dir: str, ) -> None: if objfile.endswith(".dylib"): # Erase the original location from the id of the shared # object. It doesn't appear to hurt to retain it, but # it does look weird, so let's rewrite it to be sure. self.check_call_verbose( ["install_name_tool", "-id", os.path.basename(objfile), objfile] ) final_dep: str = os.path.join( final_lib_dir, os.path.relpath(new_dep, self.munged_lib_dir), ) self.check_call_verbose( ["install_name_tool", "-change", depname, final_dep, objfile] ) def create_dyn_dep_munger( buildopts: BuildOptions, env: Env, install_dirs: list[str], strip: bool = False, ) -> DepBase | None: if buildopts.is_linux(): return ElfDeps(buildopts, env, install_dirs, strip) if buildopts.is_darwin(): return MachDeps(buildopts, env, install_dirs, strip) if buildopts.is_windows(): return WinDeps(buildopts, env, install_dirs, strip) if buildopts.is_freebsd(): return ElfDeps(buildopts, env, install_dirs, strip) return None ================================================ FILE: build/fbcode_builder/getdeps/envfuncs.py ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. # pyre-strict from __future__ import annotations import os import shlex import sys from collections.abc import ItemsView, Iterator, KeysView, Mapping, ValuesView class Env: def __init__(self, src: Mapping[str, str] | None = None) -> None: self._dict: dict[str, str] = {} if src is None: self.update(os.environ) else: self.update(src) def update(self, src: Mapping[str, str]) -> None: for k, v in src.items(): self.set(k, v) def copy(self) -> Env: return Env(self._dict) def _key(self, key: str) -> str | None: # The `str` cast may not appear to be needed, but without it we run # into issues when passing the environment to subprocess. The main # issue is that in python2 `os.environ` (which is the initial source # of data for the environment) uses byte based strings, but this # project uses `unicode_literals`. `subprocess` will raise an error # if the environment that it is passed has a mixture of byte and # unicode strings. # It is simplest to force everything to be `str` for the sake of # consistency. key = str(key) if sys.platform.startswith("win"): # Windows env var names are case insensitive but case preserving. # An implementation of PAR files on windows gets confused if # the env block contains keys with conflicting case, so make a # pass over the contents to remove any. # While this O(n) scan is technically expensive and gross, it # is practically not a problem because the volume of calls is # relatively low and the cost of manipulating the env is dwarfed # by the cost of spawning a process on windows. In addition, # since the processes that we run are expensive anyway, this # overhead is not the worst thing to worry about. for k in list(self._dict.keys()): if str(k).lower() == key.lower(): return k elif key in self._dict: return key return None def get(self, key: str, defval: str | None = None) -> str | None: resolved_key = self._key(key) if resolved_key is None: return defval return self._dict[resolved_key] def __getitem__(self, key: str) -> str: val = self.get(key) if val is None: raise KeyError(key) return val def unset(self, key: str) -> None: if key is None: raise KeyError("attempting to unset env[None]") resolved_key = self._key(key) if resolved_key: del self._dict[resolved_key] def __delitem__(self, key: str) -> None: self.unset(key) def __repr__(self) -> str: return repr(self._dict) def set(self, key: str, value: str) -> None: if key is None: raise KeyError("attempting to assign env[None] = %r" % value) if value is None: raise ValueError("attempting to assign env[%s] = None" % key) # The `str` conversion is important to avoid triggering errors # with subprocess if we pass in a unicode value; see commentary # in the `_key` method. key = str(key) value = str(value) # The `unset` call is necessary on windows where the keys are # case insensitive. Since this dict is case sensitive, simply # assigning the value to the new key is not sufficient to remove # the old value. The `unset` call knows how to match keys and # remove any potential duplicates. self.unset(key) self._dict[key] = value def __setitem__(self, key: str, value: str) -> None: self.set(key, value) def __iter__(self) -> Iterator[str]: return self._dict.__iter__() def __len__(self) -> int: return len(self._dict) def keys(self) -> KeysView[str]: return self._dict.keys() def values(self) -> ValuesView[str]: return self._dict.values() def items(self) -> ItemsView[str, str]: return self._dict.items() def add_path_entry( env: Env, name: str, item: str, append: bool = True, separator: str = os.pathsep ) -> None: """Cause `item` to be added to the path style env var named `name` held in the `env` dict. `append` specifies whether the item is added to the end (the default) or should be prepended if `name` already exists.""" val = env.get(name, "") if val is not None and len(val) > 0: val_list = val.split(separator) else: val_list = [] if append: val_list.append(item) else: val_list.insert(0, item) env.set(name, separator.join(val_list)) def add_flag(env: Env, name: str, flag: str, append: bool = True) -> None: """Cause `flag` to be added to the CXXFLAGS-style env var named `name` held in the `env` dict. `append` specifies whether the flag is added to the end (the default) or should be prepended if `name` already exists.""" val = shlex.split(env.get(name, "") or "") if append: val.append(flag) else: val.insert(0, flag) env.set(name, " ".join(val)) _path_search_cache: dict[object, str | None] = {} _not_found: object = object() def tpx_path() -> str: return "xplat/testinfra/tpx/ctp.tpx" def path_search( env: Mapping[str, str], exename: str, defval: str | None = None ) -> str | None: """Search for exename in the PATH specified in env. exename is eg: `ninja` and this function knows to append a .exe to the end on windows. Returns the path to the exe if found, or None if either no PATH is set in env or no executable is found.""" path = env.get("PATH", None) if path is None: return defval # The project hash computation code searches for C++ compilers (g++, clang, etc) # repeatedly. Cache the result so we don't end up searching for these over and over # again. cache_key = (path, exename) result = _path_search_cache.get(cache_key, _not_found) if result is _not_found: result = _perform_path_search(path, exename) _path_search_cache[cache_key] = result # pyre-fixme[7]: Expected `Optional[str]` but got `Optional[object]`. return result def _perform_path_search(path: str, exename: str) -> str | None: is_win = sys.platform.startswith("win") if is_win: exename = "%s.exe" % exename for bindir in path.split(os.pathsep): full_name = os.path.join(bindir, exename) if os.path.exists(full_name) and os.path.isfile(full_name): if not is_win and not os.access(full_name, os.X_OK): continue return full_name return None ================================================ FILE: build/fbcode_builder/getdeps/errors.py ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. # pyre-strict class TransientFailure(Exception): """Raising this error causes getdeps to return with an error code that Sandcastle will consider to be a retryable transient infrastructure error""" pass class ManifestNotFound(Exception): def __init__(self, manifest_name: str) -> None: super(Exception, self).__init__("Unable to find manifest '%s'" % manifest_name) ================================================ FILE: build/fbcode_builder/getdeps/expr.py ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. # pyre-strict from __future__ import annotations import re import shlex from collections.abc import Callable def parse_expr(expr_text: str, valid_variables: set[str]) -> ExprNode: """parses the simple criteria expression syntax used in dependency specifications. Returns an ExprNode instance that can be evaluated like this: ``` expr = parse_expr("os=windows") ok = expr.eval({ "os": "windows" }) ``` Whitespace is allowed between tokens. The following terms are recognized: KEY = VALUE # Evaluates to True if ctx[KEY] == VALUE not(EXPR) # Evaluates to True if EXPR evaluates to False # and vice versa all(EXPR1, EXPR2, ...) # Evaluates True if all of the supplied # EXPR's also evaluate True any(EXPR1, EXPR2, ...) # Evaluates True if any of the supplied # EXPR's also evaluate True, False if # none of them evaluated true. """ p = Parser(expr_text, valid_variables) return p.parse() class ExprNode: def eval(self, ctx: dict[str, str | None]) -> bool: return False class TrueExpr(ExprNode): def eval(self, ctx: dict[str, str | None]) -> bool: return True def __str__(self) -> str: return "true" class NotExpr(ExprNode): def __init__(self, node: ExprNode) -> None: self._node: ExprNode = node def eval(self, ctx: dict[str, str | None]) -> bool: return not self._node.eval(ctx) def __str__(self) -> str: return "not(%s)" % self._node class AllExpr(ExprNode): def __init__(self, nodes: list[ExprNode]) -> None: self._nodes: list[ExprNode] = nodes def eval(self, ctx: dict[str, str | None]) -> bool: for node in self._nodes: if not node.eval(ctx): return False return True def __str__(self) -> str: items: list[str] = [] for node in self._nodes: items.append(str(node)) return "all(%s)" % ",".join(items) class AnyExpr(ExprNode): def __init__(self, nodes: list[ExprNode]) -> None: self._nodes: list[ExprNode] = nodes def eval(self, ctx: dict[str, str | None]) -> bool: for node in self._nodes: if node.eval(ctx): return True return False def __str__(self) -> str: items: list[str] = [] for node in self._nodes: items.append(str(node)) return "any(%s)" % ",".join(items) class EqualExpr(ExprNode): def __init__(self, key: str, value: str) -> None: self._key: str = key self._value: str = value def eval(self, ctx: dict[str, str | None]) -> bool: return ctx.get(self._key) == self._value def __str__(self) -> str: return "%s=%s" % (self._key, self._value) class Parser: def __init__(self, text: str, valid_variables: set[str]) -> None: self.text: str = text self.lex: shlex.shlex = shlex.shlex(text) self.valid_variables: set[str] = valid_variables def parse(self) -> ExprNode: expr = self.top() garbage = self.lex.get_token() if garbage != "": raise Exception( "Unexpected token %s after EqualExpr in %s" % (garbage, self.text) ) return expr def top(self) -> ExprNode: name = self.ident() op = self.lex.get_token() if op == "(": parsers: dict[str, Callable[[], ExprNode]] = { "not": self.parse_not, "any": self.parse_any, "all": self.parse_all, } func = parsers.get(name) if not func: raise Exception("invalid term %s in %s" % (name, self.text)) return func() if op == "=": if name not in self.valid_variables: raise Exception("unknown variable %r in expression" % (name,)) # remove shell quote from value so can test things with period in them, e.g "18.04" token = self.lex.get_token() if token is None: raise Exception("unexpected end of expression in %s" % self.text) unquoted = " ".join(shlex.split(token)) return EqualExpr(name, unquoted) raise Exception( "Unexpected token sequence '%s %s' in %s" % (name, op, self.text) ) def ident(self) -> str: ident = self.lex.get_token() if ident is None or not re.match("[a-zA-Z]+", ident): raise Exception("expected identifier found %s" % ident) return ident def parse_not(self) -> NotExpr: node = self.top() expr = NotExpr(node) tok = self.lex.get_token() if tok != ")": raise Exception("expected ')' found %s" % tok) return expr def parse_any(self) -> AnyExpr: nodes: list[ExprNode] = [] while True: nodes.append(self.top()) tok = self.lex.get_token() if tok == ")": break if tok != ",": raise Exception("expected ',' or ')' but found %s" % tok) return AnyExpr(nodes) def parse_all(self) -> AllExpr: nodes: list[ExprNode] = [] while True: nodes.append(self.top()) tok = self.lex.get_token() if tok == ")": break if tok != ",": raise Exception("expected ',' or ')' but found %s" % tok) return AllExpr(nodes) ================================================ FILE: build/fbcode_builder/getdeps/fetcher.py ================================================ #!/usr/bin/env python3 # Copyright (c) Meta Platforms, Inc. and affiliates. # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. from __future__ import annotations # pyre-strict import errno import hashlib import os import random import re import shlex import shutil import stat import subprocess import sys import tarfile import time import zipfile from abc import ABC, abstractmethod from collections.abc import Iterator from datetime import datetime from typing import NamedTuple, TYPE_CHECKING from urllib.parse import urlparse from urllib.request import Request, urlopen from .copytree import prefetch_dir_if_eden from .envfuncs import Env from .errors import TransientFailure from .platform import HostType, is_windows from .runcmd import run_cmd if TYPE_CHECKING: from .buildopts import BuildOptions from .manifest import ManifestContext, ManifestParser def file_name_is_cmake_file(file_name: str) -> bool: file_name = file_name.lower() base = os.path.basename(file_name) return ( base.endswith(".cmake") or base.endswith(".cmake.in") or base == "cmakelists.txt" ) class ChangeStatus: """Indicates the nature of changes that happened while updating the source directory. There are two broad uses: * When extracting archives for third party software we want to know that we did something (eg: we either extracted code or we didn't do anything) * For 1st party code where we use shipit to transform the code, we want to know if we changed anything so that we can perform a build, but we generally want to be a little more nuanced and be able to distinguish between just changing a source file and whether we might need to reconfigure the build system. """ def __init__(self, all_changed: bool = False) -> None: """Construct a ChangeStatus object. The default is to create a status that indicates no changes, but passing all_changed=True will create one that indicates that everything changed""" if all_changed: self.source_files: int = 1 self.make_files: int = 1 else: self.source_files: int = 0 self.make_files: int = 0 def record_change(self, file_name: str) -> None: """Used by the shipit fetcher to record changes as it updates files in the destination. If the file name might be one used in the cmake build system that we use for 1st party code, then record that as a "make file" change. We could broaden this to match any file used by various build systems, but it is only really useful for our internal cmake stuff at this time. If the file isn't a build file and is under the `fbcode_builder` dir then we don't class that as an interesting change that we might need to rebuild, so we ignore it. Otherwise we record the file as a source file change.""" file_name = file_name.lower() if file_name_is_cmake_file(file_name): self.make_files += 1 elif "/fbcode_builder/cmake" in file_name: self.source_files += 1 elif "/fbcode_builder/" not in file_name: self.source_files += 1 def sources_changed(self) -> bool: """Returns true if any source files were changed during an update operation. This will typically be used to decide that the build system to be run on the source dir in an incremental mode""" return self.source_files > 0 def build_changed(self) -> bool: """Returns true if any build files were changed during an update operation. This will typically be used to decidfe that the build system should be reconfigured and re-run as a full build""" return self.make_files > 0 class Fetcher(ABC): """The Fetcher is responsible for fetching and extracting the sources for project. The Fetcher instance defines where the extracted data resides and reports this to the consumer via its `get_src_dir` method.""" def update(self) -> ChangeStatus: """Brings the src dir up to date, ideally minimizing changes so that a subsequent build doesn't over-build. Returns a ChangeStatus object that helps the caller to understand the nature of the changes required during the update.""" return ChangeStatus() @abstractmethod def clean(self) -> None: """Reverts any changes that might have been made to the src dir""" pass @abstractmethod def hash(self) -> str: """Returns a hash that identifies the version of the code in the working copy. For a git repo this is commit hash for the working copy. For other Fetchers this should relate to the version of the code in the src dir. The intent is that if a manifest changes the version/rev of a project that the hash be different. Importantly, this should be computable without actually fetching the code, as we want this to factor into a hash used to download a pre-built version of the code, without having to first download and extract its sources (eg: boost on windows is pretty painful). """ pass @abstractmethod def get_src_dir(self) -> str: """Returns the source directory that the project was extracted into""" pass class LocalDirFetcher: """This class exists to override the normal fetching behavior, and use an explicit user-specified directory for the project sources. This fetcher cannot update or track changes. It always reports that the project has changed, forcing it to always be built.""" def __init__(self, path: str) -> None: self.path: str = os.path.realpath(path) def update(self) -> ChangeStatus: return ChangeStatus(all_changed=True) def hash(self) -> str: return "0" * 40 def get_src_dir(self) -> str: return self.path def clean(self) -> None: pass class SystemPackageFetcher: def __init__( self, build_options: BuildOptions, packages: dict[str, list[str]] ) -> None: self.manager: str | None = build_options.host_type.get_package_manager() # pyre-fixme[6]: For 1st argument expected `str` but got `Optional[str]`. self.packages: list[str] | None = packages.get(self.manager) self.host_type: HostType = build_options.host_type if self.packages: self.installed: bool | None = None else: self.installed = False def packages_are_installed(self) -> bool: if self.installed is not None: return self.installed cmd = None if self.manager == "rpm": # pyre-fixme[6]: For 1st argument expected # `pyre_extensions.PyreReadOnly[Iterable[SupportsRichComparisonT]]` but # got `Optional[List[str]]`. cmd = ["rpm", "-q"] + sorted(self.packages) elif self.manager == "deb": # pyre-fixme[6]: For 1st argument expected # `pyre_extensions.PyreReadOnly[Iterable[SupportsRichComparisonT]]` but # got `Optional[List[str]]`. cmd = ["dpkg", "-s"] + sorted(self.packages) elif self.manager == "homebrew": # pyre-fixme[6]: For 1st argument expected # `pyre_extensions.PyreReadOnly[Iterable[SupportsRichComparisonT]]` but # got `Optional[List[str]]`. cmd = ["brew", "ls", "--versions"] + sorted(self.packages) if cmd: proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) if proc.returncode == 0: # captured as binary as we will hash this later # pyre-fixme[8]: Attribute has type `Optional[bool]`; used as `bytes`. self.installed = proc.stdout else: # Need all packages to be present to consider us installed self.installed = False else: self.installed = False return bool(self.installed) def update(self) -> ChangeStatus: assert self.installed return ChangeStatus(all_changed=False) def hash(self) -> str: if self.packages_are_installed(): return hashlib.sha256(self.installed).hexdigest() else: return "0" * 40 def get_src_dir(self) -> None: return None class PreinstalledNopFetcher(SystemPackageFetcher): def __init__(self) -> None: self.installed = True class GitFetcher(Fetcher): DEFAULT_DEPTH = 1 def __init__( self, build_options: BuildOptions, manifest: ManifestParser, repo_url: str, rev: str, depth: int, branch: str, ) -> None: # Extract the host/path portions of the URL and generate a flattened # directory name. eg: # github.com/facebook/folly.git -> github.com-facebook-folly.git url = urlparse(repo_url) directory = "%s%s%s" % (url.netloc, url.path, branch if branch else "") for s in ["/", "\\", ":"]: directory = directory.replace(s, "-") # Place it in a repos dir in the scratch space repos_dir = os.path.join(build_options.scratch_dir, "repos") if not os.path.exists(repos_dir): os.makedirs(repos_dir) self.repo_dir: str = os.path.join(repos_dir, directory) if not rev and build_options.project_hashes: hash_file = os.path.join( build_options.project_hashes, re.sub("\\.git$", "-rev.txt", url.path[1:]), ) if os.path.exists(hash_file): with open(hash_file, "r") as f: data = f.read() m = re.match("Subproject commit ([a-fA-F0-9]{40})", data) if not m: raise Exception("Failed to parse rev from %s" % hash_file) rev = m.group(1) print( "Using pinned rev %s for %s" % (rev, repo_url), file=sys.stderr ) self.rev: str = rev or branch or "main" self.origin_repo: str = repo_url self.manifest: ManifestParser = manifest self.depth: int = depth if depth else GitFetcher.DEFAULT_DEPTH self.branch: str = branch def _update(self) -> ChangeStatus: current_hash = ( subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=self.repo_dir) .strip() .decode("utf-8") ) target_hash = ( subprocess.check_output(["git", "rev-parse", self.rev], cwd=self.repo_dir) .strip() .decode("utf-8") ) if target_hash == current_hash: # It's up to date, so there are no changes. This doesn't detect eg: # if origin/main moved and rev='main', but that's ok for our purposes; # we should be using explicit hashes or eg: a stable branch for the cases # that we care about, and it isn't unreasonable to require that the user # explicitly perform a clean build if those have moved. For the most # part we prefer that folks build using a release tarball from github # rather than use the git protocol, as it is generally a bit quicker # to fetch and easier to hash and verify tarball downloads. return ChangeStatus() print("Updating %s -> %s" % (self.repo_dir, self.rev)) run_cmd(["git", "fetch", "origin", self.rev], cwd=self.repo_dir) run_cmd(["git", "checkout", self.rev], cwd=self.repo_dir) run_cmd(["git", "submodule", "update", "--init"], cwd=self.repo_dir) return ChangeStatus(True) def update(self) -> ChangeStatus: if os.path.exists(self.repo_dir): return self._update() self._clone() return ChangeStatus(True) def _clone(self) -> None: print("Cloning %s..." % self.origin_repo) # The basename/dirname stuff allows us to dance around issues where # eg: this python process is native win32, but the git.exe is cygwin # or msys and doesn't like the absolute windows path that we'd otherwise # pass to it. Careful use of cwd helps avoid headaches with cygpath. cmd = [ "git", "clone", "--depth=" + str(self.depth), ] if self.branch: cmd.append("--branch=" + self.branch) cmd += [ "--", self.origin_repo, os.path.basename(self.repo_dir), ] run_cmd(cmd, cwd=os.path.dirname(self.repo_dir)) self._update() def clean(self) -> None: if os.path.exists(self.repo_dir): run_cmd(["git", "clean", "-fxd"], cwd=self.repo_dir) def hash(self) -> str: return self.rev def get_src_dir(self) -> str: return self.repo_dir def does_file_need_update( src_name: str, src_st: os.stat_result, dest_name: str ) -> bool: try: target_st = os.lstat(dest_name) except OSError as exc: if exc.errno != errno.ENOENT: raise return True if src_st.st_size != target_st.st_size: return True if stat.S_IFMT(src_st.st_mode) != stat.S_IFMT(target_st.st_mode): return True if stat.S_ISLNK(src_st.st_mode): return os.readlink(src_name) != os.readlink(dest_name) if not stat.S_ISREG(src_st.st_mode): return True # They might have the same content; compare. with open(src_name, "rb") as sf, open(dest_name, "rb") as df: chunk_size = 8192 while True: src_data = sf.read(chunk_size) dest_data = df.read(chunk_size) if src_data != dest_data: return True if len(src_data) < chunk_size: # EOF break return False def copy_if_different(src_name: str, dest_name: str) -> bool: """Copy src_name -> dest_name, but only touch dest_name if src_name is different from dest_name, making this a more build system friendly way to copy.""" src_st = os.lstat(src_name) if not does_file_need_update(src_name, src_st, dest_name): return False dest_parent = os.path.dirname(dest_name) if not os.path.exists(dest_parent): os.makedirs(dest_parent) if stat.S_ISLNK(src_st.st_mode): try: os.unlink(dest_name) except OSError as exc: if exc.errno != errno.ENOENT: raise target = os.readlink(src_name) os.symlink(target, dest_name) else: shutil.copy2(src_name, dest_name) return True def filter_strip_marker(dest_name: str, marker: str) -> None: """Strip lines/blocks tagged with the given marker from a file.""" try: with open(dest_name, "r") as f: content = f.read() except (UnicodeDecodeError, PermissionError): return if marker not in content: return escaped = re.escape(marker) block_re = re.compile( r"[^\n]*" + escaped + r"-start[^\n]*\n.*?[^\n]*" + escaped + r"-end[^\n]*\n?", re.DOTALL, ) line_re = re.compile(r".*" + escaped + r".*\n?") filtered = block_re.sub("", content) filtered = line_re.sub("", filtered) if filtered != content: with open(dest_name, "w") as f: f.write(filtered) def list_files_under_dir_newer_than_timestamp( dir_to_scan: str, ts: int ) -> Iterator[str]: for root, _dirs, files in os.walk(dir_to_scan): for src_file in files: full_name = os.path.join(root, src_file) st = os.lstat(full_name) if st.st_mtime > ts: yield full_name class ShipitPathMap: def __init__(self) -> None: self.roots: list[str] = [] self.mapping: list[str] = [] self.exclusion: list[str] = [] self.strip_marker: str = "@fb-only" def add_mapping(self, fbsource_dir: str, target_dir: str) -> None: """Add a posix path or pattern. We cannot normpath the input here because that would change the paths from posix to windows form and break the logic throughout this class.""" self.roots.append(fbsource_dir) # pyre-fixme[6]: For 1st argument expected `str` but got `Tuple[str, str]`. self.mapping.append((fbsource_dir, target_dir)) def add_exclusion(self, pattern: str) -> None: # pyre-fixme[6]: For 1st argument expected `str` but got `Pattern[str]`. self.exclusion.append(re.compile(pattern)) def _minimize_roots(self) -> None: """compute the de-duplicated set of roots within fbsource. We take the shortest common directory prefix to make this determination""" self.roots.sort(key=len) minimized = [] for r in self.roots: add_this_entry = True for existing in minimized: if r.startswith(existing + "/"): add_this_entry = False break if add_this_entry: minimized.append(r) self.roots = minimized def _sort_mapping(self) -> None: self.mapping.sort(reverse=True, key=lambda x: len(x[0])) def _map_name(self, norm_name: str, dest_root: str) -> str | None: if norm_name.endswith(".pyc") or norm_name.endswith(".swp"): # Ignore some incidental garbage while iterating return None for excl in self.exclusion: # pyre-fixme[16]: `str` has no attribute `match`. if excl.match(norm_name): return None for src_name, dest_name in self.mapping: if norm_name == src_name or norm_name.startswith(src_name + "/"): rel_name = os.path.relpath(norm_name, src_name) # We can have "." as a component of some paths, depending # on the contents of the shipit transformation section. # normpath doesn't always remove `.` as the final component # of the path, which be problematic when we later mkdir # the dirname of the path that we return. Take care to avoid # returning a path with a `.` in it. rel_name = os.path.normpath(rel_name) if dest_name == ".": return os.path.normpath(os.path.join(dest_root, rel_name)) dest_name = os.path.normpath(dest_name) return os.path.normpath(os.path.join(dest_root, dest_name, rel_name)) raise Exception("%s did not match any rules" % norm_name) def mirror(self, fbsource_root: str, dest_root: str) -> ChangeStatus: self._minimize_roots() self._sort_mapping() change_status = ChangeStatus() # Record the full set of files that should be in the tree full_file_list = set() if sys.platform == "win32": # Let's not assume st_dev has a consistent value on Windows. def st_dev(path: str) -> int: return 1 else: def st_dev(path: str) -> int: return os.lstat(path).st_dev for fbsource_subdir in self.roots: dir_to_mirror = os.path.join(fbsource_root, fbsource_subdir) root_dev = st_dev(dir_to_mirror) prefetch_dir_if_eden(dir_to_mirror) if not os.path.exists(dir_to_mirror): raise Exception( "%s doesn't exist; check your sparse profile!" % dir_to_mirror ) update_count = 0 for root, dirs, files in os.walk(dir_to_mirror): dirs[:] = [d for d in dirs if root_dev == st_dev(os.path.join(root, d))] for src_file in files: full_name = os.path.join(root, src_file) rel_name = os.path.relpath(full_name, fbsource_root) norm_name = rel_name.replace("\\", "/") target_name = self._map_name(norm_name, dest_root) if target_name: full_file_list.add(target_name) if copy_if_different(full_name, target_name): filter_strip_marker(target_name, self.strip_marker) change_status.record_change(target_name) if update_count < 10: print("Updated %s -> %s" % (full_name, target_name)) elif update_count == 10: print("...") update_count += 1 if update_count: print("Updated %s for %s" % (update_count, fbsource_subdir)) # Compare the list of previously shipped files; if a file is # in the old list but not the new list then it has been # removed from the source and should be removed from the # destination. # Why don't we simply create this list by walking dest_root? # Some builds currently have to be in-source builds and # may legitimately need to keep some state in the source tree :-/ installed_name = os.path.join(dest_root, ".shipit_shipped") if os.path.exists(installed_name): with open(installed_name, "rb") as f: for name in f.read().decode("utf-8").splitlines(): name = name.strip() if name not in full_file_list: print("Remove %s" % name) os.unlink(name) change_status.record_change(name) with open(installed_name, "wb") as f: for name in sorted(full_file_list): f.write(("%s\n" % name).encode("utf-8")) return change_status class FbsourceRepoData(NamedTuple): hash: str date: str FBSOURCE_REPO_DATA: dict[str, FbsourceRepoData] = {} def get_fbsource_repo_data(build_options: BuildOptions) -> FbsourceRepoData: """Returns the commit metadata for the fbsource repo. Since we may have multiple first party projects to hash, and because we don't mutate the repo, we cache this hash in a global.""" # pyre-fixme[6]: For 1st argument expected `str` but got `Optional[str]`. cached_data = FBSOURCE_REPO_DATA.get(build_options.fbsource_dir) if cached_data: return cached_data if "GETDEPS_HG_REPO_DATA" in os.environ: log_data = os.environ["GETDEPS_HG_REPO_DATA"] else: cmd = ["hg", "log", "-r.", "-T{node}\n{date|hgdate}"] env = Env() env.set("HGPLAIN", "1") log_data = subprocess.check_output( cmd, cwd=build_options.fbsource_dir, env=dict(env.items()) ).decode("ascii") (hash, datestr) = log_data.split("\n") # datestr is like "seconds fractionalseconds" # We want "20200324.113140" (unixtime, _fractional) = datestr.split(" ") date = datetime.fromtimestamp(int(unixtime)).strftime("%Y%m%d.%H%M%S") cached_data = FbsourceRepoData(hash=hash, date=date) # pyre-fixme[6]: For 1st argument expected `str` but got `Optional[str]`. FBSOURCE_REPO_DATA[build_options.fbsource_dir] = cached_data return cached_data def is_public_commit(build_options: BuildOptions) -> bool: # noqa: C901 """Check if the current commit is public (shipped/will be shipped to remote). Works across git, sapling (sl), and hg repositories: - For hg/sapling: Uses 'phase' command to check if commit is public - For git: Checks if commit exists in remote branches Returns True if public, False if draft/local-only or on error (conservative). """ # Use fbsource_dir if available (Meta internal), otherwise fall back to repo_root repo_dir = build_options.fbsource_dir or build_options.repo_root if not repo_dir: # No repository detected, be conservative return False env = Env() env.set("HGPLAIN", "1") env_dict = dict(env.items()) try: # Try hg/sapling phase command first (works for both hg and sl) # Try 'sl' first as it's the preferred tool at Meta for cmd in [["sl", "phase", "-r", "."], ["hg", "phase", "-r", "."]]: try: output = ( subprocess.check_output( cmd, cwd=repo_dir, env=env_dict, stderr=subprocess.DEVNULL ) .decode("ascii") .strip() ) # Output format: "hash: public" or "hash: draft" return "public" in output except (subprocess.CalledProcessError, FileNotFoundError): continue # Try git if hg/sl didn't work try: # Detect the default branch for origin remote default_branch = None try: # Get the symbolic ref for origin/HEAD to find default branch output = ( subprocess.check_output( ["git", "symbolic-ref", "refs/remotes/origin/HEAD"], cwd=repo_dir, stderr=subprocess.DEVNULL, ) .decode("ascii") .strip() ) # Output format: "refs/remotes/origin/main" if output.startswith("refs/remotes/"): default_branch = output except subprocess.CalledProcessError: # If symbolic-ref fails, fall back to common names pass # Build list of branches to check branches_to_check = [] if default_branch: branches_to_check.append(default_branch) # Also try common defaults as fallback branches_to_check.extend(["origin/main", "origin/master"]) # Check if HEAD is an ancestor of any of these branches for branch in branches_to_check: try: subprocess.check_output( ["git", "merge-base", "--is-ancestor", "HEAD", branch], cwd=repo_dir, stderr=subprocess.DEVNULL, ) # If command succeeds (exit 0), HEAD is an ancestor of the branch return True except subprocess.CalledProcessError: # Not an ancestor of this branch, try next continue # HEAD is not in any default branch return False except FileNotFoundError: pass # If all VCS commands failed, be conservative and don't upload return False except Exception: # On any unexpected error, be conservative and don't upload return False class SimpleShipitTransformerFetcher(Fetcher): def __init__( self, build_options: BuildOptions, manifest: ManifestParser, ctx: ManifestContext, ) -> None: self.build_options: BuildOptions = build_options self.manifest: ManifestParser = manifest self.repo_dir: str = os.path.join( build_options.scratch_dir, "shipit", manifest.name ) self.ctx: ManifestContext = ctx def clean(self) -> None: if os.path.exists(self.repo_dir): shutil.rmtree(self.repo_dir) def update(self) -> ChangeStatus: mapping = ShipitPathMap() for src, dest in self.manifest.get_section_as_ordered_pairs( "shipit.pathmap", self.ctx ): # pyre-fixme[6]: For 2nd argument expected `str` but got `Optional[str]`. mapping.add_mapping(src, dest) if self.manifest.shipit_fbcode_builder: mapping.add_mapping( "fbcode/opensource/fbcode_builder", "build/fbcode_builder" ) for pattern in self.manifest.get_section_as_args("shipit.strip", self.ctx): mapping.add_exclusion(pattern) # pyre-fixme[8]: Attribute has type `str`; used as `Optional[str]`. mapping.strip_marker = self.manifest.shipit_strip_marker # pyre-fixme[6]: In call `ShipitPathMap.mirror`, for 1st positional argument, expected `str` but got `Optional[str]` return mapping.mirror(self.build_options.fbsource_dir, self.repo_dir) def hash(self) -> str: # We return a fixed non-hash string for in-fbsource builds. # We're relying on the `update` logic to correctly invalidate # the build in the case that files have changed. return "fbsource" def get_src_dir(self) -> str: return self.repo_dir class SubFetcher(Fetcher): """Fetcher for a project with subprojects""" def __init__(self, base: Fetcher, subs: list[tuple[Fetcher, str]]) -> None: self.base: Fetcher = base self.subs: list[tuple[Fetcher, str]] = subs def update(self) -> ChangeStatus: base = self.base.update() changed = base.build_changed() or base.sources_changed() for fetcher, dir in self.subs: stat = fetcher.update() if stat.build_changed() or stat.sources_changed(): changed = True link = self.base.get_src_dir() + "/" + dir if not os.path.exists(link): os.symlink(fetcher.get_src_dir(), link) return ChangeStatus(changed) def clean(self) -> None: self.base.clean() for fetcher, _ in self.subs: fetcher.clean() def hash(self) -> str: my_hash = self.base.hash() for fetcher, _ in self.subs: my_hash += fetcher.hash() return my_hash def get_src_dir(self) -> str: return self.base.get_src_dir() class ShipitTransformerFetcher(Fetcher): @classmethod def _shipit_paths(cls, build_options: BuildOptions) -> list[str]: www_path = ["/var/www/scripts/opensource/codesync"] if build_options.fbsource_dir: fbcode_path = [ os.path.join( build_options.fbsource_dir, "fbcode/opensource/codesync/codesync-cli/codesync", ) ] else: fbcode_path = [] return www_path + fbcode_path def __init__( self, build_options: BuildOptions, project_name: str, external_branch: str ) -> None: self.build_options: BuildOptions = build_options self.project_name: str = project_name self.external_branch: str = external_branch self.repo_dir: str = os.path.join( build_options.scratch_dir, "shipit", project_name ) self.shipit: str | None = None for path in ShipitTransformerFetcher._shipit_paths(build_options): if os.path.exists(path): self.shipit = path break def update(self) -> ChangeStatus: if os.path.exists(self.repo_dir): return ChangeStatus() self.run_shipit() return ChangeStatus(True) def clean(self) -> None: if os.path.exists(self.repo_dir): shutil.rmtree(self.repo_dir) @classmethod def available(cls, build_options: BuildOptions) -> bool: return any( os.path.exists(path) for path in ShipitTransformerFetcher._shipit_paths(build_options) ) def run_shipit(self) -> None: tmp_path = self.repo_dir + ".new" try: if os.path.exists(tmp_path): shutil.rmtree(tmp_path) os.makedirs(os.path.dirname(tmp_path), exist_ok=True) cmd = [ self.shipit, "shipit", "--project=" + self.project_name, "--create-new-repo", # pyre-fixme[58]: `+` is not supported for operand types `str` and # `Optional[str]`. "--source-repo-dir=" + self.build_options.fbsource_dir, "--source-branch=.", "--skip-source-init", "--skip-source-pull", "--skip-source-clean", "--skip-push", "--destination-use-anonymous-https", "--create-new-repo-output-path=" + tmp_path, ] if self.external_branch: cmd += [ f"--external-branch={self.external_branch}", ] # Run shipit # pyre-fixme[6]: For 1st argument expected `List[str]` but got # `List[Optional[str]]`. run_cmd(cmd) # Remove the .git directory from the repository it generated. # There is no need to commit this. repo_git_dir = os.path.join(tmp_path, ".git") shutil.rmtree(repo_git_dir) os.rename(tmp_path, self.repo_dir) except Exception: # Clean up after a failed extraction if os.path.exists(tmp_path): shutil.rmtree(tmp_path) self.clean() raise def hash(self) -> str: # We return a fixed non-hash string for in-fbsource builds. return "fbsource" def get_src_dir(self) -> str: return self.repo_dir def download_url_to_file_with_progress(url: str, file_name: str) -> None: print("Download with %s -> %s ..." % (url, file_name)) class Progress: last_report: float = 0 def write_update(self, total: int, amount: int) -> None: if total == -1: total = "(Unknown)" if sys.stdout.isatty(): sys.stdout.write("\r downloading %s of %s " % (amount, total)) else: # When logging to CI logs, avoid spamming the logs and print # status every few seconds now = time.time() if now - self.last_report > 5: sys.stdout.write(".. %s of %s " % (amount, total)) self.last_report = now sys.stdout.flush() def progress_pycurl( self, total: float, amount: float, _uploadtotal: float, _uploadamount: float ) -> None: self.write_update(total, amount) progress = Progress() start = time.time() try: if os.environ.get("GETDEPS_USE_WGET") is not None: procargs = ( [ "wget", ] + os.environ.get("GETDEPS_WGET_ARGS", "").split() + [ "-O", file_name, url, ] ) subprocess.run(procargs, capture_output=True) headers = None elif os.environ.get("GETDEPS_USE_LIBCURL") is not None: import pycurl with open(file_name, "wb") as f: c = pycurl.Curl() c.setopt(pycurl.URL, url) c.setopt(pycurl.WRITEDATA, f) # display progress c.setopt(pycurl.NOPROGRESS, False) c.setopt(pycurl.XFERINFOFUNCTION, progress.progress_pycurl) c.perform() c.close() headers = None else: try: req_header = {"Accept": "application/*"} res = urlopen(Request(url, None, req_header)) chunk_size = 8192 # urlretrieve uses this value headers = res.headers content_length = res.headers.get("Content-Length") total = int(content_length.strip()) if content_length else -1 amount = 0 with open(file_name, "wb") as f: chunk = res.read(chunk_size) while chunk: f.write(chunk) amount += len(chunk) progress.write_update(total, amount) chunk = res.read(chunk_size) except (OSError, IOError) as exc: # noqa: B014 # Downloading from within Meta's network needs to use a proxy. if shutil.which("fwdproxy-config") is None: print( "Note: Could not find Meta-specific fallback 'fwdproxy-config'. " "If you are working externally, you can ignore this message." ) raise print("Default download failed, retrying with curl and fwdproxy...") cmd = f"curl -L $(fwdproxy-config curl) -o {shlex.quote(file_name)} {shlex.quote(url)}" print(f"Running command: {cmd}") result = subprocess.run(cmd, shell=True, capture_output=True) if result.returncode != 0: raise TransientFailure( f"Failed to download {url} to {file_name}: {exc} (fwdproxy fallback failed: {result.stderr.decode()})" ) headers = None except (OSError, IOError) as exc: # noqa: B014 raise TransientFailure( "Failed to download %s to %s: %s" % (url, file_name, str(exc)) ) end = time.time() sys.stdout.write(" [Complete in %f seconds]\n" % (end - start)) sys.stdout.flush() if headers is not None: print(f"{headers}") class ArchiveFetcher(Fetcher): def __init__( self, build_options: BuildOptions, manifest: ManifestParser, url: str, sha256: str, ) -> None: self.manifest: ManifestParser = manifest self.url: str = url self.sha256: str = sha256 self.build_options: BuildOptions = build_options parsed_url = urlparse(self.url) basename = "%s-%s" % (manifest.name, os.path.basename(parsed_url.path)) self.file_name: str = os.path.join( build_options.scratch_dir, "downloads", basename ) self.src_dir: str = os.path.join( build_options.scratch_dir, "extracted", basename ) self.hash_file: str = self.src_dir + ".hash" def _verify_hash(self) -> None: h = hashlib.sha256() with open(self.file_name, "rb") as f: while True: block = f.read(8192) if not block: break h.update(block) digest = h.hexdigest() if digest != self.sha256: os.unlink(self.file_name) raise Exception( "%s: expected sha256 %s but got %s" % (self.url, self.sha256, digest) ) def _download_dir(self) -> str: """returns the download dir, creating it if it doesn't already exist""" download_dir = os.path.dirname(self.file_name) if not os.path.exists(download_dir): os.makedirs(download_dir) return download_dir def _download(self) -> None: self._download_dir() max_attempts = 5 delay = 1 for attempt in range(max_attempts): try: download_url_to_file_with_progress(self.url, self.file_name) break except TransientFailure as tf: if attempt < max_attempts - 1: delay *= 2 delay_with_jitter = delay * (1 + random.random() * 0.1) time.sleep(min(delay_with_jitter, 10)) else: print(f"Failed after retries: {tf}") raise self._verify_hash() def clean(self) -> None: if os.path.exists(self.src_dir): shutil.rmtree(self.src_dir) def update(self) -> ChangeStatus: try: with open(self.hash_file, "r") as f: saved_hash = f.read().strip() if saved_hash == self.sha256 and os.path.exists(self.src_dir): # Everything is up to date return ChangeStatus() print( "saved hash %s doesn't match expected hash %s, re-validating" % (saved_hash, self.sha256) ) os.unlink(self.hash_file) except EnvironmentError: pass # If we got here we know the contents of src_dir are either missing # or wrong, so blow away whatever happened to be there first. if os.path.exists(self.src_dir): shutil.rmtree(self.src_dir) # If we already have a file here, make sure it looks legit before # proceeding: any errors and we just remove it and re-download if os.path.exists(self.file_name): try: self._verify_hash() except Exception: if os.path.exists(self.file_name): os.unlink(self.file_name) if not os.path.exists(self.file_name): self._download() self._verify_hash() if tarfile.is_tarfile(self.file_name): opener = tarfile.open elif zipfile.is_zipfile(self.file_name): opener = zipfile.ZipFile else: raise Exception("don't know how to extract %s" % self.file_name) os.makedirs(self.src_dir) print("Extract %s -> %s" % (self.file_name, self.src_dir)) if is_windows(): # Ensure that we don't fall over when dealing with long paths # on windows src = r"\\?\%s" % os.path.normpath(self.src_dir) else: src = self.src_dir with opener(self.file_name) as t: # The `str` here is necessary to ensure that we don't pass a unicode # object down to tarfile.extractall on python2. When extracting # the boost tarball it makes some assumptions and tries to convert # a non-ascii path to ascii and throws. src = str(src) t.extractall(src) if is_windows(): subdir = self.manifest.get("build", "subdir") checkdir = src if subdir: checkdir = src + "\\" + subdir if os.path.exists(checkdir): children = os.listdir(checkdir) print(f"Extracted to {checkdir} contents: {children}") with open(self.hash_file, "w") as f: f.write(self.sha256) return ChangeStatus(True) def hash(self) -> str: return self.sha256 def get_src_dir(self) -> str: return self.src_dir def homebrew_package_prefix(package: str) -> str | None: cmd = ["brew", "--prefix", package] try: proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) except FileNotFoundError: return None if proc.returncode == 0: return proc.stdout.decode("utf-8").rstrip() ================================================ FILE: build/fbcode_builder/getdeps/include_rewriter.py ================================================ #!/usr/bin/env python3 # Copyright (c) Meta Platforms, Inc. and affiliates. # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. # pyre-strict from __future__ import annotations """ Include Path Rewriter for getdeps This module provides functionality to rewrite #include statements in C++ files to handle differences between fbcode and open source project structures. """ import os import re import typing from pathlib import Path from typing import Any if typing.TYPE_CHECKING: from .manifest import ManifestParser class IncludePathRewriter: """Rewrites #include paths in C++ source files based on path mappings.""" # C++ file extensions to process CPP_EXTENSIONS: set[str] = { ".cpp", ".cc", ".cxx", ".c", ".h", ".hpp", ".hxx", ".tcc", ".inc", } def __init__(self, mappings: list[tuple[str, str]], verbose: bool = False) -> None: """ Initialize the rewriter with path mappings. Args: mappings: List of (old_path_prefix, new_path_prefix) tuples verbose: Enable verbose output """ self.mappings: list[tuple[str, str]] = mappings self.verbose: bool = verbose # Compile regex patterns for efficiency self.patterns: list[tuple[re.Pattern[str], str, str]] = [] for old_prefix, new_prefix in mappings: # Match both quoted and angle bracket includes # Pattern matches: #include "old_prefix/rest" or #include pattern = re.compile( r'(#\s*include\s*[<"])(' + re.escape(old_prefix) + r'/[^">]+)([">])', re.MULTILINE, ) self.patterns.append((pattern, old_prefix, new_prefix)) def rewrite_file(self, file_path: Path, dry_run: bool = False) -> bool: """ Rewrite includes in a single file. Args: file_path: Path to the file to process dry_run: If True, don't actually modify files Returns: True if file was modified, False otherwise """ try: with open(file_path, "r", encoding="utf-8") as f: original_content: str = f.read() except (IOError, UnicodeDecodeError) as e: if self.verbose: print(f"Warning: Could not read {file_path}: {e}") return False modified_content: str = original_content changes_made: bool = False for pattern, old_prefix, new_prefix in self.patterns: def make_replace_func( old_prefix: str, new_prefix: str ) -> typing.Callable[[re.Match[str]], str]: def replace_func(match: re.Match[str]) -> str: nonlocal changes_made prefix: str = match.group(1) # #include [<"] full_path: str = match.group(2) # full path suffix: str = match.group(3) # [">] # Replace the old prefix with new prefix new_path: str = full_path.replace(old_prefix, new_prefix, 1) if self.verbose and not changes_made: print(f" {full_path} -> {new_path}") changes_made = True return f"{prefix}{new_path}{suffix}" return replace_func modified_content = pattern.sub( make_replace_func(old_prefix, new_prefix), modified_content ) if changes_made and not dry_run: try: with open(file_path, "w", encoding="utf-8") as f: f.write(modified_content) if self.verbose: print(f"Modified: {file_path}") except IOError as e: print(f"Error: Could not write {file_path}: {e}") return False elif changes_made and dry_run: if self.verbose: print(f"Would modify: {file_path}") return changes_made def process_directory(self, source_dir: Path, dry_run: bool = False) -> int: """ Process all C++ files in a directory recursively. Args: source_dir: Root directory to process dry_run: If True, don't actually modify files Returns: Number of files modified """ if not source_dir.exists(): if self.verbose: print(f"Warning: Directory {source_dir} does not exist") return 0 modified_count: int = 0 processed_count: int = 0 for root, dirs, files in os.walk(source_dir): # Skip hidden directories and common build directories dirs[:] = [ d for d in dirs if not d.startswith(".") and d not in {"build", "_build", "__pycache__", "CMakeFiles"} ] for file in files: file_path: Path = Path(root) / file # Only process C++ files if file_path.suffix.lower() not in self.CPP_EXTENSIONS: continue processed_count += 1 if self.verbose: print(f"Processing: {file_path}") if self.rewrite_file(file_path, dry_run): modified_count += 1 if self.verbose or modified_count > 0: print(f"Processed {processed_count} files, modified {modified_count} files") return modified_count def rewrite_includes_from_manifest( manifest: ManifestParser, ctx: Any, source_dir: str, verbose: bool = False ) -> int: """ Rewrite includes using mappings from a manifest file. Args: manifest: The manifest object containing shipit.pathmap section ctx: The manifest context source_dir: Directory containing source files to process verbose: Enable verbose output Returns: Number of files modified """ mappings: list[tuple[str, str]] = [] # Get mappings from the manifest's shipit.pathmap section for src, dest in manifest.get_section_as_ordered_pairs("shipit.pathmap", ctx): # Remove fbcode/ or xplat/ prefixes from src since they won't appear in #include statements if src.startswith("fbcode/"): src = src[len("fbcode/") :] elif src.startswith("xplat/"): src = src[len("xplat/") :] # pyre-fixme[6]: For 1st argument expected `Tuple[str, str]` but got # `Tuple[str, Optional[str]]`. mappings.append((src, dest)) if not mappings: if verbose: print("No include path mappings found in manifest") return 0 if verbose: print("Include path mappings:") for old_path, new_path in mappings: print(f" {old_path} -> {new_path}") rewriter: IncludePathRewriter = IncludePathRewriter(mappings, verbose) return rewriter.process_directory(Path(source_dir), dry_run=False) ================================================ FILE: build/fbcode_builder/getdeps/load.py ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. # pyre-strict from __future__ import annotations import base64 import copy import hashlib import os import typing from collections.abc import Iterator from . import fetcher from .envfuncs import path_search from .errors import ManifestNotFound from .manifest import ManifestParser if typing.TYPE_CHECKING: from .buildopts import BuildOptions from .manifest import ContextGenerator, ManifestContext class Loader: """The loader allows our tests to patch the load operation""" def _list_manifests(self, build_opts: BuildOptions) -> Iterator[str]: """Returns a generator that iterates all the available manifests""" for path, _, files in os.walk(build_opts.manifests_dir): for name in files: # skip hidden files if name.startswith("."): continue yield os.path.join(path, name) def _load_manifest(self, path: str) -> ManifestParser: return ManifestParser(path) def load_project( self, build_opts: BuildOptions, project_name: str ) -> ManifestParser: if "/" in project_name or "\\" in project_name: # Assume this is a path already return ManifestParser(project_name) for manifest in self._list_manifests(build_opts): if os.path.basename(manifest) == project_name: return ManifestParser(manifest) raise ManifestNotFound(project_name) def load_all(self, build_opts: BuildOptions) -> dict[str, ManifestParser]: manifests_by_name: dict[str, ManifestParser] = {} for manifest in self._list_manifests(build_opts): m = self._load_manifest(manifest) if m.name in manifests_by_name: raise Exception("found duplicate manifest '%s'" % m.name) manifests_by_name[m.name] = m return manifests_by_name class ResourceLoader(Loader): def __init__(self, namespace: str, manifests_dir: str) -> None: self.namespace: str = namespace self.manifests_dir: str = manifests_dir def _list_manifests(self, build_opts: BuildOptions) -> Iterator[str]: import pkg_resources dirs: list[str] = [self.manifests_dir] while dirs: current = dirs.pop(0) for name in pkg_resources.resource_listdir(self.namespace, current): path = "%s/%s" % (current, name) if pkg_resources.resource_isdir(self.namespace, path): dirs.append(path) else: yield "%s/%s" % (current, name) def _find_manifest(self, project_name: str) -> str: # pyre-fixme[20]: Call `ResourceLoader._list_manifests` expects argument `build_opts`. for name in self._list_manifests(): if name.endswith("/%s" % project_name): return name raise ManifestNotFound(project_name) def _load_manifest(self, path: str) -> ManifestParser: import pkg_resources contents = pkg_resources.resource_string(self.namespace, path).decode("utf8") return ManifestParser(file_name=path, fp=contents) def load_project( self, build_opts: BuildOptions, project_name: str ) -> ManifestParser: project_name = self._find_manifest(project_name) # pyre-fixme[16]: `ResourceLoader` has no attribute `_load_resource_manifest`. return self._load_resource_manifest(project_name) LOADER: Loader = Loader() def patch_loader(namespace: str, manifests_dir: str = "manifests") -> None: global LOADER LOADER = ResourceLoader(namespace, manifests_dir) def load_project(build_opts: BuildOptions, project_name: str) -> ManifestParser: """given the name of a project or a path to a manifest file, load up the ManifestParser instance for it and return it""" return LOADER.load_project(build_opts, project_name) def load_all_manifests(build_opts: BuildOptions) -> dict[str, ManifestParser]: return LOADER.load_all(build_opts) class ManifestLoader: """ManifestLoader stores information about project manifest relationships for a given set of (build options + platform) configuration. The ManifestLoader class primarily serves as a location to cache project dependency relationships and project hash values for this build configuration. """ def __init__( self, build_opts: BuildOptions, ctx_gen: ContextGenerator | None = None ) -> None: self._loader: Loader = LOADER self.build_opts: BuildOptions = build_opts if ctx_gen is None: self.ctx_gen: ContextGenerator = self.build_opts.get_context_generator() else: self.ctx_gen = ctx_gen self.manifests_by_name: dict[str, ManifestParser] = {} self._loaded_all: bool = False self._project_hashes: dict[str, str] = {} self._fetcher_overrides: dict[str, fetcher.LocalDirFetcher] = {} self._build_dir_overrides: dict[str, str] = {} self._install_dir_overrides: dict[str, str] = {} self._install_prefix_overrides: dict[str, str] = {} def load_manifest(self, name: str) -> ManifestParser: manifest = self.manifests_by_name.get(name) if manifest is None: manifest = self._loader.load_project(self.build_opts, name) self.manifests_by_name[name] = manifest return manifest def load_all_manifests(self) -> dict[str, ManifestParser]: if not self._loaded_all: all_manifests_by_name = self._loader.load_all(self.build_opts) if self.manifests_by_name: # To help ensure that we only ever have a single manifest object for a # given project, and that it can't change once we have loaded it, # only update our mapping for projects that weren't already loaded. for name, manifest in all_manifests_by_name.items(): self.manifests_by_name.setdefault(name, manifest) else: self.manifests_by_name = all_manifests_by_name self._loaded_all = True return self.manifests_by_name def dependencies_of(self, manifest: ManifestParser) -> list[ManifestParser]: """Returns the dependencies of the given project, not including the project itself, in topological order.""" return [ dep for dep in self.manifests_in_dependency_order(manifest) if dep != manifest ] def manifests_in_dependency_order( self, manifest: ManifestParser | None = None ) -> list[ManifestParser]: """Compute all dependencies of the specified project. Returns a list of the dependencies plus the project itself, in topologically sorted order. Each entry in the returned list only depends on projects that appear before it in the list. If the input manifest is None, the dependencies for all currently loaded projects will be computed. i.e., if you call load_all_manifests() followed by manifests_in_dependency_order() this will return a global dependency ordering of all projects.""" # The list of deps that have been fully processed seen: set[str] = set() # The list of deps which have yet to be evaluated. This # can potentially contain duplicates. if manifest is None: deps: list[ManifestParser] = list(self.manifests_by_name.values()) else: assert manifest.name in self.manifests_by_name deps = [manifest] # The list of manifests in dependency order dep_order: list[ManifestParser] = [] system_packages: dict[str, list[str]] = {} while len(deps) > 0: m = deps.pop(0) if m.name in seen: continue # Consider its deps, if any. # We sort them for increased determinism; we'll produce # a correct order even if they aren't sorted, but we prefer # to produce the same order regardless of how they are listed # in the project manifest files. ctx: ManifestContext = self.ctx_gen.get_context(m.name) dep_list: list[str] = m.get_dependencies(ctx) dep_count: int = 0 for dep_name in dep_list: # If we're not sure whether it is done, queue it up if dep_name not in seen: dep = self.manifests_by_name.get(dep_name) if dep is None: dep = self._loader.load_project(self.build_opts, dep_name) self.manifests_by_name[dep.name] = dep deps.append(dep) dep_count += 1 if dep_count > 0: # If we queued anything, re-queue this item, as it depends # those new item(s) and their transitive deps. deps.append(m) continue # Its deps are done, so we can emit it seen.add(m.name) # Capture system packages as we may need to set PATHs to then later if ( self.build_opts.allow_system_packages and self.build_opts.host_type.get_package_manager() ): packages: dict[str, list[str]] = m.get_required_system_packages(ctx) for pkg_type, v in packages.items(): merged: list[str] = system_packages.get(pkg_type, []) if v not in merged: merged += v system_packages[pkg_type] = merged # A manifest depends on all system packages in it dependencies as well # pyre-fixme[8]: Attribute has type `Dict[str, str]`; used as # `Dict[str, List[str]]`. m.resolved_system_packages = copy.copy(system_packages) dep_order.append(m) return dep_order def set_project_src_dir(self, project_name: str, path: str) -> None: self._fetcher_overrides[project_name] = fetcher.LocalDirFetcher(path) def set_project_build_dir(self, project_name: str, path: str) -> None: self._build_dir_overrides[project_name] = path def set_project_install_dir(self, project_name: str, path: str) -> None: self._install_dir_overrides[project_name] = path def set_project_install_prefix(self, project_name: str, path: str) -> None: self._install_prefix_overrides[project_name] = path def create_fetcher( self, manifest: ManifestParser ) -> fetcher.Fetcher | fetcher.LocalDirFetcher: override = self._fetcher_overrides.get(manifest.name) if override is not None: return override ctx: ManifestContext = self.ctx_gen.get_context(manifest.name) return manifest.create_fetcher(self.build_opts, self, ctx) def get_project_hash(self, manifest: ManifestParser) -> str: h = self._project_hashes.get(manifest.name) if h is None: h = self._compute_project_hash(manifest) self._project_hashes[manifest.name] = h return h def _compute_project_hash(self, manifest: ManifestParser) -> str: """This recursive function computes a hash for a given manifest. The hash takes into account some environmental factors on the host machine and includes the hashes of its dependencies. No caching of the computation is performed, which is theoretically wasteful but the computation is fast enough that it is not required to cache across multiple invocations.""" ctx: ManifestContext = self.ctx_gen.get_context(manifest.name) hasher = hashlib.sha256() # Some environmental and configuration things matter env: dict[str, str | None] = {} env["install_dir"] = self.build_opts.install_dir env["scratch_dir"] = self.build_opts.scratch_dir env["vcvars_path"] = self.build_opts.vcvars_path env["os"] = self.build_opts.host_type.ostype env["distro"] = self.build_opts.host_type.distro env["distro_vers"] = self.build_opts.host_type.distrovers env["shared_libs"] = str(self.build_opts.shared_libs) for name in [ "CXXFLAGS", "CPPFLAGS", "LDFLAGS", "CXX", "CC", "GETDEPS_CMAKE_DEFINES", ]: env[name] = os.environ.get(name) for tool in ["cc", "c++", "gcc", "g++", "clang", "clang++"]: env["tool-%s" % tool] = path_search(os.environ, tool) for name in manifest.get_section_as_args("depends.environment", ctx): env[name] = os.environ.get(name) fetcher_inst: fetcher.Fetcher | fetcher.LocalDirFetcher = self.create_fetcher( manifest ) env["fetcher.hash"] = fetcher_inst.hash() for name in sorted(env.keys()): hasher.update(name.encode("utf-8")) value = env.get(name) if value is not None: try: hasher.update(value.encode("utf-8")) except AttributeError as exc: raise AttributeError("name=%r, value=%r: %s" % (name, value, exc)) manifest.update_hash(hasher, ctx) # If a patchfile is specified, include its contents in the hash patchfile: str | None = manifest.get("build", "patchfile", ctx=ctx) if patchfile: patchfile_path: str = os.path.join( self.build_opts.fbcode_builder_dir, "patches", patchfile ) if os.path.exists(patchfile_path): with open(patchfile_path, "rb") as f: hasher.update(f.read()) dep_list: list[str] = manifest.get_dependencies(ctx) for dep in dep_list: dep_manifest: ManifestParser = self.load_manifest(dep) dep_hash: str = self.get_project_hash(dep_manifest) hasher.update(dep_hash.encode("utf-8")) # Use base64 to represent the hash, rather than the simple hex digest, # so that the string is shorter. Use the URL-safe encoding so that # the hash can also be safely used as a filename component. h: str = base64.urlsafe_b64encode(hasher.digest()).decode("ascii") # ... and because cmd.exe is troublesome with `=` signs, nerf those. # They tend to be padding characters at the end anyway, so we can # safely discard them. h = h.replace("=", "") return h def _get_project_dir_name(self, manifest: ManifestParser) -> str: if manifest.is_first_party_project(): return manifest.name else: project_hash: str = self.get_project_hash(manifest) return "%s-%s" % (manifest.name, project_hash) def get_project_install_dir(self, manifest: ManifestParser) -> str: override = self._install_dir_overrides.get(manifest.name) if override: return override project_dir_name: str = self._get_project_dir_name(manifest) return os.path.join(self.build_opts.install_dir, project_dir_name) def get_project_build_dir(self, manifest: ManifestParser) -> str: override = self._build_dir_overrides.get(manifest.name) if override: return override project_dir_name: str = self._get_project_dir_name(manifest) return os.path.join(self.build_opts.scratch_dir, "build", project_dir_name) def get_project_install_prefix(self, manifest: ManifestParser) -> str | None: return self._install_prefix_overrides.get(manifest.name) def get_project_install_dir_respecting_install_prefix( self, manifest: ManifestParser ) -> str: inst_dir: str = self.get_project_install_dir(manifest) prefix: str | None = self.get_project_install_prefix(manifest) if prefix: return inst_dir + prefix return inst_dir ================================================ FILE: build/fbcode_builder/getdeps/manifest.py ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. # pyre-strict from __future__ import annotations import configparser import hashlib import io import os import sys import typing from .builder import ( AutoconfBuilder, Boost, CMakeBootStrapBuilder, CMakeBuilder, Iproute2Builder, MakeBuilder, MesonBuilder, NinjaBootstrap, NopBuilder, OpenSSLBuilder, SetupPyBuilder, SqliteBuilder, ) from .cargo import CargoBuilder from .expr import ExprNode, parse_expr from .fetcher import ( ArchiveFetcher, GitFetcher, PreinstalledNopFetcher, ShipitTransformerFetcher, SimpleShipitTransformerFetcher, SubFetcher, SystemPackageFetcher, ) from .py_wheel_builder import PythonWheelBuilder if typing.TYPE_CHECKING: from .builder import BuilderBase from .buildopts import BuildOptions from .fetcher import Fetcher from .load import ManifestLoader REQUIRED: str = "REQUIRED" OPTIONAL: str = "OPTIONAL" SCHEMA: dict[str, dict[str, object]] = { "manifest": { "optional_section": False, "fields": { "name": REQUIRED, "fbsource_path": OPTIONAL, "shipit_project": OPTIONAL, "shipit_fbcode_builder": OPTIONAL, "use_shipit": OPTIONAL, "shipit_external_branch": OPTIONAL, "shipit_strip_marker": OPTIONAL, }, }, "dependencies": {"optional_section": True, "allow_values": False}, "depends.environment": {"optional_section": True}, "git": { "optional_section": True, "fields": { "repo_url": REQUIRED, "rev": OPTIONAL, "depth": OPTIONAL, "branch": OPTIONAL, }, }, "download": { "optional_section": True, "fields": {"url": REQUIRED, "sha256": REQUIRED}, }, "build": { "optional_section": True, "fields": { "builder": REQUIRED, "subdir": OPTIONAL, "make_binary": OPTIONAL, "build_in_src_dir": OPTIONAL, "only_install": OPTIONAL, "job_weight_mib": OPTIONAL, "patchfile": OPTIONAL, "patchfile_opts": OPTIONAL, "rewrite_includes": OPTIONAL, }, }, "msbuild": {"optional_section": True, "fields": {"project": REQUIRED}}, "cargo": { "optional_section": True, "fields": { "build_doc": OPTIONAL, "workspace_dir": OPTIONAL, "manifests_to_build": OPTIONAL, # Where to write cargo config (defaults to build_dir/.cargo/config.toml) "cargo_config_file": OPTIONAL, }, }, "github.actions": { "optional_section": True, "fields": { "run_tests": OPTIONAL, "required_locales": OPTIONAL, "rust_version": OPTIONAL, "build_type": OPTIONAL, }, }, "crate.pathmap": {"optional_section": True}, "cmake.defines": {"optional_section": True}, "autoconf.args": {"optional_section": True}, "autoconf.envcmd.LDFLAGS": {"optional_section": True}, "rpms": {"optional_section": True}, "debs": {"optional_section": True}, "homebrew": {"optional_section": True}, "pps": {"optional_section": True}, "preinstalled.env": {"optional_section": True}, "bootstrap.args": {"optional_section": True}, "b2.args": {"optional_section": True}, "make.build_args": {"optional_section": True}, "make.install_args": {"optional_section": True}, "make.test_args": {"optional_section": True}, "meson.setup_args": {"optional_section": True}, "header-only": {"optional_section": True, "fields": {"includedir": REQUIRED}}, "shipit.pathmap": {"optional_section": True}, "shipit.strip": {"optional_section": True}, "install.files": {"optional_section": True}, "subprojects": {"optional_section": True}, # fb-only "sandcastle": {"optional_section": True, "fields": {"run_tests": OPTIONAL}}, "setup-py.test": {"optional_section": True, "fields": {"python_script": REQUIRED}}, "setup-py.env": {"optional_section": True}, } # These sections are allowed to vary for different platforms # using the expression syntax to enable/disable sections ALLOWED_EXPR_SECTIONS: list[str] = [ "autoconf.args", "autoconf.envcmd.LDFLAGS", "build", "cmake.defines", "dependencies", "make.build_args", "make.install_args", "bootstrap.args", "b2.args", "download", "git", "install.files", "rpms", "debs", "shipit.pathmap", "shipit.strip", "homebrew", "github.actions", "pps", ] def parse_conditional_section_name(name: str, section_def: str) -> ExprNode: expr = name[len(section_def) + 1 :] return parse_expr(expr, ManifestContext.ALLOWED_VARIABLES) def validate_allowed_fields( file_name: str, section: str, config: configparser.RawConfigParser, allowed_fields: dict[str, str], ) -> None: for field in config.options(section): if not allowed_fields.get(field): raise Exception( ("manifest file %s section '%s' contains " "unknown field '%s'") % (file_name, section, field) ) for field in allowed_fields: if allowed_fields[field] == REQUIRED and not config.has_option(section, field): raise Exception( ("manifest file %s section '%s' is missing " "required field '%s'") % (file_name, section, field) ) def validate_allow_values( file_name: str, section: str, config: configparser.RawConfigParser ) -> None: for field in config.options(section): value = config.get(section, field) if value is not None: raise Exception( ( "manifest file %s section '%s' has '%s = %s' but " "this section doesn't allow specifying values " "for its entries" ) % (file_name, section, field, value) ) def validate_section( file_name: str, section: str, config: configparser.RawConfigParser ) -> str: section_def = SCHEMA.get(section) if not section_def: for name in ALLOWED_EXPR_SECTIONS: if section.startswith(name + "."): # Verify that the conditional parses, but discard it try: parse_conditional_section_name(section, name) except Exception as exc: raise Exception( ("manifest file %s section '%s' has invalid " "conditional: %s") % (file_name, section, str(exc)) ) section_def = SCHEMA.get(name) canonical_section_name = name break if not section_def: raise Exception( "manifest file %s contains unknown section '%s'" % (file_name, section) ) else: canonical_section_name = section allowed_fields = section_def.get("fields") if allowed_fields: # pyre-ignore[6]: Expected `dict[str, str]` but got `object`. validate_allowed_fields(file_name, section, config, allowed_fields) elif not section_def.get("allow_values", True): validate_allow_values(file_name, section, config) # pyre-fixme[61]: `canonical_section_name` is undefined, or not always defined. return canonical_section_name class ManifestParser: def __init__(self, file_name: str, fp: str | typing.IO[str] | None = None) -> None: # allow_no_value enables listing parameters in the # autoconf.args section one per line config = configparser.RawConfigParser(allow_no_value=True) config.optionxform = str # type: ignore[assignment] # make it case sensitive if fp is None: with open(file_name, "r") as fp: config.read_file(fp) elif isinstance(fp, type("")): # For testing purposes, parse from a string (str # or unicode) config.read_file(io.StringIO(fp)) else: config.read_file(fp) # validate against the schema seen_sections: set[str] = set() for section in config.sections(): seen_sections.add(validate_section(file_name, section, config)) for section in SCHEMA.keys(): section_def = SCHEMA[section] if ( not section_def.get("optional_section", False) and section not in seen_sections ): raise Exception( "manifest file %s is missing required section %s" % (file_name, section) ) self._config: configparser.RawConfigParser = config self.name: str = config.get("manifest", "name") self.fbsource_path: str | None = self.get("manifest", "fbsource_path") self.shipit_project: str | None = self.get("manifest", "shipit_project") self.shipit_fbcode_builder: str | None = self.get( "manifest", "shipit_fbcode_builder" ) self.resolved_system_packages: dict[str, str] = {} self.shipit_strip_marker: str | None = self.get( "manifest", "shipit_strip_marker", defval="@fb-only" ) if self.name != os.path.basename(file_name): raise Exception( "filename of the manifest '%s' does not match the manifest name '%s'" % (file_name, self.name) ) if "." in self.name: raise Exception( f"manifest name ({self.name}) must not contain the '.' character (it is incompatible with github actions)" ) def get( self, section: str, key: str, defval: str | None = None, ctx: ManifestContext | dict[str, str | None] | None = None, ) -> str | None: ctx = ctx or {} for s in self._config.sections(): if s == section: if self._config.has_option(s, key): return self._config.get(s, key) return defval if s.startswith(section + "."): expr = parse_conditional_section_name(s, section) # pyre-fixme[6]: For 1st argument expected `Dict[str, # Optional[str]]` but got `Union[Dict[str, Optional[str]], # ManifestContext]`. if not expr.eval(ctx): continue if self._config.has_option(s, key): return self._config.get(s, key) return defval def get_dependencies(self, ctx: ManifestContext) -> list[str]: dep_list = list(self.get_section_as_dict("dependencies", ctx).keys()) dep_list.sort() builder = self.get("build", "builder", ctx=ctx) if builder in ("cmake", "python-wheel"): dep_list.insert(0, "cmake") elif builder == "autoconf" and self.name not in ( "autoconf", "libtool", "automake", ): # they need libtool and its deps (automake, autoconf) so add # those as deps (but obviously not if we're building those # projects themselves) dep_list.insert(0, "libtool") return dep_list def get_section_as_args( self, section: str, ctx: ManifestContext | dict[str, str | None] | None = None, ) -> list[str]: """Intended for use with the make.[build_args/install_args] and autoconf.args sections, this method collects the entries and returns an array of strings. If the manifest contains conditional sections, ctx is used to evaluate the condition and merge in the values. """ args: list[str] = [] ctx = ctx or {} for s in self._config.sections(): if s != section: if not s.startswith(section + "."): continue expr = parse_conditional_section_name(s, section) # pyre-fixme[6]: For 1st argument expected `Dict[str, # Optional[str]]` but got `Union[Dict[str, Optional[str]], # ManifestContext]`. if not expr.eval(ctx): continue for field in self._config.options(s): value = self._config.get(s, field) if value is None: args.append(field) else: args.append("%s=%s" % (field, value)) return args def get_section_as_ordered_pairs( self, section: str, ctx: ManifestContext | dict[str, str | None] | None = None, ) -> list[tuple[str, str | None]]: """Used for eg: shipit.pathmap which has strong ordering requirements""" res: list[tuple[str, str | None]] = [] ctx = ctx or {} for s in self._config.sections(): if s != section: if not s.startswith(section + "."): continue expr = parse_conditional_section_name(s, section) # pyre-fixme[6]: For 1st argument expected `Dict[str, # Optional[str]]` but got `Union[Dict[str, Optional[str]], # ManifestContext]`. if not expr.eval(ctx): continue for key in self._config.options(s): value = self._config.get(s, key) res.append((key, value)) return res def get_section_as_dict( self, section: str, ctx: ManifestContext | dict[str, str | None] | None, ) -> dict[str, str | None]: d: dict[str, str | None] = {} for s in self._config.sections(): if s != section: if not s.startswith(section + "."): continue expr = parse_conditional_section_name(s, section) # pyre-fixme[6]: For 1st argument expected `Dict[str, # Optional[str]]` but got `Union[None, Dict[str, Optional[str]], # ManifestContext]`. if not expr.eval(ctx): continue for field in self._config.options(s): value = self._config.get(s, field) d[field] = value return d def update_hash(self, hasher: hashlib._Hash, ctx: ManifestContext) -> None: """Compute a hash over the configuration for the given context. The goal is for the hash to change if the config for that context changes, but not if a change is made to the config only for a different platform than that expressed by ctx. The hash is intended to be used to help invalidate a future cache for the third party build products. The hasher argument is a hash object returned from hashlib.""" for section in sorted(SCHEMA.keys()): hasher.update(section.encode("utf-8")) # Note: at the time of writing, nothing in the implementation # relies on keys in any config section being ordered. # In theory we could have conflicting flags in different # config sections and later flags override earlier flags. # For the purposes of computing a hash we're not super # concerned about this: manifest changes should be rare # enough and we'd rather that this trigger an invalidation # than strive for a cache hit at this time. pairs = self.get_section_as_ordered_pairs(section, ctx) pairs.sort(key=lambda pair: pair[0]) for key, value in pairs: hasher.update(key.encode("utf-8")) if value is not None: hasher.update(value.encode("utf-8")) def is_first_party_project(self) -> bool: """returns true if this is an FB first-party project""" return self.shipit_project is not None def get_required_system_packages( self, ctx: ManifestContext ) -> dict[str, list[str]]: """Returns dictionary of packager system -> list of packages""" return { "rpm": self.get_section_as_args("rpms", ctx), "deb": self.get_section_as_args("debs", ctx), "homebrew": self.get_section_as_args("homebrew", ctx), "pacman-package": self.get_section_as_args("pps", ctx), } def _is_satisfied_by_preinstalled_environment(self, ctx: ManifestContext) -> bool: envs = self.get_section_as_args("preinstalled.env", ctx) if not envs: return False for key in envs: val = os.environ.get(key, None) print( f"Testing ENV[{key}]: {repr(val)}", file=sys.stderr, ) if val is None: return False if len(val) == 0: return False return True def get_repo_url(self, ctx: ManifestContext) -> str | None: return self.get("git", "repo_url", ctx=ctx) def _create_fetcher( self, build_options: BuildOptions, ctx: ManifestContext ) -> Fetcher: real_shipit_available = ShipitTransformerFetcher.available(build_options) use_real_shipit = real_shipit_available and ( build_options.use_shipit or self.get("manifest", "use_shipit", defval="false", ctx=ctx) == "true" ) if ( not use_real_shipit and self.fbsource_path and build_options.fbsource_dir and self.shipit_project ): return SimpleShipitTransformerFetcher(build_options, self, ctx) if ( self.fbsource_path and build_options.fbsource_dir and self.shipit_project and real_shipit_available ): # We can use the code from fbsource return ShipitTransformerFetcher( build_options, self.shipit_project, # pyre-fixme[6]: For 3rd argument expected `str` but got # `Optional[str]`. self.get("manifest", "shipit_external_branch"), ) # If both of these are None, the package can only be coming from # preinstalled toolchain or system packages repo_url = self.get_repo_url(ctx) url = self.get("download", "url", ctx=ctx) # Can we satisfy this dep with system packages? if (repo_url is None and url is None) or build_options.allow_system_packages: if self._is_satisfied_by_preinstalled_environment(ctx): # pyre-fixme[7]: Expected `Fetcher` but got `PreinstalledNopFetcher`. return PreinstalledNopFetcher() if build_options.host_type.get_package_manager(): packages = self.get_required_system_packages(ctx) package_fetcher = SystemPackageFetcher(build_options, packages) if package_fetcher.packages_are_installed(): # pyre-fixme[7]: Expected `Fetcher` but got `SystemPackageFetcher`. return package_fetcher if repo_url: rev = self.get("git", "rev") depth = self.get("git", "depth") branch = self.get("git", "branch") # pyre-fixme[6]: For 4th argument expected `str` but got `Optional[str]`. # pyre-fixme[6]: For 5th argument expected `int` but got `Optional[str]`. # pyre-fixme[6]: For 6th argument expected `str` but got `Optional[str]`. return GitFetcher(build_options, self, repo_url, rev, depth, branch) if url: # We need to defer this import until now to avoid triggering # a cycle when the facebook/__init__.py is loaded. try: from .facebook.lfs import LFSCachingArchiveFetcher return LFSCachingArchiveFetcher( build_options, self, url, # pyre-fixme[6]: For 4th argument expected `str` but got # `Optional[str]`. self.get("download", "sha256", ctx=ctx), ) except ImportError: # This FB internal module isn't shippped to github, # so just use its base class return ArchiveFetcher( build_options, self, url, # pyre-fixme[6]: For 4th argument expected `str` but got # `Optional[str]`. self.get("download", "sha256", ctx=ctx), ) raise KeyError( f"project {self.name} has no fetcher configuration or system packages matching {ctx} - have you run `getdeps.py install-system-deps --recursive`?" ) def create_fetcher( self, build_options: BuildOptions, loader: ManifestLoader, ctx: ManifestContext, ) -> Fetcher: fetcher = self._create_fetcher(build_options, ctx) subprojects = self.get_section_as_ordered_pairs("subprojects", ctx) if subprojects: subs: list[tuple[Fetcher, str | None]] = [] for project, subdir in subprojects: submanifest = loader.load_manifest(project) subfetcher = submanifest.create_fetcher(build_options, loader, ctx) subs.append((subfetcher, subdir)) # pyre-fixme[6]: For 2nd argument expected `List[Tuple[Fetcher, str]]` # but got `List[Tuple[Fetcher, Optional[str]]]`. return SubFetcher(fetcher, subs) else: return fetcher def get_builder_name(self, ctx: ManifestContext) -> str: builder = self.get("build", "builder", ctx=ctx) if not builder: raise Exception("project %s has no builder for %r" % (self.name, ctx)) return builder def create_builder( # noqa:C901 self, build_options: BuildOptions, src_dir: str, build_dir: str, inst_dir: str, ctx: ManifestContext, loader: ManifestLoader, dep_manifests: list[ManifestParser], final_install_prefix: str | None = None, extra_cmake_defines: dict[str, str] | None = None, cmake_targets: list[str] | None = None, extra_b2_args: list[str] | None = None, ) -> BuilderBase: builder = self.get_builder_name(ctx) build_in_src_dir = self.get("build", "build_in_src_dir", "false", ctx=ctx) if build_in_src_dir == "true": # Some scripts don't work when they are configured and build in # a different directory than source (or when the build directory # is not a subdir of source). build_dir = src_dir subdir = self.get("build", "subdir", None, ctx=ctx) if subdir is not None: build_dir = os.path.join(build_dir, subdir) print("build_dir is %s" % build_dir) # just to quiet lint if builder == "make" or builder == "cmakebootstrap": build_args = self.get_section_as_args("make.build_args", ctx) install_args = self.get_section_as_args("make.install_args", ctx) test_args = self.get_section_as_args("make.test_args", ctx) if builder == "cmakebootstrap": return CMakeBootStrapBuilder( loader, dep_manifests, build_options, ctx, self, src_dir, # pyre-fixme[6]: For 7th argument expected `str` but got `None`. None, inst_dir, build_args, install_args, test_args, ) else: return MakeBuilder( loader, dep_manifests, build_options, ctx, self, src_dir, # pyre-fixme[6]: For 7th argument expected `str` but got `None`. None, inst_dir, build_args, install_args, test_args, ) if builder == "autoconf": args = self.get_section_as_args("autoconf.args", ctx) conf_env_args: dict[str, list[str]] = {} ldflags_cmd = self.get_section_as_args("autoconf.envcmd.LDFLAGS", ctx) if ldflags_cmd: conf_env_args["LDFLAGS"] = ldflags_cmd return AutoconfBuilder( loader, dep_manifests, build_options, ctx, self, src_dir, build_dir, inst_dir, args, conf_env_args, ) if builder == "boost": args = self.get_section_as_args("b2.args", ctx) if extra_b2_args is not None: args += extra_b2_args return Boost( loader, dep_manifests, build_options, ctx, self, src_dir, build_dir, inst_dir, args, ) if builder == "cmake": defines = self.get_section_as_dict("cmake.defines", ctx) return CMakeBuilder( loader, dep_manifests, build_options, ctx, self, src_dir, build_dir, inst_dir, # pyre-fixme[6]: For 9th argument expected `Optional[Dict[str, # str]]` but got `Dict[str, Optional[str]]`. defines, final_install_prefix, extra_cmake_defines, cmake_targets, ) if builder == "python-wheel": return PythonWheelBuilder( loader, dep_manifests, build_options, ctx, self, src_dir, build_dir, inst_dir, ) if builder == "sqlite": return SqliteBuilder( loader, dep_manifests, build_options, ctx, self, src_dir, build_dir, inst_dir, ) if builder == "ninja_bootstrap": return NinjaBootstrap( loader, dep_manifests, build_options, ctx, self, build_dir, src_dir, inst_dir, ) if builder == "nop": return NopBuilder( loader, dep_manifests, build_options, ctx, self, src_dir, inst_dir ) if builder == "openssl": return OpenSSLBuilder( loader, dep_manifests, build_options, ctx, self, build_dir, src_dir, inst_dir, ) if builder == "iproute2": return Iproute2Builder( loader, dep_manifests, build_options, ctx, self, src_dir, build_dir, inst_dir, ) if builder == "meson": return MesonBuilder( loader, dep_manifests, build_options, ctx, self, src_dir, build_dir, inst_dir, ) if builder == "setup-py": return SetupPyBuilder( loader, dep_manifests, build_options, ctx, self, src_dir, build_dir, inst_dir, ) if builder == "cargo": return self.create_cargo_builder( loader, dep_manifests, build_options, ctx, src_dir, build_dir, inst_dir, ) raise KeyError("project %s has no known builder" % (self.name)) def create_prepare_builders( self, build_options: BuildOptions, ctx: ManifestContext, src_dir: str, build_dir: str, inst_dir: str, loader: ManifestLoader, dep_manifests: list[ManifestParser], ) -> list[BuilderBase]: """Create builders that have a prepare step run, e.g. to write config files""" prepare_builders: list[BuilderBase] = [] builder = self.get_builder_name(ctx) cargo = self.get_section_as_dict("cargo", ctx) if not builder == "cargo" and cargo: cargo_builder = self.create_cargo_builder( loader, dep_manifests, build_options, ctx, src_dir, build_dir, inst_dir, ) prepare_builders.append(cargo_builder) return prepare_builders def create_cargo_builder( self, loader: ManifestLoader, dep_manifests: list[ManifestParser], build_options: BuildOptions, ctx: ManifestContext, src_dir: str, build_dir: str, inst_dir: str, ) -> CargoBuilder: # pyre-fixme[6]: For 3rd argument expected `Optional[str]` but got `bool`. build_doc = self.get("cargo", "build_doc", False, ctx) workspace_dir = self.get("cargo", "workspace_dir", None, ctx) manifests_to_build = self.get("cargo", "manifests_to_build", None, ctx) cargo_config_file = self.get("cargo", "cargo_config_file", None, ctx) return CargoBuilder( loader, dep_manifests, build_options, ctx, self, src_dir, build_dir, inst_dir, # pyre-fixme[6]: For 9th argument expected `bool` but got `Optional[str]`. build_doc, workspace_dir, manifests_to_build, cargo_config_file, ) class ManifestContext: """ProjectContext contains a dictionary of values to use when evaluating boolean expressions in a project manifest. This object should be passed as the `ctx` parameter in ManifestParser.get() calls. """ ALLOWED_VARIABLES: set[str] = { "os", "distro", "distro_vers", "fb", "fbsource", "test", "shared_libs", } def __init__(self, ctx_dict: dict[str, str | None]) -> None: assert set(ctx_dict.keys()) == self.ALLOWED_VARIABLES self.ctx_dict: dict[str, str | None] = ctx_dict def get(self, key: str) -> str | None: return self.ctx_dict[key] def set(self, key: str, value: str | None) -> None: assert key in self.ALLOWED_VARIABLES self.ctx_dict[key] = value def copy(self) -> ManifestContext: return ManifestContext(dict(self.ctx_dict)) def __str__(self) -> str: s = ", ".join( "%s=%s" % (key, value) for key, value in sorted(self.ctx_dict.items()) ) return "{" + s + "}" class ContextGenerator: """ContextGenerator allows creating ManifestContext objects on a per-project basis. This allows us to evaluate different projects with slightly different contexts. For instance, this can be used to only enable tests for some projects.""" def __init__(self, default_ctx: dict[str, str | None]) -> None: self.default_ctx: ManifestContext = ManifestContext(default_ctx) self.ctx_by_project: dict[str, ManifestContext] = {} def set_value_for_project( self, project_name: str, key: str, value: str | None ) -> None: project_ctx = self.ctx_by_project.get(project_name) if project_ctx is None: project_ctx = self.default_ctx.copy() self.ctx_by_project[project_name] = project_ctx project_ctx.set(key, value) def set_value_for_all_projects(self, key: str, value: str | None) -> None: self.default_ctx.set(key, value) for ctx in self.ctx_by_project.values(): ctx.set(key, value) def get_context(self, project_name: str) -> ManifestContext: return self.ctx_by_project.get(project_name, self.default_ctx) ================================================ FILE: build/fbcode_builder/getdeps/platform.py ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. # pyre-strict from __future__ import annotations import os import platform import re import shlex import sys def is_windows() -> bool: """Returns true if the system we are currently running on is a Windows system""" return sys.platform.startswith("win") def get_linux_type() -> tuple[str | None, str | None, str | None]: try: with open("/etc/os-release") as f: data = f.read() except EnvironmentError: return (None, None, None) os_vars: dict[str, str] = {} for line in data.splitlines(): parts = line.split("=", 1) if len(parts) != 2: continue key = parts[0].strip() value_parts = shlex.split(parts[1].strip()) if not value_parts: value = "" else: value = value_parts[0] os_vars[key] = value name = os_vars.get("NAME") if name: name = name.lower() name = re.sub("linux", "", name) name = name.strip().replace(" ", "_") version_id = os_vars.get("VERSION_ID") if version_id: version_id = version_id.lower() return "linux", name, version_id # Ideally we'd use a common library like `psutil` to read system information, # but getdeps can't take third-party dependencies. def _get_available_ram_linux() -> int: # TODO: Ideally, this function would inspect the current cgroup for any # limits, rather than solely relying on system RAM. meminfo_path = "/proc/meminfo" try: with open(meminfo_path) as f: for line in f: try: key, value = line.split(":", 1) except ValueError: continue suffix = " kB\n" if key == "MemAvailable" and value.endswith(suffix): value = value[: -len(suffix)] try: return int(value) // 1024 except ValueError: continue except OSError: print("error opening {}".format(meminfo_path), end="", file=sys.stderr) else: print( "{} had no valid MemAvailable".format(meminfo_path), end="", file=sys.stderr ) guess = 8 print(", guessing {} GiB".format(guess), file=sys.stderr) return guess * 1024 def _get_available_ram_macos() -> int: import ctypes.util libc = ctypes.CDLL(ctypes.util.find_library("libc"), use_errno=True) sysctlbyname = libc.sysctlbyname sysctlbyname.restype = ctypes.c_int sysctlbyname.argtypes = [ ctypes.c_char_p, ctypes.c_void_p, ctypes.POINTER(ctypes.c_size_t), ctypes.c_void_p, ctypes.c_size_t, ] # TODO: There may be some way to approximate an availability # metric, but just use total RAM for now. memsize = ctypes.c_int64() memsizesize = ctypes.c_size_t(8) res = sysctlbyname( b"hw.memsize", ctypes.byref(memsize), ctypes.byref(memsizesize), None, 0 ) if res != 0: raise NotImplementedError( f"failed to retrieve hw.memsize sysctl: {ctypes.get_errno()}" ) return memsize.value // (1024 * 1024) def _get_available_ram_windows() -> int: import ctypes DWORD = ctypes.c_uint32 QWORD = ctypes.c_uint64 class MEMORYSTATUSEX(ctypes.Structure): _fields_ = [ ("dwLength", DWORD), ("dwMemoryLoad", DWORD), ("ullTotalPhys", QWORD), ("ullAvailPhys", QWORD), ("ullTotalPageFile", QWORD), ("ullAvailPageFile", QWORD), ("ullTotalVirtual", QWORD), ("ullAvailVirtual", QWORD), ("ullExtendedVirtual", QWORD), ] ms = MEMORYSTATUSEX() ms.dwLength = ctypes.sizeof(ms) # pyre-ignore[16] res = ctypes.windll.kernel32.GlobalMemoryStatusEx(ctypes.byref(ms)) if res == 0: raise NotImplementedError("error calling GlobalMemoryStatusEx") # This is fuzzy, but AvailPhys is too conservative, and AvailTotal is too # aggressive, so average the two. It's okay for builds to use some swap. return (ms.ullAvailPhys + ms.ullTotalPhys) // (2 * 1024 * 1024) def _get_available_ram_freebsd() -> int: import ctypes.util libc = ctypes.CDLL(ctypes.util.find_library("libc"), use_errno=True) sysctlbyname = libc.sysctlbyname sysctlbyname.restype = ctypes.c_int sysctlbyname.argtypes = [ ctypes.c_char_p, ctypes.c_void_p, ctypes.POINTER(ctypes.c_size_t), ctypes.c_void_p, ctypes.c_size_t, ] # hw.usermem is pretty close to what we want. memsize = ctypes.c_int64() memsizesize = ctypes.c_size_t(8) res = sysctlbyname( b"hw.usermem", ctypes.byref(memsize), ctypes.byref(memsizesize), None, 0 ) if res != 0: raise NotImplementedError( f"failed to retrieve hw.memsize sysctl: {ctypes.get_errno()}" ) return memsize.value // (1024 * 1024) def get_available_ram() -> int: """ Returns a platform-appropriate available RAM metric in MiB. """ if sys.platform == "linux": return _get_available_ram_linux() elif sys.platform == "darwin": return _get_available_ram_macos() elif sys.platform == "win32": return _get_available_ram_windows() elif sys.platform.startswith("freebsd"): return _get_available_ram_freebsd() else: raise NotImplementedError( f"platform {sys.platform} does not have an implementation of get_available_ram" ) def is_current_host_arm() -> bool: if sys.platform.startswith("darwin"): # platform.machine() can be fooled by rosetta for python < 3.9.2 return "ARM64" in os.uname().version else: machine = platform.machine().lower() return "arm" in machine or "aarch" in machine class HostType: def __init__( self, ostype: str | None = None, distro: str | None = None, distrovers: str | None = None, ) -> None: # Maybe we should allow callers to indicate whether this machine uses # an ARM architecture, but we need to change HostType serialization # and deserialization in that case and hunt down anywhere that is # persisting that serialized data. isarm = False if ostype is None: distro = None distrovers = None if sys.platform.startswith("linux"): ostype, distro, distrovers = get_linux_type() elif sys.platform.startswith("darwin"): ostype = "darwin" elif is_windows(): ostype = "windows" distrovers = str(sys.getwindowsversion().major) elif sys.platform.startswith("freebsd"): ostype = "freebsd" else: ostype = sys.platform isarm = is_current_host_arm() # The operating system type self.ostype: str | None = ostype # The distribution, if applicable self.distro: str | None = distro # The OS/distro version if known self.distrovers: str | None = distrovers # Does the CPU use an ARM architecture? ARM includes Apple Silicon # Macs as well as other ARM systems that might be running Linux or # something. self.isarm: bool = isarm def is_windows(self) -> bool: return self.ostype == "windows" # is_arm is kinda half implemented at the moment. This method is only # intended to be used when HostType represents information about the # current machine we are running on. # When HostType is being used to enumerate platform types (represent # information about machine types that we may or may not be running on) # the result could be nonsense (under the current implementation its always # false.) def is_arm(self) -> bool: return self.isarm def is_darwin(self) -> bool: return self.ostype == "darwin" def is_linux(self) -> bool: return self.ostype == "linux" def is_freebsd(self) -> bool: return self.ostype == "freebsd" def as_tuple_string(self) -> str: return "%s-%s-%s" % ( self.ostype, self.distro or "none", self.distrovers or "none", ) def get_package_manager(self) -> str | None: if not self.is_linux() and not self.is_darwin(): return None if self.is_darwin(): return "homebrew" if self.distro in ("fedora", "centos", "centos_stream", "rocky"): return "rpm" if self.distro is not None and self.distro.startswith( ("debian", "ubuntu", "pop!_os", "mint") ): return "deb" if self.distro == "arch": return "pacman-package" return None @staticmethod def from_tuple_string(s: str) -> HostType: ostype, distro, distrovers = s.split("-") return HostType(ostype=ostype, distro=distro, distrovers=distrovers) def __eq__(self, b: object) -> bool: if not isinstance(b, HostType): return False return ( self.ostype == b.ostype and self.distro == b.distro and self.distrovers == b.distrovers ) ================================================ FILE: build/fbcode_builder/getdeps/py_wheel_builder.py ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. # pyre-strict from __future__ import annotations import codecs import collections import email import email.message import os import re import stat from .builder import BuilderBase, CMakeBuilder WheelNameInfo = collections.namedtuple( "WheelNameInfo", ("distribution", "version", "build", "python", "abi", "platform") ) CMAKE_HEADER = """ cmake_minimum_required(VERSION 3.8) project("{manifest_name}" LANGUAGES C) set(CMAKE_MODULE_PATH "{cmake_dir}" ${{CMAKE_MODULE_PATH}} ) include(FBPythonBinary) set(CMAKE_INSTALL_DIR lib/cmake/{manifest_name} CACHE STRING "The subdirectory where CMake package config files should be installed") """ CMAKE_FOOTER = """ install_fb_python_library({lib_name} EXPORT all) install( EXPORT all FILE {manifest_name}-targets.cmake NAMESPACE {namespace}:: DESTINATION ${{CMAKE_INSTALL_DIR}} ) include(CMakePackageConfigHelpers) configure_package_config_file( ${{CMAKE_BINARY_DIR}}/{manifest_name}-config.cmake.in {manifest_name}-config.cmake INSTALL_DESTINATION ${{CMAKE_INSTALL_DIR}} PATH_VARS CMAKE_INSTALL_DIR ) install( FILES ${{CMAKE_CURRENT_BINARY_DIR}}/{manifest_name}-config.cmake DESTINATION ${{CMAKE_INSTALL_DIR}} ) """ CMAKE_CONFIG_FILE = """ @PACKAGE_INIT@ include(CMakeFindDependencyMacro) set_and_check({upper_name}_CMAKE_DIR "@PACKAGE_CMAKE_INSTALL_DIR@") if (NOT TARGET {namespace}::{lib_name}) include("${{{upper_name}_CMAKE_DIR}}/{manifest_name}-targets.cmake") endif() set({upper_name}_LIBRARIES {namespace}::{lib_name}) {find_dependency_lines} if (NOT {manifest_name}_FIND_QUIETLY) message(STATUS "Found {manifest_name}: ${{PACKAGE_PREFIX_DIR}}") endif() """ # Note: for now we are manually manipulating the wheel packet contents. # The wheel format is documented here: # https://www.python.org/dev/peps/pep-0491/#file-format # # We currently aren't particularly smart about correctly handling the full wheel # functionality, but this is good enough to handle simple pure-python wheels, # which is the main thing we care about right now. # # We could potentially use pip to install the wheel to a temporary location and # then copy its "installed" files, but this has its own set of complications. # This would require pip to already be installed and available, and we would # need to correctly find the right version of pip or pip3 to use. # If we did ever want to go down that path, we would probably want to use # something like the following pip3 command: # pip3 --isolated install --no-cache-dir --no-index --system \ # --target class PythonWheelBuilder(BuilderBase): """This Builder can take Python wheel archives and install them as python libraries that can be used by add_fb_python_library()/add_fb_python_executable() CMake rules. """ # pyre-fixme[13]: Attribute `dist_info_dir` is never initialized. dist_info_dir: str # pyre-fixme[13]: Attribute `template_format_dict` is never initialized. template_format_dict: dict[str, str] def _build(self, reconfigure: bool) -> None: # When we are invoked, self.src_dir contains the unpacked wheel contents. # # Since a wheel file is just a zip file, the Fetcher code recognizes it as such # and goes ahead and unpacks it. (We could disable that Fetcher behavior in the # future if we ever wanted to, say if we wanted to call pip here.) wheel_name = self._parse_wheel_name() name_version_prefix = "-".join((wheel_name.distribution, wheel_name.version)) dist_info_name = name_version_prefix + ".dist-info" data_dir_name = name_version_prefix + ".data" self.dist_info_dir = os.path.join(self.src_dir, dist_info_name) wheel_metadata = self._read_wheel_metadata(wheel_name) # Check that we can understand the wheel version. # We don't really care about wheel_metadata["Root-Is-Purelib"] since # we are generating our own standalone python archives rather than installing # into site-packages. version = wheel_metadata["Wheel-Version"] if not version.startswith("1."): raise Exception("unsupported wheel version %s" % (version,)) # Add a find_dependency() call for each of our dependencies. # The dependencies are also listed in the wheel METADATA file, but it is simpler # to pull this directly from the getdeps manifest. dep_list = sorted( self.manifest.get_section_as_dict("dependencies", self.ctx).keys() ) find_dependency_lines = ["find_dependency({})".format(dep) for dep in dep_list] getdeps_cmake_dir = os.path.join( os.path.dirname(os.path.dirname(__file__)), "CMake" ) self.template_format_dict = { # Note that CMake files always uses forward slash separators in path names, # even on Windows. Therefore replace path separators here. "cmake_dir": _to_cmake_path(getdeps_cmake_dir), "lib_name": self.manifest.name, "manifest_name": self.manifest.name, "namespace": self.manifest.name, "upper_name": self.manifest.name.upper().replace("-", "_"), "find_dependency_lines": "\n".join(find_dependency_lines), } # Find sources from the root directory path_mapping: dict[str, str] = {} for entry in os.listdir(self.src_dir): if entry == data_dir_name: continue self._add_sources(path_mapping, os.path.join(self.src_dir, entry), entry) # Files under the .data directory also need to be installed in the correct # locations if os.path.exists(data_dir_name): # TODO: process the subdirectories of data_dir_name # This isn't implemented yet since for now we have only needed dependencies # on some simple pure Python wheels, so I haven't tested against wheels with # additional files in the .data directory. raise Exception( "handling of the subdirectories inside %s is not implemented yet" % data_dir_name ) # Emit CMake files self._write_cmakelists(path_mapping, dep_list) self._write_cmake_config_template() # Run the build self._run_cmake_build(reconfigure) def _run_cmake_build(self, reconfigure: bool) -> None: cmake_builder = CMakeBuilder( loader=self.loader, dep_manifests=self.dep_manifests, build_opts=self.build_opts, ctx=self.ctx, manifest=self.manifest, # Note that we intentionally supply src_dir=build_dir, # since we wrote out our generated CMakeLists.txt in the build directory src_dir=self.build_dir, build_dir=self.build_dir, inst_dir=self.inst_dir, defines={}, final_install_prefix=None, ) cmake_builder.build(reconfigure=reconfigure) def _write_cmakelists( self, path_mapping: dict[str, str], dependencies: list[str] ) -> None: cmake_path = os.path.join(self.build_dir, "CMakeLists.txt") with open(cmake_path, "w") as f: f.write(CMAKE_HEADER.format(**self.template_format_dict)) for dep in dependencies: f.write("find_package({0} REQUIRED)\n".format(dep)) f.write( "add_fb_python_library({lib_name}\n".format(**self.template_format_dict) ) f.write(' BASE_DIR "%s"\n' % _to_cmake_path(self.src_dir)) f.write(" SOURCES\n") for src_path, install_path in path_mapping.items(): f.write( ' "%s=%s"\n' % (_to_cmake_path(src_path), _to_cmake_path(install_path)) ) if dependencies: f.write(" DEPENDS\n") for dep in dependencies: f.write(' "{0}::{0}"\n'.format(dep)) f.write(")\n") f.write(CMAKE_FOOTER.format(**self.template_format_dict)) def _write_cmake_config_template(self) -> None: config_path_name = self.manifest.name + "-config.cmake.in" output_path = os.path.join(self.build_dir, config_path_name) with open(output_path, "w") as f: f.write(CMAKE_CONFIG_FILE.format(**self.template_format_dict)) def _add_sources( self, path_mapping: dict[str, str], src_path: str, install_path: str ) -> None: s = os.lstat(src_path) if not stat.S_ISDIR(s.st_mode): path_mapping[src_path] = install_path return for entry in os.listdir(src_path): self._add_sources( path_mapping, os.path.join(src_path, entry), os.path.join(install_path, entry), ) def _parse_wheel_name(self) -> WheelNameInfo: # The ArchiveFetcher prepends "manifest_name-", so strip that off first. wheel_name = os.path.basename(self.src_dir) prefix = self.manifest.name + "-" if not wheel_name.startswith(prefix): raise Exception( "expected wheel source directory to be of the form %s-NAME.whl" % (prefix,) ) wheel_name = wheel_name[len(prefix) :] wheel_name_re = re.compile( r"(?P[^-]+)" r"-(?P\d+[^-]*)" r"(-(?P\d+[^-]*))?" r"-(?P\w+\d+(\.\w+\d+)*)" r"-(?P\w+)" r"-(?P\w+(\.\w+)*)" r"\.whl" ) match = wheel_name_re.match(wheel_name) if not match: raise Exception( "bad python wheel name %s: expected to have the form " "DISTRIBUTION-VERSION-[-BUILD]-PYTAG-ABI-PLATFORM" ) return WheelNameInfo( distribution=match.group("distribution"), version=match.group("version"), build=match.group("build"), python=match.group("python"), abi=match.group("abi"), platform=match.group("platform"), ) # pyre-fixme[24]: Generic type `email.message.Message` expects 2 type parameters. def _read_wheel_metadata(self, wheel_name: WheelNameInfo) -> email.message.Message: metadata_path = os.path.join(self.dist_info_dir, "WHEEL") with codecs.open(metadata_path, "r", encoding="utf-8") as f: return email.message_from_file(f) def _to_cmake_path(path: str) -> str: # CMake always uses forward slashes to separate paths in CMakeLists.txt files, # even on Windows. It treats backslashes as character escapes, so using # backslashes in the path will cause problems. Therefore replace all path # separators with forward slashes to make sure the paths are correct on Windows. # e.g. "C:\foo\bar.txt" becomes "C:/foo/bar.txt" return path.replace(os.path.sep, "/") ================================================ FILE: build/fbcode_builder/getdeps/runcmd.py ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. # pyre-strict from __future__ import annotations import os import select import subprocess import sys from collections.abc import Callable from shlex import quote as shellquote from .envfuncs import Env from .platform import is_windows class RunCommandError(Exception): pass def make_memory_limit_preexec_fn( job_weight_mib: int, ) -> Callable[[], None] | None: """Create a preexec_fn that sets a per-process virtual memory limit. When getdeps spawns build commands (cmake -> ninja -> N compiler processes), the parallelism is computed from available RAM divided by job_weight_mib. However, there is no enforcement of that budget: if a compiler or linker process exceeds its expected memory usage, the system can run out of RAM and the Linux OOM killer may terminate arbitrary processes — including the user's shell or terminal. This function returns a callable suitable for subprocess.Popen's preexec_fn parameter. It runs in each child process after fork() but before exec(), setting RLIMIT_AS (virtual address space limit) so that a runaway process gets a failed allocation (std::bad_alloc / ENOMEM) instead of triggering the OOM killer. The limit is inherited by all descendant processes (ninja, compiler invocations, etc.). The per-process limit is set to job_weight_mib * 10. The 10x multiplier accounts for the fact that RLIMIT_AS caps virtual address space, which is typically 2-4x larger than resident (physical) memory for C++ compilers due to memory-mapped files, shared libraries, and address space reservations that don't consume physical RAM. The multiplier is intentionally generous: the goal is a safety net that catches genuine runaways before the OOM killer fires, not a tight per-job budget. Only applies on Linux, where the OOM killer is the problem. Returns None on other platforms. """ if sys.platform != "linux": return None # Each job is budgeted job_weight_mib of physical RAM. Virtual address # space is typically 2-4x RSS. Use 10x as a generous safety net: tight # enough to stop a runaway process before the OOM killer fires, but loose # enough to avoid false positives from normal virtual memory overhead. limit_bytes: int = job_weight_mib * 10 * 1024 * 1024 def _set_memory_limit() -> None: import resource resource.setrlimit(resource.RLIMIT_AS, (limit_bytes, limit_bytes)) return _set_memory_limit def _print_env_diff(env: Env, log_fn: Callable[[str], None]) -> None: current_keys = set(os.environ.keys()) wanted_env = set(env.keys()) unset_keys = current_keys.difference(wanted_env) for k in sorted(unset_keys): log_fn("+ unset %s\n" % k) added_keys = wanted_env.difference(current_keys) for k in wanted_env.intersection(current_keys): if os.environ[k] != env[k]: added_keys.add(k) for k in sorted(added_keys): if ("PATH" in k) and (os.pathsep in env[k]): log_fn("+ %s=\\\n" % k) for elem in env[k].split(os.pathsep): log_fn("+ %s%s\\\n" % (shellquote(elem), os.pathsep)) else: log_fn("+ %s=%s \\\n" % (k, shellquote(env[k]))) def check_cmd( cmd: list[str], env: Env | None = None, cwd: str | None = None, allow_fail: bool = False, log_file: str | None = None, ) -> None: """Run the command and abort on failure""" rc = run_cmd(cmd, env=env, cwd=cwd, allow_fail=allow_fail, log_file=log_file) if rc != 0: raise RuntimeError(f"Failure exit code {rc} for command {cmd}") def run_cmd( cmd: list[str], env: Env | None = None, cwd: str | None = None, allow_fail: bool = False, log_file: str | None = None, preexec_fn: Callable[[], None] | None = None, ) -> int: def log_to_stdout(msg: str) -> None: sys.stdout.buffer.write(msg.encode(errors="surrogateescape")) if log_file is not None: with open(log_file, "a", encoding="utf-8", errors="surrogateescape") as log: # pyre-fixme[53]: Captured variable `log` is not annotated. def log_function(msg: str) -> None: log.write(msg) log_to_stdout(msg) return _run_cmd( cmd, env=env, cwd=cwd, allow_fail=allow_fail, log_fn=log_function, preexec_fn=preexec_fn, ) else: return _run_cmd( cmd, env=env, cwd=cwd, allow_fail=allow_fail, log_fn=log_to_stdout, preexec_fn=preexec_fn, ) def _run_cmd( cmd: list[str], env: Env | None, cwd: str | None, allow_fail: bool, log_fn: Callable[[str], None], preexec_fn: Callable[[], None] | None = None, ) -> int: log_fn("---\n") try: cmd_str = " \\\n+ ".join(shellquote(arg) for arg in cmd) except TypeError: # eg: one of the elements is None raise RunCommandError("problem quoting cmd: %r" % cmd) if env: assert isinstance(env, Env) _print_env_diff(env, log_fn) # Convert from our Env type to a regular dict. # This is needed because python3 looks up b'PATH' and 'PATH' # and emits an error if both are present. In our Env type # we'll return the same value for both requests, but we don't # have duplicate potentially conflicting values which is the # spirit of the check. env_dict: dict[str, str] | None = dict(env.items()) else: env_dict = None if cwd: log_fn("+ cd %s && \\\n" % shellquote(cwd)) # Our long path escape sequence may confuse cmd.exe, so if the cwd # is short enough, strip that off. if is_windows() and (len(cwd) < 250) and cwd.startswith("\\\\?\\"): cwd = cwd[4:] log_fn("+ %s\n" % cmd_str) isinteractive = os.isatty(sys.stdout.fileno()) if isinteractive: stdout = None sys.stdout.buffer.flush() else: stdout = subprocess.PIPE try: p = subprocess.Popen( cmd, env=env_dict, cwd=cwd, stdout=stdout, stderr=subprocess.STDOUT, preexec_fn=preexec_fn, ) except (TypeError, ValueError, OSError) as exc: log_fn("error running `%s`: %s" % (cmd_str, exc)) raise RunCommandError( "%s while running `%s` with env=%r\nos.environ=%r" % (str(exc), cmd_str, env_dict, os.environ) ) if not isinteractive: _pipe_output(p, log_fn) p.wait() if p.returncode != 0 and not allow_fail: raise subprocess.CalledProcessError(p.returncode, cmd) return p.returncode if hasattr(select, "poll"): def _pipe_output(p: subprocess.Popen[bytes], log_fn: Callable[[str], None]) -> None: """Read output from p.stdout and call log_fn() with each chunk of data as it becomes available.""" # Perform non-blocking reads import fcntl assert p.stdout is not None fcntl.fcntl(p.stdout.fileno(), fcntl.F_SETFL, os.O_NONBLOCK) poll = select.poll() poll.register(p.stdout.fileno(), select.POLLIN) buffer_size = 4096 while True: poll.poll() data = p.stdout.read(buffer_size) if not data: break # log_fn() accepts arguments as str (binary in Python 2, unicode in # Python 3). In Python 3 the subprocess output will be plain bytes, # and need to be decoded. if not isinstance(data, str): data = data.decode("utf-8", errors="surrogateescape") log_fn(data) else: def _pipe_output(p: subprocess.Popen[bytes], log_fn: Callable[[str], None]) -> None: """Read output from p.stdout and call log_fn() with each chunk of data as it becomes available.""" # Perform blocking reads. Use a smaller buffer size to avoid blocking # for very long when data is available. assert p.stdout is not None buffer_size = 64 while True: # pyre-fixme[16]: Optional type has no attribute `read`. data = p.stdout.read(buffer_size) if not data: break # log_fn() accepts arguments as str (binary in Python 2, unicode in # Python 3). In Python 3 the subprocess output will be plain bytes, # and need to be decoded. if not isinstance(data, str): data = data.decode("utf-8", errors="surrogateescape") log_fn(data) ================================================ FILE: build/fbcode_builder/getdeps/subcmd.py ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. # pyre-strict from __future__ import annotations import argparse from collections.abc import Callable class SubCmd: NAME: str | None = None HELP: str | None = None def run(self, args: argparse.Namespace) -> int: """perform the command""" return 0 def setup_parser(self, parser: argparse.ArgumentParser) -> None: # Subclasses should override setup_parser() if they have any # command line options or arguments. pass CmdTable: list[type[SubCmd]] = [] def add_subcommands( parser: argparse._SubParsersAction[argparse.ArgumentParser], common_args: argparse.ArgumentParser, cmd_table: list[type[SubCmd]] = CmdTable, ) -> None: """Register parsers for the defined commands with the provided parser""" for cls in cmd_table: command = cls() command_parser = parser.add_parser( # pyre-fixme[6]: For 1st argument expected `str` but got `Optional[str]`. command.NAME, help=command.HELP, parents=[common_args], ) command.setup_parser(command_parser) command_parser.set_defaults(func=command.run) def cmd( name: str, help: str | None = None, cmd_table: list[type[SubCmd]] = CmdTable, ) -> Callable[[type[SubCmd]], type[SubCmd]]: """ @cmd() is a decorator that can be used to help define Subcmd instances Example usage: @subcmd('list', 'Show the result list') class ListCmd(Subcmd): def run(self, args): # Perform the command actions here... pass """ def wrapper(cls: type[SubCmd]) -> type[SubCmd]: class SubclassedCmd(cls): NAME = name HELP = help # pyre-fixme[6]: For 1st argument expected `Type[SubCmd]` but got # `Type[SubclassedCmd]`. # pyre-fixme[16]: Callable `cmd` has no attribute `wrapper`. cmd_table.append(SubclassedCmd) # pyre-fixme[7]: Expected `Type[SubCmd]` but got `Type[SubclassedCmd]`. return SubclassedCmd return wrapper ================================================ FILE: build/fbcode_builder/getdeps/test/expr_test.py ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. # pyre-unsafe import unittest from ..expr import parse_expr class ExprTest(unittest.TestCase): def test_equal(self) -> None: valid_variables = {"foo", "some_var", "another_var"} e = parse_expr("foo=bar", valid_variables) self.assertTrue(e.eval({"foo": "bar"})) self.assertFalse(e.eval({"foo": "not-bar"})) self.assertFalse(e.eval({"not-foo": "bar"})) def test_not_equal(self) -> None: valid_variables = {"foo"} e = parse_expr("not(foo=bar)", valid_variables) self.assertFalse(e.eval({"foo": "bar"})) self.assertTrue(e.eval({"foo": "not-bar"})) def test_bad_not(self) -> None: valid_variables = {"foo"} with self.assertRaises(Exception): parse_expr("foo=not(bar)", valid_variables) def test_bad_variable(self) -> None: valid_variables = {"bar"} with self.assertRaises(Exception): parse_expr("foo=bar", valid_variables) def test_all(self) -> None: valid_variables = {"foo", "baz"} e = parse_expr("all(foo = bar, baz = qux)", valid_variables) self.assertTrue(e.eval({"foo": "bar", "baz": "qux"})) self.assertFalse(e.eval({"foo": "bar", "baz": "nope"})) self.assertFalse(e.eval({"foo": "nope", "baz": "nope"})) def test_any(self) -> None: valid_variables = {"foo", "baz"} e = parse_expr("any(foo = bar, baz = qux)", valid_variables) self.assertTrue(e.eval({"foo": "bar", "baz": "qux"})) self.assertTrue(e.eval({"foo": "bar", "baz": "nope"})) self.assertFalse(e.eval({"foo": "nope", "baz": "nope"})) ================================================ FILE: build/fbcode_builder/getdeps/test/fixtures/duplicate/foo ================================================ [manifest] name = foo ================================================ FILE: build/fbcode_builder/getdeps/test/fixtures/duplicate/subdir/foo ================================================ [manifest] name = foo ================================================ FILE: build/fbcode_builder/getdeps/test/manifest_test.py ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. # pyre-unsafe import sys import unittest from ..load import load_all_manifests, patch_loader from ..manifest import ManifestParser class ManifestTest(unittest.TestCase): def test_missing_section(self) -> None: with self.assertRaisesRegex( Exception, "manifest file test is missing required section manifest" ): ManifestParser("test", "") def test_missing_name(self) -> None: with self.assertRaisesRegex( Exception, "manifest file test section 'manifest' is missing required field 'name'", ): ManifestParser( "test", """ [manifest] """, ) def test_minimal(self) -> None: p = ManifestParser( "test", """ [manifest] name = test """, ) self.assertEqual(p.name, "test") self.assertEqual(p.fbsource_path, None) def test_minimal_with_fbsource_path(self) -> None: p = ManifestParser( "test", """ [manifest] name = test fbsource_path = fbcode/wat """, ) self.assertEqual(p.name, "test") self.assertEqual(p.fbsource_path, "fbcode/wat") def test_unknown_field(self) -> None: with self.assertRaisesRegex( Exception, ( "manifest file test section 'manifest' contains " "unknown field 'invalid.field'" ), ): ManifestParser( "test", """ [manifest] name = test invalid.field = woot """, ) def test_invalid_section_name(self) -> None: with self.assertRaisesRegex( Exception, "manifest file test contains unknown section 'invalid.section'" ): ManifestParser( "test", """ [manifest] name = test [invalid.section] foo = bar """, ) def test_value_in_dependencies_section(self) -> None: with self.assertRaisesRegex( Exception, ( "manifest file test section 'dependencies' has " "'foo = bar' but this section doesn't allow " "specifying values for its entries" ), ): ManifestParser( "test", """ [manifest] name = test [dependencies] foo = bar """, ) def test_invalid_conditional_section_name(self) -> None: with self.assertRaisesRegex( Exception, ( "manifest file test section 'dependencies.=' " "has invalid conditional: expected " "identifier found =" ), ): ManifestParser( "test", """ [manifest] name = test [dependencies.=] """, ) def test_section_as_args(self) -> None: p = ManifestParser( "test", """ [manifest] name = test [dependencies] a b c [dependencies.test=on] foo """, ) self.assertEqual(p.get_section_as_args("dependencies"), ["a", "b", "c"]) self.assertEqual( p.get_section_as_args("dependencies", {"test": "off"}), ["a", "b", "c"] ) self.assertEqual( p.get_section_as_args("dependencies", {"test": "on"}), ["a", "b", "c", "foo"], ) p2 = ManifestParser( "test", """ [manifest] name = test [autoconf.args] --prefix=/foo --with-woot """, ) self.assertEqual( p2.get_section_as_args("autoconf.args"), ["--prefix=/foo", "--with-woot"] ) def test_section_as_dict(self) -> None: p = ManifestParser( "test", """ [manifest] name = test [cmake.defines] foo = bar [cmake.defines.test=on] foo = baz """, ) self.assertEqual(p.get_section_as_dict("cmake.defines", {}), {"foo": "bar"}) self.assertEqual( p.get_section_as_dict("cmake.defines", {"test": "on"}), {"foo": "baz"} ) p2 = ManifestParser( "test", """ [manifest] name = test [cmake.defines.test=on] foo = baz [cmake.defines] foo = bar """, ) self.assertEqual( p2.get_section_as_dict("cmake.defines", {"test": "on"}), {"foo": "bar"}, msg="sections cascade in the order they appear in the manifest", ) def test_parse_common_manifests(self) -> None: patch_loader(__name__) # pyre-fixme[6]: For 1st argument expected `BuildOptions` but got `None`. manifests = load_all_manifests(None) self.assertNotEqual(0, len(manifests), msg="parsed some number of manifests") def test_mismatch_name(self) -> None: with self.assertRaisesRegex( Exception, "filename of the manifest 'foo' does not match the manifest name 'bar'", ): ManifestParser( "foo", """ [manifest] name = bar """, ) def test_duplicate_manifest(self) -> None: patch_loader(__name__, "fixtures/duplicate") with self.assertRaisesRegex(Exception, "found duplicate manifest 'foo'"): # pyre-fixme[6]: For 1st argument expected `BuildOptions` but got `None`. load_all_manifests(None) if sys.version_info < (3, 2): def assertRaisesRegex(self, *args, **kwargs): return self.assertRaisesRegex(*args, **kwargs) ================================================ FILE: build/fbcode_builder/getdeps/test/platform_test.py ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. # pyre-unsafe import unittest from ..platform import HostType class PlatformTest(unittest.TestCase): def test_create(self) -> None: p = HostType() self.assertNotEqual(p.ostype, None, msg="probed and returned something") tuple_string = p.as_tuple_string() round_trip = HostType.from_tuple_string(tuple_string) self.assertEqual(round_trip, p) def test_rendering_of_none(self) -> None: p = HostType(ostype="foo") self.assertEqual(p.as_tuple_string(), "foo-none-none") def test_is_methods(self) -> None: p = HostType(ostype="windows") self.assertTrue(p.is_windows()) self.assertFalse(p.is_darwin()) self.assertFalse(p.is_linux()) p = HostType(ostype="darwin") self.assertFalse(p.is_windows()) self.assertTrue(p.is_darwin()) self.assertFalse(p.is_linux()) p = HostType(ostype="linux") self.assertFalse(p.is_windows()) self.assertFalse(p.is_darwin()) self.assertTrue(p.is_linux()) ================================================ FILE: build/fbcode_builder/getdeps/test/retry_test.py ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. # pyre-unsafe import unittest from unittest.mock import call, MagicMock, patch from ..buildopts import BuildOptions from ..errors import TransientFailure from ..fetcher import ArchiveFetcher from ..manifest import ManifestParser class RetryTest(unittest.TestCase): def _get_build_opts(self) -> BuildOptions: mock_build_opts = MagicMock(spec=BuildOptions) mock_build_opts.scratch_dir = "/path/to/scratch_dir" return mock_build_opts def _get_manifest(self) -> ManifestParser: mock_manifest_parser = MagicMock(spec=ManifestParser) mock_manifest_parser.name = "mock_manifest_parser" return mock_manifest_parser def _get_archive_fetcher(self) -> ArchiveFetcher: return ArchiveFetcher( build_options=self._get_build_opts(), manifest=self._get_manifest(), url="https://github.com/systemd/systemd/archive/refs/tags/v256.7.tar.gz", sha256="896d76ff65c88f5fd9e42f90d152b0579049158a163431dd77cdc57748b1d7b0", ) @patch("os.makedirs") @patch("os.environ.get") @patch("time.sleep") @patch("subprocess.run") def test_no_retries( self, mock_run, mock_sleep, mock_os_environ_get, mock_makedirs ) -> None: def custom_makedirs(path, exist_ok=False): return None def custom_get(key, default=None): if key == "GETDEPS_USE_WGET": return "1" elif key == "GETDEPS_WGET_ARGS": return "" else: return None mock_makedirs.side_effect = custom_makedirs mock_os_environ_get.side_effect = custom_get mock_sleep.side_effect = None fetcher = self._get_archive_fetcher() fetcher._verify_hash = MagicMock(return_value=None) fetcher._download() mock_sleep.assert_has_calls([], any_order=False) mock_run.assert_called_once_with( [ "wget", "-O", "/path/to/scratch_dir/downloads/mock_manifest_parser-v256.7.tar.gz", "https://github.com/systemd/systemd/archive/refs/tags/v256.7.tar.gz", ], capture_output=True, ) @patch("random.random") @patch("os.makedirs") @patch("os.environ.get") @patch("time.sleep") @patch("subprocess.run") def test_retries( self, mock_run, mock_sleep, mock_os_environ_get, mock_makedirs, mock_random ) -> None: def custom_makedirs(path, exist_ok=False): return None def custom_get(key, default=None): if key == "GETDEPS_USE_WGET": return "1" elif key == "GETDEPS_WGET_ARGS": return "" else: return None mock_random.return_value = 0 mock_run.side_effect = [ IOError(""), IOError(""), None, ] mock_makedirs.side_effect = custom_makedirs mock_os_environ_get.side_effect = custom_get mock_sleep.side_effect = None fetcher = self._get_archive_fetcher() fetcher._verify_hash = MagicMock(return_value=None) fetcher._download() mock_sleep.assert_has_calls([call(2), call(4)], any_order=False) calls = [ call( [ "wget", "-O", "/path/to/scratch_dir/downloads/mock_manifest_parser-v256.7.tar.gz", "https://github.com/systemd/systemd/archive/refs/tags/v256.7.tar.gz", ], capture_output=True, ), ] * 3 mock_run.assert_has_calls(calls, any_order=False) @patch("random.random") @patch("os.makedirs") @patch("os.environ.get") @patch("time.sleep") @patch("subprocess.run") def test_all_retries( self, mock_run, mock_sleep, mock_os_environ_get, mock_makedirs, mock_random ) -> None: def custom_makedirs(path, exist_ok=False): return None def custom_get(key, default=None): if key == "GETDEPS_USE_WGET": return "1" elif key == "GETDEPS_WGET_ARGS": return "" else: return None mock_random.return_value = 0 mock_run.side_effect = IOError( "" ) mock_makedirs.side_effect = custom_makedirs mock_os_environ_get.side_effect = custom_get mock_sleep.side_effect = None fetcher = self._get_archive_fetcher() fetcher._verify_hash = MagicMock(return_value=None) with self.assertRaises(TransientFailure): fetcher._download() mock_sleep.assert_has_calls( [call(2), call(4), call(8), call(10)], any_order=False ) calls = [ call( [ "wget", "-O", "/path/to/scratch_dir/downloads/mock_manifest_parser-v256.7.tar.gz", "https://github.com/systemd/systemd/archive/refs/tags/v256.7.tar.gz", ], capture_output=True, ), ] * 5 mock_run.assert_has_calls(calls, any_order=False) ================================================ FILE: build/fbcode_builder/getdeps/test/scratch_test.py ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. # pyre-unsafe import unittest from ..buildopts import find_existing_win32_subst_for_path class Win32SubstTest(unittest.TestCase): def test_no_existing_subst(self) -> None: self.assertIsNone( find_existing_win32_subst_for_path( r"C:\users\alice\appdata\local\temp\fbcode_builder_getdeps", subst_mapping={}, ) ) self.assertIsNone( find_existing_win32_subst_for_path( r"C:\users\alice\appdata\local\temp\fbcode_builder_getdeps", subst_mapping={"X:\\": r"C:\users\alice\appdata\local\temp\other"}, ) ) def test_exact_match_returns_drive_path(self) -> None: self.assertEqual( find_existing_win32_subst_for_path( r"C:\temp\fbcode_builder_getdeps", subst_mapping={"X:\\": r"C:\temp\fbcode_builder_getdeps"}, ), "X:\\", ) self.assertEqual( find_existing_win32_subst_for_path( r"C:/temp/fbcode_builder_getdeps", subst_mapping={"X:\\": r"C:/temp/fbcode_builder_getdeps"}, ), "X:\\", ) def test_multiple_exact_matches_returns_arbitrary_drive_path(self) -> None: self.assertIn( find_existing_win32_subst_for_path( r"C:\temp\fbcode_builder_getdeps", subst_mapping={ "X:\\": r"C:\temp\fbcode_builder_getdeps", "Y:\\": r"C:\temp\fbcode_builder_getdeps", "Z:\\": r"C:\temp\fbcode_builder_getdeps", }, ), ("X:\\", "Y:\\", "Z:\\"), ) def test_drive_letter_is_case_insensitive(self) -> None: self.assertEqual( find_existing_win32_subst_for_path( r"C:\temp\fbcode_builder_getdeps", subst_mapping={"X:\\": r"c:\temp\fbcode_builder_getdeps"}, ), "X:\\", ) def test_path_components_are_case_insensitive(self) -> None: self.assertEqual( find_existing_win32_subst_for_path( r"C:\TEMP\FBCODE_builder_getdeps", subst_mapping={"X:\\": r"C:\temp\fbcode_builder_getdeps"}, ), "X:\\", ) self.assertEqual( find_existing_win32_subst_for_path( r"C:\temp\fbcode_builder_getdeps", subst_mapping={"X:\\": r"C:\TEMP\FBCODE_builder_getdeps"}, ), "X:\\", ) ================================================ FILE: build/fbcode_builder/getdeps/test/strip_marker_test.py ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. # pyre-strict import os import tempfile import unittest from ..fetcher import filter_strip_marker from ..manifest import ManifestParser class ManifestStripMarkerTest(unittest.TestCase): def test_default_strip_marker(self) -> None: p = ManifestParser( "test", """ [manifest] name = test """, ) self.assertEqual(p.shipit_strip_marker, "@fb-only") def test_custom_strip_marker(self) -> None: p = ManifestParser( "test", """ [manifest] name = test shipit_strip_marker = @oss-disable """, ) self.assertEqual(p.shipit_strip_marker, "@oss-disable") class FilterStripMarkerTest(unittest.TestCase): def _write_temp(self, content: str) -> str: fd, path = tempfile.mkstemp(suffix=".txt") os.close(fd) with open(path, "w") as f: f.write(content) return path def _read(self, path: str) -> str: with open(path, "r") as f: return f.read() def test_single_line_removal(self) -> None: path = self._write_temp("keep this\nremove this @fb-only\nkeep this too\n") try: filter_strip_marker(path, "@fb-only") self.assertEqual(self._read(path), "keep this\nkeep this too\n") finally: os.unlink(path) def test_block_removal(self) -> None: content = ( "before\n" "// @fb-only-start\n" "secret stuff\n" "more secret\n" "// @fb-only-end\n" "after\n" ) path = self._write_temp(content) try: filter_strip_marker(path, "@fb-only") self.assertEqual(self._read(path), "before\nafter\n") finally: os.unlink(path) def test_no_marker_present_no_change(self) -> None: original = "nothing special here\njust plain code\n" path = self._write_temp(original) try: filter_strip_marker(path, "@fb-only") self.assertEqual(self._read(path), original) finally: os.unlink(path) def test_custom_marker_single_line(self) -> None: content = "keep\nremove @oss-disable\nkeep too\n" path = self._write_temp(content) try: filter_strip_marker(path, "@oss-disable") self.assertEqual(self._read(path), "keep\nkeep too\n") finally: os.unlink(path) def test_custom_marker_block(self) -> None: content = ( "before\n" "# @oss-disable-start\n" "internal only\n" "# @oss-disable-end\n" "after\n" ) path = self._write_temp(content) try: filter_strip_marker(path, "@oss-disable") self.assertEqual(self._read(path), "before\nafter\n") finally: os.unlink(path) def test_custom_marker_ignores_default(self) -> None: """When using a custom marker, @fb-only lines should be kept.""" content = "keep @fb-only\nremove @oss-disable\nplain\n" path = self._write_temp(content) try: filter_strip_marker(path, "@oss-disable") self.assertEqual(self._read(path), "keep @fb-only\nplain\n") finally: os.unlink(path) def test_mixed_single_and_block(self) -> None: content = ( "line1\n" "line2 @fb-only\n" "line3\n" "// @fb-only-start\n" "block content\n" "// @fb-only-end\n" "line4\n" ) path = self._write_temp(content) try: filter_strip_marker(path, "@fb-only") self.assertEqual(self._read(path), "line1\nline3\nline4\n") finally: os.unlink(path) def test_marker_with_regex_metacharacters(self) -> None: """Markers containing regex metacharacters should be escaped properly.""" content = "keep\nremove @fb.only\nkeep too\n" path = self._write_temp(content) try: # With proper escaping, the dot is literal, not a wildcard filter_strip_marker(path, "@fb.only") self.assertEqual(self._read(path), "keep\nkeep too\n") finally: os.unlink(path) def test_binary_file_skipped(self) -> None: """Binary files that can't be decoded as UTF-8 should be skipped.""" fd, path = tempfile.mkstemp(suffix=".bin") os.close(fd) binary_content = b"\x80\x81\x82\xff\xfe" with open(path, "wb") as f: f.write(binary_content) try: filter_strip_marker(path, "@fb-only") with open(path, "rb") as f: self.assertEqual(f.read(), binary_content) finally: os.unlink(path) ================================================ FILE: build/fbcode_builder/getdeps.py ================================================ #!/usr/bin/env python3 # Copyright (c) Meta Platforms, Inc. and affiliates. # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. import argparse import json import os import shutil import subprocess import sys import tarfile import tempfile # We don't import cache.create_cache directly as the facebook # specific import below may monkey patch it, and we want to # observe the patched version of this function! import getdeps.cache as cache_module from getdeps.buildopts import setup_build_options from getdeps.dyndeps import create_dyn_dep_munger from getdeps.errors import TransientFailure from getdeps.fetcher import ( file_name_is_cmake_file, is_public_commit, list_files_under_dir_newer_than_timestamp, SystemPackageFetcher, ) from getdeps.load import ManifestLoader from getdeps.manifest import ManifestParser from getdeps.platform import HostType from getdeps.runcmd import check_cmd from getdeps.subcmd import add_subcommands, cmd, SubCmd try: import getdeps.facebook # noqa: F401 except ImportError: # we don't ship the facebook specific subdir, # so allow that to fail silently pass sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "getdeps")) class UsageError(Exception): pass # Shared argument definition for --build-type used by multiple commands BUILD_TYPE_ARG = { "help": "Set the build type explicitly: Debug (unoptimized, debug symbols), RelWithDebInfo (optimized with debug symbols, default), MinSizeRel (size-optimized, no debug), or Release (optimized, no debug).", "choices": ["Debug", "Release", "RelWithDebInfo", "MinSizeRel"], "action": "store", "default": "RelWithDebInfo", } @cmd("validate-manifest", "parse a manifest and validate that it is correct") class ValidateManifest(SubCmd): def run(self, args): try: ManifestParser(file_name=args.file_name) print("OK", file=sys.stderr) return 0 except Exception as exc: print("ERROR: %s" % str(exc), file=sys.stderr) return 1 def setup_parser(self, parser): parser.add_argument("file_name", help="path to the manifest file") @cmd("show-host-type", "outputs the host type tuple for the host machine") class ShowHostType(SubCmd): def run(self, args): host = HostType() print("%s" % host.as_tuple_string()) return 0 class ProjectCmdBase(SubCmd): def run(self, args): opts = setup_build_options(args) if args.current_project is not None: opts.repo_project = args.current_project if args.project is None: if opts.repo_project is None: raise UsageError( "no project name specified, and no .projectid file found" ) if opts.repo_project == "fbsource": # The fbsource repository is a little special. There is no project # manifest file for it. A specific project must always be explicitly # specified when building from fbsource. raise UsageError( "no project name specified (required when building in fbsource)" ) args.project = opts.repo_project ctx_gen = opts.get_context_generator() if args.test_dependencies: ctx_gen.set_value_for_all_projects("test", "on") if args.enable_tests: ctx_gen.set_value_for_project(args.project, "test", "on") else: ctx_gen.set_value_for_project(args.project, "test", "off") if opts.shared_libs: ctx_gen.set_value_for_all_projects("shared_libs", "on") loader = ManifestLoader(opts, ctx_gen) self.process_project_dir_arguments(args, loader) manifest = loader.load_manifest(args.project) return self.run_project_cmd(args, loader, manifest) def process_project_dir_arguments(self, args, loader): def parse_project_arg(arg, arg_type): parts = arg.split(":") if len(parts) == 2: project, path = parts elif len(parts) == 1: project = args.project path = parts[0] # On Windows path contains colon, e.g. C:\open elif os.name == "nt" and len(parts) == 3: project = parts[0] path = parts[1] + ":" + parts[2] else: raise UsageError( "invalid %s argument; too many ':' characters: %s" % (arg_type, arg) ) return project, os.path.abspath(path) # If we are currently running from a project repository, # use the current repository for the project sources. build_opts = loader.build_opts if build_opts.repo_project is not None and build_opts.repo_root is not None: loader.set_project_src_dir(build_opts.repo_project, build_opts.repo_root) for arg in args.src_dir: project, path = parse_project_arg(arg, "--src-dir") loader.set_project_src_dir(project, path) for arg in args.build_dir: project, path = parse_project_arg(arg, "--build-dir") loader.set_project_build_dir(project, path) for arg in args.install_dir: project, path = parse_project_arg(arg, "--install-dir") loader.set_project_install_dir(project, path) for arg in args.project_install_prefix: project, path = parse_project_arg(arg, "--install-prefix") loader.set_project_install_prefix(project, path) def setup_parser(self, parser): parser.add_argument( "project", nargs="?", help=( "name of the project or path to a manifest " "file describing the project" ), ) parser.add_argument( "--no-tests", action="store_false", dest="enable_tests", default=True, help="Disable building tests for this project.", ) parser.add_argument( "--test-dependencies", action="store_true", help="Enable building tests for dependencies as well.", ) parser.add_argument( "--current-project", help="Specify the name of the fbcode_builder manifest file for the " "current repository. If not specified, the code will attempt to find " "this in a .projectid file in the repository root.", ) parser.add_argument( "--src-dir", default=[], action="append", help="Specify a local directory to use for the project source, " "rather than fetching it.", ) parser.add_argument( "--build-dir", default=[], action="append", help="Explicitly specify the build directory to use for the " "project, instead of the default location in the scratch path. " "This only affects the project specified, and not its dependencies.", ) parser.add_argument( "--install-dir", default=[], action="append", help="Explicitly specify the install directory to use for the " "project, instead of the default location in the scratch path. " "This only affects the project specified, and not its dependencies.", ) parser.add_argument( "--project-install-prefix", default=[], action="append", help="Specify the final deployment installation path for a project", ) self.setup_project_cmd_parser(parser) def setup_project_cmd_parser(self, parser): pass def create_builder(self, loader, manifest): fetcher = loader.create_fetcher(manifest) src_dir = fetcher.get_src_dir() ctx = loader.ctx_gen.get_context(manifest.name) build_dir = loader.get_project_build_dir(manifest) inst_dir = loader.get_project_install_dir(manifest) return manifest.create_builder( loader.build_opts, src_dir, build_dir, inst_dir, ctx, loader, loader.dependencies_of(manifest), ) def check_built(self, loader, manifest): built_marker = os.path.join( loader.get_project_install_dir(manifest), ".built-by-getdeps" ) return os.path.exists(built_marker) class CachedProject: """A helper that allows calling the cache logic for a project from both the build and the fetch code""" def __init__(self, cache, loader, m): self.m = m self.inst_dir = loader.get_project_install_dir(m) self.project_hash = loader.get_project_hash(m) self.ctx = loader.ctx_gen.get_context(m.name) self.loader = loader self.cache = cache self.cache_key = "-".join( ( m.name, self.ctx.get("os"), self.ctx.get("distro") or "none", self.ctx.get("distro_vers") or "none", self.project_hash, ) ) self.cache_file_name = self.cache_key + "-buildcache.tgz" def is_cacheable(self): """We only cache third party projects""" return self.cache and self.m.shipit_project is None def was_cached(self): cached_marker = os.path.join(self.inst_dir, ".getdeps-cached-build") return os.path.exists(cached_marker) def download(self): if self.is_cacheable() and not os.path.exists(self.inst_dir): print("check cache for %s" % self.cache_file_name) dl_dir = os.path.join(self.loader.build_opts.scratch_dir, "downloads") if not os.path.exists(dl_dir): os.makedirs(dl_dir) try: target_file_name = os.path.join(dl_dir, self.cache_file_name) if self.cache.download_to_file(self.cache_file_name, target_file_name): tf = tarfile.open(target_file_name, "r") print( "Extracting %s -> %s..." % (self.cache_file_name, self.inst_dir) ) tf.extractall(self.inst_dir) cached_marker = os.path.join(self.inst_dir, ".getdeps-cached-build") with open(cached_marker, "w") as f: f.write("\n") return True except Exception as exc: print("%s" % str(exc)) return False def upload(self): if self.is_cacheable(): # We can prepare an archive and stick it in LFS tempdir = tempfile.mkdtemp() tarfilename = os.path.join(tempdir, self.cache_file_name) print("Archiving for cache: %s..." % tarfilename) tf = tarfile.open(tarfilename, "w:gz") tf.add(self.inst_dir, arcname=".") tf.close() try: self.cache.upload_from_file(self.cache_file_name, tarfilename) except Exception as exc: print( "Failed to upload to cache (%s), continue anyway" % str(exc), file=sys.stderr, ) shutil.rmtree(tempdir) @cmd("fetch", "fetch the code for a given project") class FetchCmd(ProjectCmdBase): def setup_project_cmd_parser(self, parser): parser.add_argument( "--recursive", help="fetch the transitive deps also", action="store_true", default=False, ) parser.add_argument( "--host-type", help=( "When recursively fetching, fetch deps for " "this host type rather than the current system" ), ) def run_project_cmd(self, args, loader, manifest): if args.recursive: projects = loader.manifests_in_dependency_order() else: projects = [manifest] cache = cache_module.create_cache() for m in projects: fetcher = loader.create_fetcher(m) if isinstance(fetcher, SystemPackageFetcher): # We are guaranteed that if the fetcher is set to # SystemPackageFetcher then this item is completely # satisfied by the appropriate system packages continue cached_project = CachedProject(cache, loader, m) if cached_project.download(): continue inst_dir = loader.get_project_install_dir(m) built_marker = os.path.join(inst_dir, ".built-by-getdeps") if os.path.exists(built_marker): with open(built_marker, "r") as f: built_hash = f.read().strip() project_hash = loader.get_project_hash(m) if built_hash == project_hash: continue # We need to fetch the sources fetcher.update() @cmd("install-system-deps", "Install system packages to satisfy the deps for a project") class InstallSysDepsCmd(ProjectCmdBase): def setup_project_cmd_parser(self, parser): parser.add_argument( "--recursive", help="install the transitive deps also", action="store_true", default=False, ) parser.add_argument( "--dry-run", action="store_true", default=False, help="Don't install, just print the commands specs we would run", ) parser.add_argument( "--os-type", help="Filter to just this OS type to run", choices=["linux", "darwin", "windows", "pacman-package"], action="store", dest="ostype", default=None, ) parser.add_argument( "--distro", help="Filter to just this distro to run", choices=["ubuntu", "centos_stream"], action="store", dest="distro", default=None, ) parser.add_argument( "--distro-version", help="Filter to just this distro version", action="store", dest="distrovers", default=None, ) def run_project_cmd(self, args, loader, manifest): if args.recursive: projects = loader.manifests_in_dependency_order() else: projects = [manifest] rebuild_ctx_gen = False if args.ostype: loader.build_opts.host_type.ostype = args.ostype loader.build_opts.host_type.distro = None loader.build_opts.host_type.distrovers = None rebuild_ctx_gen = True if args.distro: loader.build_opts.host_type.distro = args.distro loader.build_opts.host_type.distrovers = None rebuild_ctx_gen = True if args.distrovers: loader.build_opts.host_type.distrovers = args.distrovers rebuild_ctx_gen = True if rebuild_ctx_gen: loader.ctx_gen = loader.build_opts.get_context_generator() manager = loader.build_opts.host_type.get_package_manager() all_packages = {} for m in projects: ctx = loader.ctx_gen.get_context(m.name) packages = m.get_required_system_packages(ctx) for k, v in packages.items(): merged = all_packages.get(k, []) merged += v all_packages[k] = merged cmd_argss = [] if manager == "rpm": packages = sorted(set(all_packages["rpm"])) if packages: cmd_argss.append( ["sudo", "dnf", "install", "-y", "--skip-broken"] + packages ) elif manager == "deb": packages = sorted(set(all_packages["deb"])) if packages: cmd_argss.append( [ "sudo", "--preserve-env=http_proxy", "apt-get", "install", "-y", ] + packages ) cmd_argss.append(["pip", "install", "pex"]) elif manager == "homebrew": packages = sorted(set(all_packages["homebrew"])) if packages: cmd_argss.append(["brew", "install"] + packages) elif manager == "pacman-package": packages = sorted(list(set(all_packages["pacman-package"]))) if packages: cmd_argss.append(["pacman", "-S"] + packages) else: host_tuple = loader.build_opts.host_type.as_tuple_string() print( f"I don't know how to install any packages on this system {host_tuple}" ) return for cmd_args in cmd_argss: if args.dry_run: print(" ".join(cmd_args)) else: check_cmd(cmd_args) else: print("no packages to install") @cmd("list-deps", "lists the transitive deps for a given project") class ListDepsCmd(ProjectCmdBase): def run_project_cmd(self, args, loader, manifest): for m in loader.manifests_in_dependency_order(): print(m.name) return 0 def setup_project_cmd_parser(self, parser): parser.add_argument( "--host-type", help=( "Produce the list for the specified host type, " "rather than that of the current system" ), ) def clean_dirs(opts): for d in ["build", "installed", "extracted", "shipit"]: d = os.path.join(opts.scratch_dir, d) print("Cleaning %s..." % d) if os.path.exists(d): shutil.rmtree(d) @cmd("clean", "clean up the scratch dir") class CleanCmd(SubCmd): def run(self, args): opts = setup_build_options(args) clean_dirs(opts) @cmd("show-scratch-dir", "show the scratch dir") class ShowScratchDirCmd(SubCmd): def run(self, args): opts = setup_build_options(args) print(opts.scratch_dir) @cmd("show-build-dir", "print the build dir for a given project") class ShowBuildDirCmd(ProjectCmdBase): def run_project_cmd(self, args, loader, manifest): if args.recursive: manifests = loader.manifests_in_dependency_order() else: manifests = [manifest] for m in manifests: inst_dir = loader.get_project_build_dir(m) print(inst_dir) def setup_project_cmd_parser(self, parser): parser.add_argument( "--recursive", help="print the transitive deps also", action="store_true", default=False, ) @cmd("show-inst-dir", "print the installation dir for a given project") class ShowInstDirCmd(ProjectCmdBase): def run_project_cmd(self, args, loader, manifest): if args.recursive: manifests = loader.manifests_in_dependency_order() else: manifests = [manifest] for m in manifests: fetcher = loader.create_fetcher(m) if isinstance(fetcher, SystemPackageFetcher): # We are guaranteed that if the fetcher is set to # SystemPackageFetcher then this item is completely # satisfied by the appropriate system packages continue inst_dir = loader.get_project_install_dir_respecting_install_prefix(m) print(inst_dir) def setup_project_cmd_parser(self, parser): parser.add_argument( "--recursive", help="print the transitive deps also", action="store_true", default=False, ) @cmd("query-paths", "print the paths for tooling to use") class QueryPathsCmd(ProjectCmdBase): def run_project_cmd(self, args, loader, manifest): if args.recursive: manifests = loader.manifests_in_dependency_order() else: manifests = [manifest] cache = cache_module.create_cache() for m in manifests: fetcher = loader.create_fetcher(m) if isinstance(fetcher, SystemPackageFetcher): # We are guaranteed that if the fetcher is set to # SystemPackageFetcher then this item is completely # satisfied by the appropriate system packages continue src_dir = fetcher.get_src_dir() print(f"{m.name}_SOURCE={src_dir}") inst_dir = loader.get_project_install_dir_respecting_install_prefix(m) print(f"{m.name}_INSTALL={inst_dir}") cached_project = CachedProject(cache, loader, m) print(f"{m.name}_CACHE_KEY={cached_project.cache_key}") def setup_project_cmd_parser(self, parser): parser.add_argument( "--recursive", help="print the transitive deps also", action="store_true", default=False, ) @cmd("show-source-dir", "print the source dir for a given project") class ShowSourceDirCmd(ProjectCmdBase): def run_project_cmd(self, args, loader, manifest): if args.recursive: manifests = loader.manifests_in_dependency_order() else: manifests = [manifest] for m in manifests: fetcher = loader.create_fetcher(m) print(fetcher.get_src_dir()) def setup_project_cmd_parser(self, parser): parser.add_argument( "--recursive", help="print the transitive deps also", action="store_true", default=False, ) @cmd("build", "build a given project") class BuildCmd(ProjectCmdBase): def run_project_cmd(self, args, loader, manifest): if args.clean: clean_dirs(loader.build_opts) print("Building on %s" % loader.ctx_gen.get_context(args.project)) projects = loader.manifests_in_dependency_order() cache = cache_module.create_cache() if args.use_build_cache else None dep_manifests = [] for m in projects: dep_manifests.append(m) fetcher = loader.create_fetcher(m) if args.build_skip_lfs_download and hasattr(fetcher, "skip_lfs_download"): print("skipping lfs download for %s" % m.name) fetcher.skip_lfs_download() if isinstance(fetcher, SystemPackageFetcher): # We are guaranteed that if the fetcher is set to # SystemPackageFetcher then this item is completely # satisfied by the appropriate system packages continue if args.clean: fetcher.clean() build_dir = loader.get_project_build_dir(m) inst_dir = loader.get_project_install_dir(m) if ( m == manifest and not args.only_deps or m != manifest and not args.no_deps ): print("Assessing %s..." % m.name) project_hash = loader.get_project_hash(m) ctx = loader.ctx_gen.get_context(m.name) built_marker = os.path.join(inst_dir, ".built-by-getdeps") cached_project = CachedProject(cache, loader, m) reconfigure, sources_changed = self.compute_source_change_status( cached_project, fetcher, m, built_marker, project_hash ) if os.path.exists(built_marker) and not cached_project.was_cached(): # We've previously built this. We may need to reconfigure if # our deps have changed, so let's check them. dep_reconfigure, dep_build = self.compute_dep_change_status( m, built_marker, loader ) if dep_reconfigure: reconfigure = True if dep_build: sources_changed = True extra_cmake_defines = ( json.loads(args.extra_cmake_defines) if args.extra_cmake_defines else {} ) extra_b2_args = args.extra_b2_args or [] cmake_targets = args.cmake_target or ["install"] if sources_changed or reconfigure or not os.path.exists(built_marker): if os.path.exists(built_marker): os.unlink(built_marker) src_dir = fetcher.get_src_dir() # Prepare builders write out config before the main builder runs prepare_builders = m.create_prepare_builders( loader.build_opts, ctx, src_dir, build_dir, inst_dir, loader, dep_manifests, ) for preparer in prepare_builders: preparer.prepare(reconfigure=reconfigure) builder = m.create_builder( loader.build_opts, src_dir, build_dir, inst_dir, ctx, loader, dep_manifests, final_install_prefix=loader.get_project_install_prefix(m), extra_cmake_defines=extra_cmake_defines, cmake_targets=(cmake_targets if m == manifest else ["install"]), extra_b2_args=extra_b2_args, ) builder.build(reconfigure=reconfigure) # If we are building the project (not dependency) and a specific # cmake_target (not 'install') has been requested, then we don't # set the built_marker. This allows subsequent runs of getdeps.py # for the project to run with different cmake_targets to trigger # cmake has_built_marker = False if not (m == manifest and "install" not in cmake_targets): os.makedirs(os.path.dirname(built_marker), exist_ok=True) with open(built_marker, "w") as f: f.write(project_hash) has_built_marker = True # Only populate the cache from continuous build runs, and # only if we have a built_marker. if not args.skip_upload and has_built_marker: if args.schedule_type == "continuous": cached_project.upload() elif args.schedule_type == "base_retry": # Check if on public commit before uploading if is_public_commit(loader.build_opts): cached_project.upload() elif args.verbose: print("found good %s" % built_marker) def compute_dep_change_status(self, m, built_marker, loader): reconfigure = False sources_changed = False st = os.lstat(built_marker) ctx = loader.ctx_gen.get_context(m.name) dep_list = m.get_dependencies(ctx) for dep in dep_list: if reconfigure and sources_changed: break dep_manifest = loader.load_manifest(dep) dep_root = loader.get_project_install_dir(dep_manifest) for dep_file in list_files_under_dir_newer_than_timestamp( dep_root, st.st_mtime ): if os.path.basename(dep_file) == ".built-by-getdeps": continue if file_name_is_cmake_file(dep_file): if not reconfigure: reconfigure = True print( f"Will reconfigure cmake because {dep_file} is newer than {built_marker}" ) else: if not sources_changed: sources_changed = True print( f"Will run build because {dep_file} is newer than {built_marker}" ) if reconfigure and sources_changed: break return reconfigure, sources_changed def compute_source_change_status( self, cached_project, fetcher, m, built_marker, project_hash ): reconfigure = False sources_changed = False if cached_project.download(): if not os.path.exists(built_marker): fetcher.update() else: check_fetcher = True if os.path.exists(built_marker): check_fetcher = False with open(built_marker, "r") as f: built_hash = f.read().strip() if built_hash == project_hash: if cached_project.is_cacheable(): # We can blindly trust the build status reconfigure = False sources_changed = False else: # Otherwise, we may have changed the source, so let's # check in with the fetcher layer check_fetcher = True else: # Some kind of inconsistency with a prior build, # let's run it again to be sure os.unlink(built_marker) reconfigure = True sources_changed = True # While we don't need to consult the fetcher for the # status in this case, we may still need to have eg: shipit # run in order to have a correct source tree. fetcher.update() if check_fetcher: change_status = fetcher.update() reconfigure = change_status.build_changed() sources_changed = change_status.sources_changed() return reconfigure, sources_changed def setup_project_cmd_parser(self, parser): parser.add_argument( "--clean", action="store_true", default=False, help=( "Clean up the build and installation area prior to building, " "causing the projects to be built from scratch" ), ) parser.add_argument( "--no-deps", action="store_true", default=False, help=( "Only build the named project, not its deps. " "This is most useful after you've built all of the deps, " "and helps to avoid waiting for relatively " "slow up-to-date-ness checks" ), ) parser.add_argument( "--only-deps", action="store_true", default=False, help=( "Only build the named project's deps. " "This is most useful when you want to separate out building " "of all of the deps and your project" ), ) parser.add_argument( "--no-build-cache", action="store_false", default=True, dest="use_build_cache", help="Do not attempt to use the build cache.", ) parser.add_argument( "--cmake-target", help=("Repeatable argument that specifies targets for cmake build."), default=[], action="append", ) parser.add_argument( "--extra-b2-args", help=( "Repeatable argument that contains extra arguments to pass " "to b2, which compiles boost. " "e.g.: 'cxxflags=-fPIC' 'cflags=-fPIC'" ), action="append", ) parser.add_argument( "--free-up-disk", help="Remove unused tools and clean up intermediate files if possible to maximise space for the build", action="store_true", default=False, ) parser.add_argument("--build-type", **BUILD_TYPE_ARG) @cmd("fixup-dyn-deps", "Adjusts dynamic dependencies for packaging purposes") class FixupDeps(ProjectCmdBase): def run_project_cmd(self, args, loader, manifest): projects = loader.manifests_in_dependency_order() # Accumulate the install directories so that the build steps # can find their dep installation install_dirs = [] dep_manifests = [] for m in projects: inst_dir = loader.get_project_install_dir_respecting_install_prefix(m) install_dirs.append(inst_dir) dep_manifests.append(m) if m == manifest: ctx = loader.ctx_gen.get_context(m.name) env = loader.build_opts.compute_env_for_install_dirs( loader, dep_manifests, ctx ) dep_munger = create_dyn_dep_munger( loader.build_opts, env, install_dirs, args.strip ) if dep_munger is None: print(f"dynamic dependency fixups not supported on {sys.platform}") else: dep_munger.process_deps(args.destdir, args.final_install_prefix) def setup_project_cmd_parser(self, parser): parser.add_argument("destdir", help="Where to copy the fixed up executables") parser.add_argument( "--final-install-prefix", help="specify the final installation prefix" ) parser.add_argument( "--strip", action="store_true", default=False, help="Strip debug info while processing executables", ) @cmd("test", "test a given project") class TestCmd(ProjectCmdBase): def run_project_cmd(self, args, loader, manifest): if not self.check_built(loader, manifest): print("project %s has not been built" % manifest.name) return 1 return self.create_builder(loader, manifest).run_tests( schedule_type=args.schedule_type, owner=args.test_owner, test_filter=args.filter, test_exclude=args.exclude, retry=args.retry, no_testpilot=args.no_testpilot, timeout=args.timeout, ) def setup_project_cmd_parser(self, parser): parser.add_argument("--test-owner", help="Owner for testpilot") parser.add_argument("--filter", help="Only run the tests matching the regex") parser.add_argument("--exclude", help="Exclude tests matching the regex") parser.add_argument( "--retry", type=int, default=3, help="Number of immediate retries for failed tests " "(noop in continuous and testwarden runs)", ) parser.add_argument( "--no-testpilot", help="Do not use Test Pilot even when available", action="store_true", ) parser.add_argument( "--timeout", type=int, default=None, help="Timeout in seconds for each individual test", ) parser.add_argument("--build-type", **BUILD_TYPE_ARG) @cmd( "debug", "start a shell in the given project's build dir with the correct environment for running the build", ) class DebugCmd(ProjectCmdBase): def run_project_cmd(self, args, loader, manifest): self.create_builder(loader, manifest).debug(reconfigure=False) @cmd( "env", "print the environment in a shell sourceable format", ) class EnvCmd(ProjectCmdBase): def setup_project_cmd_parser(self, parser): parser.add_argument( "--os-type", help="Filter to just this OS type to run", choices=["linux", "darwin", "windows"], action="store", dest="ostype", default=None, ) def run_project_cmd(self, args, loader, manifest): if args.ostype: loader.build_opts.host_type.ostype = args.ostype self.create_builder(loader, manifest).printenv(reconfigure=False) @cmd("generate-github-actions", "generate a GitHub actions configuration") class GenerateGitHubActionsCmd(ProjectCmdBase): RUN_ON_ALL = """ [push, pull_request]""" WORKFLOW_DISPATCH_TMATE = """ workflow_dispatch: inputs: tmate_enabled: description: 'Start a tmate SSH session on failure' required: false default: false type: boolean""" def run_project_cmd(self, args, loader, manifest): platforms = [ HostType("linux", "ubuntu", "24"), HostType("darwin", None, None), HostType("windows", None, None), ] for p in platforms: if args.os_types and p.ostype not in args.os_types: continue self.write_job_for_platform(p, args) def get_run_on(self, args): if args.run_on_all_branches: return ( """ push: pull_request:""" + self.WORKFLOW_DISPATCH_TMATE ) if args.cron: if args.cron == "never": return " {}" elif args.cron == "workflow_dispatch": return self.WORKFLOW_DISPATCH_TMATE else: return ( f""" schedule: - cron: '{args.cron}'""" + self.WORKFLOW_DISPATCH_TMATE ) return ( f""" push: branches: - {args.main_branch} pull_request: branches: - {args.main_branch}""" + self.WORKFLOW_DISPATCH_TMATE ) # TODO: Break up complex function def write_job_for_platform(self, platform, args): # noqa: C901 build_opts = setup_build_options(args, platform) ctx_gen = build_opts.get_context_generator() if args.enable_tests: ctx_gen.set_value_for_project(args.project, "test", "on") else: ctx_gen.set_value_for_project(args.project, "test", "off") loader = ManifestLoader(build_opts, ctx_gen) self.process_project_dir_arguments(args, loader) manifest = loader.load_manifest(args.project) manifest_ctx = loader.ctx_gen.get_context(manifest.name) run_tests = ( args.enable_tests and manifest.get("github.actions", "run_tests", ctx=manifest_ctx) != "off" ) rust_version = ( manifest.get("github.actions", "rust_version", ctx=manifest_ctx) or "stable" ) override_build_type = args.build_type or manifest.get( "github.actions", "build_type", ctx=manifest_ctx ) if run_tests: manifest_ctx.set("test", "on") run_on = self.get_run_on(args) tests_arg = "--no-tests " if run_tests: tests_arg = "" # Some projects don't do anything "useful" as a leaf project, only # as a dep for a leaf project. Check for those here; we don't want # to waste the effort scheduling them on CI. # We do this by looking at the builder type in the manifest file # rather than creating a builder and checking its type because we # don't know enough to create the full builder instance here. builder_name = manifest.get("build", "builder", ctx=manifest_ctx) if builder_name == "nop": return None # We want to be sure that we're running things with python 3 # but python versioning is honestly a bit of a frustrating mess. # `python` may be version 2 or version 3 depending on the system. # python3 may not be a thing at all! # Assume an optimistic default py3 = "python3" if build_opts.is_linux(): artifacts = "linux" if args.runs_on: runs_on = args.runs_on else: runs_on = f"ubuntu-{args.ubuntu_version}" if args.cpu_cores: runs_on = f"{args.cpu_cores}-core-ubuntu-{args.ubuntu_version}" elif build_opts.is_windows(): artifacts = "windows" if args.runs_on: runs_on = args.runs_on else: runs_on = "windows-2022" # The windows runners are python 3 by default; python2.exe # is available if needed. py3 = "python" else: artifacts = "mac" if args.runs_on: runs_on = args.runs_on else: runs_on = "macOS-latest" os.makedirs(args.output_dir, exist_ok=True) job_file_prefix = "getdeps_" if args.job_file_prefix: job_file_prefix = args.job_file_prefix output_file = os.path.join(args.output_dir, f"{job_file_prefix}{artifacts}.yml") if args.job_name_prefix: job_name = args.job_name_prefix + artifacts.capitalize() else: job_name = artifacts with open(output_file, "w") as out: # Deliberate line break here because the @ and the generated # symbols are meaningful to our internal tooling when they # appear in a single token out.write("# This file was @") out.write("generated by getdeps.py\n") out.write( f""" name: {job_name} on:{run_on} permissions: contents: read # to fetch code (actions/checkout) jobs: """ ) getdepscmd = f"{py3} build/fbcode_builder/getdeps.py" out.write(" build:\n") out.write(" runs-on: %s\n" % runs_on) out.write(" steps:\n") if build_opts.is_windows(): # cmake relies on BOOST_ROOT but GH deliberately don't set it in order # to avoid versioning issues: # https://github.com/actions/virtual-environments/issues/319 # Instead, set the version we think we need; this is effectively # coupled with the boost manifest # This is the unusual syntax for setting an env var for the rest of # the steps in a workflow: # https://github.blog/changelog/2020-10-01-github-actions-deprecating-set-env-and-add-path-commands/ out.write(" - name: Export boost environment\n") out.write( ' run: "echo BOOST_ROOT=%BOOST_ROOT_1_83_0% >> %GITHUB_ENV%"\n' ) out.write(" shell: cmd\n") out.write(" - name: Fix Git config\n") out.write(" run: >\n") out.write(" git config --system core.longpaths true &&\n") out.write(" git config --system core.autocrlf false &&\n") # cxx crate needs symlinks enabled out.write(" git config --system core.symlinks true\n") # && is not supported on default windows powershell, so use cmd out.write(" shell: cmd\n") out.write(" - uses: actions/checkout@v6\n") build_type_arg = "" if override_build_type: build_type_arg = f"--build-type {override_build_type} " if args.shared_libs: build_type_arg += "--shared-libs " if build_opts.free_up_disk: free_up_disk = "--free-up-disk " if not build_opts.is_windows(): out.write(" - name: Show disk space at start\n") out.write(" run: df -h\n") # remove the unused github supplied android dev tools out.write(" - name: Free up disk space\n") out.write(" run: sudo rm -rf /usr/local/lib/android\n") out.write(" - name: Show disk space after freeing up\n") out.write(" run: df -h\n") else: free_up_disk = "" allow_sys_arg = "" if ( build_opts.allow_system_packages and build_opts.host_type.get_package_manager() ): sudo_arg = "sudo --preserve-env=http_proxy " allow_sys_arg = " --allow-system-packages" if build_opts.host_type.get_package_manager() == "deb": out.write(" - name: Update system package info\n") out.write(f" run: {sudo_arg}apt-get update\n") out.write(" - name: Install system deps\n") if build_opts.is_darwin(): # brew is installed as regular user sudo_arg = "" system_deps_cmd = f"{sudo_arg}{getdepscmd}{allow_sys_arg} install-system-deps {tests_arg}--recursive {manifest.name}" if build_opts.is_linux() or build_opts.is_freebsd(): system_deps_cmd += f" && {sudo_arg}{getdepscmd}{allow_sys_arg} install-system-deps {tests_arg}--recursive patchelf" out.write(f" run: {system_deps_cmd}\n") required_locales = manifest.get( "github.actions", "required_locales", ctx=manifest_ctx ) if ( build_opts.host_type.get_package_manager() == "deb" and required_locales ): # ubuntu doesn't include this by default out.write(" - name: Install locale-gen\n") out.write(f" run: {sudo_arg}apt-get install locales\n") for loc in required_locales.split(): out.write(f" - name: Ensure {loc} locale present\n") out.write(f" run: {sudo_arg}locale-gen {loc}\n") out.write(" - id: paths\n") out.write(" name: Query paths\n") if build_opts.is_windows(): out.write( f" run: {getdepscmd}{allow_sys_arg} query-paths {tests_arg}--recursive --src-dir=. {manifest.name} >> $env:GITHUB_OUTPUT\n" ) out.write(" shell: pwsh\n") else: out.write( f' run: {getdepscmd}{allow_sys_arg} query-paths {tests_arg}--recursive --src-dir=. {manifest.name} >> "$GITHUB_OUTPUT"\n' ) projects = loader.manifests_in_dependency_order() main_repo_url = manifest.get_repo_url(manifest_ctx) has_same_repo_dep = False # Add the rust dep which doesn't have a manifest for m in projects: if m == manifest: continue mbuilder_name = m.get("build", "builder", ctx=manifest_ctx) if ( m.name == "rust" or builder_name == "cargo" or mbuilder_name == "cargo" ): out.write(f" - name: Install Rust {rust_version.capitalize()}\n") out.write(f" uses: dtolnay/rust-toolchain@{rust_version}\n") break # Normal deps that have manifests for m in projects: if m == manifest or m.name == "rust": continue ctx = loader.ctx_gen.get_context(m.name) if m.get_repo_url(ctx) != main_repo_url: out.write(" - name: Fetch %s\n" % m.name) out.write( f" if: ${{{{ steps.paths.outputs.{m.name}_SOURCE }}}}\n" ) out.write( f" run: {getdepscmd}{allow_sys_arg} fetch --no-tests {m.name}\n" ) for m in projects: if m == manifest or m.name == "rust": continue src_dir_arg = "" ctx = loader.ctx_gen.get_context(m.name) if main_repo_url and m.get_repo_url(ctx) == main_repo_url: # Its in the same repo, so src-dir is also . src_dir_arg = "--src-dir=. " has_same_repo_dep = True if args.use_build_cache and not src_dir_arg: out.write(f" - name: Restore {m.name} from cache\n") out.write(f" id: restore_{m.name}\n") # only need to restore if would build it out.write( f" if: ${{{{ steps.paths.outputs.{m.name}_SOURCE }}}}\n" ) out.write(" uses: actions/cache/restore@v4\n") out.write(" with:\n") out.write( f" path: ${{{{ steps.paths.outputs.{m.name}_INSTALL }}}}\n" ) out.write( f" key: ${{{{ steps.paths.outputs.{m.name}_CACHE_KEY }}}}-install\n" ) out.write(" - name: Build %s\n" % m.name) if not src_dir_arg: if args.use_build_cache: out.write( f" if: ${{{{ steps.paths.outputs.{m.name}_SOURCE && ! steps.restore_{m.name}.outputs.cache-hit }}}}\n" ) else: out.write( f" if: ${{{{ steps.paths.outputs.{m.name}_SOURCE }}}}\n" ) out.write( f" run: {getdepscmd}{allow_sys_arg} build {build_type_arg}{src_dir_arg}{free_up_disk}--no-tests {m.name}\n" ) if args.use_build_cache and not src_dir_arg: out.write(f" - name: Save {m.name} to cache\n") out.write(" uses: actions/cache/save@v4\n") out.write( f" if: ${{{{ steps.paths.outputs.{m.name}_SOURCE && ! steps.restore_{m.name}.outputs.cache-hit }}}}\n" ) out.write(" with:\n") out.write( f" path: ${{{{ steps.paths.outputs.{m.name}_INSTALL }}}}\n" ) out.write( f" key: ${{{{ steps.paths.outputs.{m.name}_CACHE_KEY }}}}-install\n" ) out.write(" - name: Build %s\n" % manifest.name) project_prefix = "" if not build_opts.is_windows(): prefix = loader.get_project_install_prefix(manifest) or "/usr/local" project_prefix = " --project-install-prefix %s:%s" % ( manifest.name, prefix, ) # If we have dep from same repo, we already built it and don't want to rebuild it again no_deps_arg = "" if has_same_repo_dep: no_deps_arg = "--no-deps " out.write( f" run: {getdepscmd}{allow_sys_arg} build {build_type_arg}{tests_arg}{no_deps_arg}--src-dir=. {manifest.name}{project_prefix}\n" ) out.write(" - name: Copy artifacts\n") if build_opts.is_linux(): # Strip debug info from the binaries, but only on linux. # While the `strip` utility is also available on macOS, # attempting to strip there results in an error. # The `strip` utility is not available on Windows. strip = " --strip" else: strip = "" out.write( f" run: {getdepscmd}{allow_sys_arg} fixup-dyn-deps{strip} " f"--src-dir=. {manifest.name} _artifacts/{artifacts}{project_prefix} " f"--final-install-prefix /usr/local\n" ) out.write(" - uses: actions/upload-artifact@v6\n") out.write(" with:\n") out.write(" name: %s\n" % manifest.name) out.write(" path: _artifacts\n") if run_tests: num_jobs_arg = "" if args.num_jobs: num_jobs_arg = f"--num-jobs {args.num_jobs} " out.write(" - name: Test %s\n" % manifest.name) out.write( f" run: {getdepscmd}{allow_sys_arg} test {build_type_arg}{num_jobs_arg}--src-dir=. {manifest.name}{project_prefix}\n" ) if build_opts.free_up_disk and not build_opts.is_windows(): out.write(" - name: Show disk space at end\n") out.write(" if: always()\n") out.write(" run: df -h\n") out.write(" - name: Setup tmate session\n") out.write( " if: failure() && github.event_name == 'workflow_dispatch' && inputs.tmate_enabled\n" ) out.write(" uses: mxschmitt/action-tmate@v3\n") def setup_project_cmd_parser(self, parser): parser.add_argument( "--disallow-system-packages", help="Disallow satisfying third party deps from installed system packages", action="store_true", default=False, ) parser.add_argument( "--output-dir", help="The directory that will contain the yml files" ) parser.add_argument( "--run-on-all-branches", action="store_true", help="Allow CI to fire on all branches - Handy for testing", ) parser.add_argument( "--ubuntu-version", default="24.04", help="Version of Ubuntu to use" ) parser.add_argument( "--cpu-cores", help="Number of CPU cores to use (applicable for Linux OS)", ) parser.add_argument( "--runs-on", help="Allow specifying explicit runs-on: for github actions", ) parser.add_argument( "--cron", help="Specify that the job runs on a cron schedule instead of on pushes. Pass never to disable the action.", ) parser.add_argument( "--main-branch", default="main", help="Main branch to trigger GitHub Action on", ) parser.add_argument( "--os-type", help="Filter to just this OS type to run", choices=["linux", "darwin", "windows"], action="append", dest="os_types", default=[], ) parser.add_argument( "--job-file-prefix", type=str, help="add a prefix to all job file names", default=None, ) parser.add_argument( "--job-name-prefix", type=str, help="add a prefix to all job names", default=None, ) parser.add_argument( "--free-up-disk", help="Remove unused tools and clean up intermediate files if possible to maximise space for the build", action="store_true", default=False, ) parser.add_argument("--build-type", **BUILD_TYPE_ARG) parser.add_argument( "--no-build-cache", action="store_false", default=True, dest="use_build_cache", help="Do not attempt to use the build cache.", ) def get_arg_var_name(args): for arg in args: if arg.startswith("--"): return arg[2:].replace("-", "_") raise Exception("unable to determine argument variable name from %r" % (args,)) def parse_args(): # We want to allow common arguments to be specified either before or after # the subcommand name. In order to do this we add them to the main parser # and to subcommand parsers. In order for this to work, we need to tell # argparse that the default value is SUPPRESS, so that the default values # from the subparser arguments won't override values set by the user from # the main parser. We maintain our own list of desired defaults in the # common_defaults dictionary, and manually set those if the argument wasn't # present at all. common_args = argparse.ArgumentParser(add_help=False) common_defaults = {} def add_common_arg(*args, **kwargs): var_name = get_arg_var_name(args) default_value = kwargs.pop("default", None) common_defaults[var_name] = default_value kwargs["default"] = argparse.SUPPRESS common_args.add_argument(*args, **kwargs) add_common_arg("--scratch-path", help="Where to maintain checkouts and build dirs") add_common_arg( "--vcvars-path", default=None, help="Path to the vcvarsall.bat on Windows." ) add_common_arg( "--install-prefix", help=( "Where the final build products will be installed " "(default is [scratch-path]/installed)" ), ) add_common_arg( "--num-jobs", type=int, help=( "Number of concurrent jobs to use while building. " "(default=number of cpu cores)" ), ) add_common_arg( "--use-shipit", help="use the real ShipIt instead of the simple shipit transformer", action="store_true", default=False, ) add_common_arg( "--facebook-internal", help="Setup the build context as an FB internal build", action="store_true", default=None, ) add_common_arg( "--no-facebook-internal", help="Perform a non-FB internal build, even when in an fbsource repository", action="store_false", dest="facebook_internal", ) add_common_arg( "--shared-libs", help="Build shared libraries if possible", action="store_true", default=False, ) add_common_arg( "--extra-cmake-defines", help=( "Input json map that contains extra cmake defines to be used " "when compiling the current project and all its deps. " 'e.g: \'{"CMAKE_CXX_FLAGS": "--bla"}\'' ), ) add_common_arg( "--allow-system-packages", help="Allow satisfying third party deps from installed system packages", action="store_true", default=False, ) add_common_arg( "-v", "--verbose", help="Print more output", action="store_true", default=False, ) add_common_arg( "-su", "--skip-upload", help="skip upload steps", action="store_true", default=False, ) add_common_arg( "--lfs-path", help="Provide a parent directory for lfs when fbsource is unavailable", default=None, ) add_common_arg( "--build-skip-lfs-download", action="store_true", default=False, help=( "Download from the URL, rather than LFS. This is useful " "in cases where the upstream project has uploaded a new " "version of the archive with a different hash" ), ) add_common_arg( "--schedule-type", nargs="?", help="Indicates how the build was activated", ) ap = argparse.ArgumentParser( description="Get and build dependencies and projects", parents=[common_args] ) sub = ap.add_subparsers( # metavar suppresses the long and ugly default list of subcommands on a # single line. We still render the nicer list below where we would # have shown the nasty one. metavar="", title="Available commands", help="", ) add_subcommands(sub, common_args) args = ap.parse_args() for var_name, default_value in common_defaults.items(): if not hasattr(args, var_name): setattr(args, var_name, default_value) return ap, args def main(): ap, args = parse_args() if getattr(args, "func", None) is None: ap.print_help() return 0 try: return args.func(args) except UsageError as exc: ap.error(str(exc)) return 1 except TransientFailure as exc: print("TransientFailure: %s" % str(exc)) # This return code is treated as a retryable transient infrastructure # error by Facebook's internal CI, rather than eg: a build or code # related error that needs to be fixed before progress can be made. return 128 except subprocess.CalledProcessError as exc: print("%s" % str(exc), file=sys.stderr) print("!! Failed", file=sys.stderr) return 1 if __name__ == "__main__": sys.exit(main()) ================================================ FILE: build/fbcode_builder/manifests/CLI11 ================================================ [manifest] name = CLI11 [download] url = https://github.com/CLIUtils/CLI11/archive/v2.0.0.tar.gz sha256 = 2c672f17bf56e8e6223a3bfb74055a946fa7b1ff376510371902adb9cb0ab6a3 [build] builder = cmake subdir = CLI11-2.0.0 [cmake.defines] CLI11_BUILD_TESTS = OFF CLI11_BUILD_EXAMPLES = OFF ================================================ FILE: build/fbcode_builder/manifests/autoconf ================================================ [manifest] name = autoconf [debs] autoconf [homebrew] autoconf [rpms] autoconf [pps] autoconf [download] url = https://ftpmirror.gnu.org/gnu/autoconf/autoconf-2.69.tar.gz sha256 = 954bd69b391edc12d6a4a51a2dd1476543da5c6bbf05a95b59dc0dd6fd4c2969 [build] builder = autoconf subdir = autoconf-2.69 ================================================ FILE: build/fbcode_builder/manifests/automake ================================================ [manifest] name = automake [homebrew] automake [debs] automake [rpms] automake [pps] automake [download] url = https://ftpmirror.gnu.org/gnu/automake/automake-1.16.1.tar.gz sha256 = 608a97523f97db32f1f5d5615c98ca69326ced2054c9f82e65bade7fc4c9dea8 [build] builder = autoconf subdir = automake-1.16.1 [dependencies] autoconf ================================================ FILE: build/fbcode_builder/manifests/benchmark ================================================ [manifest] name = benchmark [download] url = https://github.com/google/benchmark/archive/refs/tags/v1.8.0.tar.gz sha256 = ea2e94c24ddf6594d15c711c06ccd4486434d9cf3eca954e2af8a20c88f9f172 [build] builder = cmake subdir = benchmark-1.8.0/ [cmake.defines] BENCHMARK_ENABLE_TESTING=OFF ================================================ FILE: build/fbcode_builder/manifests/blake3 ================================================ [manifest] name = blake3 [download] url = https://github.com/BLAKE3-team/BLAKE3/archive/refs/tags/1.5.1.tar.gz sha256 = 822cd37f70152e5985433d2c50c8f6b2ec83aaf11aa31be9fe71486a91744f37 [build] builder = cmake subdir = BLAKE3-1.5.1/c ================================================ FILE: build/fbcode_builder/manifests/boost ================================================ [manifest] name = boost [download.not(os=windows)] url = https://archives.boost.io/release/1.83.0/source/boost_1_83_0.tar.gz sha256 = c0685b68dd44cc46574cce86c4e17c0f611b15e195be9848dfd0769a0a207628 [download.os=windows] url = https://archives.boost.io/release/1.83.0/source/boost_1_83_0.zip sha256 = c86bd9d9eef795b4b0d3802279419fde5221922805b073b9bd822edecb1ca28e [preinstalled.env] # Here we list the acceptable versions that cmake needs a hint to find BOOST_ROOT_1_69_0 BOOST_ROOT_1_83_0 [debs] libboost-all-dev [homebrew] boost # Boost cmake detection on homebrew adds this as requirement: https://github.com/Homebrew/homebrew-core/issues/67427#issuecomment-754187345 icu4c [pps] boost [rpms.all(distro=centos_stream,distro_vers=8)] boost169 boost169-math boost169-test boost169-fiber boost169-graph boost169-log boost169-openmpi boost169-timer boost169-chrono boost169-locale boost169-thread boost169-atomic boost169-random boost169-static boost169-contract boost169-date-time boost169-iostreams boost169-container boost169-coroutine boost169-filesystem boost169-system boost169-stacktrace boost169-regex boost169-devel boost169-context boost169-python3-devel boost169-type_erasure boost169-wave boost169-python3 boost169-serialization boost169-program-options [rpms.distro=fedora] boost-devel boost-static [build] builder = boost job_weight_mib = 512 patchfile = boost_1_83_0.patch [b2.args] --with-atomic --with-chrono --with-container --with-context --with-contract --with-coroutine --with-date_time --with-exception --with-fiber --with-filesystem --with-graph --with-graph_parallel --with-iostreams --with-locale --with-log --with-math --with-mpi --with-program_options --with-python --with-random --with-regex --with-serialization --with-stacktrace --with-system --with-test --with-thread --with-timer --with-type_erasure [bootstrap.args.os=darwin] # Not really gcc, but CI puts a broken clang in the PATH, and saying gcc # here selects the correct one from Xcode. --with-toolset=gcc [b2.args.os=linux] # RHEL hardened gcc is not compatible with PCH # https://bugzilla.redhat.com/show_bug.cgi?id=1806545 pch=off [b2.args.os=darwin] toolset=clang # Since Xcode 15.3 std::piecewise_construct is only visible in C++17 and later modes cxxflags="-DBOOST_UNORDERED_HAVE_PIECEWISE_CONSTRUCT=0" [b2.args.all(os=windows,fb=on)] toolset=msvc-14.3 ================================================ FILE: build/fbcode_builder/manifests/boost-python ================================================ [manifest] name = boost-python [download.not(os=windows)] url = https://archives.boost.io/release/1.83.0/source/boost_1_83_0.tar.gz sha256 = c0685b68dd44cc46574cce86c4e17c0f611b15e195be9848dfd0769a0a207628 [download.os=windows] url = https://archives.boost.io/release/1.83.0/source/boost_1_83_0.zip sha256 = c86bd9d9eef795b4b0d3802279419fde5221922805b073b9bd822edecb1ca28e [preinstalled.env] # Here we list the acceptable versions that cmake needs a hint to find BOOST_ROOT_1_69_0 BOOST_ROOT_1_83_0 [homebrew] boost # Boost cmake detection on homebrew adds this as requirement: https://github.com/Homebrew/homebrew-core/issues/67427#issuecomment-754187345 icu4c [pps] boost [rpms.all(distro=centos_stream,distro_vers=8)] boost169 boost169-math boost169-test boost169-fiber boost169-graph boost169-log boost169-openmpi boost169-timer boost169-chrono boost169-locale boost169-thread boost169-atomic boost169-random boost169-static boost169-contract boost169-date-time boost169-iostreams boost169-container boost169-coroutine boost169-filesystem boost169-system boost169-stacktrace boost169-regex boost169-devel boost169-context boost169-python3-devel boost169-type_erasure boost169-wave boost169-python3 boost169-serialization boost169-program-options [rpms.distro=fedora] boost-devel boost-static [build] builder = boost job_weight_mib = 512 patchfile = boost_1_83_0.patch [build.not(os=linux)] builder = nop [b2.args] --with-atomic --with-chrono --with-container --with-context --with-contract --with-coroutine --with-date_time --with-exception --with-fiber --with-filesystem --with-graph --with-graph_parallel --with-iostreams --with-locale --with-log --with-math --with-mpi --with-program_options --with-python --with-random --with-regex --with-serialization --with-stacktrace --with-system --with-test --with-thread --with-timer --with-type_erasure [bootstrap.args.os=darwin] # Not really gcc, but CI puts a broken clang in the PATH, and saying gcc # here selects the correct one from Xcode. --with-toolset=gcc [b2.args.os=linux] # RHEL hardened gcc is not compatible with PCH # https://bugzilla.redhat.com/show_bug.cgi?id=1806545 pch=off # Python extensions need -fPIC for static library linking into shared objects cxxflags="-fPIC" [b2.args.os=darwin] toolset=clang # Since Xcode 15.3 std::piecewise_construct is only visible in C++17 and later modes cxxflags="-DBOOST_UNORDERED_HAVE_PIECEWISE_CONSTRUCT=0" [b2.args.all(os=windows,fb=on)] toolset=msvc-14.3 ================================================ FILE: build/fbcode_builder/manifests/bz2 ================================================ [manifest] name = bz2 [debs] libbz2-dev bzip2 [homebrew] bzip2 [rpms] bzip2-devel bzip2 [download] url = https://sourceware.org/pub/bzip2/bzip2-1.0.8.tar.gz sha256 = ab5a03176ee106d3f0fa90e381da478ddae405918153cca248e682cd0c4a2269 [build.not(os=windows)] builder = make subdir = bzip2-1.0.8 [make.build_args.os=linux] # python bz2 support on linux needs dynamic library -f Makefile-libbz2_so [make.install_args] install [build.os=windows] builder = nop ================================================ FILE: build/fbcode_builder/manifests/c-ares ================================================ [manifest] name = c-ares [download] url = https://github.com/c-ares/c-ares/releases/download/v1.34.6/c-ares-1.34.6.tar.gz sha256 = 912dd7cc3b3e8a79c52fd7fb9c0f4ecf0aaa73e45efda880266a2d6e26b84ef5 [build] builder = cmake subdir = c-ares-1.34.6 [cmake.defines] CARES_STATIC = ON [cmake.defines.shared_libs=off] CARES_SHARED = OFF ================================================ FILE: build/fbcode_builder/manifests/cabal ================================================ [manifest] name = cabal [download.os=linux] url = https://downloads.haskell.org/~cabal/cabal-install-3.6.2.0/cabal-install-3.6.2.0-x86_64-linux-deb10.tar.xz sha256 = 4759b56e9257e02f29fa374a6b25d6cb2f9d80c7e3a55d4f678a8e570925641c [build] builder = nop [install.files] cabal = bin/cabal ================================================ FILE: build/fbcode_builder/manifests/cachelib ================================================ [manifest] name = cachelib fbsource_path = fbcode/cachelib shipit_project = cachelib shipit_fbcode_builder = true [git] repo_url = https://github.com/facebook/cachelib.git [build] builder = cmake subdir = cachelib job_weight_mib = 2048 [dependencies] zlib fizz fmt folly fbthrift googletest sparsemap wangle zstd mvfst numa libaio magic_enum # cachelib also depends on openssl but since the latter requires a platform- # specific configuration we rely on the folly manifest to provide this # dependency to avoid duplication. [dependencies.all(distro=centos_stream,distro_vers=9)] gcc14 [cmake.defines.all(distro=centos_stream,distro_vers=9)] CMAKE_C_COMPILER=/opt/rh/gcc-toolset-14/root/usr/bin/gcc CMAKE_CXX_COMPILER=/opt/rh/gcc-toolset-14/root/usr/bin/g++ [shipit.pathmap] fbcode/cachelib = cachelib fbcode/cachelib/public_tld = . [shipit.strip] ^fbcode/cachelib/examples(/|$) ^fbcode/cachelib/facebook(/|$) ^fbcode/cachelib/public_tld/website/docs/facebook(/|$) ^fbcode/cachelib/public_tld/website/node_modules(/|$) ^fbcode/cachelib/public_tld/website/build(/|$) ================================================ FILE: build/fbcode_builder/manifests/cinderx-3_14 ================================================ [manifest] name = cinderx-3_14 fbsource_path = fbcode/cinderx shipit_project = facebookincubator/cinderx [git] repo_url = https://github.com/facebookincubator/cinderx.git [build.os=linux] builder = setup-py [build.not(os=linux)] builder = nop [dependencies] python-setuptools python-3_14 [shipit.pathmap] fbcode/cinderx = cinderx fbcode/cinderx/oss_toplevel = . [setup-py.test] python_script = cinderx/PythonLib/test_cinderx/test_oss_quick.py [setup-py.env] CINDERX_ENABLE_PGO=1 CINDERX_ENABLE_LTO=1 ================================================ FILE: build/fbcode_builder/manifests/cinderx-main ================================================ # For building CinderX against CPython main. # Note that externally this can be broken because in that environment we will # be checking out the head of the CPython repo. However CinderX is only built # and tested against our internal copy of CPython which updates ~daily, and so # may be behind CPython head. [manifest] name = cinderx-main fbsource_path = fbcode/cinderx shipit_project = facebookincubator/cinderx [git] repo_url = https://github.com/facebookincubator/cinderx.git [build.os=linux] builder = setup-py [build.not(os=linux)] builder = nop [dependencies] python-setuptools python-main [shipit.pathmap] fbcode/cinderx = cinderx fbcode/cinderx/oss_toplevel = . [setup-py.test] python_script = cinderx/PythonLib/test_cinderx/test_oss_quick.py [setup-py.env] CINDERX_ENABLE_PGO=1 CINDERX_ENABLE_LTO=1 ================================================ FILE: build/fbcode_builder/manifests/clang ================================================ [manifest] name = clang [rpms] clang15-devel ================================================ FILE: build/fbcode_builder/manifests/clang19 ================================================ [manifest] name = clang19 [debs.os=linux] clang-19 [rpms.os=linux] clang ================================================ FILE: build/fbcode_builder/manifests/cmake ================================================ [manifest] name = cmake [homebrew] cmake # 18.04 cmake is too old [debs.not(all(distro=ubuntu,distro_vers="18.04"))] cmake [rpms] cmake [pps] cmake [dependencies] ninja [download.os=windows] url = https://github.com/Kitware/CMake/releases/download/v3.20.4/cmake-3.20.4-windows-x86_64.zip sha256 = 965d2f001c3ca807d288f2b6b15c42b25579a0e73ef12c2a72c95f4c69123638 [download.os=darwin] url = https://github.com/Kitware/CMake/releases/download/v3.20.4/cmake-3.20.4-macos-universal.tar.gz sha256 = df90016635e3183834143c6d94607f0804fe9762f7cc6032f6a4afd7c19cd43b [download.any(os=linux,os=freebsd)] url = https://github.com/Kitware/CMake/releases/download/v3.20.4/cmake-3.20.4.tar.gz sha256 = 87a4060298f2c6bb09d479de1400bc78195a5b55a65622a7dceeb3d1090a1b16 [build.os=windows] builder = nop subdir = cmake-3.20.4-windows-x86_64 [build.os=darwin] builder = nop subdir = cmake-3.20.4-macos-universal [install.files.os=darwin] CMake.app/Contents/bin = bin CMake.app/Contents/share = share [build.any(os=linux,os=freebsd)] builder = cmakebootstrap subdir = cmake-3.20.4 [make.install_args.any(os=linux,os=freebsd)] install ================================================ FILE: build/fbcode_builder/manifests/cpptoml ================================================ [manifest] name = cpptoml [homebrew] cpptoml [download] url = https://github.com/chadaustin/cpptoml/archive/refs/tags/v0.1.2.tar.gz sha256 = beda37e94f9746874436c8090c045fd80ae6f8a51f7c668c932a2b110a4fc277 [build] builder = cmake subdir = cpptoml-0.1.2 [cmake.defines.os=freebsd] ENABLE_LIBCXX=NO ================================================ FILE: build/fbcode_builder/manifests/double-conversion ================================================ [manifest] name = double-conversion [download] url = https://github.com/google/double-conversion/archive/v3.1.4.tar.gz sha256 = 95004b65e43fefc6100f337a25da27bb99b9ef8d4071a36a33b5e83eb1f82021 [homebrew] double-conversion [debs] libdouble-conversion-dev [rpms] double-conversion double-conversion-devel [pps] double-conversion [build] builder = cmake subdir = double-conversion-3.1.4 ================================================ FILE: build/fbcode_builder/manifests/double-conversion-python ================================================ [manifest] name = double-conversion-python [download] url = https://github.com/google/double-conversion/archive/v3.1.4.tar.gz sha256 = 95004b65e43fefc6100f337a25da27bb99b9ef8d4071a36a33b5e83eb1f82021 [homebrew] double-conversion [debs] libdouble-conversion-dev [rpms] double-conversion double-conversion-devel [pps] double-conversion [build] builder = cmake subdir = double-conversion-3.1.4 [build.not(os=linux)] builder = nop [cmake.defines] CMAKE_POSITION_INDEPENDENT_CODE=ON ================================================ FILE: build/fbcode_builder/manifests/eden ================================================ [manifest] name = eden fbsource_path = fbcode/eden shipit_project = eden shipit_fbcode_builder = true [git] repo_url = https://github.com/facebook/sapling.git [github.actions] run_tests = off [sandcastle] run_tests = off [build] builder = cmake [dependencies] blake3 googletest folly fbthrift fb303 cpptoml rocksdb re2 libgit2 pexpect python-psutil python-toml python-filelock edencommon rust-shed [dependencies.fbsource=on] rust # macOS ships with sqlite3, and some of the core system # frameworks require that that version be linked rather # than the one we might build for ourselves here, so we # skip building it on macos. [dependencies.not(os=darwin)] sqlite3 [dependencies.os=darwin] osxfuse [dependencies.not(os=windows)] # TODO: teach getdeps to compile curl on Windows. # Enabling curl on Windows requires us to find a way to compile libcurl with # msvc. libcurl # Added so that OSS doesn't see system "python" which is python 2 on darwin and some linux python # TODO: teach getdeps to compile lmdb on Windows. lmdb [dependencies.test=on] # sapling CLI is needed to run the tests sapling [shipit.pathmap.fb=on] # for internal builds that use getdeps fbcode/fb303 = fb303 fbcode/common/rust/shed = common/rust/shed fbcode/thrift/lib/cpp = thrift/lib/cpp fbcode/thrift/lib/cpp2 = thrift/lib/cpp2 fbcode/thrift/lib/java = thrift/lib/java fbcode/thrift/lib/py = thrift/lib/py fbcode/thrift/lib/python = thrift/lib/python fbcode/thrift/lib/rust = thrift/lib/rust [shipit.pathmap] # Map hostcaps for now as eden C++ includes its .h. Rust-shed should install it fbcode/common/rust/shed/hostcaps = common/rust/shed/hostcaps fbcode/configerator/structs/scm/hg = configerator/structs/scm/hg fbcode/eden/oss = . fbcode/eden = eden fbcode/tools/lfs = tools/lfs [shipit.pathmap.fb=off] fbcode/eden/fs/public_autocargo = eden/fs fbcode/eden/scm/public_autocargo = eden/scm fbcode/common/rust/shed/hostcaps/public_cargo = common/rust/shed/hostcaps fbcode/configerator/structs/scm/hg/public_autocargo = configerator/structs/scm/hg [shipit.strip] ^fbcode/eden/addons/.*$ ^fbcode/eden/fs/eden-config\.h$ ^fbcode/eden/fs/py/eden/config\.py$ ^fbcode/eden/hg-server/.*$ ^fbcode/eden/mononoke/(?!lfs_protocol) ^fbcode/eden/scm/build/.*$ ^fbcode/eden/scm/lib/third-party/rust/.*/Cargo.toml$ ^fbcode/eden/website/.*$ ^fbcode/eden/.*/\.cargo/.*$ /Cargo\.lock$ \.pyc$ [shipit.strip.fb=off] ^fbcode/common/rust/shed(?!/public_autocargo).*/Cargo\.toml$ ^fbcode/configerator/structs/scm/hg(?!/public_autocargo).*/Cargo\.toml$ ^fbcode/eden/fs(?!/public_autocargo).*/Cargo\.toml$ ^fbcode/eden/scm(?!/public_autocargo|/saplingnative).*/Cargo\.toml$ ^.*/facebook/.*$ ^.*/fb/.*$ [cmake.defines.all(fb=on,os=windows)] ENABLE_GIT=OFF INSTALL_PYTHON_LIB=ON [cmake.defines.all(not(fb=on),os=windows)] ENABLE_GIT=OFF [cmake.defines.fbsource=on] USE_CARGO_VENDOR=ON [cmake.defines.fb=on] IS_FB_BUILD=ON [depends.environment] EDEN_VERSION_OVERRIDE ================================================ FILE: build/fbcode_builder/manifests/edencommon ================================================ [manifest] name = edencommon fbsource_path = fbcode/eden/common shipit_project = edencommon shipit_fbcode_builder = true [git] repo_url = https://github.com/facebookexperimental/edencommon.git [build] builder = cmake [dependencies] fbthrift fb303 fmt folly gflags glog [cmake.defines.test=on] BUILD_TESTS=ON [cmake.defines.test=off] BUILD_TESTS=OFF [shipit.pathmap] fbcode/eden/common = eden/common fbcode/eden/common/oss = . [shipit.strip] @README.facebook@ ================================================ FILE: build/fbcode_builder/manifests/exprtk ================================================ [manifest] name = exprtk [download] url = https://github.com/ArashPartow/exprtk/archive/refs/tags/0.0.1.tar.gz sha256 = fb72791c88ae3b3426e14fdad630027715682584daf56b973569718c56e33f28 [build.not(os=windows)] builder = nop subdir = exprtk-0.0.1 [install.files] exprtk.hpp = exprtk.hpp [dependencies] ================================================ FILE: build/fbcode_builder/manifests/fast_float ================================================ [manifest] name = fast_float [download] url = https://github.com/fastfloat/fast_float/archive/refs/tags/v8.0.0.tar.gz sha256 = f312f2dc34c61e665f4b132c0307d6f70ad9420185fa831911bc24408acf625d [build] builder = cmake subdir = fast_float-8.0.0 [cmake.defines] FASTFLOAT_TEST = OFF FASTFLOAT_SANITIZE = OFF [debs.not(all(distro=ubuntu,any(distro_vers="18.04",distro_vers="20.04",distro_vers="22.04",distro_vers="24.04")))] libfast-float-dev [rpms.distro=fedora] fast_float-devel ================================================ FILE: build/fbcode_builder/manifests/fatal ================================================ [manifest] name = fatal fbsource_path = fbcode/fatal shipit_project = fatal [git] repo_url = https://github.com/facebook/fatal.git [shipit.pathmap] fbcode/fatal = fatal fbcode/fatal/public_tld = . [build] builder = nop subdir = . [install.files] fatal/portability.h = fatal/portability.h fatal/preprocessor.h = fatal/preprocessor.h fatal/container = fatal/container fatal/functional = fatal/functional fatal/math = fatal/math fatal/string = fatal/string fatal/type = fatal/type ================================================ FILE: build/fbcode_builder/manifests/fb303 ================================================ [manifest] name = fb303 fbsource_path = fbcode/fb303 shipit_project = fb303 shipit_fbcode_builder = true [git] repo_url = https://github.com/facebook/fb303.git [cargo] cargo_config_file = source/fb303/thrift/.cargo/config.toml [crate.pathmap] fb303_core = fb303/thrift/rust [build] builder = cmake [dependencies] folly gflags glog fbthrift [cmake.defines.test=on] BUILD_TESTS=ON [cmake.defines.test=off] BUILD_TESTS=OFF [shipit.pathmap] fbcode/fb303/github = . fbcode/fb303/public_autocargo = fb303 fbcode/fb303 = fb303 [shipit.strip] ^fbcode/fb303/(?!public_autocargo).+/Cargo\.toml$ ================================================ FILE: build/fbcode_builder/manifests/fboss ================================================ [manifest] name = fboss fbsource_path = fbcode/fboss shipit_project = fboss shipit_fbcode_builder = true [git] repo_url = https://github.com/facebook/fboss.git [build.os=linux] builder = cmake # fboss files take a lot of RAM to compile. job_weight_mib = 3072 [build.not(os=linux)] builder = nop [dependencies] folly fb303 wangle fizz mvfst fmt libsodium googletest zstd fatal fbthrift iproute2 libusb libcurl libnl libsai re2 python yaml-cpp libyaml CLI11 exprtk nlohmann-json libgpiod systemd range-v3 tabulate gcc12 python-pyyaml # ShipitPathMap always assume to use directory to fetch the source code. # If you need to sync the files to fboss github repo, please make changes in # configerator/source/opensource/shipit_config/facebook/fboss.cconf [shipit.pathmap] fbcode/fboss/github = . fbcode/fboss/common = common fbcode/fboss = fboss # NOTE: Although this directory has other thrift files might not be ready for # opensource, this pathmap should only be used internally `getdeps.py fetch` fbcode/configerator/structs/neteng/fboss/thrift = configerator/structs/neteng/fboss/thrift [shipit.strip] ^fbcode/fboss/github/docs/.* ^fbcode/fboss/oss/.* ^fbcode/fboss/github/.github/.* ^fbcode/fboss/github/fboss-image/.* ^fbcode/fboss/github/.pre-commit-config.yaml ^fbcode/fboss/github/requirements-dev.txt ^fbcode/fboss/.llms/.* [sandcastle] run_tests = off ================================================ FILE: build/fbcode_builder/manifests/fbthrift ================================================ [manifest] name = fbthrift fbsource_path = xplat/thrift shipit_project = fbthrift shipit_fbcode_builder = true [git] repo_url = https://github.com/facebook/fbthrift.git [cargo] cargo_config_file = source/thrift/lib/rust/.cargo/config.toml [crate.pathmap] fbthrift = thrift/lib/rust [build] builder = cmake job_weight_mib = 2048 [cmake.defines.all(not(os=windows),test=on)] enable_tests=ON [cmake.defines.any(os=windows,test=off)] enable_tests=OFF [dependencies] fizz fmt folly googletest libsodium wangle zstd mvfst xxhash # Thrift also depends on openssl but since the latter requires a platform- # specific configuration we rely on the folly manifest to provide this # dependency to avoid duplication. [shipit.pathmap] xplat/thrift/public_tld = . xplat/thrift = thrift [shipit.strip] ^xplat/thrift/thrift-config\.h$ ^xplat/thrift/perf/canary.py$ ^xplat/thrift/perf/loadtest.py$ ^xplat/thrift/.castle/.* ================================================ FILE: build/fbcode_builder/manifests/fbthrift-python ================================================ [manifest] name = fbthrift-python fbsource_path = xplat/thrift shipit_project = fbthrift shipit_fbcode_builder = true [git] repo_url = https://github.com/facebook/fbthrift.git [cargo] cargo_config_file = source/thrift/lib/rust/.cargo/config.toml [crate.pathmap] fbthrift = thrift/lib/rust [build] builder = cmake job_weight_mib = 2048 [build.not(os=linux)] builder = nop [cmake.defines.all(not(os=windows),test=on)] enable_tests=ON [cmake.defines.any(os=windows,test=off)] enable_tests=OFF [cmake.defines.os=linux] thrift_python=ON enable_tests=ON [dependencies] fizz-python fmt-python folly-python googletest libsodium wangle-python zstd-python mvfst-python xxhash # Thrift also depends on openssl but since the latter requires a platform- # specific configuration we rely on the folly manifest to provide this # dependency to avoid duplication. [dependencies.os=linux] libaio-python libevent-python [shipit.pathmap] xplat/thrift/public_tld = . xplat/thrift = thrift [shipit.strip] ^xplat/thrift/thrift-config\.h$ ^xplat/thrift/perf/canary.py$ ^xplat/thrift/perf/loadtest.py$ ^xplat/thrift/.castle/.* ================================================ FILE: build/fbcode_builder/manifests/fizz ================================================ [manifest] name = fizz fbsource_path = fbcode/fizz shipit_project = fizz shipit_fbcode_builder = true [git] repo_url = https://github.com/facebookincubator/fizz.git [build] builder = cmake subdir = fizz [cmake.defines] BUILD_EXAMPLES = OFF [cmake.defines.test=on] BUILD_TESTS = ON [cmake.defines.all(os=windows, test=on)] BUILD_TESTS = OFF [cmake.defines.test=off] BUILD_TESTS = OFF [dependencies] folly liboqs libsodium zlib zstd [dependencies.all(test=on, not(os=windows))] googletest [shipit.pathmap] fbcode/fizz/public_tld = . fbcode/fizz = fizz ================================================ FILE: build/fbcode_builder/manifests/fizz-python ================================================ [manifest] name = fizz-python fbsource_path = fbcode/fizz shipit_project = fizz shipit_fbcode_builder = true [git] repo_url = https://github.com/facebookincubator/fizz.git [build] builder = cmake subdir = fizz [build.not(os=linux)] builder = nop [cmake.defines] BUILD_EXAMPLES = OFF [cmake.defines.os=linux] CMAKE_POSITION_INDEPENDENT_CODE = ON BUILD_SHARED_LIBS = ON [cmake.defines.test=on] BUILD_TESTS = ON [cmake.defines.all(os=windows, test=on)] BUILD_TESTS = OFF [cmake.defines.test=off] BUILD_TESTS = OFF [dependencies] folly-python liboqs libsodium zlib-python zstd-python [dependencies.all(test=on, not(os=windows))] googletest [shipit.pathmap] fbcode/fizz/public_tld = . fbcode/fizz = fizz ================================================ FILE: build/fbcode_builder/manifests/fmt ================================================ [manifest] name = fmt [download] url = https://github.com/fmtlib/fmt/archive/refs/tags/12.1.0.tar.gz sha256 = ea7de4299689e12b6dddd392f9896f08fb0777ac7168897a244a6d6085043fea [build] builder = cmake subdir = fmt-12.1.0 [cmake.defines] FMT_TEST = OFF FMT_DOC = OFF [homebrew] fmt [rpms.distro=fedora] fmt-devel ================================================ FILE: build/fbcode_builder/manifests/fmt-python ================================================ [manifest] name = fmt-python [download] url = https://github.com/fmtlib/fmt/archive/refs/tags/12.1.0.tar.gz sha256 = ea7de4299689e12b6dddd392f9896f08fb0777ac7168897a244a6d6085043fea [build] builder = cmake subdir = fmt-12.1.0 [build.not(os=linux)] builder = nop [cmake.defines] FMT_TEST = OFF FMT_DOC = OFF # Build as shared library so Python extensions can find fmt symbols at runtime # (fmt uses -fvisibility=hidden, so static linking leaves symbols unexported) BUILD_SHARED_LIBS = ON [homebrew] fmt [rpms.distro=fedora] fmt-devel ================================================ FILE: build/fbcode_builder/manifests/folly ================================================ [manifest] name = folly fbsource_path = fbcode/folly shipit_project = folly shipit_fbcode_builder = true [git] repo_url = https://github.com/facebook/folly.git [build] builder = cmake job_weight_mib = 1024 [dependencies] gflags glog googletest boost libdwarf libevent libsodium double-conversion fast_float fmt lz4 snappy zstd # no openssl or zlib in the linux case, why? # these are usually installed on the system # and are the easiest system deps to pull in. # In the future we want to be able to express # that a system dep is sufficient in the manifest # for eg: openssl and zlib, but for now we don't # have it. # macOS doesn't expose the openssl api so we need # to build our own. [dependencies.os=darwin] openssl # Windows has neither openssl nor zlib, so we get # to provide both [dependencies.os=windows] openssl zlib [dependencies.os=linux] libaio libiberty libunwind # xz depends on autoconf which does not build on # Windows [dependencies.not(os=windows)] xz [shipit.pathmap] fbcode/folly/public_tld = . fbcode/folly = folly [shipit.strip] ^fbcode/folly/folly-config\.h$ ^fbcode/folly/public_tld/build/facebook_.* [cmake.defines] BUILD_SHARED_LIBS=OFF [cmake.defines.not(os=windows)] BOOST_LINK_STATIC=ON [cmake.defines.os=freebsd] LIBDWARF_FOUND=NO [cmake.defines.test=on] BUILD_TESTS=ON BUILD_BENCHMARKS=OFF [cmake.defines.test=off] BUILD_TESTS=OFF BUILD_BENCHMARKS=OFF ================================================ FILE: build/fbcode_builder/manifests/folly-python ================================================ [manifest] name = folly-python fbsource_path = fbcode/folly shipit_project = folly shipit_fbcode_builder = true [git] repo_url = https://github.com/facebook/folly.git [build] builder = cmake job_weight_mib = 1024 [build.not(os=linux)] builder = nop [dependencies] gflags glog googletest boost-python libdwarf-python libevent-python libsodium double-conversion-python fast_float fmt-python lz4-python snappy zstd-python # no openssl or zlib in the linux case, why? # these are usually installed on the system # and are the easiest system deps to pull in. # In the future we want to be able to express # that a system dep is sufficient in the manifest # for eg: openssl and zlib, but for now we don't # have it. # macOS doesn't expose the openssl api so we need # to build our own. [dependencies.os=darwin] openssl # Windows has neither openssl nor zlib, so we get # to provide both [dependencies.os=windows] openssl zlib [dependencies.os=linux] libaio-python libiberty-python libunwind # xz depends on autoconf which does not build on # Windows [dependencies.not(os=windows)] xz [shipit.pathmap] fbcode/folly/public_tld = . fbcode/folly = folly [shipit.strip] ^fbcode/folly/folly-config\.h$ ^fbcode/folly/public_tld/build/facebook_.* [cmake.defines.os=linux] PYTHON_EXTENSIONS=ON BUILD_SHARED_LIBS=ON [cmake.defines.not(os=windows)] BOOST_LINK_STATIC=ON [cmake.defines.os=freebsd] LIBDWARF_FOUND=NO [cmake.defines.test=on] BUILD_TESTS=ON BUILD_BENCHMARKS=OFF [cmake.defines.test=off] BUILD_TESTS=OFF BUILD_BENCHMARKS=OFF ================================================ FILE: build/fbcode_builder/manifests/gcc12 ================================================ [manifest] name = gcc12 [rpms.all(distro=centos_stream,distro_vers=9)] gcc-toolset-12 ================================================ FILE: build/fbcode_builder/manifests/gcc14 ================================================ [manifest] name = gcc14 [rpms.all(distro=centos_stream,distro_vers=9)] gcc-toolset-14 ================================================ FILE: build/fbcode_builder/manifests/gflags ================================================ [manifest] name = gflags [download] url = https://github.com/gflags/gflags/archive/v2.2.2.tar.gz sha256 = 34af2f15cf7367513b352bdcd2493ab14ce43692d2dcd9dfc499492966c64dcf [build] builder = cmake subdir = gflags-2.2.2 [cmake.defines] BUILD_SHARED_LIBS = ON BUILD_STATIC_LIBS = ON #BUILD_gflags_nothreads_LIB = OFF BUILD_gflags_LIB = ON [homebrew] gflags [debs] libgflags-dev [rpms.distro=fedora] gflags-devel ================================================ FILE: build/fbcode_builder/manifests/ghc ================================================ [manifest] name = ghc [download.os=linux] url = https://downloads.haskell.org/~ghc/9.2.8/ghc-9.2.8-x86_64-fedora27-linux.tar.xz sha256 = 845f63cd365317bb764d81025554a2527dbe315d6fa268c9859e21b911bf2d3c [build] builder = autoconf subdir = ghc-9.2.8 build_in_src_dir = true only_install = true [make.install_args] install ================================================ FILE: build/fbcode_builder/manifests/git-lfs ================================================ [manifest] name = git-lfs [rpms] git-lfs [debs] git-lfs [homebrew] git-lfs # only used from system packages currently [build] builder = nop ================================================ FILE: build/fbcode_builder/manifests/glean ================================================ [manifest] name = glean fbsource_path = fbcode/glean shipit_project = facebookincubator/Glean use_shipit = true [shipit.pathmap] # These are only used by target determinator to trigger builds, the # real path mappings are in the ShipIt config. fbcode/glean = glean fbcode/common/hs = hsthrift [subprojects] hsthrift = hsthrift [dependencies] cabal ghc gflags glog folly rocksdb xxhash llvm clang re2 [build] builder = make [make.build_args] setup-folly setup-folly-version cabal-update all glean-hie glass glean-clang EXTRA_GHC_OPTS=-j4 +RTS -A32m -n4m -RTS CABAL_CONFIG_FLAGS=-f-hack-tests -f-typescript-tests -f-python-tests -f-dotnet-tests -f-go-tests -f-rust-tests -f-java-lsif-tests -f-flow-tests -f-bundled-folly [make.install_args] install [make.test_args] test EXTRA_GHC_OPTS=-j4 +RTS -A32m -n4m -RTS CABAL_CONFIG_FLAGS=-f-hack-tests -f-typescript-tests -f-python-tests -f-dotnet-tests -f-go-tests -f-rust-tests -f-java-lsif-tests -f-flow-tests -f-bundled-folly ================================================ FILE: build/fbcode_builder/manifests/glog ================================================ [manifest] name = glog [download] url = https://github.com/google/glog/archive/v0.5.0.tar.gz sha256 = eede71f28371bf39aa69b45de23b329d37214016e2055269b3b5e7cfd40b59f5 [build] builder = cmake subdir = glog-0.5.0 [dependencies] gflags [cmake.defines] BUILD_SHARED_LIBS=ON BUILD_TESTING=NO WITH_PKGCONFIG=ON [cmake.defines.os=freebsd] HAVE_TR1_UNORDERED_MAP=OFF HAVE_TR1_UNORDERED_SET=OFF [homebrew] glog # on ubuntu glog brings in liblzma-dev, which in turn breaks watchman tests [debs.not(distro=ubuntu)] libgoogle-glog-dev [rpms.distro=fedora] glog-devel ================================================ FILE: build/fbcode_builder/manifests/googletest ================================================ [manifest] name = googletest [download] url = https://github.com/google/googletest/archive/refs/tags/v1.17.0.tar.gz sha256 = 65fab701d9829d38cb77c14acdc431d2108bfdbf8979e40eb8ae567edf10b27c [build] builder = cmake subdir = googletest-1.17.0 [cmake.defines] # Everything else defaults to the shared runtime, so tell gtest that # it should not use its choice of the static runtime gtest_force_shared_crt=ON [cmake.defines.os=windows] BUILD_SHARED_LIBS=ON [homebrew] googletest # packaged googletest is too old [debs.not(all(distro=ubuntu,any(distro_vers="18.04",distro_vers="20.04",distro_vers="22.04")))] libgtest-dev libgmock-dev [rpms.distro=fedora] gmock-devel gtest-devel ================================================ FILE: build/fbcode_builder/manifests/gperf ================================================ [manifest] name = gperf [download] url = https://ftpmirror.gnu.org/gnu/gperf/gperf-3.1.tar.gz sha256 = 588546b945bba4b70b6a3a616e80b4ab466e3f33024a352fc2198112cdbb3ae2 [build.not(os=windows)] builder = autoconf subdir = gperf-3.1 [build.os=windows] builder = nop ================================================ FILE: build/fbcode_builder/manifests/hexdump ================================================ [manifest] name = hexdump [rpms] util-linux [debs] bsdmainutils # only used from system packages currently [build] builder = nop ================================================ FILE: build/fbcode_builder/manifests/hsthrift ================================================ [manifest] name = hsthrift fbsource_path = fbcode/common/hs shipit_project = facebookincubator/hsthrift use_shipit = true [shipit.pathmap] # These are only used by target determinator to trigger builds, the # real path mappings are in the ShipIt config. fbcode/common/hs = . [dependencies] cabal ghc gflags glog folly fbthrift wangle fizz boost [build] builder = make [make.build_args] setup-folly setup-meta cabal-update all [make.install_args] install [make.test_args] test ================================================ FILE: build/fbcode_builder/manifests/iproute2 ================================================ [manifest] name = iproute2 [download] url = https://mirrors.edge.kernel.org/pub/linux/utils/net/iproute2/iproute2-4.12.0.tar.gz sha256 = 46612a1e2d01bb31932557bccdb1b8618cae9a439dfffc08ef35ed8e197f14ce [build.os=linux] builder = iproute2 subdir = iproute2-4.12.0 patchfile = iproute2_oss.patch [build.not(os=linux)] builder = nop ================================================ FILE: build/fbcode_builder/manifests/jom ================================================ # jom is compatible with MSVC nmake, but adds the /j argment which # speeds up openssl build a lot [manifest] name = jom # see https://download.qt.io/official_releases/jom/changelog.txt for latest version [download.os=windows] url = https://download.qt.io/official_releases/jom/jom_1_1_4.zip sha256 = d533c1ef49214229681e90196ed2094691e8c4a0a0bef0b2c901debcb562682b [build.os=windows] builder = nop [install.files.os=windows] . = bin ================================================ FILE: build/fbcode_builder/manifests/jq ================================================ [manifest] name = jq [rpms.distro=fedora] jq [homebrew] jq [download.not(os=windows)] # we use jq-1.7+ to get fix for number truncation https://github.com/jqlang/jq/pull/1752 url = https://github.com/jqlang/jq/releases/download/jq-1.7.1/jq-1.7.1.tar.gz sha256 = 478c9ca129fd2e3443fe27314b455e211e0d8c60bc8ff7df703873deeee580c2 [build.not(os=windows)] builder = autoconf subdir = jq-1.7.1 [build.os=windows] builder = nop [autoconf.args] # This argument turns off some developers tool and it is recommended in jq's # README --disable-maintainer-mode ================================================ FILE: build/fbcode_builder/manifests/katran ================================================ [manifest] name = katran fbsource_path = fbcode/katran shipit_project = katran shipit_fbcode_builder = true [git] repo_url = https://github.com/facebookincubator/katran.git [build.not(os=linux)] builder = nop [build.os=linux] builder = cmake subdir = . [cmake.defines.test=on] BUILD_TESTS=ON [cmake.defines.test=off] BUILD_TESTS=OFF [dependencies] folly fizz libbpf libmnl zlib googletest fmt [debs] libssl-dev [shipit.pathmap] fbcode/katran/public_root = . fbcode/katran = katran [shipit.strip] ^fbcode/katran/facebook ^fbcode/katran/OSS_SYNC ================================================ FILE: build/fbcode_builder/manifests/libaio ================================================ [manifest] name = libaio [debs] libaio-dev [rpms.distro=centos_stream] libaio-devel [download] url = https://pagure.io/libaio/archive/libaio-0.3.113/libaio-libaio-0.3.113.tar.gz sha256 = 716c7059703247344eb066b54ecbc3ca2134f0103307192e6c2b7dab5f9528ab [build] builder = make subdir = libaio-libaio-0.3.113 [make.build_args.shared_libs=off] ENABLE_SHARED=0 [make.install_args] install [make.install_args.shared_libs=off] ENABLE_SHARED=0 ================================================ FILE: build/fbcode_builder/manifests/libaio-python ================================================ [manifest] name = libaio-python [debs] libaio-dev [rpms.distro=centos_stream] libaio-devel [download] url = https://pagure.io/libaio/archive/libaio-0.3.113/libaio-libaio-0.3.113.tar.gz sha256 = 716c7059703247344eb066b54ecbc3ca2134f0103307192e6c2b7dab5f9528ab [build] builder = make subdir = libaio-libaio-0.3.113 [build.not(os=linux)] builder = nop [make.build_args] CFLAGS=-fPIC -O2 [make.install_args] install ================================================ FILE: build/fbcode_builder/manifests/libbpf ================================================ [manifest] name = libbpf [download] url = https://github.com/libbpf/libbpf/archive/refs/tags/v1.6.2.tar.gz sha256 = 16f31349c70764cba8e0fad3725cc9f52f6cf952554326aa0229daaa21ef4fbd # BPF only builds on linux, so make it a NOP on other platforms [build.not(os=linux)] builder = nop [build.os=linux] builder = make subdir = libbpf-1.6.2/src [make.build_args] BUILD_STATIC_ONLY=y # libbpf-0.3 requires uapi headers >= 5.8 [make.install_args] install install_uapi_headers BUILD_STATIC_ONLY=y [dependencies] libelf ================================================ FILE: build/fbcode_builder/manifests/libcurl ================================================ [manifest] name = libcurl [rpms] libcurl-devel libcurl-minimal [debs] libcurl4-openssl-dev [pps] libcurl-gnutls [download] url = https://curl.haxx.se/download/curl-7.65.1.tar.gz sha256 = 821aeb78421375f70e55381c9ad2474bf279fc454b791b7e95fc83562951c690 [dependencies] nghttp2 # We use system OpenSSL on Linux (see folly's manifest for details) [dependencies.not(os=linux)] openssl [build.not(os=windows)] builder = autoconf subdir = curl-7.65.1 [autoconf.args] # fboss (which added the libcurl dep) doesn't need ldap so it is disabled here. # if someone in the future wants to add ldap for something else, it won't hurt # fboss. However, that would require adding an ldap manifest. # # For the same reason, we disable libssh2 and libidn2 which aren't really used # but would require adding manifests if we don't disable them. --disable-ldap --without-libssh2 --without-libidn2 [build.os=windows] builder = cmake subdir = curl-7.65.1 ================================================ FILE: build/fbcode_builder/manifests/libdwarf ================================================ [manifest] name = libdwarf [rpms] libdwarf-devel libdwarf [debs] libdwarf-dev [homebrew] dwarfutils [download] url = https://www.prevanders.net/libdwarf-0.9.2.tar.xz sha256 = 22b66d06831a76f6a062126cdcad3fcc58540b89a1acb23c99f8861f50999ec3 [build] builder = cmake subdir = libdwarf-0.9.2 ================================================ FILE: build/fbcode_builder/manifests/libdwarf-python ================================================ [manifest] name = libdwarf-python [rpms] libdwarf-devel libdwarf [debs] libdwarf-dev [homebrew] dwarfutils [download] url = https://www.prevanders.net/libdwarf-0.9.2.tar.xz sha256 = 22b66d06831a76f6a062126cdcad3fcc58540b89a1acb23c99f8861f50999ec3 [build] builder = cmake subdir = libdwarf-0.9.2 [build.not(os=linux)] builder = nop [cmake.defines] CMAKE_POSITION_INDEPENDENT_CODE=ON ================================================ FILE: build/fbcode_builder/manifests/libelf ================================================ [manifest] name = libelf [rpms] elfutils-libelf-devel-static [debs] libelf-dev [pps] libelf [download] url = https://sourceware.org/elfutils/ftp/0.193/elfutils-0.193.tar.bz2 sha256 = 7857f44b624f4d8d421df851aaae7b1402cfe6bcdd2d8049f15fc07d3dde7635 # libelf only makes sense on linux, so make it a NOP on other platforms [build.not(os=linux)] builder = nop [build.os=linux] builder = autoconf subdir = elfutils-0.193 ================================================ FILE: build/fbcode_builder/manifests/libevent ================================================ [manifest] name = libevent [debs] libevent-dev [homebrew] libevent [rpms] libevent-devel [pps] libevent # Note that the CMakeLists.txt file is present only in # git repo and not in the release tarball, so take care # to use the github generated source tarball rather than # the explicitly uploaded source tarball [download] url = https://github.com/libevent/libevent/releases/download/release-2.1.12-stable/libevent-2.1.12-stable.tar.gz sha256 = 92e6de1be9ec176428fd2367677e61ceffc2ee1cb119035037a27d346b0403bb [build] builder = cmake subdir = libevent-2.1.12-stable [cmake.defines] EVENT__DISABLE_TESTS = ON EVENT__DISABLE_BENCHMARK = ON EVENT__DISABLE_SAMPLES = ON EVENT__DISABLE_REGRESS = ON [cmake.defines.shared_libs=on] EVENT__BUILD_SHARED_LIBRARIES = ON [cmake.defines.shared_libs=off] EVENT__LIBRARY_TYPE = STATIC [cmake.defines.os=windows] EVENT__LIBRARY_TYPE = STATIC [dependencies.not(any(os=linux, os=freebsd))] openssl ================================================ FILE: build/fbcode_builder/manifests/libevent-python ================================================ [manifest] name = libevent-python # NOTE: System packages (debs, rpms) removed because they don't include # LibeventConfig.cmake which is required by find_package(Libevent REQUIRED CONFIG). # Building from source ensures CMake config files are present. [homebrew] libevent [pps] libevent # Note that the CMakeLists.txt file is present only in # git repo and not in the release tarball, so take care # to use the github generated source tarball rather than # the explicitly uploaded source tarball [download] url = https://github.com/libevent/libevent/releases/download/release-2.1.12-stable/libevent-2.1.12-stable.tar.gz sha256 = 92e6de1be9ec176428fd2367677e61ceffc2ee1cb119035037a27d346b0403bb [build] builder = cmake subdir = libevent-2.1.12-stable [build.not(os=linux)] builder = nop [cmake.defines] EVENT__DISABLE_TESTS = ON EVENT__DISABLE_BENCHMARK = ON EVENT__DISABLE_SAMPLES = ON EVENT__DISABLE_REGRESS = ON CMAKE_POSITION_INDEPENDENT_CODE=ON [cmake.defines.shared_libs=on] EVENT__BUILD_SHARED_LIBRARIES = ON [cmake.defines.os=windows] EVENT__LIBRARY_TYPE = STATIC [dependencies.not(any(os=linux, os=freebsd))] openssl ================================================ FILE: build/fbcode_builder/manifests/libffi ================================================ [manifest] name = libffi [debs] libffi-dev [homebrew] libffi [rpms] libffi-devel libffi [pps] libffi [download] url = https://github.com/libffi/libffi/releases/download/v3.4.2/libffi-3.4.2.tar.gz sha256 = 540fb721619a6aba3bdeef7d940d8e9e0e6d2c193595bc243241b77ff9e93620 [build] builder = autoconf subdir = libffi-3.4.2 ================================================ FILE: build/fbcode_builder/manifests/libgit2 ================================================ [manifest] name = libgit2 [homebrew] libgit2 [rpms] libgit2-devel [pps] libgit2 # Ubuntu 18.04 libgit2 has clash with libcurl4-openssl-dev as it depends on # libcurl4-gnutls-dev. Should be ok from 20.04 again # There is a description at https://github.com/r-hub/sysreqsdb/issues/77 [debs.not(all(distro=ubuntu,distro_vers="18.04"))] libgit2-dev [download] url = https://github.com/libgit2/libgit2/archive/v0.28.1.tar.gz sha256 = 0ca11048795b0d6338f2e57717370208c2c97ad66c6d5eac0c97a8827d13936b [build] builder = cmake subdir = libgit2-0.28.1 [cmake.defines] # Could turn this on if we also wanted to add a manifest for libssh2 USE_SSH = OFF BUILD_CLAR = OFF # Have to build shared to work around annoying problems with cmake # mis-parsing the frameworks required to link this on macos :-/ BUILD_SHARED_LIBS = ON ================================================ FILE: build/fbcode_builder/manifests/libgpiod ================================================ [manifest] name = libgpiod [download] url = https://cdn.kernel.org/pub/software/libs/libgpiod/libgpiod-1.6.tar.xz sha256 = 62908023d59e8cbb9137ddd14deec50ced862d8f9b8749f288d3dbe7967151ef [build] builder = autoconf subdir = libgpiod-1.6 ================================================ FILE: build/fbcode_builder/manifests/libiberty ================================================ [manifest] name = libiberty [rpms] binutils-devel binutils [debs.not(all(distro=ubuntu,distro_vers="24.04"))] binutils-dev [debs.all(distro=ubuntu,distro_vers="24.04")] binutils-x86-64-linux-gnu [download] url = https://ftpmirror.gnu.org/gnu/binutils/binutils-2.43.tar.xz sha256 = b53606f443ac8f01d1d5fc9c39497f2af322d99e14cea5c0b4b124d630379365 [dependencies] zlib [build] builder = autoconf subdir = binutils-2.43/libiberty patchfile = libiberty_install_pic_lib.patch # only build the parts needed for demangling # as we still want to use system linker and assembler etc [autoconf.args] --enable-install-libiberty ================================================ FILE: build/fbcode_builder/manifests/libiberty-python ================================================ [manifest] name = libiberty-python [rpms] binutils-devel binutils [debs.not(all(distro=ubuntu,distro_vers="24.04"))] binutils-dev [debs.all(distro=ubuntu,distro_vers="24.04")] binutils-x86-64-linux-gnu [download] url = https://ftpmirror.gnu.org/gnu/binutils/binutils-2.43.tar.xz sha256 = b53606f443ac8f01d1d5fc9c39497f2af322d99e14cea5c0b4b124d630379365 [dependencies] zlib-python [build] builder = autoconf subdir = binutils-2.43/libiberty patchfile = libiberty_install_pic_lib.patch [build.not(os=linux)] builder = nop # only build the parts needed for demangling # as we still want to use system linker and assembler etc [autoconf.args] --enable-install-libiberty CFLAGS=-fPIC -O2 PICFLAG=-fPIC ================================================ FILE: build/fbcode_builder/manifests/libibverbs ================================================ [manifest] name = libibverbs [debs] libibverbs-dev rdma-core [rpms] libibverbs rdma-core-devel [download] url = https://github.com/linux-rdma/rdma-core/releases/download/v60.0/rdma-core-60.0.tar.gz sha256 = 9b1b892e4eaaaa5dfbade07a290fbf5079e39117724fa1ef80d0ad78839328de [build] builder = cmake subdir = rdma-core-60.0 [dependencies] libnl [cmake.defines] NO_MAN_PAGES=1 NO_PYVERBS=1 ENABLE_RESOLVE_NEIGH=0 # Use absolute short path for runtime dir to avoid Unix socket path length limit (108 chars) CMAKE_INSTALL_RUNDIR=/tmp/ibacm ================================================ FILE: build/fbcode_builder/manifests/libmnl ================================================ [manifest] name = libmnl [rpms] libmnl-devel # all centos 8 distros are missing this, # but its in fedora so may be back in a later version [rpms.not(all(any(distro=centos_stream,distro=centos),distro_vers=8))] libmnl-static [debs] libmnl-dev [pps] libmnl [download] url = https://www.netfilter.org/pub/libmnl/libmnl-1.0.4.tar.bz2 sha256 = 171f89699f286a5854b72b91d06e8f8e3683064c5901fb09d954a9ab6f551f81 [build.os=linux] builder = autoconf subdir = libmnl-1.0.4 ================================================ FILE: build/fbcode_builder/manifests/libnl ================================================ [manifest] name = libnl [rpms] libnl3-devel libnl3 [debs] libnl-3-dev libnl-route-3-dev [pps] libnl [download] url = https://github.com/thom311/libnl/releases/download/libnl3_2_25/libnl-3.2.25.tar.gz sha256 = 8beb7590674957b931de6b7f81c530b85dc7c1ad8fbda015398bc1e8d1ce8ec5 [build.os=linux] builder = autoconf subdir = libnl-3.2.25 ================================================ FILE: build/fbcode_builder/manifests/liboqs ================================================ [manifest] name = liboqs [download] url = https://github.com/open-quantum-safe/liboqs/archive/refs/tags/0.12.0.tar.gz sha256 = df999915204eb1eba311d89e83d1edd3a514d5a07374745d6a9e5b2dd0d59c08 [build] builder = cmake subdir = liboqs-0.12.0 [cmake.defines] OQS_MINIMAL_BUILD = KEM_kyber_512;KEM_kyber_768;KEM_kyber_1024;KEM_ml_kem_512;KEM_ml_kem_768;KEM_ml_kem_1024 [dependencies] openssl ================================================ FILE: build/fbcode_builder/manifests/libsai ================================================ [manifest] name = libsai [download] url = https://github.com/opencomputeproject/SAI/archive/v1.16.3.tar.gz sha256 = 5c89cdb6b2e4f1b42ced6b78d43d06d22434ddbf423cdc551f7c2001f12e63d9 [build] builder = nop subdir = SAI-1.16.3 [install.files] inc = include experimental = experimental ================================================ FILE: build/fbcode_builder/manifests/libsodium ================================================ [manifest] name = libsodium [debs] libsodium-dev [homebrew] libsodium [rpms] libsodium-devel libsodium-static [pps] libsodium [download.not(os=windows)] url = https://github.com/jedisct1/libsodium/releases/download/1.0.20-RELEASE/libsodium-1.0.20.tar.gz sha256 = ebb65ef6ca439333c2bb41a0c1990587288da07f6c7fd07cb3a18cc18d30ce19 [build.not(os=windows)] builder = autoconf subdir = libsodium-1.0.20 [download.os=windows] url = https://github.com/jedisct1/libsodium/releases/download/1.0.20-RELEASE/libsodium-1.0.20-msvc.zip sha256 = 2ff97f9e3f5b341bdc808e698057bea1ae454f99e29ff6f9b62e14d0eb1b1baa [build.os=windows] builder = nop [install.files.os=windows] libsodium/x64/Release/v143/dynamic/libsodium.dll = bin/libsodium.dll libsodium/x64/Release/v143/dynamic/libsodium.lib = lib/libsodium.lib libsodium/x64/Release/v143/dynamic/libsodium.exp = lib/libsodium.exp libsodium/x64/Release/v143/dynamic/libsodium.pdb = lib/libsodium.pdb libsodium/include = include [autoconf.args] --with-pic ================================================ FILE: build/fbcode_builder/manifests/libunwind ================================================ [manifest] name = libunwind [rpms] libunwind-devel libunwind # on ubuntu this brings in liblzma-dev, which in turn breaks watchman tests [debs.not(distro=ubuntu)] libunwind-dev # The current libunwind v1.8.1 release has compiler issues with aarch64 (https://github.com/libunwind/libunwind/issues/702). # This more recent libunwind version (based on the latest commit, not a release version) got it fixed. [download] url = https://github.com/libunwind/libunwind/archive/f081cf42917bdd5c428b77850b473f31f81767cf.tar.gz sha256 = 4ff5c335c02d225491d6c885db827fb5fa505fee4e68b4d7e866efc0087e7264 [build] builder = autoconf subdir = libunwind-f081cf42917bdd5c428b77850b473f31f81767cf [autoconf.args] --with-pic ================================================ FILE: build/fbcode_builder/manifests/libusb ================================================ [manifest] name = libusb [debs] libusb-1.0-0-dev [homebrew] libusb [rpms] libusb-devel libusb [pps] libusb [download] url = https://github.com/libusb/libusb/releases/download/v1.0.22/libusb-1.0.22.tar.bz2 sha256 = 75aeb9d59a4fdb800d329a545c2e6799f732362193b465ea198f2aa275518157 [build.os=linux] builder = autoconf subdir = libusb-1.0.22 [autoconf.args] # fboss (which added the libusb dep) doesn't need udev so it is disabled here. # if someone in the future wants to add udev for something else, it won't hurt # fboss. --disable-udev ================================================ FILE: build/fbcode_builder/manifests/libyaml ================================================ [manifest] name = libyaml [download] url = https://pyyaml.org/download/libyaml/yaml-0.1.7.tar.gz sha256 = 8088e457264a98ba451a90b8661fcb4f9d6f478f7265d48322a196cec2480729 [build.os=linux] builder = autoconf subdir = yaml-0.1.7 [build.not(os=linux)] builder = nop ================================================ FILE: build/fbcode_builder/manifests/llvm ================================================ [manifest] name = llvm [rpms] llvm15-devel ================================================ FILE: build/fbcode_builder/manifests/lmdb ================================================ [manifest] name = lmdb [build] builder = make subdir = lmdb-LMDB_0.9.31/libraries/liblmdb [download] url = https://github.com/LMDB/lmdb/archive/refs/tags/LMDB_0.9.31.tar.gz sha256 = dd70a8c67807b3b8532b3e987b0a4e998962ecc28643e1af5ec77696b081c9b0 [make.build_args] BUILD_STATIC_ONLY=y [make.install_args] install BUILD_STATIC_ONLY=y ================================================ FILE: build/fbcode_builder/manifests/lz4 ================================================ [manifest] name = lz4 [homebrew] lz4 [rpms] lz4-devel # centos 8 and centos_stream 9 are missing this rpm [rpms.not(any(all(distro=centos,distro_vers=8),all(distro=centos_stream,distro_vers=9)))] lz4-static [debs] liblz4-dev [pps] lz4 [download] url = https://github.com/lz4/lz4/releases/download/v1.10.0/lz4-1.10.0.tar.gz sha256 = 537512904744b35e232912055ccf8ec66d768639ff3abe5788d90d792ec5f48b [build] builder = cmake subdir = lz4-1.10.0/build/cmake ================================================ FILE: build/fbcode_builder/manifests/lz4-python ================================================ [manifest] name = lz4-python [homebrew] lz4 [rpms] lz4-devel # centos 8 and centos_stream 9 are missing this rpm [rpms.not(any(all(distro=centos,distro_vers=8),all(distro=centos_stream,distro_vers=9)))] lz4-static [debs] liblz4-dev [pps] lz4 [download] url = https://github.com/lz4/lz4/releases/download/v1.10.0/lz4-1.10.0.tar.gz sha256 = 537512904744b35e232912055ccf8ec66d768639ff3abe5788d90d792ec5f48b [build] builder = cmake subdir = lz4-1.10.0/build/cmake [build.not(os=linux)] builder = nop [cmake.defines] CMAKE_POSITION_INDEPENDENT_CODE=ON ================================================ FILE: build/fbcode_builder/manifests/magic_enum ================================================ [manifest] name = magic_enum [download] url = https://github.com/Neargye/magic_enum/releases/download/v0.9.7/magic_enum-v0.9.7.tar.gz sha256 = c047bc7ca0b76752168140e7ae9a4a30d72bf6530c196fdfbf5105a39d40cc46 [build] builder = cmake [cmake.defines] MAGIC_ENUM_OPT_BUILD_EXAMPLES = OFF MAGIC_ENUM_OPT_BUILD_TESTS = OFF MAGIC_ENUM_OPT_INSTALL = ON ================================================ FILE: build/fbcode_builder/manifests/mcrouter ================================================ [manifest] name = mcrouter fbsource_path = fbcode/mcrouter shipit_project = mcrouter shipit_fbcode_builder = true [git] repo_url = https://github.com/facebook/mcrouter.git [dependencies] folly wangle fizz fbthrift mvfst ragel gflags glog boost libevent openssl zlib double-conversion [shipit.pathmap] fbcode/mcrouter/public_tld = . fbcode/mcrouter = mcrouter [shipit.strip] ^fbcode/mcrouter/(.*/)?facebook/ [build] builder = cmake [cmake.defines] BUILD_SHARED_LIBS=OFF [cmake.defines.test=on] BUILD_TESTS=ON [cmake.defines.test=off] BUILD_TESTS=OFF ================================================ FILE: build/fbcode_builder/manifests/mononoke ================================================ [manifest] name = mononoke fbsource_path = fbcode/eden shipit_project = eden shipit_fbcode_builder = true [git] repo_url = https://github.com/facebook/sapling.git [build.not(os=windows)] builder = cargo [build.os=windows] # building Mononoke on windows is not supported builder = nop [cargo] build_doc = true workspace_dir = eden/mononoke [github.actions] rust_version = 1.91 build_type = MinSizeRel [shipit.pathmap] fbcode/configerator/structs/scm/hg = configerator/structs/scm/hg fbcode/configerator/structs/scm/hg/public_autocargo = configerator/structs/scm/hg fbcode/configerator/structs/scm/mononoke/public_autocargo = configerator/structs/scm/mononoke fbcode/configerator/structs/scm/mononoke = configerator/structs/scm/mononoke fbcode/eden/oss = . fbcode/eden = eden fbcode/eden/fs/public_autocargo = eden/fs fbcode/eden/mononoke/public_autocargo = eden/mononoke fbcode/eden/scm/public_autocargo = eden/scm fbcode/tools/lfs = tools/lfs tools/rust/ossconfigs = . [shipit.strip] ^fbcode/configerator/structs/scm/hg(?!/public_autocargo).*/Cargo\.toml$ ^fbcode/configerator/structs/scm/mononoke(?!/public_autocargo).*/Cargo\.toml$ ^fbcode/eden/fs(?!/public_autocargo).*/Cargo\.toml$ ^fbcode/eden/scm/lib/third-party/rust/.*/Cargo\.toml$ ^fbcode/eden/mononoke(?!/public_autocargo).*/Cargo\.toml$ # strip other scm code unrelated to mononoke to prevent triggering unnecessary checks ^fbcode/eden(?!/mononoke|/scm/(lib|public_autocargo))/.*$ ^.*/facebook/.*$ ^.*/fb/.*$ [dependencies] fb303 fbthrift rust-shed [dependencies.fb=on] rust ================================================ FILE: build/fbcode_builder/manifests/mononoke_integration ================================================ [manifest] name = mononoke_integration fbsource_path = fbcode/eden shipit_project = eden shipit_fbcode_builder = true [git] repo_url = https://github.com/facebook/sapling.git [build.not(os=windows)] builder = make subdir = eden/mononoke/tests/integration [build.os=windows] # building Mononoke on windows is not supported builder = nop [make.build_args] build-getdeps [make.install_args] install-getdeps [make.test_args] test-getdeps [shipit.pathmap] fbcode/eden/mononoke/tests/integration = eden/mononoke/tests/integration [shipit.strip] ^.*/facebook/.*$ ^.*/fb/.*$ [dependencies] git-lfs jq mononoke nmap python python-click ripgrep sapling tree zstd [dependencies.os=linux] sqlite3 ================================================ FILE: build/fbcode_builder/manifests/moxygen ================================================ [manifest] name = moxygen fbsource_path = fbcode/moxygen shipit_project = moxygen shipit_fbcode_builder = true [git] repo_url = https://github.com/facebookexperimental/moxygen.git [build.os=windows] builder = nop [build] builder = cmake subdir = . job_weight_mib = 3072 rewrite_includes = true [cmake.defines.test=on] BUILD_TESTS = ON [cmake.defines.test=off] BUILD_TESTS = OFF [dependencies] folly fizz wangle mvfst proxygen [dependencies.test=on] googletest [shipit.pathmap] fbcode/ti/experimental/moxygen/project_root = . fbcode/ti/experimental/moxygen = moxygen ================================================ FILE: build/fbcode_builder/manifests/mvfst ================================================ [manifest] name = mvfst fbsource_path = fbcode/quic shipit_project = mvfst shipit_fbcode_builder = true [git] repo_url = https://github.com/facebook/mvfst.git [build] builder = cmake subdir = . [cmake.defines.test=on] BUILD_TESTS = ON [cmake.defines.all(os=windows, test=on)] BUILD_TESTS = OFF [cmake.defines.test=off] BUILD_TESTS = OFF [dependencies] folly fizz [dependencies.all(test=on, not(os=windows))] googletest [shipit.pathmap] fbcode/quic/public_root = . fbcode/quic = quic ================================================ FILE: build/fbcode_builder/manifests/mvfst-python ================================================ [manifest] name = mvfst-python fbsource_path = fbcode/quic shipit_project = mvfst shipit_fbcode_builder = true [git] repo_url = https://github.com/facebook/mvfst.git [build] builder = cmake subdir = . [build.not(os=linux)] builder = nop [cmake.defines.test=on] BUILD_TESTS = ON [cmake.defines.all(os=windows, test=on)] BUILD_TESTS = OFF [cmake.defines.test=off] BUILD_TESTS = OFF [cmake.defines.os=linux] CMAKE_POSITION_INDEPENDENT_CODE = ON BUILD_SHARED_LIBS = ON [dependencies] folly-python fizz-python [dependencies.all(test=on, not(os=windows))] googletest [shipit.pathmap] fbcode/quic/public_root = . fbcode/quic = quic ================================================ FILE: build/fbcode_builder/manifests/ncurses ================================================ [manifest] name = ncurses [debs] libncurses-dev [homebrew] ncurses [rpms] ncurses-devel [download] url = https://ftpmirror.gnu.org/gnu/ncurses/ncurses-6.3.tar.gz sha256 = 97fc51ac2b085d4cde31ef4d2c3122c21abc217e9090a43a30fc5ec21684e059 [build.not(os=windows)] builder = autoconf subdir = ncurses-6.3 [autoconf.args] --without-cxx-binding --without-ada [autoconf.args.os=linux] --enable-shared --with-shared [build.os=windows] builder = nop ================================================ FILE: build/fbcode_builder/manifests/nghttp2 ================================================ [manifest] name = nghttp2 [rpms] libnghttp2-devel libnghttp2 [debs] libnghttp2-dev [pps] libnghttp2 [download] url = https://github.com/nghttp2/nghttp2/releases/download/v1.47.0/nghttp2-1.47.0.tar.gz sha256 = 62f50f0e9fc479e48b34e1526df8dd2e94136de4c426b7680048181606832b7c [build] builder = autoconf subdir = nghttp2-1.47.0 [autoconf.args] --enable-lib-only --disable-dependency-tracking ================================================ FILE: build/fbcode_builder/manifests/ninja ================================================ [manifest] name = ninja [debs] ninja-build [homebrew] ninja [rpms] ninja-build [pps] ninja [download.os=windows] url = https://github.com/ninja-build/ninja/releases/download/v1.12.1/ninja-win.zip sha256 = f550fec705b6d6ff58f2db3c374c2277a37691678d6aba463adcbb129108467a [build.os=windows] builder = nop [install.files.os=windows] ninja.exe = bin/ninja.exe [download.not(os=windows)] url = https://github.com/ninja-build/ninja/archive/v1.12.1.tar.gz sha256 = 821bdff48a3f683bc4bb3b6f0b5fe7b2d647cf65d52aeb63328c91a6c6df285a [build.not(os=windows)] builder = ninja_bootstrap subdir = ninja-1.12.1 ================================================ FILE: build/fbcode_builder/manifests/nlohmann-json ================================================ [manifest] name = nlohmann-json [download] url = https://github.com/nlohmann/json/archive/refs/tags/v3.10.5.tar.gz sha256 = 5daca6ca216495edf89d167f808d1d03c4a4d929cef7da5e10f135ae1540c7e4 [dependencies] [build] builder = cmake subdir = json-3.10.5 ================================================ FILE: build/fbcode_builder/manifests/nmap ================================================ [manifest] name = nmap [rpms] nmap nmap-ncat [debs] nmap # 18.04 combines ncat into the nmap package, newer need the separate one [debs.not(all(distro=ubuntu,distro_vers="18.04"))] ncat [download.not(os=windows)] url = https://api.github.com/repos/nmap/nmap/tarball/ef8213a36c2e89233c806753a57b5cd473605408 sha256 = eda39e5a8ef4964fac7db16abf91cc11ff568eac0fa2d680b0bfa33b0ed71f4a [build.not(os=windows)] builder = autoconf subdir = nmap-nmap-ef8213a build_in_src_dir = true [build.os=windows] builder = nop [autoconf.args] # Without this option the build was filing to find some third party libraries # that we don't need enable_rdma=no ================================================ FILE: build/fbcode_builder/manifests/numa ================================================ [manifest] name = numa [download] url = https://github.com/numactl/numactl/releases/download/v2.0.19/numactl-2.0.19.tar.gz sha256 = f2672a0381cb59196e9c246bf8bcc43d5568bc457700a697f1a1df762b9af884 [build] builder = autoconf subdir = numactl-2.0.19 [rpms.distro=centos_stream] numactl-devel ================================================ FILE: build/fbcode_builder/manifests/openr ================================================ [manifest] name = openr fbsource_path = facebook/openr shipit_project = openr shipit_fbcode_builder = true [git] repo_url = https://github.com/facebook/openr.git [build.os=linux] builder = cmake # openr files take a lot of RAM to compile. job_weight_mib = 3072 [build.not(os=linux)] # boost.fiber is required and that is not available on macos. builder = nop [dependencies] boost fb303 fbthrift folly googletest re2 range-v3 [cmake.defines.test=on] BUILD_TESTS=ON ADD_ROOT_TESTS=OFF [cmake.defines.test=off] BUILD_TESTS=OFF [shipit.pathmap] fbcode/openr = openr fbcode/openr/public_tld = . ================================================ FILE: build/fbcode_builder/manifests/openssl ================================================ [manifest] name = openssl [debs] libssl-dev [homebrew] openssl # on homebrew need the matching curl and ca- [rpms] openssl openssl-devel openssl-libs [pps] openssl # no need to download on the systems where we always use the system libs [download.not(any(os=linux, os=freebsd))] # match the openssl version packages in ubuntu LTS folly current supports url = https://www.openssl.org/source/openssl-3.0.15.tar.gz sha256 = 23c666d0edf20f14249b3d8f0368acaee9ab585b09e1de82107c66e1f3ec9533 # We use the system openssl on these platforms even without --allow-system-packages [build.any(os=linux, os=freebsd)] builder = nop [build.not(any(os=linux, os=freebsd))] builder = openssl subdir = openssl-3.0.15 [dependencies.os=windows] jom perl ================================================ FILE: build/fbcode_builder/manifests/osxfuse ================================================ [manifest] name = osxfuse [download] url = https://github.com/osxfuse/osxfuse/archive/osxfuse-3.8.3.tar.gz sha256 = 93bab6731bdfe8dc1ef069483437270ce7fe5a370f933d40d8d0ef09ba846c0c [build] builder = nop [install.files] osxfuse-osxfuse-3.8.3/common = include ================================================ FILE: build/fbcode_builder/manifests/patchelf ================================================ [manifest] name = patchelf [rpms] patchelf [debs] patchelf [pps] patchelf [download] url = https://github.com/NixOS/patchelf/archive/0.10.tar.gz sha256 = b3cb6bdedcef5607ce34a350cf0b182eb979f8f7bc31eae55a93a70a3f020d13 [build] builder = autoconf subdir = patchelf-0.10 ================================================ FILE: build/fbcode_builder/manifests/pcre2 ================================================ [manifest] name = pcre2 [homebrew] pcre2 [rpms] pcre2-devel pcre-static [debs] libpcre2-dev [download] url = https://github.com/PCRE2Project/pcre2/releases/download/pcre2-10.40/pcre2-10.40.tar.bz2 sha256 = 14e4b83c4783933dc17e964318e6324f7cae1bc75d8f3c79bc6969f00c159d68 [build] builder = cmake subdir = pcre2-10.40 ================================================ FILE: build/fbcode_builder/manifests/perl ================================================ [manifest] name = perl [download.os=windows] url = https://strawberryperl.com/download/5.28.1.1/strawberry-perl-5.28.1.1-64bit-portable.zip sha256 = 935c95ba096fa11c4e1b5188732e3832d330a2a79e9882ab7ba8460ddbca810d [build.os=windows] builder = nop subdir = perl ================================================ FILE: build/fbcode_builder/manifests/pexpect ================================================ [manifest] name = pexpect [download] url = https://files.pythonhosted.org/packages/0e/3e/377007e3f36ec42f1b84ec322ee12141a9e10d808312e5738f52f80a232c/pexpect-4.7.0-py2.py3-none-any.whl sha256 = 2094eefdfcf37a1fdbfb9aa090862c1a4878e5c7e0e7e7088bdb511c558e5cd1 [build] builder = python-wheel [dependencies] python-ptyprocess ================================================ FILE: build/fbcode_builder/manifests/proxygen ================================================ [manifest] name = proxygen fbsource_path = fbcode/proxygen shipit_project = proxygen shipit_fbcode_builder = true [git] repo_url = https://github.com/facebook/proxygen.git [build.os=windows] builder = nop [build] builder = cmake subdir = . job_weight_mib = 3072 [cmake.defines.test=on] BUILD_TESTS = ON [cmake.defines.test=off] BUILD_TESTS = OFF [dependencies] zlib gperf folly fizz wangle mvfst c-ares [dependencies.test=on] googletest [shipit.pathmap] fbcode/proxygen/public_tld = . fbcode/proxygen = proxygen ================================================ FILE: build/fbcode_builder/manifests/python ================================================ [manifest] name = python [homebrew] python@3.10 # sapling needs match statements with arrive in python 3.12 in centos 10 [rpms.not(all(distro=centos_stream,distro_vers=9))] python3 python3-devel # Centos Stream 9 default python is 3.9, sapling needs 3.10+ [rpms.all(distro=centos_stream,distro_vers=9)] python3.12 python3.12-devel # sapling needs match statements with arrive in python 3.10 in ubuntu 22.04 [debs.not(all(distro=ubuntu,any(distro_vers="18.04",distro_vers="20.04")))] python3-all-dev [pps] python3 [download] url = https://www.python.org/ftp/python/3.10.19/Python-3.10.19.tgz sha256 = a078fb2d7a216071ebbe2e34b5f5355dd6b6e9b0cd1bacc4a41c63990c5a0eec [build] builder = autoconf subdir = Python-3.10.19 [autoconf.args] --enable-shared --with-ensurepip=install # python's pkg-config libffi detection is broken # See https://bugs.python.org/issue34823 for clearest description # and pending PR https://github.com/python/cpython/pull/20451 # The documented workaround requires an environment variable derived from # pkg-config to be passed into its configure step [autoconf.envcmd.LDFLAGS] pkg-config --libs-only-L libffi [dependencies] libffi # eden tests expect the python bz2 support bz2 # eden tests expect the python curses support ncurses ================================================ FILE: build/fbcode_builder/manifests/python-3_14 ================================================ # This is primarily to support CinderX's CI, so it's not heavily configured. [manifest] name = python-3_14 [download] url = https://github.com/python/cpython/archive/refs/tags/v3.14.3.tar.gz sha256 = f229a232052ae318d2fc8eb0aca4a02d631e7e1a8790ef1f9b65e1632743a469 [build] builder = autoconf subdir = cpython-3.14.3 ================================================ FILE: build/fbcode_builder/manifests/python-click ================================================ [manifest] name = python-click [download] url = https://files.pythonhosted.org/packages/d2/3d/fa76db83bf75c4f8d338c2fd15c8d33fdd7ad23a9b5e57eb6c5de26b430e/click-7.1.2-py2.py3-none-any.whl sha256 = dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc [build] builder = python-wheel [rpms] python3-click [debs] python3-click ================================================ FILE: build/fbcode_builder/manifests/python-filelock ================================================ [manifest] name = python-filelock [download] url = https://files.pythonhosted.org/packages/31/24/ee722b92f23b9ebd87783e893a75352c048bbbc1f67dce0d63b58b46cb48/filelock-3.3.2-py3-none-any.whl sha256 = bb2a1c717df74c48a2d00ed625e5a66f8572a3a30baacb7657add1d7bac4097b [build] builder = python-wheel ================================================ FILE: build/fbcode_builder/manifests/python-main ================================================ # This is primarily to support CinderX's CI, so it's not heavily configured. [manifest] name = python-main fbsource_path = third-party/python/main/pristine # We don't actually have a shipit project for python-main, but we use getdeps # built-in shipit implementation which just needs a shipit.pathmap. shipit_project = dummy-name [git] repo_url = https://github.com/python/cpython.git [shipit.pathmap] third-party/python/main/pristine = . [build] builder = autoconf ================================================ FILE: build/fbcode_builder/manifests/python-psutil ================================================ [manifest] name = python-psutil [download] url = https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl sha256 = 4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34 [build] builder = python-wheel ================================================ FILE: build/fbcode_builder/manifests/python-ptyprocess ================================================ [manifest] name = python-ptyprocess [download] url = https://files.pythonhosted.org/packages/d1/29/605c2cc68a9992d18dada28206eeada56ea4bd07a239669da41674648b6f/ptyprocess-0.6.0-py2.py3-none-any.whl sha256 = d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f [build] builder = python-wheel ================================================ FILE: build/fbcode_builder/manifests/python-pyyaml ================================================ [manifest] name = python-pyyaml [download] url = https://files.pythonhosted.org/packages/25/a2/b725b61ac76a75583ae7104b3209f75ea44b13cfd026aa535ece22b7f22e/PyYAML-6.0.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl sha256 = 22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6 [build] builder = python-wheel ================================================ FILE: build/fbcode_builder/manifests/python-setuptools ================================================ [manifest] name = python-setuptools [download] url = https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl sha256 = 062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922 [build] builder = python-wheel [rpms] python3-setuptools # Centos Stream 9 default python is 3.9, sapling needs 3.10+ [rpms.all(distro=centos_stream,distro_vers=9)] python3.12-setuptools [homebrew] python-setuptools [debs] python3-setuptools ================================================ FILE: build/fbcode_builder/manifests/python-setuptools-69 ================================================ [manifest] name = python-setuptools-69 [download] url = https://files.pythonhosted.org/packages/c0/7a/3da654f49c95d0cc6e9549a855b5818e66a917e852ec608e77550c8dc08b/setuptools-69.1.1-py3-none-any.whl sha256 = 02fa291a0471b3a18b2b2481ed902af520c69e8ae0919c13da936542754b4c56 [build] builder = python-wheel [rpms] python3-setuptools [homebrew] python-setuptools [debs] python3-setuptools ================================================ FILE: build/fbcode_builder/manifests/python-six ================================================ [manifest] name = python-six [download] url = https://files.pythonhosted.org/packages/73/fb/00a976f728d0d1fecfe898238ce23f502a721c0ac0ecfedb80e0d88c64e9/six-1.12.0-py2.py3-none-any.whl sha256 = 3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c [build] builder = python-wheel ================================================ FILE: build/fbcode_builder/manifests/python-toml ================================================ [manifest] name = python-toml [download] url = https://files.pythonhosted.org/packages/a2/12/ced7105d2de62fa7c8fb5fce92cc4ce66b57c95fb875e9318dba7f8c5db0/toml-0.10.0-py2.py3-none-any.whl sha256 = 235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e [build] builder = python-wheel ================================================ FILE: build/fbcode_builder/manifests/ragel ================================================ [manifest] name = ragel [debs] ragel [homebrew] ragel [rpms] ragel [download] url = https://www.colm.net/files/ragel/ragel-6.10.tar.gz sha256 = 5f156edb65d20b856d638dd9ee2dfb43285914d9aa2b6ec779dac0270cd56c3f [build] builder = autoconf subdir = ragel-6.10 ================================================ FILE: build/fbcode_builder/manifests/range-v3 ================================================ [manifest] name = range-v3 [download] url = https://github.com/ericniebler/range-v3/archive/refs/tags/0.11.0.tar.gz sha256 = 376376615dbba43d3bef75aa590931431ecb49eb36d07bb726a19f680c75e20c [build] builder = cmake subdir = range-v3-0.11.0 [cmake.defines] RANGE_V3_EXAMPLES=OFF ================================================ FILE: build/fbcode_builder/manifests/rdma-core ================================================ [manifest] name = rdma-core [debs] rdma-core [rpms] rdma-core-devel ================================================ FILE: build/fbcode_builder/manifests/re2 ================================================ [manifest] name = re2 [homebrew] re2 [debs] libre2-dev [rpms] re2 re2-devel [pps] re2 [download] url = https://github.com/google/re2/archive/2020-11-01.tar.gz sha256 = 8903cc66c9d34c72e2bc91722288ebc7e3ec37787ecfef44d204b2d6281954d7 [build] builder = cmake subdir = re2-2020-11-01 ================================================ FILE: build/fbcode_builder/manifests/rebalancer ================================================ [manifest] name = rebalancer fbsource_path = fbcode/algopt/rebalancer/ shipit_project = rebalancer shipit_fbcode_builder = true use_shipit = true [git] # To git clone on devserver, setup fwdproxy: # https://www.internalfb.com/wiki/Open_Source/Maintain_a_FB_OSS_Project/Devserver_GitHub_Access/ repo_url = https://github.com/facebookincubator/rebalancer.git branch = main [build] builder = cmake [dependencies] boost fbthrift fizz fmt folly gflags glog googletest xxhash [dependencies.os=linux] clang19 [cmake.defines.os=linux] CMAKE_C_COMPILER=clang-19 CMAKE_CXX_COMPILER=clang++-19 [cmake.defines.os=darwin] REBALANCER_USE_SCIP=0 [shipit.strip] ^.*/fb/.*$ [shipit.pathmap] fbcode/algopt/rebalancer/oss_root = . fbcode/algopt/rebalancer = algopt/rebalancer fbcode/algopt/lp = algopt/lp [dependencies.all(distro=centos_stream,distro_vers=9)] clang19 [cmake.defines.all(distro=centos_stream,distro_vers=9)] CMAKE_C_COMPILER=clang-19 CMAKE_CXX_COMPILER=clang++-19 ================================================ FILE: build/fbcode_builder/manifests/ripgrep ================================================ [manifest] name = ripgrep [rpms] ripgrep [debs] ripgrep [homebrew] ripgrep # only used from system packages currently [build] builder = nop ================================================ FILE: build/fbcode_builder/manifests/rocksdb ================================================ [manifest] name = rocksdb [download] url = https://github.com/facebook/rocksdb/archive/refs/tags/v8.7.3.zip sha256 = 36c06b61dc167f2455990d60dd88d734b73aa8c4dfc095243efd0243834c6cd3 [dependencies] lz4 snappy [build] builder = cmake subdir = rocksdb-8.7.3 [cmake.defines] WITH_SNAPPY=ON WITH_LZ4=ON WITH_TESTS=OFF WITH_BENCHMARK_TOOLS=OFF # We get relocation errors with the static gflags lib, # and there's no clear way to make it pick the shared gflags # so just turn it off. WITH_GFLAGS=OFF # Disable the use of -Werror FAIL_ON_WARNINGS = OFF [cmake.defines.os=windows] ROCKSDB_INSTALL_ON_WINDOWS=ON # RocksDB hard codes the paths to the snappy libs to something # that doesn't exist; ignoring the usual cmake rules. As a result, # we can't build it with snappy without either patching rocksdb or # without introducing more complex logic to the build system to # connect the snappy build outputs to rocksdb's custom logic here. # Let's just turn it off on windows. WITH_SNAPPY=OFF WITH_LZ4=ON ROCKSDB_SKIP_THIRDPARTY=ON ================================================ FILE: build/fbcode_builder/manifests/rust-shed ================================================ [manifest] name = rust-shed fbsource_path = fbcode/common/rust/shed shipit_project = rust-shed shipit_fbcode_builder = true [git] repo_url = https://github.com/facebookexperimental/rust-shed.git [build] builder = cargo [cargo] build_doc = true workspace_dir = [shipit.pathmap] fbcode/common/rust/shed = shed fbcode/common/rust/shed/public_autocargo = shed fbcode/common/rust/shed/public_tld = . tools/rust/ossconfigs = . [shipit.strip] ^fbcode/common/rust/shed/(?!public_autocargo|public_tld).+/Cargo\.toml$ [dependencies] fbthrift fb303 # We use the system openssl on linux [dependencies.not(os=linux)] openssl [dependencies.fbsource=on] rust ================================================ FILE: build/fbcode_builder/manifests/sapling ================================================ [manifest] name = sapling fbsource_path = fbcode/eden shipit_project = eden shipit_fbcode_builder = true [github.actions] required_locales = en_US.UTF-8 [git] repo_url = https://github.com/facebook/sapling.git [build.not(os=windows)] builder = make subdir = eden/scm [build.os=windows] # For now the biggest blocker is missing "make" on windows, but there are bound # to be more builder = nop [make.build_args] getdepsbuild [make.install_args] install-getdeps [make.test_args] test-getdeps [shipit.pathmap] fbcode/configerator/structs/scm/hg = configerator/structs/scm/hg fbcode/configerator/structs/scm/hg/public_autocargo = configerator/structs/scm/hg fbcode/eden/oss = . fbcode/eden = eden fbcode/eden/fs/public_autocargo = eden/fs fbcode/eden/mononoke/public_autocargo = eden/mononoke fbcode/eden/scm/public_autocargo = eden/scm fbcode/tools/lfs = tools/lfs [shipit.strip] ^fbcode/configerator/structs/scm/hg(?!/public_autocargo).*/Cargo\.toml$ ^fbcode/eden/addons/.*$ ^fbcode/eden/fs/eden-config\.h$ ^fbcode/eden/fs/py/eden/config\.py$ ^fbcode/eden/hg-server/.*$ ^fbcode/eden/fs(?!/public_autocargo).*/Cargo\.toml$ ^fbcode/eden/mononoke(?!/public_autocargo).*/Cargo\.toml$ ^fbcode/eden/scm(?!/public_autocargo|/edenscmnative/bindings).*/Cargo\.toml$ ^fbcode/eden/scm/build/.*$ ^fbcode/eden/website/.*$ ^fbcode/eden/.*/\.cargo/.*$ ^.*/facebook/.*$ ^.*/fb/.*$ /Cargo\.lock$ \.pyc$ [dependencies] fb303 fbthrift rust-shed [dependencies.all(test=on,not(os=darwin))] hexdump [dependencies.not(os=windows)] python python-setuptools # We use the system openssl on linux [dependencies.not(os=linux)] openssl [dependencies.fbsource=on] rust ================================================ FILE: build/fbcode_builder/manifests/snappy ================================================ [manifest] name = snappy [homebrew] snappy [debs] libsnappy-dev [rpms] snappy-devel [pps] snappy [download] url = https://github.com/google/snappy/archive/1.1.7.tar.gz sha256 = 3dfa02e873ff51a11ee02b9ca391807f0c8ea0529a4924afa645fbf97163f9d4 [build] builder = cmake subdir = snappy-1.1.7 [cmake.defines] SNAPPY_BUILD_TESTS = OFF # Avoid problems like `relocation R_X86_64_PC32 against symbol` on ELF systems # when linking rocksdb, which builds PIC even when building a static lib [cmake.defines.os=linux] BUILD_SHARED_LIBS = ON ================================================ FILE: build/fbcode_builder/manifests/sparsemap ================================================ [manifest] name = sparsemap [download] url = https://github.com/Tessil/sparse-map/archive/refs/tags/v0.6.2.tar.gz sha256 = 7020c21e8752e59d72e37456cd80000e18671c803890a3e55ae36b295eba99f6 [build] builder = cmake subdir = sparse-map-0.6.2/ ================================================ FILE: build/fbcode_builder/manifests/sqlite3 ================================================ [manifest] name = sqlite3 [debs] libsqlite3-dev sqlite3 [homebrew] sqlite [rpms] sqlite-devel sqlite-libs sqlite [pps] sqlite3 [download] url = https://sqlite.org/2019/sqlite-amalgamation-3280000.zip sha256 = d02fc4e95cfef672b45052e221617a050b7f2e20103661cda88387349a9b1327 [dependencies] cmake ninja [build] builder = sqlite subdir = sqlite-amalgamation-3280000 ================================================ FILE: build/fbcode_builder/manifests/systemd ================================================ [manifest] name = systemd [rpms] systemd systemd-devel [download] url = https://github.com/systemd/systemd/archive/refs/tags/v256.7.tar.gz sha256 = 896d76ff65c88f5fd9e42f90d152b0579049158a163431dd77cdc57748b1d7b0 [build.os=linux] builder = meson subdir = systemd-256.7 [meson.setup_args] -Dstatic-libsystemd=true -Dprefix=/ ================================================ FILE: build/fbcode_builder/manifests/tabulate ================================================ [manifest] name = tabulate [download] url = https://github.com/p-ranav/tabulate/archive/refs/tags/v1.5.tar.gz sha256 = 16b289f46306283544bb593f4601e80d6ea51248fde52e910cc569ef08eba3fb [build] builder = cmake subdir = tabulate-1.5 [cmake.defines] tabulate_BUILD_TESTS = OFF tabulate_BUILD_SAMPLES = OFF ================================================ FILE: build/fbcode_builder/manifests/tree ================================================ [manifest] name = tree [debs] tree [homebrew] tree [rpms] tree [download.os=linux] url = https://salsa.debian.org/debian/tree-packaging/-/archive/debian/1.8.0-1/tree-packaging-debian-1.8.0-1.tar.gz sha256 = a841eee1d52bfd64a48f54caab9937b9bd92935055c48885c4ab1ae4dab7fae5 [download.os=darwin] # The official package of tree source requires users of non-Linux platform to # comment/uncomment certain lines in the Makefile to build for their platform. # Besauce getdeps.py doesn't have that functionality we just use this custom # fork of tree which has proper lines uncommented for a OSX build url = https://github.com/lukaspiatkowski/tree-command/archive/debian/1.8.0-1-macos.tar.gz sha256 = 9cbe889553d95cf5a2791dd0743795d46a3c092c5bba691769c0e5c52e11229e [build.os=linux] builder = make subdir = tree-packaging-debian-1.8.0-1 [build.os=darwin] builder = make subdir = tree-command-debian-1.8.0-1-macos [build.os=windows] builder = nop [make.install_args] install ================================================ FILE: build/fbcode_builder/manifests/wangle ================================================ [manifest] name = wangle fbsource_path = fbcode/wangle shipit_project = wangle shipit_fbcode_builder = true [git] repo_url = https://github.com/facebook/wangle.git [build] builder = cmake subdir = wangle [cmake.defines.test=on] BUILD_TESTS=ON [cmake.defines.test=off] BUILD_TESTS=OFF [dependencies] folly googletest fizz [shipit.pathmap] fbcode/wangle/public_tld = . fbcode/wangle = wangle ================================================ FILE: build/fbcode_builder/manifests/wangle-python ================================================ [manifest] name = wangle-python fbsource_path = fbcode/wangle shipit_project = wangle shipit_fbcode_builder = true [git] repo_url = https://github.com/facebook/wangle.git [build] builder = cmake subdir = wangle [build.not(os=linux)] builder = nop [cmake.defines.test=on] BUILD_TESTS=ON [cmake.defines.test=off] BUILD_TESTS=OFF [cmake.defines.os=linux] CMAKE_POSITION_INDEPENDENT_CODE = ON BUILD_SHARED_LIBS = ON [dependencies] folly-python googletest fizz-python [shipit.pathmap] fbcode/wangle/public_tld = . fbcode/wangle = wangle ================================================ FILE: build/fbcode_builder/manifests/watchman ================================================ [manifest] name = watchman fbsource_path = fbcode/watchman shipit_project = watchman shipit_fbcode_builder = true [git] repo_url = https://github.com/facebook/watchman.git [build] builder = cmake [dependencies] boost cpptoml edencommon fb303 fbthrift folly pcre2 googletest python-setuptools-69 [dependencies.fbsource=on] rust [shipit.pathmap] fbcode/watchman = watchman fbcode/watchman/oss = . fbcode/eden/fs = eden/fs [shipit.strip] ^fbcode/eden/fs/(?!.*\.thrift|service/shipit_test_file\.txt) [cmake.defines.fb=on] ENABLE_EDEN_SUPPORT=ON IS_FB_BUILD=ON # FB macos specific settings [cmake.defines.all(fb=on,os=darwin)] # this path is coupled with the FB internal watchman-osx.spec WATCHMAN_STATE_DIR=/opt/facebook/watchman/var/run/watchman # tell cmake not to try to create /opt/facebook/... INSTALL_WATCHMAN_STATE_DIR=OFF USE_SYS_PYTHON=OFF [depends.environment] WATCHMAN_VERSION_OVERRIDE ================================================ FILE: build/fbcode_builder/manifests/xxhash ================================================ [manifest] name = xxhash [download] url = https://github.com/Cyan4973/xxHash/archive/refs/tags/v0.8.2.tar.gz sha256 = baee0c6afd4f03165de7a4e67988d16f0f2b257b51d0e3cb91909302a26a79c4 [rpms] xxhash-devel [debs] libxxhash-dev xxhash [homebrew] xxhash [build] builder = cmake subdir = xxHash-0.8.2/cmake_unofficial [cmake.defines] CMAKE_POSITION_INDEPENDENT_CODE = ON ================================================ FILE: build/fbcode_builder/manifests/xz ================================================ [manifest] name = xz # ubuntu's package causes watchman's tests to hang [debs.not(distro=ubuntu)] liblzma-dev [homebrew] xz [rpms] xz-devel [download] url = https://tukaani.org/xz/xz-5.2.5.tar.gz sha256 = f6f4910fd033078738bd82bfba4f49219d03b17eb0794eb91efbae419f4aba10 [build] builder = autoconf subdir = xz-5.2.5 [autoconf.args] --with-pic ================================================ FILE: build/fbcode_builder/manifests/yaml-cpp ================================================ [manifest] name = yaml-cpp [download] url = https://github.com/jbeder/yaml-cpp/archive/yaml-cpp-0.6.2.tar.gz sha256 = e4d8560e163c3d875fd5d9e5542b5fd5bec810febdcba61481fe5fc4e6b1fd05 [build.os=linux] builder = cmake subdir = yaml-cpp-yaml-cpp-0.6.2 [build.not(os=linux)] builder = nop [dependencies] boost googletest [cmake.defines] YAML_CPP_BUILD_TESTS=OFF ================================================ FILE: build/fbcode_builder/manifests/zlib ================================================ [manifest] name = zlib [debs] zlib1g-dev [homebrew] zlib [rpms.not(distro=fedora)] zlib-devel zlib-static [rpms.distro=fedora] zlib-ng-compat-devel zlib-ng-compat-static [pps] zlib [download] url = https://github.com/madler/zlib/releases/download/v1.3.1/zlib-1.3.1.tar.gz sha256 = 9a93b2b7dfdac77ceba5a558a580e74667dd6fede4585b91eefb60f03b72df23 [build] builder = cmake subdir = zlib-1.3.1 patchfile = zlib_dont_build_more_than_needed.patch ================================================ FILE: build/fbcode_builder/manifests/zlib-python ================================================ [manifest] name = zlib-python [debs] zlib1g-dev [homebrew] zlib [rpms.not(distro=fedora)] zlib-devel zlib-static [rpms.distro=fedora] zlib-ng-compat-devel zlib-ng-compat-static [pps] zlib [download] url = https://github.com/madler/zlib/releases/download/v1.3.1/zlib-1.3.1.tar.gz sha256 = 9a93b2b7dfdac77ceba5a558a580e74667dd6fede4585b91eefb60f03b72df23 [build] builder = cmake subdir = zlib-1.3.1 patchfile = zlib_dont_build_more_than_needed.patch [build.not(os=linux)] builder = nop [cmake.defines] CMAKE_POSITION_INDEPENDENT_CODE=ON ================================================ FILE: build/fbcode_builder/manifests/zstd ================================================ [manifest] name = zstd [homebrew] zstd # 18.04 zstd is too old [debs.not(all(distro=ubuntu,distro_vers="18.04"))] libclang-dev libzstd-dev zstd [rpms] libzstd-devel libzstd [pps] zstd [download] url = https://github.com/facebook/zstd/releases/download/v1.5.5/zstd-1.5.5.tar.gz sha256 = 9c4396cc829cfae319a6e2615202e82aad41372073482fce286fac78646d3ee4 [build] builder = cmake subdir = zstd-1.5.5/build/cmake # The zstd cmake build explicitly sets the install name # for the shared library in such a way that cmake discards # the path to the library from the install_name, rendering # the library non-resolvable during the build. The short # term solution for this is just to link static on macos. # # And while we're at it, let's just always link statically. [cmake.defines] ZSTD_BUILD_SHARED = OFF ================================================ FILE: build/fbcode_builder/manifests/zstd-python ================================================ [manifest] name = zstd-python [homebrew] zstd # 18.04 zstd is too old [debs.not(all(distro=ubuntu,distro_vers="18.04"))] libclang-dev libzstd-dev zstd [rpms] libzstd-devel libzstd [pps] zstd [download] url = https://github.com/facebook/zstd/releases/download/v1.5.5/zstd-1.5.5.tar.gz sha256 = 9c4396cc829cfae319a6e2615202e82aad41372073482fce286fac78646d3ee4 [build] builder = cmake subdir = zstd-1.5.5/build/cmake [build.not(os=linux)] builder = nop # The zstd cmake build explicitly sets the install name # for the shared library in such a way that cmake discards # the path to the library from the install_name, rendering # the library non-resolvable during the build. The short # term solution for this is just to link static on macos. # # And while we're at it, let's just always link statically. [cmake.defines] ZSTD_BUILD_SHARED = OFF CMAKE_POSITION_INDEPENDENT_CODE=ON ================================================ FILE: build/fbcode_builder/patches/boost_1_83_0.patch ================================================ diff --git a/boost/serialization/strong_typedef.hpp b/boost/serialization/strong_typedef.hpp --- a/boost/serialization/strong_typedef.hpp +++ b/boost/serialization/strong_typedef.hpp @@ -44,6 +44,7 @@ operator const T&() const {return t;} \ operator T&() {return t;} \ bool operator==(const D& rhs) const {return t == rhs.t;} \ + bool operator==(const T& lhs) const {return t == lhs;} \ bool operator<(const D& rhs) const {return t < rhs.t;} \ }; diff --git a/tools/build/src/tools/msvc.jam b/tools/build/src/tools/msvc.jam --- a/tools/build/src/tools/msvc.jam +++ b/tools/build/src/tools/msvc.jam @@ -1137,6 +1137,14 @@ } else { + if [ MATCH "(14.4)" : $(version) ] + { + if $(.debug-configuration) + { + ECHO "notice: [generate-setup-cmd] $(version) is 14.4x" ; + } + parent = [ path.native [ path.join $(parent) "..\\..\\..\\..\\..\\Auxiliary\\Build" ] ] ; + } if [ MATCH "(14.3)" : $(version) ] { if $(.debug-configuration) ================================================ FILE: build/fbcode_builder/patches/iproute2_oss.patch ================================================ diff --git a/bridge/fdb.c b/bridge/fdb.c --- a/bridge/fdb.c +++ b/bridge/fdb.c @@ -31,7 +31,7 @@ static unsigned int filter_index, filter_vlan, filter_state; -json_writer_t *jw_global; +static json_writer_t *jw_global; static void usage(void) { diff --git a/ip/ipmroute.c b/ip/ipmroute.c --- a/ip/ipmroute.c +++ b/ip/ipmroute.c @@ -44,7 +44,7 @@ exit(-1); } -struct rtfilter { +static struct rtfilter { int tb; int af; int iif; diff --git a/ip/xfrm_monitor.c b/ip/xfrm_monitor.c --- a/ip/xfrm_monitor.c +++ b/ip/xfrm_monitor.c @@ -34,7 +34,7 @@ #include "ip_common.h" static void usage(void) __attribute__((noreturn)); -int listen_all_nsid; +static int listen_all_nsid; static void usage(void) { ================================================ FILE: build/fbcode_builder/patches/libiberty_install_pic_lib.patch ================================================ diff --git a/Makefile.in b/Makefile.in index b77a41c..cbe71fe 100644 --- a/Makefile.in +++ b/Makefile.in @@ -389,7 +389,7 @@ MULTIOSDIR = `$(CC) $(CFLAGS) -print-multi-os-directory` install_to_libdir: all if test -n "${target_header_dir}"; then \ ${mkinstalldirs} $(DESTDIR)$(libdir)/$(MULTIOSDIR); \ - $(INSTALL_DATA) $(TARGETLIB) $(DESTDIR)$(libdir)/$(MULTIOSDIR)/$(TARGETLIB)n; \ + $(INSTALL_DATA) pic/$(TARGETLIB) $(DESTDIR)$(libdir)/$(MULTIOSDIR)/$(TARGETLIB)n; \ ( cd $(DESTDIR)$(libdir)/$(MULTIOSDIR) ; chmod 644 $(TARGETLIB)n ;$(RANLIB) $(TARGETLIB)n ); \ mv -f $(DESTDIR)$(libdir)/$(MULTIOSDIR)/$(TARGETLIB)n $(DESTDIR)$(libdir)/$(MULTIOSDIR)/$(TARGETLIB); \ case "${target_header_dir}" in \ ================================================ FILE: build/fbcode_builder/patches/zlib_dont_build_more_than_needed.patch ================================================ diff -Naur ../zlib-1.3.1/CMakeLists.txt ./CMakeLists.txt --- ../zlib-1.3.1/CMakeLists.txt 2024-01-22 10:32:37.000000000 -0800 +++ ./CMakeLists.txt 2024-01-23 13:14:09.870289968 -0800 @@ -149,10 +149,8 @@ set(ZLIB_DLL_SRCS ${CMAKE_CURRENT_BINARY_DIR}/zlib1rc.obj) endif(MINGW) -add_library(zlib SHARED ${ZLIB_SRCS} ${ZLIB_DLL_SRCS} ${ZLIB_PUBLIC_HDRS} ${ZLIB_PRIVATE_HDRS}) +add_library(zlib ${ZLIB_SRCS} ${ZLIB_DLL_SRCS} ${ZLIB_PUBLIC_HDRS} ${ZLIB_PRIVATE_HDRS}) target_include_directories(zlib PUBLIC ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) -add_library(zlibstatic STATIC ${ZLIB_SRCS} ${ZLIB_PUBLIC_HDRS} ${ZLIB_PRIVATE_HDRS}) -target_include_directories(zlibstatic PUBLIC ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) set_target_properties(zlib PROPERTIES DEFINE_SYMBOL ZLIB_DLL) set_target_properties(zlib PROPERTIES SOVERSION 1) @@ -169,7 +167,7 @@ if(UNIX) # On unix-like platforms the library is almost always called libz - set_target_properties(zlib zlibstatic PROPERTIES OUTPUT_NAME z) + set_target_properties(zlib PROPERTIES OUTPUT_NAME z) if(NOT APPLE AND NOT(CMAKE_SYSTEM_NAME STREQUAL AIX)) set_target_properties(zlib PROPERTIES LINK_FLAGS "-Wl,--version-script,\"${CMAKE_CURRENT_SOURCE_DIR}/zlib.map\"") endif() @@ -179,7 +177,7 @@ endif() if(NOT SKIP_INSTALL_LIBRARIES AND NOT SKIP_INSTALL_ALL ) - install(TARGETS zlib zlibstatic + install(TARGETS zlib RUNTIME DESTINATION "${INSTALL_BIN_DIR}" ARCHIVE DESTINATION "${INSTALL_LIB_DIR}" LIBRARY DESTINATION "${INSTALL_LIB_DIR}" ) ================================================ FILE: build.bat ================================================ @REM Copyright (c) Meta Platforms, Inc. and affiliates. @REM @REM Licensed under the Apache License, Version 2.0 (the "License"); @REM you may not use this file except in compliance with the License. @REM You may obtain a copy of the License at @REM @REM http://www.apache.org/licenses/LICENSE-2.0 @REM @REM Unless required by applicable law or agreed to in writing, software @REM distributed under the License is distributed on an "AS IS" BASIS, @REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @REM See the License for the specific language governing permissions and @REM limitations under the License. @echo off SETLOCAL if exist %~dp0\..\..\opensource\fbcode_builder\getdeps.py ( set GETDEPS=%~dp0\..\..\opensource\fbcode_builder\getdeps.py ) else if exist %~dp0\build\fbcode_builder\getdeps.py ( set GETDEPS=%~dp0\build\fbcode_builder\getdeps.py ) else ( echo "error: unable to find getdeps.py" exit 1 ) python3.exe %GETDEPS% build folly %* ================================================ FILE: build.sh ================================================ #!/bin/bash # Copyright (c) Meta Platforms, Inc. and affiliates. # # 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. # This script is just a simple wrapper around the # build/fbcode_builder/getdeps.py script. # # Feel free to invoke getdeps.py directly to have more control over the build. SCRIPT_DIR=$(dirname "${BASH_SOURCE[0]}") GETDEPS_PATHS=( "$SCRIPT_DIR/build/fbcode_builder/getdeps.py" "$SCRIPT_DIR/../../opensource/fbcode_builder/getdeps.py" ) for getdeps in "${GETDEPS_PATHS[@]}"; do if [[ -x "$getdeps" ]]; then exec "$getdeps" build folly "$@" fi done echo "Could not find getdeps.py" >&2 exit 1 ================================================ FILE: folly/.clang-format ================================================ # The canary clang-format config file. # # Changes from head: # - AllowAllParametersOfDeclarationOnNextLine: Safe # - AllowShortFunctionsOnASingleLine: Safe # - IncludeIsMainRegex: May cause errors, as it may reorder includes. # - IncludeCategories: May cause errors, as it may reorder includes. --- AccessModifierOffset: -1 AlignAfterOpenBracket: AlwaysBreak AlignConsecutiveMacros: false AlignConsecutiveAssignments: false AlignConsecutiveBitFields: false AlignConsecutiveDeclarations: false AlignEscapedNewlines: Left AlignOperands: DontAlign AlignTrailingComments: false AllowAllArgumentsOnNextLine: true AllowAllConstructorInitializersOnNextLine: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortEnumsOnASingleLine: true AllowShortBlocksOnASingleLine: Never AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: Inline AllowShortLambdasOnASingleLine: All AllowShortIfStatementsOnASingleLine: Never AllowShortLoopsOnASingleLine: false AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: true AlwaysBreakTemplateDeclarations: Yes BinPackArguments: false BinPackParameters: false BreakBeforeBinaryOperators: None BreakBeforeBraces: Attach BreakInheritanceList: BeforeColon BreakBeforeTernaryOperators: true BreakConstructorInitializers: BeforeColon BreakAfterJavaFieldAnnotations: false BreakStringLiterals: false ColumnLimit: 80 CommentPragmas: '^ IWYU pragma:' CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: true ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: true DeriveLineEnding: true DerivePointerAlignment: false DisableFormat: false FixNamespaceComments: true ForEachMacros: - FOR_EACH - FOR_EACH_R - FOR_EACH_RANGE IncludeBlocks: Preserve IncludeCategories: - Regex: '^<(sodium|Python)\.h>$' Priority: 3 - Regex: '^<((arpa|net|netinet|sys|linux)\/)?[-_[:alnum:]]+\.h>$' Priority: 1 - Regex: '^<[\/-_[:alnum:]]+>$' Priority: 2 - Regex: '^<((bistro|caffe2|cachelib|fatal|fb303|faiss|fbmeshd|fboss|fbzmq|fizz|folly|gloo|hphp|logdevice|magma|mcrouter|openr|proxygen|quic|rsocket|scape|squangle|tensorpipe|thrift|wangle|wdt|yarpl)\/).*>$' Priority: 4 - Regex: '^<.*>$' Priority: 3 - Regex: '.*' Priority: 5 IncludeIsMainRegex: '((Test|Tests|_test|Bench|Benchmark|Performance)[0-9]*)?$' IndentCaseLabels: true IndentCaseBlocks: false IndentGotoLabels: true IndentPPDirectives: None IndentExternBlock: AfterExternBlock IndentWidth: 2 IndentWrappedFunctionNames: false InsertTrailingCommas: None JavaScriptQuotes: Leave JavaScriptWrapImports: true KeepEmptyLinesAtTheStartOfBlocks: false MacroBlockBegin: '' MacroBlockEnd: '' MaxEmptyLinesToKeep: 1 NamespaceIndentation: None ObjCBinPackProtocolList: Auto ObjCBlockIndentWidth: 2 ObjCBreakBeforeNestedBlockParam: true ObjCSpaceAfterProperty: false ObjCSpaceBeforeProtocolList: false PenaltyBreakAssignment: 2 PenaltyBreakBeforeFirstCallParameter: 1 PenaltyBreakComment: 300 PenaltyBreakFirstLessLess: 120 PenaltyBreakString: 1000 PenaltyBreakTemplateDeclaration: 10 PenaltyExcessCharacter: 1000000 PenaltyIndentedWhitespace: 10 PenaltyReturnTypeOnItsOwnLine: 200 PointerAlignment: Left ReflowComments: true SortIncludes: true SortUsingDeclarations: true SpaceAfterCStyleCast: false SpaceAfterLogicalNot: false SpaceAfterTemplateKeyword: true SpaceBeforeAssignmentOperators: true SpaceBeforeCpp11BracedList: false SpaceBeforeCtorInitializerColon: true SpaceBeforeInheritanceColon: true SpaceBeforeParens: ControlStatements SpaceBeforeRangeBasedForLoopColon: true SpaceInEmptyBlock: false SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: false SpacesInConditionalStatement: false SpacesInContainerLiterals: true SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false SpaceBeforeSquareBrackets: false Standard: Latest TabWidth: 8 UseCRLF: false UseTab: Never ... ================================================ FILE: folly/AtomicHashArray-inl.h ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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. */ #ifndef FOLLY_ATOMICHASHARRAY_H_ #error "This should only be included by AtomicHashArray.h" #endif #include #include #include #include #include namespace folly { // AtomicHashArray private constructor -- template < class KeyT, class ValueT, class HashFcn, class EqualFcn, class Allocator, class ProbeFcn, class KeyConvertFcn> AtomicHashArray< KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn, KeyConvertFcn>:: AtomicHashArray( size_t capacity, KeyT emptyKey, KeyT lockedKey, KeyT erasedKey, double _maxLoadFactor, uint32_t cacheSize) : capacity_(capacity), maxEntries_(size_t(_maxLoadFactor * capacity_ + 0.5)), kEmptyKey_(emptyKey), kLockedKey_(lockedKey), kErasedKey_(erasedKey), kAnchorMask_(nextPowTwo(capacity_) - 1), numEntries_(0, cacheSize), numPendingEntries_(0, cacheSize), isFull_(0), numErases_(0) { if (capacity == 0) { throw_exception("capacity"); } } /* * findInternal -- * * Sets ret.second to value found and ret.index to index * of key and returns true, or if key does not exist returns false and * ret.index is set to capacity_. */ template < class KeyT, class ValueT, class HashFcn, class EqualFcn, class Allocator, class ProbeFcn, class KeyConvertFcn> template typename AtomicHashArray< KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn, KeyConvertFcn>::SimpleRetT AtomicHashArray< KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn, KeyConvertFcn>::findInternal(const LookupKeyT key_in) { checkLegalKeyIfKey(key_in); for (size_t idx = keyToAnchorIdx(key_in), numProbes = 0; ; idx = ProbeFcn()(idx, numProbes, capacity_)) { const KeyT key = acquireLoadKey(cells_[idx]); if (FOLLY_LIKELY(LookupEqualFcn()(key, key_in))) { return SimpleRetT(idx, true); } if (FOLLY_UNLIKELY(key == kEmptyKey_)) { // if we hit an empty element, this key does not exist return SimpleRetT(capacity_, false); } // NOTE: the way we count numProbes must be same in find(), insert(), // and erase(). Otherwise it may break probing. ++numProbes; if (FOLLY_UNLIKELY(numProbes >= capacity_)) { // probed every cell...fail return SimpleRetT(capacity_, false); } } } /* * insertInternal -- * * Returns false on failure due to key collision or full. * Also sets ret.index to the index of the key. If the map is full, sets * ret.index = capacity_. Also sets ret.second to cell value, thus if insert * successful this will be what we just inserted, if there is a key collision * this will be the previously inserted value, and if the map is full it is * default. */ template < class KeyT, class ValueT, class HashFcn, class EqualFcn, class Allocator, class ProbeFcn, class KeyConvertFcn> template < typename LookupKeyT, typename LookupHashFcn, typename LookupEqualFcn, typename LookupKeyToKeyFcn, typename... ArgTs> typename AtomicHashArray< KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn, KeyConvertFcn>::SimpleRetT AtomicHashArray< KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn, KeyConvertFcn>::insertInternal(LookupKeyT key_in, ArgTs&&... vCtorArgs) { const short NO_NEW_INSERTS = 1; const short NO_PENDING_INSERTS = 2; checkLegalKeyIfKey(key_in); size_t idx = keyToAnchorIdx(key_in); size_t numProbes = 0; for (;;) { DCHECK_LT(idx, capacity_); value_type* cell = &cells_[idx]; if (relaxedLoadKey(*cell) == kEmptyKey_) { // NOTE: isFull_ is set based on numEntries_.readFast(), so it's // possible to insert more than maxEntries_ entries. However, it's not // possible to insert past capacity_. ++numPendingEntries_; if (isFull_.load(std::memory_order_acquire)) { --numPendingEntries_; // Before deciding whether this insert succeeded, this thread needs to // wait until no other thread can add a new entry. // Correctness assumes isFull_ is true at this point. If // another thread now does ++numPendingEntries_, we expect it // to pass the isFull_.load() test above. (It shouldn't insert // a new entry.) detail::atomic_hash_spin_wait([&] { return (isFull_.load(std::memory_order_acquire) != NO_PENDING_INSERTS) && (numPendingEntries_.readFull() != 0); }); isFull_.store(NO_PENDING_INSERTS, std::memory_order_release); if (relaxedLoadKey(*cell) == kEmptyKey_) { // Don't insert past max load factor return SimpleRetT(capacity_, false); } } else { // An unallocated cell. Try once to lock it. If we succeed, insert here. // If we fail, fall through to comparison below; maybe the insert that // just beat us was for this very key.... if (tryLockCell(cell)) { KeyT key_new; // Write the value - done before unlocking try { key_new = LookupKeyToKeyFcn()(key_in); typedef typename std::remove_const::type LookupKeyTNoConst; constexpr bool kAlreadyChecked = std::is_same::value; if (!kAlreadyChecked) { checkLegalKeyIfKey(key_new); } DCHECK(relaxedLoadKey(*cell) == kLockedKey_); // A const mapped_type is only constant once constructed, so cast // away any const for the placement new here. using mapped = typename std::remove_const::type; new (const_cast(&cell->second)) ValueT(std::forward(vCtorArgs)...); unlockCell(cell, key_new); // Sets the new key } catch (...) { // Transition back to empty key---requires handling // locked->empty below. unlockCell(cell, kEmptyKey_); --numPendingEntries_; throw; } // An erase() can race here and delete right after our insertion // Direct comparison rather than EqualFcn ok here // (we just inserted it) DCHECK( relaxedLoadKey(*cell) == key_new || relaxedLoadKey(*cell) == kErasedKey_); --numPendingEntries_; ++numEntries_; // This is a thread cached atomic increment :) if (numEntries_.readFast() >= maxEntries_) { isFull_.store(NO_NEW_INSERTS, std::memory_order_relaxed); } return SimpleRetT(idx, true); } --numPendingEntries_; } } DCHECK(relaxedLoadKey(*cell) != kEmptyKey_); if (kLockedKey_ == acquireLoadKey(*cell)) { detail::atomic_hash_spin_wait([&] { return kLockedKey_ == acquireLoadKey(*cell); }); } const KeyT thisKey = acquireLoadKey(*cell); if (LookupEqualFcn()(thisKey, key_in)) { // Found an existing entry for our key, but we don't overwrite the // previous value. return SimpleRetT(idx, false); } else if (thisKey == kEmptyKey_ || thisKey == kLockedKey_) { // We need to try again (i.e., don't increment numProbes or // advance idx): this case can happen if the constructor for // ValueT threw for this very cell (the rethrow block above). continue; } // NOTE: the way we count numProbes must be same in find(), // insert(), and erase(). Otherwise it may break probing. ++numProbes; if (FOLLY_UNLIKELY(numProbes >= capacity_)) { // probed every cell...fail return SimpleRetT(capacity_, false); } idx = ProbeFcn()(idx, numProbes, capacity_); } } /* * erase -- * * This will attempt to erase the given key key_in if the key is found. It * returns 1 iff the key was located and marked as erased, and 0 otherwise. * * Memory is not freed or reclaimed by erase, i.e. the cell containing the * erased key will never be reused. If there's an associated value, we won't * touch it either. */ template < class KeyT, class ValueT, class HashFcn, class EqualFcn, class Allocator, class ProbeFcn, class KeyConvertFcn> size_t AtomicHashArray< KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn, KeyConvertFcn>::erase(KeyT key_in) { CHECK_NE(key_in, kEmptyKey_); CHECK_NE(key_in, kLockedKey_); CHECK_NE(key_in, kErasedKey_); for (size_t idx = keyToAnchorIdx(key_in), numProbes = 0;; idx = ProbeFcn()(idx, numProbes, capacity_)) { DCHECK_LT(idx, capacity_); value_type* cell = &cells_[idx]; KeyT currentKey = acquireLoadKey(*cell); if (currentKey == kEmptyKey_ || currentKey == kLockedKey_) { // If we hit an empty (or locked) element, this key does not exist. This // is similar to how it's handled in find(). return 0; } if (EqualFcn()(currentKey, key_in)) { // Found an existing entry for our key, attempt to mark it erased. // Some other thread may have erased our key, but this is ok. KeyT expect = currentKey; if (cellKeyPtr(*cell)->compare_exchange_strong(expect, kErasedKey_)) { numErases_.fetch_add(1, std::memory_order_relaxed); // Even if there's a value in the cell, we won't delete (or even // default construct) it because some other thread may be accessing it. // Locking it meanwhile won't work either since another thread may be // holding a pointer to it. // We found the key and successfully erased it. return 1; } // If another thread succeeds in erasing our key, we'll stop our search. return 0; } // NOTE: the way we count numProbes must be same in find(), insert(), // and erase(). Otherwise it may break probing. ++numProbes; if (FOLLY_UNLIKELY(numProbes >= capacity_)) { // probed every cell...fail return 0; } } } template < class KeyT, class ValueT, class HashFcn, class EqualFcn, class Allocator, class ProbeFcn, class KeyConvertFcn> typename AtomicHashArray< KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn, KeyConvertFcn>::SmartPtr AtomicHashArray< KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn, KeyConvertFcn>::create(size_t maxSize, const Config& c) { CHECK_LE(c.maxLoadFactor, 1.0); CHECK_GT(c.maxLoadFactor, 0.0); CHECK_NE(c.emptyKey, c.lockedKey); size_t capacity = size_t(maxSize / c.maxLoadFactor); size_t sz = sizeof(AtomicHashArray) + sizeof(value_type) * capacity; auto const mem = Allocator().allocate(sz); try { new (mem) AtomicHashArray( capacity, c.emptyKey, c.lockedKey, c.erasedKey, c.maxLoadFactor, c.entryCountThreadCacheSize); } catch (...) { Allocator().deallocate(mem, sz); throw; } SmartPtr map(static_cast((void*)mem)); /* * Mark all cells as empty. * * Note: we're bending the rules a little here accessing the key * element in our cells even though the cell object has not been * constructed, and casting them to atomic objects (see cellKeyPtr). * (Also, in fact we never actually invoke the value_type * constructor.) This is in order to avoid needing to default * construct a bunch of value_type when we first start up: if you * have an expensive default constructor for the value type this can * noticeably speed construction time for an AHA. */ FOR_EACH_RANGE (i, 0, map->capacity_) { cellKeyPtr(map->cells_[i]) ->store(map->kEmptyKey_, std::memory_order_relaxed); } return map; } template < class KeyT, class ValueT, class HashFcn, class EqualFcn, class Allocator, class ProbeFcn, class KeyConvertFcn> void AtomicHashArray< KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn, KeyConvertFcn>::destroy(AtomicHashArray* p) { assert(p); size_t sz = sizeof(AtomicHashArray) + sizeof(value_type) * p->capacity_; FOR_EACH_RANGE (i, 0, p->capacity_) { if (p->cells_[i].first != p->kEmptyKey_) { p->cells_[i].~value_type(); } } p->~AtomicHashArray(); Allocator().deallocate((char*)p, sz); } // clear -- clears all keys and values in the map and resets all counters template < class KeyT, class ValueT, class HashFcn, class EqualFcn, class Allocator, class ProbeFcn, class KeyConvertFcn> void AtomicHashArray< KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn, KeyConvertFcn>::clear() { FOR_EACH_RANGE (i, 0, capacity_) { if (cells_[i].first != kEmptyKey_) { cells_[i].~value_type(); *const_cast(&cells_[i].first) = kEmptyKey_; } CHECK(cells_[i].first == kEmptyKey_); } numEntries_.set(0); numPendingEntries_.set(0); isFull_.store(0, std::memory_order_relaxed); numErases_.store(0, std::memory_order_relaxed); } // Iterator implementation template < class KeyT, class ValueT, class HashFcn, class EqualFcn, class Allocator, class ProbeFcn, class KeyConvertFcn> template struct AtomicHashArray< KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn, KeyConvertFcn>::aha_iterator : detail::IteratorFacade< aha_iterator, IterVal, std::forward_iterator_tag> { explicit aha_iterator() : aha_(nullptr) {} // Conversion ctor for interoperability between const_iterator and // iterator. The enable_if<> magic keeps us well-behaved for // is_convertible<> (v. the iterator_facade documentation). template aha_iterator( const aha_iterator& o, typename std::enable_if< std::is_convertible::value>::type* = nullptr) : aha_(o.aha_), offset_(o.offset_) {} explicit aha_iterator(ContT* array, size_t offset) : aha_(array), offset_(offset) {} // Returns unique index that can be used with findAt(). // WARNING: The following function will fail silently for hashtable // with capacity > 2^32 uint32_t getIndex() const { return offset_; } void advancePastEmpty() { while (offset_ < aha_->capacity_ && !isValid()) { ++offset_; } } private: friend class AtomicHashArray; friend class detail:: IteratorFacade; void increment() { ++offset_; advancePastEmpty(); } bool equal(const aha_iterator& o) const { return aha_ == o.aha_ && offset_ == o.offset_; } IterVal& dereference() const { return aha_->cells_[offset_]; } bool isValid() const { KeyT key = acquireLoadKey(aha_->cells_[offset_]); return key != aha_->kEmptyKey_ && key != aha_->kLockedKey_ && key != aha_->kErasedKey_; } private: ContT* aha_; size_t offset_; }; // aha_iterator } // namespace folly ================================================ FILE: folly/AtomicHashArray.h ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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. */ /** * AtomicHashArray is the building block for AtomicHashMap. It provides the * core lock-free functionality, but is limited by the fact that it cannot * grow past its initialization size and is a little more awkward (no public * constructor, for example). If you're confident that you won't run out of * space, don't mind the awkwardness, and really need bare-metal performance, * feel free to use AHA directly. * * Check out AtomicHashMap.h for more thorough documentation on perf and * general pros and cons relative to other hash maps. * */ #pragma once #define FOLLY_ATOMICHASHARRAY_H_ #include #include #include #include namespace folly { struct AtomicHashArrayLinearProbeFcn { inline size_t operator()( size_t idx, size_t /* numProbes */, size_t capacity) const { idx += 1; // linear probing // Avoid modulus because it's slow return FOLLY_LIKELY(idx < capacity) ? idx : (idx - capacity); } }; struct AtomicHashArrayQuadraticProbeFcn { inline size_t operator()( size_t idx, size_t numProbes, size_t capacity) const { idx += numProbes; // quadratic probing // Avoid modulus because it's slow return FOLLY_LIKELY(idx < capacity) ? idx : (idx - capacity); } }; // Enables specializing checkLegalKey without specializing its class. namespace detail { template inline void checkLegalKeyIfKeyTImpl( NotKeyT /* ignored */, KeyT /* emptyKey */, KeyT /* lockedKey */, KeyT /* erasedKey */) {} template inline void checkLegalKeyIfKeyTImpl( KeyT key_in, KeyT emptyKey, KeyT lockedKey, KeyT erasedKey) { DCHECK_NE(key_in, emptyKey); DCHECK_NE(key_in, lockedKey); DCHECK_NE(key_in, erasedKey); } } // namespace detail template < class KeyT, class ValueT, class HashFcn = std::hash, class EqualFcn = std::equal_to, class Allocator = std::allocator, class ProbeFcn = AtomicHashArrayLinearProbeFcn, class KeyConvertFcn = Identity> class AtomicHashMap; template < class KeyT, class ValueT, class HashFcn = std::hash, class EqualFcn = std::equal_to, class Allocator = std::allocator, class ProbeFcn = AtomicHashArrayLinearProbeFcn, class KeyConvertFcn = Identity> class AtomicHashArray { static_assert( (std::is_convertible::value || std::is_convertible::value || std::is_convertible::value), "You are trying to use AtomicHashArray with disallowed key " "types. You must use atomically compare-and-swappable integer " "keys, or a different container class."); public: using key_type = KeyT; using mapped_type = ValueT; using hasher = HashFcn; using key_equal = EqualFcn; using key_convert = KeyConvertFcn; using value_type = std::pair; using size_type = std::size_t; using difference_type = std::ptrdiff_t; using reference = value_type&; using const_reference = const value_type&; using pointer = value_type*; using const_pointer = const value_type*; const size_t capacity_; const size_t maxEntries_; const KeyT kEmptyKey_; const KeyT kLockedKey_; const KeyT kErasedKey_; template struct aha_iterator; using const_iterator = aha_iterator; using iterator = aha_iterator; // You really shouldn't need this if you use the SmartPtr provided by create, // but if you really want to do something crazy like stick the released // pointer into a DiscriminatedPtr or something, you'll need this to clean up // after yourself. static void destroy(AtomicHashArray*); private: const size_t kAnchorMask_; struct Deleter { void operator()(AtomicHashArray* ptr) { AtomicHashArray::destroy(ptr); } }; public: using SmartPtr = std::unique_ptr; /* * create -- * * Creates AtomicHashArray objects. Use instead of constructor/destructor. * * We do things this way in order to avoid the perf penalty of a second * pointer indirection when composing these into AtomicHashMap, which needs * to store an array of pointers so that it can perform atomic operations on * them when growing. * * Instead of a mess of arguments, we take a max size and a Config struct to * simulate named ctor parameters. The Config struct has sensible defaults * for everything, but is overloaded - if you specify a positive capacity, * that will be used directly instead of computing it based on * maxLoadFactor. * * Create returns an AHA::SmartPtr which is a unique_ptr with a custom * deleter to make sure everything is cleaned up properly. */ struct Config { KeyT emptyKey; KeyT lockedKey; KeyT erasedKey; double maxLoadFactor; double growthFactor; uint32_t entryCountThreadCacheSize; size_t capacity; // if positive, overrides maxLoadFactor // Cannot have constexpr ctor because some compilers rightly complain. Config() : emptyKey((KeyT)-1), lockedKey((KeyT)-2), erasedKey((KeyT)-3), maxLoadFactor(0.8), growthFactor(-1), entryCountThreadCacheSize(1000), capacity(0) {} }; // Cannot have pre-instantiated const Config instance because of SIOF. static SmartPtr create(size_t maxSize, const Config& c = Config()); /* * find -- * * * Returns the iterator to the element if found, otherwise end(). * * As an optional feature, the type of the key to look up (LookupKeyT) is * allowed to be different from the type of keys actually stored (KeyT). * * This enables use cases where materializing the key is costly and usually * redundant, e.g., canonicalizing/interning a set of strings and being able * to look up by StringPiece. To use this feature, LookupHashFcn must take * a LookupKeyT, and LookupEqualFcn must take KeyT and LookupKeyT as first * and second parameter, respectively. * * See folly/test/ArrayHashArrayTest.cpp for sample usage. */ template < typename LookupKeyT = key_type, typename LookupHashFcn = hasher, typename LookupEqualFcn = key_equal> iterator find(LookupKeyT k) { return iterator( this, findInternal(k).idx); } template < typename LookupKeyT = key_type, typename LookupHashFcn = hasher, typename LookupEqualFcn = key_equal> const_iterator find(LookupKeyT k) const { return const_cast(this) ->find(k); } /* * insert -- * * Returns a pair with iterator to the element at r.first and bool success. * Retrieve the index with ret.first.getIndex(). * * Fails on key collision (does not overwrite) or if map becomes * full, at which point no element is inserted, iterator is set to end(), * and success is set false. On collisions, success is set false, but the * iterator is set to the existing entry. */ std::pair insert(const value_type& r) { return emplace(r.first, r.second); } std::pair insert(value_type&& r) { return emplace(r.first, std::move(r.second)); } /* * emplace -- * * Same contract as insert(), but performs in-place construction * of the value type using the specified arguments. * * Also, like find(), this method optionally allows 'key_in' to have a type * different from that stored in the table; see find(). If and only if no * equal key is already present, this method converts 'key_in' to a key of * type KeyT using the provided LookupKeyToKeyFcn. */ template < typename LookupKeyT = key_type, typename LookupHashFcn = hasher, typename LookupEqualFcn = key_equal, typename LookupKeyToKeyFcn = key_convert, typename... ArgTs> std::pair emplace(LookupKeyT key_in, ArgTs&&... vCtorArgs) { SimpleRetT ret = insertInternal< LookupKeyT, LookupHashFcn, LookupEqualFcn, LookupKeyToKeyFcn>(key_in, std::forward(vCtorArgs)...); return std::make_pair(iterator(this, ret.idx), ret.success); } // returns the number of elements erased - should never exceed 1 size_t erase(KeyT k); // clears all keys and values in the map and resets all counters. Not thread // safe. void clear(); // Exact number of elements in the map - note that readFull() acquires a // mutex. See folly/ThreadCachedInt.h for more details. size_t size() const { return numEntries_.readFull() - numErases_.load(std::memory_order_relaxed); } bool empty() const { return size() == 0; } iterator begin() { iterator it(this, 0); it.advancePastEmpty(); return it; } const_iterator begin() const { const_iterator it(this, 0); it.advancePastEmpty(); return it; } iterator end() { return iterator(this, capacity_); } const_iterator end() const { return const_iterator(this, capacity_); } // See AtomicHashMap::findAt - access elements directly // WARNING: The following 2 functions will fail silently for hashtable // with capacity > 2^32 iterator findAt(uint32_t idx) { DCHECK_LT(idx, capacity_); return iterator(this, idx); } const_iterator findAt(uint32_t idx) const { return const_cast(this)->findAt(idx); } iterator makeIter(size_t idx) { return iterator(this, idx); } const_iterator makeIter(size_t idx) const { return const_iterator(this, idx); } // The max load factor allowed for this map double maxLoadFactor() const { return ((double)maxEntries_) / capacity_; } void setEntryCountThreadCacheSize(uint32_t newSize) { numEntries_.setCacheSize(newSize); numPendingEntries_.setCacheSize(newSize); } uint32_t getEntryCountThreadCacheSize() const { return numEntries_.getCacheSize(); } /* Private data and helper functions... */ private: friend class AtomicHashMap< KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn>; struct SimpleRetT { size_t idx; bool success; SimpleRetT(size_t i, bool s) : idx(i), success(s) {} SimpleRetT() = default; }; template < typename LookupKeyT = key_type, typename LookupHashFcn = hasher, typename LookupEqualFcn = key_equal, typename LookupKeyToKeyFcn = Identity, typename... ArgTs> SimpleRetT insertInternal(LookupKeyT key, ArgTs&&... vCtorArgs); template < typename LookupKeyT = key_type, typename LookupHashFcn = hasher, typename LookupEqualFcn = key_equal> SimpleRetT findInternal(const LookupKeyT key); template void checkLegalKeyIfKey(MaybeKeyT key) { detail::checkLegalKeyIfKeyTImpl(key, kEmptyKey_, kLockedKey_, kErasedKey_); } static std::atomic* cellKeyPtr(const value_type& r) { // We need some illegal casting here in order to actually store // our value_type as a std::pair. But a little bit of // undefined behavior never hurt anyone ... static_assert( sizeof(std::atomic) == sizeof(KeyT), "std::atomic is implemented in an unexpected way for AHM"); return const_cast*>( reinterpret_cast const*>(&r.first)); } static KeyT relaxedLoadKey(const value_type& r) { return cellKeyPtr(r)->load(std::memory_order_relaxed); } static KeyT acquireLoadKey(const value_type& r) { return cellKeyPtr(r)->load(std::memory_order_acquire); } // Fun with thread local storage - atomic increment is expensive // (relatively), so we accumulate in the thread cache and periodically // flush to the actual variable, and walk through the unflushed counts when // reading the value, so be careful of calling size() too frequently. This // increases insertion throughput several times over while keeping the count // accurate. ThreadCachedInt numEntries_; // Successful key inserts ThreadCachedInt numPendingEntries_; // Used by insertInternal std::atomic isFull_; // Used by insertInternal std::atomic numErases_; // Successful key erases value_type cells_[0]; // This must be the last field of this class // Force constructor/destructor private since create/destroy should be // used externally instead AtomicHashArray( size_t capacity, KeyT emptyKey, KeyT lockedKey, KeyT erasedKey, double maxLoadFactor, uint32_t cacheSize); AtomicHashArray(const AtomicHashArray&) = delete; AtomicHashArray& operator=(const AtomicHashArray&) = delete; ~AtomicHashArray() = default; inline void unlockCell(value_type* const cell, KeyT newKey) { cellKeyPtr(*cell)->store(newKey, std::memory_order_release); } inline bool tryLockCell(value_type* const cell) { KeyT expect = kEmptyKey_; return cellKeyPtr(*cell)->compare_exchange_strong( expect, kLockedKey_, std::memory_order_acq_rel); } template inline size_t keyToAnchorIdx(const LookupKeyT k) const { const size_t hashVal = LookupHashFcn()(k); const size_t probe = hashVal & kAnchorMask_; return FOLLY_LIKELY(probe < capacity_) ? probe : hashVal % capacity_; } }; // AtomicHashArray } // namespace folly #include ================================================ FILE: folly/AtomicHashMap-inl.h ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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. */ #ifndef FOLLY_ATOMICHASHMAP_H_ #error "This should only be included by AtomicHashMap.h" #endif #include #include #include namespace folly { // AtomicHashMap constructor -- Atomic wrapper that allows growth // This class has a lot of overhead (184 Bytes) so only use for big maps template < typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn, typename Allocator, typename ProbeFcn, typename KeyConvertFcn> AtomicHashMap< KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn, KeyConvertFcn>::AtomicHashMap(size_t finalSizeEst, const Config& config) : kGrowthFrac_( config.growthFactor < 0 ? 1.0f - config.maxLoadFactor : config.growthFactor) { CHECK(config.maxLoadFactor > 0.0f && config.maxLoadFactor < 1.0f); subMaps_[0].store( SubMap::create(finalSizeEst, config).release(), std::memory_order_relaxed); auto subMapCount = kNumSubMaps_; FOR_EACH_RANGE (i, 1, subMapCount) { subMaps_[i].store(nullptr, std::memory_order_relaxed); } numMapsAllocated_.store(1, std::memory_order_relaxed); } // emplace -- template < typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn, typename Allocator, typename ProbeFcn, typename KeyConvertFcn> template < typename LookupKeyT, typename LookupHashFcn, typename LookupEqualFcn, typename LookupKeyToKeyFcn, typename... ArgTs> std::pair< typename AtomicHashMap< KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn, KeyConvertFcn>::iterator, bool> AtomicHashMap< KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn, KeyConvertFcn>::emplace(LookupKeyT k, ArgTs&&... vCtorArgs) { SimpleRetT ret = insertInternal< LookupKeyT, LookupHashFcn, LookupEqualFcn, LookupKeyToKeyFcn>(k, std::forward(vCtorArgs)...); SubMap* subMap = subMaps_[ret.i].load(std::memory_order_relaxed); return std::make_pair( iterator(this, ret.i, subMap->makeIter(ret.j)), ret.success); } // insertInternal -- Allocates new sub maps as existing ones fill up. template < typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn, typename Allocator, typename ProbeFcn, typename KeyConvertFcn> template < typename LookupKeyT, typename LookupHashFcn, typename LookupEqualFcn, typename LookupKeyToKeyFcn, typename... ArgTs> typename AtomicHashMap< KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn, KeyConvertFcn>::SimpleRetT AtomicHashMap< KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn, KeyConvertFcn>::insertInternal(LookupKeyT key, ArgTs&&... vCtorArgs) { beginInsertInternal: auto nextMapIdx = // this maintains our state numMapsAllocated_.load(std::memory_order_acquire); typename SubMap::SimpleRetT ret; FOR_EACH_RANGE (i, 0, nextMapIdx) { // insert in each map successively. If one succeeds, we're done! SubMap* subMap = subMaps_[i].load(std::memory_order_relaxed); ret = subMap->template insertInternal< LookupKeyT, LookupHashFcn, LookupEqualFcn, LookupKeyToKeyFcn>(key, std::forward(vCtorArgs)...); if (ret.idx == subMap->capacity_) { continue; // map is full, so try the next one } // Either collision or success - insert in either case return SimpleRetT(i, ret.idx, ret.success); } // If we made it this far, all maps are full and we need to try to allocate // the next one. SubMap* primarySubMap = subMaps_[0].load(std::memory_order_relaxed); if (nextMapIdx >= kNumSubMaps_ || primarySubMap->capacity_ * kGrowthFrac_ < 1.0) { // Can't allocate any more sub maps. throw AtomicHashMapFullError(); } if (tryLockMap(nextMapIdx)) { // Alloc a new map and shove it in. We can change whatever // we want because other threads are waiting on us... size_t numCellsAllocated = (size_t)(primarySubMap->capacity_ * std::pow(1.0 + kGrowthFrac_, nextMapIdx - 1)); size_t newSize = size_t(numCellsAllocated * kGrowthFrac_); DCHECK( subMaps_[nextMapIdx].load(std::memory_order_relaxed) == (SubMap*)kLockedPtr_); // create a new map using the settings stored in the first map Config config; config.emptyKey = primarySubMap->kEmptyKey_; config.lockedKey = primarySubMap->kLockedKey_; config.erasedKey = primarySubMap->kErasedKey_; config.maxLoadFactor = primarySubMap->maxLoadFactor(); config.entryCountThreadCacheSize = primarySubMap->getEntryCountThreadCacheSize(); subMaps_[nextMapIdx].store( SubMap::create(newSize, config).release(), std::memory_order_relaxed); // Publish the map to other threads. numMapsAllocated_.fetch_add(1, std::memory_order_release); DCHECK_EQ( nextMapIdx + 1, numMapsAllocated_.load(std::memory_order_relaxed)); } else { // If we lost the race, we'll have to wait for the next map to get // allocated before doing any insertion here. detail::atomic_hash_spin_wait([&] { return nextMapIdx >= numMapsAllocated_.load(std::memory_order_acquire); }); } // Relaxed is ok here because either we just created this map, or we // just did a spin wait with an acquire load on numMapsAllocated_. SubMap* loadedMap = subMaps_[nextMapIdx].load(std::memory_order_relaxed); DCHECK(loadedMap && loadedMap != (SubMap*)kLockedPtr_); ret = loadedMap->insertInternal(key, std::forward(vCtorArgs)...); if (ret.idx != loadedMap->capacity_) { return SimpleRetT(nextMapIdx, ret.idx, ret.success); } // We took way too long and the new map is already full...try again from // the top (this should pretty much never happen). goto beginInsertInternal; } // find -- template < typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn, typename Allocator, typename ProbeFcn, typename KeyConvertFcn> template typename AtomicHashMap< KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn, KeyConvertFcn>::iterator AtomicHashMap< KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn, KeyConvertFcn>::find(LookupKeyT k) { SimpleRetT ret = findInternal(k); if (!ret.success) { return end(); } SubMap* subMap = subMaps_[ret.i].load(std::memory_order_relaxed); return iterator(this, ret.i, subMap->makeIter(ret.j)); } template < typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn, typename Allocator, typename ProbeFcn, typename KeyConvertFcn> template typename AtomicHashMap< KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn, KeyConvertFcn>::const_iterator AtomicHashMap< KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn, KeyConvertFcn>::find(LookupKeyT k) const { return const_cast(this) ->find(k); } // findInternal -- template < typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn, typename Allocator, typename ProbeFcn, typename KeyConvertFcn> template typename AtomicHashMap< KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn, KeyConvertFcn>::SimpleRetT AtomicHashMap< KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn, KeyConvertFcn>::findInternal(const LookupKeyT k) const { SubMap* const primaryMap = subMaps_[0].load(std::memory_order_relaxed); typename SubMap::SimpleRetT ret = primaryMap ->template findInternal(k); if (FOLLY_LIKELY(ret.idx != primaryMap->capacity_)) { return SimpleRetT(0, ret.idx, ret.success); } const unsigned int numMaps = numMapsAllocated_.load(std::memory_order_acquire); FOR_EACH_RANGE (i, 1, numMaps) { // Check each map successively. If one succeeds, we're done! SubMap* thisMap = subMaps_[i].load(std::memory_order_relaxed); ret = thisMap ->template findInternal( k); if (FOLLY_LIKELY(ret.idx != thisMap->capacity_)) { return SimpleRetT(i, ret.idx, ret.success); } } // Didn't find our key... return SimpleRetT(numMaps, 0, false); } // findAtInternal -- see encodeIndex() for details. template < typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn, typename Allocator, typename ProbeFcn, typename KeyConvertFcn> typename AtomicHashMap< KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn, KeyConvertFcn>::SimpleRetT AtomicHashMap< KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn, KeyConvertFcn>::findAtInternal(uint32_t idx) const { uint32_t subMapIdx, subMapOffset; if (idx & kSecondaryMapBit_) { // idx falls in a secondary map idx &= ~kSecondaryMapBit_; // unset secondary bit subMapIdx = idx >> kSubMapIndexShift_; DCHECK_LT(subMapIdx, numMapsAllocated_.load(std::memory_order_relaxed)); subMapOffset = idx & kSubMapIndexMask_; } else { // idx falls in primary map subMapIdx = 0; subMapOffset = idx; } return SimpleRetT(subMapIdx, subMapOffset, true); } // erase -- template < typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn, typename Allocator, typename ProbeFcn, typename KeyConvertFcn> typename AtomicHashMap< KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn, KeyConvertFcn>::size_type AtomicHashMap< KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn, KeyConvertFcn>::erase(const KeyT k) { int const numMaps = numMapsAllocated_.load(std::memory_order_acquire); FOR_EACH_RANGE (i, 0, numMaps) { // Check each map successively. If one succeeds, we're done! if (subMaps_[i].load(std::memory_order_relaxed)->erase(k)) { return 1; } } // Didn't find our key... return 0; } // capacity -- summation of capacities of all submaps template < typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn, typename Allocator, typename ProbeFcn, typename KeyConvertFcn> size_t AtomicHashMap< KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn, KeyConvertFcn>::capacity() const { size_t totalCap(0); int const numMaps = numMapsAllocated_.load(std::memory_order_acquire); FOR_EACH_RANGE (i, 0, numMaps) { totalCap += subMaps_[i].load(std::memory_order_relaxed)->capacity_; } return totalCap; } // spaceRemaining -- // number of new insertions until current submaps are all at max load template < typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn, typename Allocator, typename ProbeFcn, typename KeyConvertFcn> size_t AtomicHashMap< KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn, KeyConvertFcn>::spaceRemaining() const { size_t spaceRem(0); int const numMaps = numMapsAllocated_.load(std::memory_order_acquire); FOR_EACH_RANGE (i, 0, numMaps) { SubMap* thisMap = subMaps_[i].load(std::memory_order_relaxed); spaceRem += std::max(0, thisMap->maxEntries_ - &thisMap->numEntries_.readFull()); } return spaceRem; } // clear -- Wipes all keys and values from primary map and destroys // all secondary maps. Not thread safe. template < typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn, typename Allocator, typename ProbeFcn, typename KeyConvertFcn> void AtomicHashMap< KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn, KeyConvertFcn>::clear() { subMaps_[0].load(std::memory_order_relaxed)->clear(); int const numMaps = numMapsAllocated_.load(std::memory_order_relaxed); FOR_EACH_RANGE (i, 1, numMaps) { SubMap* thisMap = subMaps_[i].load(std::memory_order_relaxed); DCHECK(thisMap); SubMap::destroy(thisMap); subMaps_[i].store(nullptr, std::memory_order_relaxed); } numMapsAllocated_.store(1, std::memory_order_relaxed); } // size -- template < typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn, typename Allocator, typename ProbeFcn, typename KeyConvertFcn> size_t AtomicHashMap< KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn, KeyConvertFcn>::size() const { size_t totalSize(0); int const numMaps = numMapsAllocated_.load(std::memory_order_acquire); FOR_EACH_RANGE (i, 0, numMaps) { totalSize += subMaps_[i].load(std::memory_order_relaxed)->size(); } return totalSize; } // encodeIndex -- Encode the submap index and offset into return. // index_ret must be pre-populated with the submap offset. // // We leave index_ret untouched when referring to the primary map // so it can be as large as possible (31 data bits). Max size of // secondary maps is limited by what can fit in the low 27 bits. // // Returns the following bit-encoded data in index_ret: // if subMap == 0 (primary map) => // bit(s) value // 31 0 // 0-30 submap offset (index_ret input) // // if subMap > 0 (secondary maps) => // bit(s) value // 31 1 // 27-30 which subMap // 0-26 subMap offset (index_ret input) template < typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn, typename Allocator, typename ProbeFcn, typename KeyConvertFcn> inline uint32_t AtomicHashMap< KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn, KeyConvertFcn>::encodeIndex(uint32_t subMap, uint32_t offset) { DCHECK_EQ(offset & kSecondaryMapBit_, 0); // offset can't be too big if (subMap == 0) { return offset; } // Make sure subMap isn't too big DCHECK_EQ(subMap >> kNumSubMapBits_, 0); // Make sure subMap bits of offset are clear DCHECK_EQ(offset & (~kSubMapIndexMask_ | kSecondaryMapBit_), 0); // Set high-order bits to encode which submap this index belongs to return offset | (subMap << kSubMapIndexShift_) | kSecondaryMapBit_; } // Iterator implementation template < typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn, typename Allocator, typename ProbeFcn, typename KeyConvertFcn> template struct AtomicHashMap< KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn, KeyConvertFcn>::ahm_iterator : detail::IteratorFacade< ahm_iterator, IterVal, std::forward_iterator_tag> { explicit ahm_iterator() : ahm_(nullptr) {} // Conversion ctor for interoperability between const_iterator and // iterator. The enable_if<> magic keeps us well-behaved for // is_convertible<> (v. the iterator_facade documentation). template ahm_iterator( const ahm_iterator& o, typename std::enable_if< std::is_convertible::value>::type* = nullptr) : ahm_(o.ahm_), subMap_(o.subMap_), subIt_(o.subIt_) {} /* * Returns the unique index that can be used for access directly * into the data storage. */ uint32_t getIndex() const { CHECK(!isEnd()); return ahm_->encodeIndex(subMap_, subIt_.getIndex()); } private: friend class AtomicHashMap; explicit ahm_iterator(ContT* ahm, uint32_t subMap, const SubIt& subIt) : ahm_(ahm), subMap_(subMap), subIt_(subIt) {} friend class detail:: IteratorFacade; void increment() { CHECK(!isEnd()); ++subIt_; checkAdvanceToNextSubmap(); } bool equal(const ahm_iterator& other) const { if (ahm_ != other.ahm_) { return false; } if (isEnd() || other.isEnd()) { return isEnd() == other.isEnd(); } return subMap_ == other.subMap_ && subIt_ == other.subIt_; } IterVal& dereference() const { return *subIt_; } bool isEnd() const { return ahm_ == nullptr; } void checkAdvanceToNextSubmap() { if (isEnd()) { return; } SubMap* thisMap = ahm_->subMaps_[subMap_].load(std::memory_order_relaxed); while (subIt_ == thisMap->end()) { // This sub iterator is done, advance to next one if (subMap_ + 1 < ahm_->numMapsAllocated_.load(std::memory_order_acquire)) { ++subMap_; thisMap = ahm_->subMaps_[subMap_].load(std::memory_order_relaxed); subIt_ = thisMap->begin(); } else { ahm_ = nullptr; return; } } } private: ContT* ahm_; uint32_t subMap_; SubIt subIt_; }; // ahm_iterator } // namespace folly ================================================ FILE: folly/AtomicHashMap.h ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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. */ /* * AtomicHashMap -- * * A high-performance concurrent hash map with int32_t or int64_t keys. Supports * insert, find(key), findAt(index), erase(key), size, and more. Memory cannot * be freed or reclaimed by erase. Can grow to a maximum of about 18 times the * initial capacity, but performance degrades linearly with growth. Can also be * used as an object store with unique 32-bit references directly into the * internal storage (retrieved with iterator::getIndex()). * * Advantages: * - High-performance (~2-4x tbb::concurrent_hash_map in heavily * multi-threaded environments). * - Efficient memory usage if initial capacity is not over estimated * (especially for small keys and values). * - Good fragmentation properties (only allocates in large slabs which can * be reused with clear() and never move). * - Can generate unique, long-lived 32-bit references for efficient lookup * (see findAt()). * * Disadvantages: * - Keys must be native int32_t or int64_t, or explicitly converted. * - Must be able to specify unique empty, locked, and erased keys * - Performance degrades linearly as size grows beyond initialization * capacity. * - Max size limit of ~18x initial size (dependent on max load factor). * - Memory is not freed or reclaimed by erase. * * Usage and Operation Details: * Simple performance/memory tradeoff with maxLoadFactor. Higher load factors * give better memory utilization but probe lengths increase, reducing * performance. * * Implementation and Performance Details: * AHArray is a fixed size contiguous block of value_type cells. When * writing a cell, the key is locked while the rest of the record is * written. Once done, the cell is unlocked by setting the key. find() * is completely wait-free and doesn't require any non-relaxed atomic * operations. AHA cannot grow beyond initialization capacity, but is * faster because of reduced data indirection. * * AHMap is a wrapper around AHArray sub-maps that allows growth and provides * an interface closer to the STL UnorderedAssociativeContainer concept. These * sub-maps are allocated on the fly and are processed in series, so the more * there are (from growing past initial capacity), the worse the performance. * * Insert returns false if there is a key collision and throws if the max size * of the map is exceeded. * * Benchmark performance with 8 simultaneous threads processing 1 million * unique entries on a 4-core, 2.5 GHz machine: * * Load Factor Mem Efficiency usec/Insert usec/Find * 50% 50% 0.19 0.05 * 85% 85% 0.20 0.06 * 90% 90% 0.23 0.08 * 95% 95% 0.27 0.10 * * See folly/tests/AtomicHashMapTest.cpp for more benchmarks. */ #pragma once #define FOLLY_ATOMICHASHMAP_H_ #include #include #include #include #include #include #include #include #include namespace folly { /* * AtomicHashMap provides an interface somewhat similar to the * UnorderedAssociativeContainer concept in C++. This does not * exactly match this concept (or even the basic Container concept), * because of some restrictions imposed by our datastructure. * * Specific differences (there are quite a few): * * - Efficiently thread safe for inserts (main point of this stuff), * wait-free for lookups. * * - You can erase from this container, but the cell containing the key will * not be free or reclaimed. * * - You can erase everything by calling clear() (and you must guarantee only * one thread can be using the container to do that). * * - We aren't DefaultConstructible, CopyConstructible, Assignable, or * EqualityComparable. (Most of these are probably not something * you actually want to do with this anyway.) * * - We don't support the various bucket functions, rehash(), * reserve(), or equal_range(). Also no constructors taking * iterators, although this could change. * * - Several insertion functions, notably operator[], are not * implemented. It is a little too easy to misuse these functions * with this container, where part of the point is that when an * insertion happens for a new key, it will atomically have the * desired value. * * - The map has no templated insert() taking an iterator range, but * we do provide an insert(key, value). The latter seems more * frequently useful for this container (to avoid sprinkling * make_pair everywhere), and providing both can lead to some gross * template error messages. * * - The Allocator must not be stateful (a new instance will be spun up for * each allocation), and its allocate() method must take a raw number of * bytes. * * - KeyT must be a 32 bit or 64 bit atomic integer type, and you must * define special 'locked' and 'empty' key values in the ctor * * - We don't take the Hash function object as an instance in the * constructor. * */ // Thrown when insertion fails due to running out of space for // submaps. struct FOLLY_EXPORT AtomicHashMapFullError : std::runtime_error { explicit AtomicHashMapFullError() : std::runtime_error("AtomicHashMap is full") {} }; template < class KeyT, class ValueT, class HashFcn, class EqualFcn, class Allocator, class ProbeFcn, class KeyConvertFcn> class AtomicHashMap { typedef AtomicHashArray< KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn, KeyConvertFcn> SubMap; public: typedef KeyT key_type; typedef ValueT mapped_type; typedef std::pair value_type; typedef HashFcn hasher; typedef EqualFcn key_equal; typedef KeyConvertFcn key_convert; typedef value_type* pointer; typedef value_type& reference; typedef const value_type& const_reference; typedef std::ptrdiff_t difference_type; typedef std::size_t size_type; typedef typename SubMap::Config Config; template struct ahm_iterator; typedef ahm_iterator< const AtomicHashMap, const value_type, typename SubMap::const_iterator> const_iterator; typedef ahm_iterator iterator; public: const float kGrowthFrac_; // How much to grow when we run out of capacity. // The constructor takes a finalSizeEst which is the optimal // number of elements to maximize space utilization and performance, // and a Config object to specify more advanced options. explicit AtomicHashMap(size_t finalSizeEst, const Config& c = Config()); AtomicHashMap(const AtomicHashMap&) = delete; AtomicHashMap& operator=(const AtomicHashMap&) = delete; ~AtomicHashMap() { const unsigned int numMaps = numMapsAllocated_.load(std::memory_order_relaxed); FOR_EACH_RANGE (i, 0, numMaps) { SubMap* thisMap = subMaps_[i].load(std::memory_order_relaxed); DCHECK(thisMap); SubMap::destroy(thisMap); } } key_equal key_eq() const { return key_equal(); } hasher hash_function() const { return hasher(); } /* * insert -- * * Returns a pair with iterator to the element at r.first and * success. Retrieve the index with ret.first.getIndex(). * * Does not overwrite on key collision, but returns an iterator to * the existing element (since this could due to a race with * another thread, it is often important to check this return * value). * * Allocates new sub maps as the existing ones become full. If * all sub maps are full, no element is inserted, and * AtomicHashMapFullError is thrown. */ std::pair insert(const value_type& r) { return emplace(r.first, r.second); } std::pair insert(key_type k, const mapped_type& v) { return emplace(k, v); } std::pair insert(value_type&& r) { return emplace(r.first, std::move(r.second)); } std::pair insert(key_type k, mapped_type&& v) { return emplace(k, std::move(v)); } /* * emplace -- * * Same contract as insert(), but performs in-place construction * of the value type using the specified arguments. * * Also, like find(), this method optionally allows 'key_in' to have a type * different from that stored in the table; see find(). If and only if no * equal key is already present, this method converts 'key_in' to a key of * type KeyT using the provided LookupKeyToKeyFcn. */ template < typename LookupKeyT = key_type, typename LookupHashFcn = hasher, typename LookupEqualFcn = key_equal, typename LookupKeyToKeyFcn = key_convert, typename... ArgTs> std::pair emplace(LookupKeyT k, ArgTs&&... vCtorArg); /* * find -- * * Returns the iterator to the element if found, otherwise end(). * * As an optional feature, the type of the key to look up (LookupKeyT) is * allowed to be different from the type of keys actually stored (KeyT). * * This enables use cases where materializing the key is costly and usually * redundant, e.g., canonicalizing/interning a set of strings and being able * to look up by StringPiece. To use this feature, LookupHashFcn must take * a LookupKeyT, and LookupEqualFcn must take KeyT and LookupKeyT as first * and second parameter, respectively. * * See folly/test/ArrayHashMapTest.cpp for sample usage. */ template < typename LookupKeyT = key_type, typename LookupHashFcn = hasher, typename LookupEqualFcn = key_equal> iterator find(LookupKeyT k); template < typename LookupKeyT = key_type, typename LookupHashFcn = hasher, typename LookupEqualFcn = key_equal> const_iterator find(LookupKeyT k) const; /* * erase -- * * Erases key k from the map * * Returns 1 iff the key is found and erased, and 0 otherwise. */ size_type erase(key_type k); /* * clear -- * * Wipes all keys and values from primary map and destroys all secondary * maps. Primary map remains allocated and thus the memory can be reused * in place. Not thread safe. * */ void clear(); /* * size -- * * Returns the exact size of the map. Note this is not as cheap as typical * size() implementations because, for each AtomicHashArray in this AHM, we * need to grab a lock and accumulate the values from all the thread local * counters. See folly/ThreadCachedInt.h for more details. */ size_t size() const; bool empty() const { return size() == 0; } size_type count(key_type k) const { return find(k) == end() ? 0 : 1; } /* * findAt -- * * Returns an iterator into the map. * * idx should only be an unmodified value returned by calling getIndex() on * a valid iterator returned by find() or insert(). If idx is invalid you * have a bug and the process aborts. */ iterator findAt(uint32_t idx) { SimpleRetT ret = findAtInternal(idx); DCHECK_LT(ret.i, numSubMaps()); return iterator( this, ret.i, subMaps_[ret.i].load(std::memory_order_relaxed)->makeIter(ret.j)); } const_iterator findAt(uint32_t idx) const { return const_cast(this)->findAt(idx); } // Total capacity - summation of capacities of all submaps. size_t capacity() const; // Number of new insertions until current submaps are all at max load factor. size_t spaceRemaining() const; void setEntryCountThreadCacheSize(int32_t newSize) { const int numMaps = numMapsAllocated_.load(std::memory_order_acquire); for (int i = 0; i < numMaps; ++i) { SubMap* map = subMaps_[i].load(std::memory_order_relaxed); map->setEntryCountThreadCacheSize(newSize); } } // Number of sub maps allocated so far to implement this map. The more there // are, the worse the performance. int numSubMaps() const { return numMapsAllocated_.load(std::memory_order_acquire); } iterator begin() { iterator it(this, 0, subMaps_[0].load(std::memory_order_relaxed)->begin()); it.checkAdvanceToNextSubmap(); return it; } const_iterator begin() const { const_iterator it( this, 0, subMaps_[0].load(std::memory_order_relaxed)->begin()); it.checkAdvanceToNextSubmap(); return it; } iterator end() { return iterator(); } const_iterator end() const { return const_iterator(); } /* Advanced functions for direct access: */ inline uint32_t recToIdx(const value_type& r, bool mayInsert = true) { SimpleRetT ret = mayInsert ? insertInternal(r.first, r.second) : findInternal(r.first); return encodeIndex(ret.i, ret.j); } inline uint32_t recToIdx(value_type&& r, bool mayInsert = true) { SimpleRetT ret = mayInsert ? insertInternal(r.first, std::move(r.second)) : findInternal(r.first); return encodeIndex(ret.i, ret.j); } inline uint32_t recToIdx( key_type k, const mapped_type& v, bool mayInsert = true) { SimpleRetT ret = mayInsert ? insertInternal(k, v) : findInternal(k); return encodeIndex(ret.i, ret.j); } inline uint32_t recToIdx(key_type k, mapped_type&& v, bool mayInsert = true) { SimpleRetT ret = mayInsert ? insertInternal(k, std::move(v)) : findInternal(k); return encodeIndex(ret.i, ret.j); } inline uint32_t keyToIdx(const KeyT k, bool mayInsert = false) { return recToIdx(value_type(k), mayInsert); } inline const value_type& idxToRec(uint32_t idx) const { SimpleRetT ret = findAtInternal(idx); return subMaps_[ret.i].load(std::memory_order_relaxed)->idxToRec(ret.j); } /* Private data and helper functions... */ private: // This limits primary submap size to 2^31 ~= 2 billion, secondary submap // size to 2^(32 - kNumSubMapBits_ - 1) = 2^27 ~= 130 million, and num subMaps // to 2^kNumSubMapBits_ = 16. static const uint32_t kNumSubMapBits_ = 4; static const uint32_t kSecondaryMapBit_ = 1u << 31; // Highest bit static const uint32_t kSubMapIndexShift_ = 32 - kNumSubMapBits_ - 1; static const uint32_t kSubMapIndexMask_ = (1 << kSubMapIndexShift_) - 1; static const uint32_t kNumSubMaps_ = 1 << kNumSubMapBits_; static const uintptr_t kLockedPtr_ = 0x88ULL << 48; // invalid pointer struct SimpleRetT { size_t j; uint32_t i; bool success; SimpleRetT(uint32_t ii, size_t jj, bool s) : j(jj), i(ii), success(s) {} SimpleRetT() = default; }; template < typename LookupKeyT = key_type, typename LookupHashFcn = hasher, typename LookupEqualFcn = key_equal, typename LookupKeyToKeyFcn = key_convert, typename... ArgTs> SimpleRetT insertInternal(LookupKeyT key, ArgTs&&... value); template < typename LookupKeyT = key_type, typename LookupHashFcn = hasher, typename LookupEqualFcn = key_equal> SimpleRetT findInternal(const LookupKeyT k) const; SimpleRetT findAtInternal(uint32_t idx) const; std::atomic subMaps_[kNumSubMaps_]; std::atomic numMapsAllocated_; inline bool tryLockMap(unsigned int idx) { SubMap* val = nullptr; return subMaps_[idx].compare_exchange_strong( val, (SubMap*)kLockedPtr_, std::memory_order_acquire); } static inline uint32_t encodeIndex(uint32_t subMap, uint32_t subMapIdx); }; // AtomicHashMap template < class KeyT, class ValueT, class HashFcn = std::hash, class EqualFcn = std::equal_to, class Allocator = std::allocator> using QuadraticProbingAtomicHashMap = AtomicHashMap< KeyT, ValueT, HashFcn, EqualFcn, Allocator, AtomicHashArrayQuadraticProbeFcn>; } // namespace folly #include ================================================ FILE: folly/AtomicIntrusiveLinkedList.h ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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 namespace folly { /** * A very simple atomic single-linked list primitive. * * Usage: * * class MyClass { * AtomicIntrusiveLinkedListHook hook_; * } * * AtomicIntrusiveLinkedList list; * list.insert(&a); * list.sweep([] (MyClass* c) { doSomething(c); } */ template struct AtomicIntrusiveLinkedListHook { T* next{nullptr}; }; template T::* HookMember> class AtomicIntrusiveLinkedList { public: AtomicIntrusiveLinkedList() {} AtomicIntrusiveLinkedList(const AtomicIntrusiveLinkedList&) = delete; AtomicIntrusiveLinkedList& operator=(const AtomicIntrusiveLinkedList&) = delete; AtomicIntrusiveLinkedList(AtomicIntrusiveLinkedList&& other) noexcept : head_(other.head_.exchange(nullptr, std::memory_order_acq_rel)) {} // Absent because would be too error-prone to use correctly because of // the requirement that lists are empty upon destruction. AtomicIntrusiveLinkedList& operator=( AtomicIntrusiveLinkedList&& other) noexcept = delete; /** * Move the currently held elements to a new list. * The current list becomes empty, but concurrent threads * might still add new elements to it. * * Equivalent to calling a move constructor, but more linter-friendly * in case you still need the old list. */ AtomicIntrusiveLinkedList spliceAll() { return std::move(*this); } /** * Move-assign the current list to `other`, then reverse-sweep * the old list with the provided callback `func`. * * A safe replacement for the move assignment operator, which is absent * because of the resource leak concerns. */ template void reverseSweepAndAssign(AtomicIntrusiveLinkedList&& other, F&& func) { auto otherHead = other.head_.exchange(nullptr, std::memory_order_acq_rel); auto head = head_.exchange(otherHead, std::memory_order_acq_rel); unlinkAll(head, std::forward(func)); } /** * Note: The list must be empty on destruction. */ ~AtomicIntrusiveLinkedList() { assert(empty()); } /** * Returns the current head of the list. * * WARNING: The returned pointer might not be valid if the list * is modified concurrently! */ T* unsafeHead() const { return head_.load(std::memory_order_acquire); } /** * Returns true if the list is empty. * * WARNING: This method's return value is only valid for a snapshot * of the state, it might become stale as soon as it's returned. */ bool empty() const { return unsafeHead() == nullptr; } /** * Atomically insert t at the head of the list. * @return True if the inserted element is the only one in the list * after the call. */ bool insertHead(T* t) { assert(next(t) == nullptr); auto oldHead = head_.load(std::memory_order_relaxed); do { next(t) = oldHead; /* oldHead is updated by the call below. NOTE: we don't use next(t) instead of oldHead directly due to compiler bugs (GCC prior to 4.8.3 (bug 60272), clang (bug 18899), MSVC (bug 819819); source: http://en.cppreference.com/w/cpp/atomic/atomic/compare_exchange */ } while (!head_.compare_exchange_weak( oldHead, t, std::memory_order_release, std::memory_order_relaxed)); return oldHead == nullptr; } /** * Replaces the head with nullptr, * and calls func() on the removed elements in the order from tail to head. * Returns false if the list was empty. */ template bool sweepOnce(F&& func) { if (auto head = head_.exchange(nullptr, std::memory_order_acq_rel)) { auto rhead = reverse(head); unlinkAll(rhead, std::forward(func)); return true; } return false; } /** * Repeatedly replaces the head with nullptr, * and calls func() on the removed elements in the order from tail to head. * Stops when the list is empty. */ template void sweep(F&& func) { while (sweepOnce(func)) { } } /** * Similar to sweep() but calls func() on elements in LIFO order. * * func() is called for all elements in the list at the moment * reverseSweep() is called. Unlike sweep() it does not loop to ensure the * list is empty at some point after the last invocation. This way callers * can reason about the ordering: elements inserted since the last call to * reverseSweep() will be provided in LIFO order. * * Example: if elements are inserted in the order 1-2-3, the callback is * invoked 3-2-1. If the callback moves elements onto a stack, popping off * the stack will produce the original insertion order 1-2-3. */ template void reverseSweep(F&& func) { // We don't loop like sweep() does because the overall order of callbacks // would be strand-wise LIFO which is meaningless to callers. auto head = head_.exchange(nullptr, std::memory_order_acq_rel); unlinkAll(head, std::forward(func)); } private: std::atomic head_{nullptr}; static T*& next(T* t) { return (t->*HookMember).next; } /* Reverses a linked list, returning the pointer to the new head (old tail) */ static T* reverse(T* head) { T* rhead = nullptr; while (head != nullptr) { auto t = head; head = next(t); next(t) = rhead; rhead = t; } return rhead; } /* Unlinks all elements in the linked list fragment pointed to by `head', * calling func() on every element */ template static void unlinkAll(T* head, F&& func) { while (head != nullptr) { auto t = head; head = next(t); next(t) = nullptr; func(t); } } }; } // namespace folly ================================================ FILE: folly/AtomicLinkedList.h ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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 namespace folly { /** * A very simple atomic single-linked list primitive. * * Usage: * * AtomicLinkedList list; * list.insert(a); * list.sweep([] (MyClass& c) { doSomething(c); } */ template class AtomicLinkedList { public: AtomicLinkedList() {} AtomicLinkedList(const AtomicLinkedList&) = delete; AtomicLinkedList& operator=(const AtomicLinkedList&) = delete; AtomicLinkedList(AtomicLinkedList&& other) noexcept = default; AtomicLinkedList& operator=(AtomicLinkedList&& other) noexcept { list_.reverseSweepAndAssign(std::move(other.list_), [](Wrapper* node) { delete node; }); return *this; } ~AtomicLinkedList() { sweep([](T&&) {}); } bool empty() const { return list_.empty(); } /** * Atomically insert t at the head of the list. * @return True if the inserted element is the only one in the list * after the call. */ bool insertHead(T t) { auto wrapper = std::make_unique(std::move(t)); return list_.insertHead(wrapper.release()); } /** * Repeatedly pops element from head, * and calls func() on the removed elements in the order from tail to head. * Stops when the list is empty. */ template void sweep(F&& func) { list_.sweep([&](Wrapper* wrapperPtr) mutable { std::unique_ptr wrapper(wrapperPtr); func(std::move(wrapper->data)); }); } /** * Sweeps the list a single time, as a single point in time swap with the * current contents of the list. * * Unlike sweep() it does not loop to ensure the list is empty at some point * after the last invocation. * * Returns false if the list is empty. */ template bool sweepOnce(F&& func) { return list_.sweepOnce([&](Wrapper* wrappedPtr) { std::unique_ptr wrapper(wrappedPtr); func(std::move(wrapper->data)); }); } /** * Similar to sweep() but calls func() on elements in LIFO order. * * func() is called for all elements in the list at the moment * reverseSweep() is called. Unlike sweep() it does not loop to ensure the * list is empty at some point after the last invocation. This way callers * can reason about the ordering: elements inserted since the last call to * reverseSweep() will be provided in LIFO order. * * Example: if elements are inserted in the order 1-2-3, the callback is * invoked 3-2-1. If the callback moves elements onto a stack, popping off * the stack will produce the original insertion order 1-2-3. */ template void reverseSweep(F&& func) { list_.reverseSweep([&](Wrapper* wrapperPtr) mutable { std::unique_ptr wrapper(wrapperPtr); func(std::move(wrapper->data)); }); } private: struct Wrapper { explicit Wrapper(T&& t) : data(std::move(t)) {} AtomicIntrusiveLinkedListHook hook; T data; }; AtomicIntrusiveLinkedList list_; }; } // namespace folly ================================================ FILE: folly/AtomicUnorderedMap.h ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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 #include #include #include #include #include #include #include namespace folly { /// You're probably reading this because you are looking for an /// AtomicUnorderedMap that is fully general, highly concurrent (for /// reads, writes, and iteration), and makes no performance compromises. /// We haven't figured that one out yet. What you will find here is a /// hash table implementation that sacrifices generality so that it can /// give you all of the other things. /// /// LIMITATIONS: /// /// * Insert only (*) - the only write operation supported directly by /// AtomicUnorderedInsertMap is findOrConstruct. There is a (*) because /// values aren't moved, so you can roll your own concurrency control for /// in-place updates of values (see MutableData and MutableAtom below), /// but the hash table itself doesn't help you. /// /// * No resizing - you must specify the capacity up front, and once /// the hash map gets full you won't be able to insert. Insert /// performance will degrade once the load factor is high. Insert is /// O(1/(1-actual_load_factor)). Note that this is a pretty strong /// limitation, because you can't remove existing keys. /// /// * 2^30 maximum default capacity - by default AtomicUnorderedInsertMap /// uses uint32_t internal indexes (and steals 2 bits), limiting you /// to about a billion entries. If you need more you can fill in all /// of the template params so you change IndexType to uint64_t, or you /// can use AtomicUnorderedInsertMap64. 64-bit indexes will increase /// the space over of the map, of course. /// /// WHAT YOU GET IN EXCHANGE: /// /// * Arbitrary key and value types - any K and V that can be used in a /// std::unordered_map can be used here. In fact, the key and value /// types don't even have to be copyable or moveable! /// /// * Keys and values in the map won't be moved - it is safe to keep /// pointers or references to the keys and values in the map, because /// they are never moved or destroyed (until the map itself is destroyed). /// /// * Iterators are never invalidated - writes don't invalidate iterators, /// so you can scan and insert in parallel. /// /// * Fast wait-free reads - reads are usually only a single cache miss, /// even when the hash table is very large. Wait-freedom means that /// you won't see latency outliers even in the face of concurrent writes. /// /// * Lock-free insert - writes proceed in parallel. If a thread in the /// middle of a write is unlucky and gets suspended, it doesn't block /// anybody else. /// /// COMMENTS ON INSERT-ONLY /// /// This map provides wait-free linearizable reads and lock-free /// linearizable inserts. Inserted values won't be moved, but no /// concurrency control is provided for safely updating them. To remind /// you of that fact they are only provided in const form. This is the /// only simple safe thing to do while preserving something like the normal /// std::map iteration form, which requires that iteration be exposed /// via std::pair (and prevents encapsulation of access to the value). /// /// There are a couple of reasonable policies for doing in-place /// concurrency control on the values. I am hoping that the policy can /// be injected via the value type or an extra template param, to keep /// the core AtomicUnorderedInsertMap insert-only: /// /// CONST: this is the currently implemented strategy, which is simple, /// performant, and not that expressive. You can always put in a value /// with a mutable field (see MutableAtom below), but that doesn't look /// as pretty as it should. /// /// ATOMIC: for integers and integer-size trivially copyable structs /// (via an adapter like tao/queues/AtomicStruct) the value can be a /// std::atomic and read and written atomically. /// /// SEQ-LOCK: attach a counter incremented before and after write. /// Writers serialize by using CAS to make an even->odd transition, /// then odd->even after the write. Readers grab the value with memcpy, /// checking sequence value before and after. Readers retry until they /// see an even sequence number that doesn't change. This works for /// larger structs, but still requires memcpy to be equivalent to copy /// assignment, and it is no longer lock-free. It scales very well, /// because the readers are still invisible (no cache line writes). /// /// LOCK: folly's SharedMutex would be a good choice here. /// /// MEMORY ALLOCATION /// /// Underlying memory is allocated as a big anonymous chunk. If the /// SkipKeyValueDeletion template param is true then deletion of the map /// consists of deallocating the backing memory, which is much faster than /// destructing all of the keys and values. Feel free to override if /// std::is_trivial_destructor isn't recognizing the triviality of your /// destructors. template < typename Key, typename Value, typename Hash = std::hash, typename KeyEqual = std::equal_to, bool SkipKeyValueDeletion = (std::is_trivially_destructible::value && std::is_trivially_destructible::value), template class Atom = std::atomic, typename IndexType = uint32_t, typename Allocator = folly::detail::MallocAlloc> struct AtomicUnorderedInsertMap { using key_type = Key; using mapped_type = Value; using value_type = std::pair; using size_type = std::size_t; using difference_type = std::ptrdiff_t; using hasher = Hash; using key_equal = KeyEqual; using const_reference = const value_type&; struct ConstIterator { ConstIterator(const AtomicUnorderedInsertMap& owner, IndexType slot) : owner_(owner), slot_(slot) {} ConstIterator(const ConstIterator&) = default; ConstIterator& operator=(const ConstIterator&) = default; const value_type& operator*() const { return *owner_.slots_[slot_].keyValue(); } const value_type* operator->() const { return owner_.slots_[slot_].keyValue(); } // pre-increment const ConstIterator& operator++() { while (slot_ > 0) { --slot_; if (owner_.slots_[slot_].state() == LINKED) { break; } } return *this; } // post-increment ConstIterator operator++(int /* dummy */) { auto prev = *this; ++*this; return prev; } bool operator==(const ConstIterator& rhs) const { return slot_ == rhs.slot_; } bool operator!=(const ConstIterator& rhs) const { return !(*this == rhs); } private: const AtomicUnorderedInsertMap& owner_; IndexType slot_; }; using const_iterator = ConstIterator; friend ConstIterator; /// Constructs a map that will support the insertion of maxSize key-value /// pairs without exceeding the max load factor. Load factors of greater /// than 1 are not supported, and once the actual load factor of the /// map approaches 1 the insert performance will suffer. The capacity /// is limited to 2^30 (about a billion) for the default IndexType, /// beyond which we will throw invalid_argument. explicit AtomicUnorderedInsertMap( size_t maxSize, float maxLoadFactor = 0.8f, const Allocator& alloc = Allocator()) : allocator_(alloc) { size_t capacity = size_t(maxSize / std::min(1.0f, maxLoadFactor) + 128); size_t avail = size_t{1} << (8 * sizeof(IndexType) - 2); if (capacity > avail && maxSize < avail) { // we'll do our best capacity = avail; } if (capacity < maxSize || capacity > avail) { throw std::invalid_argument( "AtomicUnorderedInsertMap capacity must fit in IndexType with 2 bits " "left over"); } numSlots_ = capacity; slotMask_ = folly::nextPowTwo(capacity * 4) - 1; allocRequested_ = sizeof(Slot) * capacity; slots_ = reinterpret_cast(allocator_.allocate(allocRequested_)); zeroFillSlots(); // mark the zero-th slot as in-use but not valid, since that happens // to be our nil value slots_[0].stateUpdate(EMPTY, CONSTRUCTING); } ~AtomicUnorderedInsertMap() { if (!SkipKeyValueDeletion) { for (size_t i = 1; i < numSlots_; ++i) { slots_[i].~Slot(); } } allocator_.deallocate(reinterpret_cast(slots_), allocRequested_); } /// Searches for the key, returning (iter,false) if it is found. /// If it is not found calls the functor Func with a void* argument /// that is raw storage suitable for placement construction of a Value /// (see raw_value_type), then returns (iter,true). May call Func and /// then return (iter,false) if there are other concurrent writes, in /// which case the newly constructed value will be immediately destroyed. /// /// This function does not block other readers or writers. If there /// are other concurrent writes, many parallel calls to func may happen /// and only the first one to complete will win. The values constructed /// by the other calls to func will be destroyed. /// /// Usage: /// /// AtomicUnorderedInsertMap memo; /// /// auto value = memo.findOrConstruct(key, [=](void* raw) { /// new (raw) std::string(computation(key)); /// })->first; template std::pair findOrConstruct(const Key& key, Func&& func) { auto const slot = keyToSlotIdx(key); auto prev = slots_[slot].headAndState_.load(std::memory_order_acquire); auto existing = find(key, slot); if (existing != 0) { return std::make_pair(ConstIterator(*this, existing), false); } // The copying of key and the calling of func and find can throw exceptions. // Nothing else in this function can throw an exception. In the event of an // exception, deallocate as if the KV was beaten in a concurrent addition. const auto idx = allocateNear(slot); auto guardSlot = folly::makeGuard([&] { slots_[idx].stateUpdate(CONSTRUCTING, EMPTY); }); value_type* addr = slots_[idx].keyValue(); new (static_cast(std::addressof(addr->first))) Key(key); auto guardKey = folly::makeGuard([&] { addr->first.~Key(); }); new (static_cast(std::addressof(addr->second))) Value(func()); auto guardMapped = folly::makeGuard([&] { addr->second.~Value(); }); while (true) { slots_[idx].next_ = prev >> 2; // we can merge the head update and the CONSTRUCTING -> LINKED update // into a single CAS if slot == idx (which should happen often) auto after = idx << 2; if (slot == idx) { after += LINKED; } else { after += (prev & 3); } if (slots_[slot].headAndState_.compare_exchange_strong(prev, after)) { // success if (idx != slot) { slots_[idx].stateUpdate(CONSTRUCTING, LINKED); } guardMapped.dismiss(); guardKey.dismiss(); guardSlot.dismiss(); return std::make_pair(ConstIterator(*this, idx), true); } // compare_exchange_strong updates its first arg on failure, so // there is no need to reread prev existing = find(key, slot); if (existing != 0) { // our allocated key and value are no longer needed // and so the guards expire and invoke the cleanups return std::make_pair(ConstIterator(*this, existing), false); } } } /// This isn't really emplace, but it is what we need to test. /// Eventually we can duplicate all of the std::pair constructor /// forms, including a recursive tuple forwarding template /// http://functionalcpp.wordpress.com/2013/08/28/tuple-forwarding/). template std::pair emplace(const K& key, V&& value) { return findOrConstruct(key, [&] { return Value(std::forward(value)); }); } const_iterator find(const Key& key) const { return ConstIterator(*this, find(key, keyToSlotIdx(key))); } const_iterator cbegin() const { IndexType slot = numSlots_ - 1; while (slot > 0 && slots_[slot].state() != LINKED) { --slot; } return ConstIterator(*this, slot); } const_iterator begin() const { return cbegin(); } const_iterator cend() const { return ConstIterator(*this, 0); } const_iterator end() const { return cend(); } private: enum : IndexType { kMaxAllocationTries = 1000, // after this we throw }; enum BucketState : IndexType { EMPTY = 0, CONSTRUCTING = 1, LINKED = 2, }; /// Lock-free insertion is easiest by prepending to collision chains. /// A large chaining hash table takes two cache misses instead of /// one, however. Our solution is to colocate the bucket storage and /// the head storage, so that even though we are traversing chains we /// are likely to stay within the same cache line. Just make sure to /// traverse head before looking at any keys. This strategy gives us /// 32 bit pointers and fast iteration. struct Slot { /// The bottom two bits are the BucketState, the rest is the index /// of the first bucket for the chain whose keys map to this slot. /// When things are going well the head usually links to this slot, /// but that doesn't always have to happen. Atom headAndState_; /// The next bucket in the chain IndexType next_; /// Key and Value aligned_storage_for_t raw_; ~Slot() { auto s = state(); assert(s == EMPTY || s == LINKED); if (s == LINKED) { keyValue()->second.~Value(); keyValue()->first.~Key(); } } BucketState state() const { return BucketState(headAndState_.load(std::memory_order_acquire) & 3); } void stateUpdate(BucketState before, BucketState after) { assert(state() == before); headAndState_ += (after - before); } value_type* keyValue() { assert(state() != EMPTY); return static_cast(static_cast(&raw_)); } const value_type* keyValue() const { assert(state() != EMPTY); return static_cast(static_cast(&raw_)); } }; // We manually manage the slot memory so we can bypass initialization // (by getting a zero-filled mmap chunk) and optionally destruction of // the slots size_t allocRequested_; size_t numSlots_; /// tricky, see keyToSlotIdx size_t slotMask_; Allocator allocator_; Slot* slots_; IndexType keyToSlotIdx(const Key& key) const { size_t h = hasher()(key); h &= slotMask_; while (h >= numSlots_) { h -= numSlots_; } return h; } IndexType find(const Key& key, IndexType slot) const { KeyEqual ke = {}; auto hs = slots_[slot].headAndState_.load(std::memory_order_acquire); for (slot = hs >> 2; slot != 0; slot = slots_[slot].next_) { if (ke(key, slots_[slot].keyValue()->first)) { return slot; } } return 0; } /// Allocates a slot and returns its index. Tries to put it near /// slots_[start]. IndexType allocateNear(IndexType start) { for (IndexType tries = 0; tries < kMaxAllocationTries; ++tries) { auto slot = allocationAttempt(start, tries); auto prev = slots_[slot].headAndState_.load(std::memory_order_acquire); if ((prev & 3) == EMPTY && slots_[slot].headAndState_.compare_exchange_strong( prev, prev + CONSTRUCTING - EMPTY)) { return slot; } } throw std::bad_alloc(); } /// Returns the slot we should attempt to allocate after tries failed /// tries, starting from the specified slot. This is pulled out so we /// can specialize it differently during deterministic testing IndexType allocationAttempt(IndexType start, IndexType tries) const { if (FOLLY_LIKELY(tries < 8 && start + tries < numSlots_)) { return IndexType(start + tries); } else { IndexType rv; if (sizeof(IndexType) <= 4) { rv = IndexType(folly::Random::rand32(numSlots_)); } else { rv = IndexType(folly::Random::rand64(numSlots_)); } assert(rv < numSlots_); return rv; } } void zeroFillSlots() { using folly::detail::GivesZeroFilledMemory; if (!GivesZeroFilledMemory::value) { memset(static_cast(slots_), 0, allocRequested_); } } }; /// AtomicUnorderedInsertMap64 is just a type alias that makes it easier /// to select a 64 bit slot index type. Use this if you need a capacity /// bigger than 2^30 (about a billion). This increases memory overheads, /// obviously. template < typename Key, typename Value, typename Hash = std::hash, typename KeyEqual = std::equal_to, bool SkipKeyValueDeletion = (std::is_trivially_destructible::value && std::is_trivially_destructible::value), template class Atom = std::atomic, typename Allocator = folly::detail::MallocAlloc> using AtomicUnorderedInsertMap64 = AtomicUnorderedInsertMap< Key, Value, Hash, KeyEqual, SkipKeyValueDeletion, Atom, uint64_t, Allocator>; /// MutableAtom is a tiny wrapper that gives you the option of atomically /// updating values inserted into an AtomicUnorderedInsertMap>. This relies on AtomicUnorderedInsertMap's guarantee /// that it doesn't move values. template class Atom = std::atomic> struct MutableAtom { mutable Atom data; explicit MutableAtom(const T& init) : data(init) {} }; /// MutableData is a tiny wrapper that gives you the option of using an /// external concurrency control mechanism to updating values inserted /// into an AtomicUnorderedInsertMap. template struct MutableData { mutable T data; explicit MutableData(const T& init) : data(init) {} }; } // namespace folly ================================================ FILE: folly/BUCK ================================================ load("@bazel_skylib//lib:selects.bzl", "selects") load("@fbcode_macros//build_defs:auto_headers.bzl", "AutoHeaders") load("@fbcode_macros//build_defs:build_file_migration.bzl", "fbcode_target", "non_fbcode_target") load("@fbcode_macros//build_defs:cpp_library.bzl", "cpp_library") load("@fbsource//tools/build_defs:fb_native_wrapper.bzl", "fb_native") load("@fbsource//tools/build_defs:fb_xplat_cxx_library.bzl", "fb_xplat_cxx_library") load("@fbsource//tools/build_defs:fb_xplat_cxx_test.bzl", "fb_xplat_cxx_test") load( "@fbsource//tools/build_defs:platform_defs.bzl", "ALL_APPLE_SDKS", "ANDROID", "APPLE", "CXX", "FBCODE", "IOS", "MACOSX", "WINDOWS", ) load("@fbsource//tools/build_defs/dirsync:fb_dirsync_cpp_library.bzl", "fb_dirsync_cpp_library") load( ":defs.bzl", "CXXFLAGS", "DEFAULT_APPLE_SDKS", "FBANDROID_CPPFLAGS", "FBANDROID_CXXFLAGS", "FBOBJC_CXXFLAGS", "cpp_flags", "folly_xplat_cxx_test", "folly_xplat_library", ) oncall("fbcode_entropy_wardens_folly") CPPFLAGS = cpp_flags() fb_dirsync_cpp_library( name = "memory", headers = ["Memory.h"], fbobjc_complete_nullability = True, xplat_impl = folly_xplat_library, exported_deps = [ ":constexpr_math", ":likely", ":portability", ":traits", ":utility", "//folly/functional:invoke", "//folly/lang:align", "//folly/lang:exception", "//folly/lang:thunk", "//folly/memory:malloc", "//folly/portability:config", "//folly/portability:constexpr", "//folly/portability:malloc", ], ) fb_dirsync_cpp_library( name = "observer_container", headers = [ "ObserverContainer.h", ], fbobjc_complete_nullability = True, xplat_impl = folly_xplat_library, exported_deps = [ "fbsource//third-party/glog:glog", ":constructor_callback_list", ":function", ":optional", ":scope_guard", ":small_vector", "//folly/io/async:destructor_check", "//folly/lang:switch", ], ) fb_dirsync_cpp_library( name = "operation_cancelled", headers = ["OperationCancelled.h"], xplat_impl = folly_xplat_library, ) fb_dirsync_cpp_library( name = "optional", headers = ["Optional.h"], # Without modules, the `-Wextra` flag added in BUILD_MODE.bzl only affected # the cpp files under `folly`, but with modules it also ends up getting # baked into the PCM files of header units. # # This is a workaround specifically for the comparison operators of # `Optional`, since those trigger `-Wsign-compare` warnings too often. compiler_flags = select({ "DEFAULT": ["-Wno-error=sign-compare"], "ovr_config//compiler:cl": ["/wd4018"], }), fbobjc_complete_nullability = True, use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [ ":portability", ":traits", ":utility", "//folly/coro:coroutine", "//folly/hash:traits", "//folly/lang:exception", ], ) fb_dirsync_cpp_library( name = "benchmark_util", srcs = [ "BenchmarkUtil.cpp", ], headers = [ "BenchmarkUtil.h", ], xplat_impl = folly_xplat_library, deps = [ "fbsource//third-party/fmt:fmt", "//folly/lang:align", "//folly/portability:sched", "//folly/portability:windows", "//folly/system/arch:x86", ], exported_deps = [ ":portability", "//folly/lang:hint", ], ) fb_dirsync_cpp_library( name = "benchmark", srcs = [ "Benchmark.cpp", # Colocated here to avoid circular dep (Adaptive needs Benchmark types). "detail/BenchmarkAdaptive.cpp", ], headers = [ "Benchmark.h", "detail/BenchmarkAdaptive.h", ], use_raw_headers = True, xplat_impl = folly_xplat_library, deps = [ ":file_util", ":map_util", ":overload", ":random", ":string", "//folly/detail:perf_scoped", "//folly/json:dynamic", "//folly/stats:streaming_stats", ], exported_deps = [ "fbsource//third-party/glog:glog", ":benchmark_util", ":portability", ":preprocessor", ":range", ":scope_guard", ":traits", "//folly/functional:invoke", "//folly/lang:hint", "//folly/portability:gflags", ], external_deps = [ ("boost", None, "boost_regex"), ], exported_external_deps = [ "boost", ], ) fb_dirsync_cpp_library( name = "scope_guard", srcs = ["ScopeGuard.cpp"], headers = ["ScopeGuard.h"], use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [ ":portability", ":preprocessor", ":utility", "//folly/lang:exception", "//folly/lang:uncaught_exceptions", ], ) fb_dirsync_cpp_library( name = "synchronized", headers = ["Synchronized.h"], use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [ "fbsource//third-party/glog:glog", ":function", ":likely", ":preprocessor", ":shared_mutex", ":traits", ":utility", "//folly/container:foreach", "//folly/functional:apply_tuple", "//folly/synchronization:lock", ], ) non_fbcode_target( _kind = folly_xplat_library, name = "singleton", srcs = [ "Singleton.cpp", ], compiler_flags = select({ "DEFAULT": [], "ovr_config//compiler:clang": [ "-Wno-invalid-noreturn", ], }), cxx_deps = [ "//xplat/third-party/linker_lib:dl", "//third-party/toolchains:rt", ], exported_preprocessor_flags = select({ "//xplat/folly/buck_config:folly-singleton-schedule-at-exit-disabled": [ "-DFOLLY_SINGLETON_SKIP_SCHEDULE_ATEXIT=1", ], "DEFAULT": [], }) + select({ # Symbolizer is not available for all platforms, disable it for now. "DEFAULT": ["-DFOLLY_HAVE_DWARF=0"], "ovr_config//os:linux": [], }), fbandroid_deps = [ "//xplat/third-party/linker_lib:dl", ], raw_headers = [ "Singleton-inl.h", "Singleton.h", # Instead of depending on experimental_symbolizer_symbolizer, reference always-included headers here. "debugging/symbolizer/Dwarf.h", "debugging/symbolizer/DwarfImpl.h", "debugging/symbolizer/DwarfLineNumberVM.h", "debugging/symbolizer/DwarfSection.h", "debugging/symbolizer/DwarfUtil.h", "debugging/symbolizer/Elf.h", "debugging/symbolizer/Elf-inl.h", "debugging/symbolizer/ElfCache.h", "debugging/symbolizer/Symbolizer.h", "debugging/symbolizer/SymbolizedFrame.h", "debugging/symbolizer/SymbolizePrinter.h", "debugging/symbolizer/StackTrace.h", "experimental/symbolizer/Dwarf.h", "experimental/symbolizer/DwarfImpl.h", "experimental/symbolizer/DwarfLineNumberVM.h", "experimental/symbolizer/DwarfSection.h", "experimental/symbolizer/DwarfUtil.h", "experimental/symbolizer/Elf.h", "experimental/symbolizer/Elf-inl.h", "experimental/symbolizer/ElfCache.h", "experimental/symbolizer/Symbolizer.h", "experimental/symbolizer/SymbolizedFrame.h", "experimental/symbolizer/SymbolizePrinter.h", "experimental/symbolizer/StackTrace.h", ], deps = [ "fbsource//xplat/folly/portability:fmt_compile", "fbsource//xplat/folly/system:at_fork", ], exported_deps = [ "fbsource//xplat/folly/hash:hash", "fbsource//xplat/folly/io:iobuf", "fbsource//xplat/folly/portability:config", "fbsource//xplat/folly/portability:sys_mman", "fbsource//xplat/folly/portability:sys_syscall", "fbsource//xplat/folly/portability:unistd", "fbsource//xplat/folly/synchronization:baton", "//third-party/double-conversion:double-conversion", "//third-party/fmt:fmt", "//third-party/glog:glog", "//xplat/folly:c_portability", "//xplat/folly:cancellation_token", "//xplat/folly:conv", "//xplat/folly:demangle", "//xplat/folly:exception", "//xplat/folly:executor", "//xplat/folly:fbstring", "//xplat/folly:file_util", "//xplat/folly:memory", "//xplat/folly:optional", "//xplat/folly:range", "//xplat/folly:scope_guard", "//xplat/folly:string", "//xplat/folly:synchronized", "//xplat/folly/concurrency:core_cached_shared_ptr", "//xplat/folly/concurrency/memory:read_mostly_shared_ptr", "//xplat/folly/container:evicting_cache_map", "//xplat/folly/detail:singleton", "//xplat/folly/detail:static_singleton_manager", "//xplat/folly/lang:exception", "//xplat/folly/memory:reentrant_allocator", "//xplat/folly/memory:sanitize_leak", ] + select({ "DEFAULT": [], "ovr_config//os:linux": ["//xplat/folly/experimental/symbolizer:symbolizer"], }), ) fb_dirsync_cpp_library( name = "thread_local", headers = ["ThreadLocal.h"], use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [ ":likely", ":portability", ":scope_guard", ":shared_mutex", "//folly/detail:thread_local_detail", ], ) fb_dirsync_cpp_library( name = "singleton_thread_local", srcs = ["SingletonThreadLocal.cpp"], headers = ["SingletonThreadLocal.h"], use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [ ":scope_guard", ":thread_local", "//folly/detail:iterators", "//folly/detail:singleton", "//folly/detail:unique_instance", "//folly/functional:invoke", "//folly/lang:hint", ], ) fb_dirsync_cpp_library( name = "random", srcs = [ "Random.cpp", ], headers = [ "Random.h", "Random-inl.h", ], use_raw_headers = True, xplat_impl = folly_xplat_library, deps = [ "fbsource//third-party/glog:glog", ":cpp_attributes", ":singleton_thread_local", ":thread_local", "//folly/detail:file_util_detail", "//folly/portability:config", "//folly/portability:sys_time", "//folly/portability:unistd", "//folly/synchronization:relaxed_atomic", ], exported_deps = [ ":portability", ":traits", "//folly/functional:invoke", "//folly/lang:bits", "//folly/random:xoshiro256pp", ] + select({ "DEFAULT": [], "ovr_config//os:windows": ["fbsource//third-party/toolchains/win:advapi32.lib"], }), ) fb_dirsync_cpp_library( name = "file", srcs = ["File.cpp"], headers = ["File.h"], use_raw_headers = True, xplat_impl = folly_xplat_library, deps = [ "fbsource//third-party/glog:glog", ":exception", ":file_util", ":scope_guard", "//folly/portability:fmt_compile", "//folly/portability:sys_file", ], exported_deps = [ ":exception_wrapper", ":expected", ":file_util", # @manual ":portability", ":range", "//folly/portability:fcntl", "//folly/portability:unistd", ], ) fb_dirsync_cpp_library( name = "file_util", srcs = ["FileUtil.cpp"], headers = ["FileUtil.h"], use_raw_headers = True, xplat_impl = folly_xplat_library, deps = [ "//folly/detail:file_util_detail", "//folly/detail:file_util_vector_detail", "//folly/net:net_ops", "//folly/portability:sockets", "//folly/portability:stdlib", "//folly/portability:sys_file", "//folly/portability:sys_stat", ], exported_deps = [ ":portability", ":range", ":scope_guard", "//folly/net:network_socket", "//folly/portability:fcntl", "//folly/portability:sys_uio", "//folly/portability:unistd", ], ) non_fbcode_target( _kind = folly_xplat_library, name = "executor", srcs = [ "Executor.cpp", ], force_static = select({ "DEFAULT": False, "ovr_config//build_mode:arvr_mode": select({ "DEFAULT": False, "ovr_config//os:macos": True, }), }), raw_headers = [ "Executor.h", ], exported_deps = [ "//third-party/glog:glog", "//xplat/folly:exception_string", "//xplat/folly:function", "//xplat/folly:optional", "//xplat/folly:portability", "//xplat/folly:range", "//xplat/folly:utility", "//xplat/folly/lang:exception", ], ) non_fbcode_target( _kind = folly_xplat_library, name = "subprocess", srcs = [ "Subprocess.cpp", ], cxx_deps = [ "//xplat/third-party/linker_lib:dl", ], fbandroid_deps = [ "//xplat/third-party/linker_lib:dl", ], fbandroid_use_host_platform = True, force_static = False, platforms = (CXX, ANDROID, APPLE), raw_headers = [ "Subprocess.h", ], deps = [ "fbsource//xplat/folly/portability:dirent", ":conv", ":logging_logging", ":scope_guard", ":string", "//third-party/boost:boost_range", "//xplat/folly/lang:assume", "//xplat/folly/portability:fcntl", "//xplat/folly/portability:sockets", "//xplat/folly/portability:stdlib", "//xplat/folly/portability:sys_syscall", "//xplat/folly/portability:unistd", "//xplat/folly/system:at_fork", "//xplat/folly/system:shell", ], exported_deps = [ ":exception", ":file", ":file_util", ":function", ":gen_string", ":io_iobuf", ":map_util", ":optional", ":portability", ":range", "//third-party/boost:boost", "//third-party/boost:boost_container", "//xplat/folly/container:span", "//xplat/folly/portability:sys_resource", ], ) non_fbcode_target( _kind = folly_xplat_library, name = "uri", srcs = [ "Uri.cpp", ], force_static = False, raw_headers = [ "Uri.h", "Uri-inl.h", ], deps = [ "//third-party/boost:boost_regex", "//third-party/glog:glog", ], exported_deps = [ "fbsource//xplat/folly/hash:hash", "//third-party/boost:boost_regex", "//xplat/folly:conv", "//xplat/folly:string", ], ) fb_dirsync_cpp_library( name = "range", headers = ["Range.h"], compiler_flags = [ "-fno-omit-frame-pointer", ], fbobjc_complete_nullability = True, use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [ "fbsource//third-party/fmt:fmt", ":cpu_id", ":likely", ":portability", ":traits", "//folly/detail:range_common", "//folly/detail:range_simd", "//folly/hash:spooky_hash_v2", "//folly/lang:c_string", "//folly/lang:exception", "//folly/portability:constexpr", ], ) fb_dirsync_cpp_library( name = "fbvector", headers = [ "FBVector.h", ], use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [ "//folly/container:fbvector", ], ) fb_dirsync_cpp_library( name = "fixed_string", headers = ["FixedString.h"], use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [ ":constexpr_math", ":portability", ":range", ":utility", "//folly/lang:exception", "//folly/lang:ordering", "//folly/portability:constexpr", ], ) fb_dirsync_cpp_library( name = "fbstring", headers = ["FBString.h"], compiler_flags = [ "-fno-omit-frame-pointer", ], xplat_impl = folly_xplat_library, exported_deps = [ "fbsource//third-party/fmt:fmt", ":c_portability", ":cpp_attributes", ":likely", ":portability", ":traits", "//folly/hash:hash", "//folly/lang:assume", "//folly/lang:checked_math", "//folly/lang:exception", "//folly/memory:malloc", ], ) fb_dirsync_cpp_library( name = "format", srcs = [ "Format.cpp", ], headers = [ "Format.h", "Format-inl.h", "FormatArg.h", ], compiler_flags = [ "-fno-omit-frame-pointer", ], xplat_impl = folly_xplat_library, deps = [ ":constexpr_math", "//folly/container:array", ], exported_deps = [ ":c_portability", ":conv", ":exception", ":format_traits", ":likely", ":map_util", ":portability", ":range", ":string", ":traits", "//folly/lang:exception", "//folly/lang:to_ascii", "//folly/portability:windows", ], external_deps = [ "double_conversion", ], ) fb_dirsync_cpp_library( name = "string", srcs = [ "String.cpp", ], headers = [ "String.h", "String-inl.h", ], compiler_flags = [ "-fno-omit-frame-pointer", ], use_raw_headers = True, xplat_impl = folly_xplat_library, deps = [ "fbsource//third-party/glog:glog", "//folly/container:array", ], exported_deps = [ ":conv", ":cpp_attributes", ":exception_string", ":optional", ":portability", ":range", ":scope_guard", ":traits", ":unit", "//folly/container:reserve", "//folly/detail:simple_simd_string_utils", "//folly/detail:split_string_simd", ], ) fb_dirsync_cpp_library( name = "bits", headers = ["Bits.h"], use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = ["//folly/lang:bits"], ) fb_dirsync_cpp_library( name = "hash", headers = ["Hash.h"], use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [ "//folly/hash:hash", ], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "hash_detail_checksum_detail", exported_deps = ["//xplat/folly/hash/detail:checksum_detail"], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "hash_checksum", exported_deps = ["//xplat/folly/hash:checksum"], ) non_fbcode_target( _kind = folly_xplat_library, name = "conv", srcs = [ "Conv.cpp", ], compiler_flags = [ "-fno-omit-frame-pointer", ], export_header_unit = "include", export_header_unit_filter = ["folly/Conv.h"], fbobjc_complete_nullability = True, raw_headers = [ "Conv.h", ], deps = [ "fbsource//third-party/fast_float:fast_float", "//xplat/folly/lang:safe_assert", ], exported_deps = [ "fbsource//xplat/folly/portability:math", "//third-party/double-conversion:double-conversion", "//xplat/folly:c_portability", "//xplat/folly:demangle", "//xplat/folly:expected", "//xplat/folly:fbstring", "//xplat/folly:likely", "//xplat/folly:portability", "//xplat/folly:range", "//xplat/folly:traits", "//xplat/folly:unit", "//xplat/folly:utility", "//xplat/folly/lang:exception", "//xplat/folly/lang:pretty", "//xplat/folly/lang:to_ascii", ], ) non_fbcode_target( _kind = folly_xplat_library, name = "dynamic", apple_sdks = DEFAULT_APPLE_SDKS, compiler_flags = [ "-fno-omit-frame-pointer", ], export_header_unit = "include", export_header_unit_filter = [".*"], fbobjc_complete_nullability = True, raw_headers = [ "DynamicConverter.h", "dynamic.h", "dynamic-inl.h", "json.h", ], deps = [ "//xplat/folly:utility", ], exported_deps = [ "fbsource//xplat/folly/hash:hash", "fbsource//xplat/folly/portability:constexpr", "//third-party/boost:boost", "//third-party/boost:boost_algorithm", "//third-party/glog:glog", "//xplat/folly:c_portability", "//xplat/folly:conv", "//xplat/folly:expected", "//xplat/folly:format", "//xplat/folly:function", "//xplat/folly:json_pointer", "//xplat/folly:likely", "//xplat/folly:optional", "//xplat/folly:portability", "//xplat/folly:range", "//xplat/folly:string", "//xplat/folly:traits", "//xplat/folly:unicode", "//xplat/folly:utility", "//xplat/folly/container:access", "//xplat/folly/container:enumerate", "//xplat/folly/container:f14_hash", "//xplat/folly/detail:iterators", "//xplat/folly/json:dynamic", "//xplat/folly/lang:assume", "//xplat/folly/lang:bits", "//xplat/folly/lang:exception", ], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "f14_hash", deps = [ "//xplat/folly/container:f14_hash", ], ) non_fbcode_target( _kind = folly_xplat_library, name = "indestructible", compiler_flags = [ "-fno-omit-frame-pointer", ], raw_headers = [ "Indestructible.h", ], exported_deps = [ "//xplat/folly:traits", "//xplat/folly:utility", ], ) non_fbcode_target( _kind = folly_xplat_library, name = "io_iobuf", exported_deps = [ "//xplat/folly/io:iobuf", ], ) # xplat/folly/test aliases non_fbcode_target( _kind = folly_xplat_library, name = "json_test_util", platforms = (CXX, ANDROID, APPLE, WINDOWS, FBCODE), raw_headers = [ "test/JsonTestUtil.h", ], exported_deps = [ "//xplat/folly/json:json_test_util", ], ) non_fbcode_target( _kind = folly_xplat_library, name = "json_mock_util", exported_headers = [ "test/JsonMockUtil.h", ], platforms = (CXX, ANDROID, APPLE, WINDOWS, FBCODE), exported_deps = [ "//xplat/folly/json:json_mock_util", ], ) non_fbcode_target( _kind = folly_xplat_library, name = "comparison_operator_test_util", exported_headers = [ "test/ComparisonOperatorTestUtil.h", ], platforms = (CXX, ANDROID, APPLE, WINDOWS, FBCODE), exported_deps = [ "fbsource//xplat/folly/portability:gtest", ], ) fb_dirsync_cpp_library( name = "cpu_id", headers = ["CpuId.h"], xplat_impl = folly_xplat_library, exported_deps = [ ":portability", "//folly/system/arch:x86", ], ) fb_dirsync_cpp_library( name = "cpp_attributes", headers = ["CppAttributes.h"], apple_sdks = ALL_APPLE_SDKS, use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [":portability"], ) non_fbcode_target( _kind = folly_xplat_library, name = "config", apple_sdks = ALL_APPLE_SDKS, exported_preprocessor_flags = CPPFLAGS, fbandroid_exported_preprocessor_flags = FBANDROID_CPPFLAGS, raw_headers = [ "folly-config.h", ], ) fb_dirsync_cpp_library( name = "constexpr_math", headers = ["ConstexprMath.h"], use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [ ":portability", "//folly/lang:checked_math", "//folly/portability:constexpr", ], ) fb_dirsync_cpp_library( name = "constructor_callback_list", headers = [ "ConstructorCallbackList.h", ], use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [ "fbsource//third-party/fmt:fmt", ":format", ":function", ":shared_mutex", "//folly/detail:static_singleton_manager", ], ) fb_dirsync_cpp_library( name = "math", headers = ["Math.h"], use_raw_headers = True, xplat_impl = folly_xplat_library, ) fb_dirsync_cpp_library( name = "move_wrapper", headers = ["MoveWrapper.h"], use_raw_headers = True, xplat_impl = folly_xplat_library, ) fb_dirsync_cpp_library( name = "likely", headers = ["Likely.h"], xplat_impl = folly_xplat_library, exported_deps = [ "//folly/lang:builtin", ], ) fb_dirsync_cpp_library( name = "unit", headers = ["Unit.h"], xplat_impl = folly_xplat_library, ) fb_dirsync_cpp_library( name = "c_portability", headers = ["CPortability.h"], apple_sdks = ALL_APPLE_SDKS, xplat_impl = folly_xplat_library, exported_deps = [ "//folly/portability:config", ], ) fb_dirsync_cpp_library( name = "preprocessor", headers = ["Preprocessor.h"], use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [ ":c_portability", ], ) fb_dirsync_cpp_library( name = "portability", headers = ["Portability.h"], apple_sdks = ALL_APPLE_SDKS, fbobjc_complete_nullability = True, use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [ ":c_portability", "//folly/portability:config", ], ) fb_dirsync_cpp_library( name = "traits", headers = ["Traits.h"], xplat_impl = folly_xplat_library, exported_deps = [":portability"], ) fb_dirsync_cpp_library( name = "utility", headers = [ "Utility.h", ], xplat_impl = folly_xplat_library, exported_deps = [ ":c_portability", ":portability", ":traits", ], ) fb_dirsync_cpp_library( name = "function", headers = ["Function.h"], fbobjc_complete_nullability = True, use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [ ":cpp_attributes", ":portability", ":traits", "//folly/functional:invoke", "//folly/lang:align", "//folly/lang:exception", "//folly/lang:new", ], ) non_fbcode_target( _kind = folly_xplat_library, name = "atomic_hash_map", compiler_flags = [ "-fno-omit-frame-pointer", ], raw_headers = [ "AtomicHashMap.h", "AtomicHashMap-inl.h", ], exported_deps = [ "fbsource//xplat/folly/hash:hash", "//xplat/folly:atomic_hash_array", "//xplat/folly:c_portability", "//xplat/folly:likely", "//xplat/folly:thread_cached_int", "//xplat/folly/container:foreach", "//xplat/folly/detail:atomic_hash_utils", "//xplat/folly/detail:iterators", ], ) non_fbcode_target( _kind = folly_xplat_library, name = "atomic_linked_list", compiler_flags = [ "-fno-omit-frame-pointer", ], raw_headers = [ "AtomicIntrusiveLinkedList.h", "AtomicLinkedList.h", ], exported_deps = [ "//xplat/folly:memory", ], ) fb_dirsync_cpp_library( name = "chrono", headers = ["Chrono.h"], use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [ ":portability", "//folly/lang:exception", "//folly/portability:time", ], ) fb_native.constraint_setting( name = "folly_demangle_macos_use_iberty_constraint", ) fb_native.constraint_value( name = "folly_demangle_macos_disable_iberty", constraint_setting = ":folly_demangle_macos_use_iberty_constraint", visibility = ["PUBLIC"], ) fb_native.config_setting( name = "folly_demangle_macos_disable_iberty_config", constraint_values = [ "ovr_config//os/constraints:macos", ":folly_demangle_macos_disable_iberty", ], ) fb_dirsync_cpp_library( name = "demangle", srcs = ["Demangle.cpp"], headers = ["Demangle.h"], compiler_flags = selects.with_or({ ( "ovr_config//os:android" ): [ "-Wno-error=deprecated-dynamic-exception-spec", "-fno-omit-frame-pointer", ], "DEFAULT": ["-fno-omit-frame-pointer"], }) + select({ "DEFAULT": [], # x86_64-conda-linux-gnu-c++ (conda-forge gcc 11.4.0-13) 11.4.0 # fails to compile Demangle.cpp because of unused static functions despite # using [[maybe_unused]] attribute. "ovr_config//distro/constraints:conda": ["-Wno-unused-function"], }), use_raw_headers = True, xplat_impl = folly_xplat_library, deps = [ ":c_portability", ":utility", "//folly/functional:invoke", "//folly/lang:c_string", ] + select({ "DEFAULT": [], "antlir//antlir/distro:build-for-distro": ["third-party//binutils:iberty"], "fbsource//xplat/folly:folly_demangle_macos_disable_iberty_config": [], "ovr_config//os:linux": ["fbsource//third-party/binutils:iberty-demangle"], "ovr_config//os:macos": ["fbsource//third-party/binutils:iberty-demangle"], "ovr_config//runtime:fbcode": ["third-party//binutils:iberty"], }), exported_deps = [ ":fbstring", "//folly/portability:config", ], ) fb_dirsync_cpp_library( name = "exception", headers = ["Exception.h"], compiler_flags = [ "-fno-omit-frame-pointer", ], xplat_impl = folly_xplat_library, exported_deps = [ ":conv", ":fbstring", ":likely", ":portability", "//folly/portability:sys_types", ], ) non_fbcode_target( _kind = folly_xplat_library, name = "exception_string", srcs = [ "ExceptionString.cpp", ], compiler_flags = [ "-fno-omit-frame-pointer", ], raw_headers = [ "ExceptionString.h", ], deps = [ "//xplat/folly:demangle", "//xplat/folly/lang:exception", "//xplat/folly/lang:type_info", ], exported_deps = [ "//xplat/folly:fbstring", ], ) non_fbcode_target( _kind = folly_xplat_library, name = "exception_wrapper", srcs = [ "ExceptionWrapper.cpp", ], raw_headers = [ "ExceptionWrapper.h", "ExceptionWrapper-inl.h", ], exported_deps = [ ":c_portability", ":cpp_attributes", ":demangle", ":exception_string", ":fbstring", ":portability", ":traits", ":utility", "//xplat/folly/functional:traits", "//xplat/folly/lang:assume", "//xplat/folly/lang:exception", ], ) fb_dirsync_cpp_library( name = "expected", headers = ["Expected.h"], compiler_flags = [ "-fno-omit-frame-pointer", ], use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [ ":c_portability", ":cpp_attributes", ":likely", ":portability", ":preprocessor", ":traits", ":unit", ":utility", "//folly/coro:coroutine", "//folly/lang:exception", "//folly/lang:hint", ], ) non_fbcode_target( _kind = folly_xplat_library, name = "fmt_utility", srcs = [ "FmtUtility.cpp", ], compiler_flags = [ "-fno-omit-frame-pointer", ], raw_headers = [ "FmtUtility.h", ], deps = [ "//xplat/folly:range", "//xplat/folly:string", "//xplat/folly/ssl:openssl_hash", ], exported_deps = [ "//xplat/folly:cpp_attributes", ], ) non_fbcode_target( _kind = folly_xplat_library, name = "format_traits", compiler_flags = [ "-fno-omit-frame-pointer", ], raw_headers = [ "FormatTraits.h", ], ) non_fbcode_target( _kind = folly_xplat_library, name = "glog", compiler_flags = [ "-fno-omit-frame-pointer", ], raw_headers = [ "GLog.h", ], exported_deps = [ "//third-party/glog:glog", "//xplat/folly:likely", ], ) non_fbcode_target( _kind = folly_xplat_library, name = "indexed_mem_pool", compiler_flags = [ "-fno-omit-frame-pointer", ], raw_headers = [ "IndexedMemPool.h", ], exported_deps = [ ":portability", "//xplat/folly/concurrency:cache_locality", "//xplat/folly/portability:sys_mman", "//xplat/folly/portability:unistd", "//xplat/folly/synchronization:atomic_struct", ], ) fb_dirsync_cpp_library( name = "intrusive_list", headers = [ "IntrusiveList.h", ], use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [ "//folly/container:intrusive_list", ], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "io_shutdown_socket_set", exported_deps = [ "//xplat/folly/io:shutdown_socket_set", ], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "io_socket_option_map", exported_deps = [ "//xplat/folly/io:socket_option_map", ], ) fb_dirsync_cpp_library( name = "map_util", headers = [ "MapUtil.h", ], use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [ "//folly/container:map_util", ], ) non_fbcode_target( _kind = folly_xplat_library, name = "micro_lock", srcs = [ "MicroLock.cpp", ], compiler_flags = [ "-fno-omit-frame-pointer", ], raw_headers = [ "MicroLock.h", ], exported_deps = [ "fbsource//xplat/folly/portability:asm", "fbsource//xplat/folly/synchronization:atomic_notification", "fbsource//xplat/folly/synchronization:atomic_ref", "//xplat/folly:optional", "//xplat/folly:portability", "//xplat/folly:utility", ], ) fb_dirsync_cpp_library( name = "mpmc_queue", headers = ["MPMCQueue.h"], use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [ ":traits", "//folly/concurrency:cache_locality", "//folly/detail:turn_sequencer", "//folly/lang:exception", "//folly/portability:unistd", ], ) non_fbcode_target( _kind = folly_xplat_library, name = "network_address", srcs = [ "IPAddress.cpp", "IPAddressV4.cpp", "IPAddressV6.cpp", "MacAddress.cpp", "SocketAddress.cpp", ], compiler_flags = [ "-fno-omit-frame-pointer", ], raw_headers = [ "IPAddress.h", "IPAddressException.h", "IPAddressV4.h", "IPAddressV6.h", "MacAddress.h", "SocketAddress.h", ], windows_exported_linker_flags = [ "iphlpapi.lib", ], deps = [ "//xplat/folly:scope_guard", "//xplat/folly:small_vector", ], exported_deps = [ "fbsource//xplat/folly/hash:hash", "fbsource//xplat/folly/portability:sockets", "//third-party/boost:boost", "//xplat/folly:c_portability", "//xplat/folly:constexpr_math", "//xplat/folly:conv", "//xplat/folly:cpp_attributes", "//xplat/folly:exception", "//xplat/folly:expected", "//xplat/folly:fbstring", "//xplat/folly:format", "//xplat/folly:optional", "//xplat/folly:portability", "//xplat/folly:range", "//xplat/folly:string", "//xplat/folly:unit", "//xplat/folly/detail:ip_address", "//xplat/folly/detail:ip_address_source", "//xplat/folly/lang:bits", "//xplat/folly/lang:exception", "//xplat/folly/net:net_ops", "//xplat/folly/net:network_socket", ], ) non_fbcode_target( _kind = folly_xplat_library, name = "overload", compiler_flags = [ "-fno-omit-frame-pointer", ], raw_headers = [ "Overload.h", ], exported_deps = [ ":portability", ":traits", "//xplat/folly/functional:invoke", ], ) non_fbcode_target( _kind = folly_xplat_library, name = "shared_mutex", srcs = [ "SharedMutex.cpp", ], compiler_flags = [ "-fno-omit-frame-pointer", ], raw_headers = [ "SharedMutex.h", ], deps = [ "//xplat/folly:indestructible", ], exported_deps = [ "fbsource//xplat/folly/portability:asm", "fbsource//xplat/folly/portability:sys_resource", "fbsource//xplat/folly/synchronization:lock", "fbsource//xplat/folly/synchronization:relaxed_atomic", "fbsource//xplat/folly/synchronization:sanitize_thread", "//xplat/folly:c_portability", "//xplat/folly:likely", "//xplat/folly/chrono:hardware", "//xplat/folly/concurrency:cache_locality", "//xplat/folly/detail:futex", "//xplat/folly/system:thread_id", ], ) fb_dirsync_cpp_library( name = "small_vector", headers = [ "small_vector.h", ], use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [ "//folly/container:small_vector", ], ) fb_dirsync_cpp_library( name = "sorted_vector_types", headers = [ "sorted_vector_types.h", ], use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [ "//folly/container:sorted_vector_types", ], ) non_fbcode_target( _kind = folly_xplat_library, name = "spin_lock", compiler_flags = [ "-fno-omit-frame-pointer", ], raw_headers = [ "SpinLock.h", ], exported_deps = [ "fbsource//xplat/folly/synchronization:small_locks", "//xplat/folly:portability", ], ) fb_dirsync_cpp_library( name = "stop_watch", headers = ["stop_watch.h"], use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [ ":chrono", ":utility", "//folly/portability:time", ], ) fb_dirsync_cpp_library( name = "varint", headers = ["Varint.h"], xplat_impl = folly_xplat_library, exported_deps = [ ":conv", ":expected", ":likely", ":portability", ":range", ], ) non_fbcode_target( _kind = folly_xplat_library, name = "external_farmhash_farmhash", srcs = [ "external/farmhash/farmhash.cpp", ], compiler_flags = [ "-fno-omit-frame-pointer", ], raw_headers = [ "external/farmhash/farmhash.h", ], exported_deps = [ "fbsource//xplat/folly/portability:config", ], ) non_fbcode_target( _kind = fb_xplat_cxx_library, name = "futures", header_namespace = "", apple_sdks = DEFAULT_APPLE_SDKS, compiler_flags = CXXFLAGS, fbandroid_compiler_flags = FBANDROID_CXXFLAGS, fbandroid_deps = [ "//xplat/third-party/linker_lib:atomic", ], fbobjc_compiler_flags = FBOBJC_CXXFLAGS, labels = [ # This library "re-exports" symbols in :futures_core. Depslint doesn't # support the concept of target-reexports (nor do we know the best # design for such a feature), so we just exclude this target from being # removed. "depslint_never_remove", # This is a legacy "umbrella" target "legacy_target", ], platforms = (ANDROID, APPLE, CXX, WINDOWS), public_include_directories = [ "..", ], soname = "libfolly-futures.$(ext)", visibility = ["PUBLIC"], windows_clang_compiler_flags_override = [ "-Wno-deprecated-declarations", ], windows_msvc_compiler_flags_override = [ "/EHsc", ], windows_preferred_linkage = "static", deps = [ "//xplat/folly:memory", ], exported_deps = [ "fbsource//third-party/boost:boost", "fbsource//third-party/double-conversion:double-conversion", "fbsource//third-party/glog:glog", "fbsource//xplat/folly:atomic_hash_map", "fbsource//xplat/folly:atomic_linked_list", "fbsource//xplat/folly:c_portability", "fbsource//xplat/folly:config", "fbsource//xplat/folly:constexpr_math", "fbsource//xplat/folly:conv", "fbsource//xplat/folly:cpp_attributes", "fbsource//xplat/folly:demangle", "fbsource//xplat/folly:dynamic", "fbsource//xplat/folly:exception", "fbsource//xplat/folly:exception_string", "fbsource//xplat/folly:executor", "fbsource//xplat/folly:expected", "fbsource//xplat/folly:fbstring", "fbsource//xplat/folly:fbvector", "fbsource//xplat/folly:file", "fbsource//xplat/folly:format", "fbsource//xplat/folly:format_traits", "fbsource//xplat/folly:function", "fbsource//xplat/folly:glog", "fbsource//xplat/folly:indestructible", "fbsource//xplat/folly:intrusive_list", "fbsource//xplat/folly:likely", "fbsource//xplat/folly:math", "fbsource//xplat/folly:micro_lock", "fbsource//xplat/folly:move_wrapper", "fbsource//xplat/folly:optional", "fbsource//xplat/folly:overload", "fbsource//xplat/folly:portability", "fbsource//xplat/folly:preprocessor", "fbsource//xplat/folly:range", "fbsource//xplat/folly:scope_guard", "fbsource//xplat/folly:shared_mutex", "fbsource//xplat/folly:singleton_thread_local", "fbsource//xplat/folly:small_vector", "fbsource//xplat/folly:sorted_vector_types", "fbsource//xplat/folly:spin_lock", "fbsource//xplat/folly:thread_local", "fbsource//xplat/folly:traits", "fbsource//xplat/folly:unit", "fbsource//xplat/folly/concurrency:cache_locality", "fbsource//xplat/folly/container:array", "fbsource//xplat/folly/container:enumerate", "fbsource//xplat/folly/container:f14_hash", "fbsource//xplat/folly/container:foreach", "fbsource//xplat/folly/container:heap_vector_types", "fbsource//xplat/folly/container:iterator", "fbsource//xplat/folly/container:sparse_byte_set", "fbsource//xplat/folly/detail:futex", "fbsource//xplat/folly/functional:apply_tuple", "fbsource//xplat/folly/functional:invoke", "fbsource//xplat/folly/functional:partial", "fbsource//xplat/folly/futures:portability", "fbsource//xplat/folly/futures/detail:core", "fbsource//xplat/folly/futures/detail:types", "fbsource//xplat/folly/io:iobuf", "fbsource//xplat/folly/lang:bits", "fbsource//xplat/folly/lang:checked_math", "fbsource//xplat/folly/lang:exception", "fbsource//xplat/folly/lang:rvalue_reference_wrapper", "fbsource//xplat/folly/lang:uncaught_exceptions", "fbsource//xplat/folly/memory:arena", "fbsource//xplat/folly/memory:malloc", "fbsource//xplat/folly/portability:asm", "fbsource//xplat/folly/portability:atomic", "fbsource//xplat/folly/portability:builtins", "fbsource//xplat/folly/portability:dirent", "fbsource//xplat/folly/portability:fcntl", "fbsource//xplat/folly/portability:gflags", "fbsource//xplat/folly/portability:iovec", "fbsource//xplat/folly/portability:math", "fbsource//xplat/folly/portability:pthread", "fbsource//xplat/folly/portability:stdlib", "fbsource//xplat/folly/portability:sys_mman", "fbsource//xplat/folly/portability:sys_resource", "fbsource//xplat/folly/portability:sys_stat", "fbsource//xplat/folly/portability:sys_syscall", "fbsource//xplat/folly/portability:sys_time", "fbsource//xplat/folly/portability:syslog", "fbsource//xplat/folly/portability:time", "fbsource//xplat/folly/portability:windows", "fbsource//xplat/folly/synchronization:call_once", "fbsource//xplat/folly/synchronization:micro_spin_lock", "fbsource//xplat/folly/synchronization:pico_spin_lock", "fbsource//xplat/folly/synchronization:small_locks", "fbsource//xplat/folly/system:thread_id", "fbsource//xplat/folly/system:thread_name", "fbsource//xplat/third-party/linker_lib:atomic", ":futures_barrier", ":futures_core", ":futures_future_splitter", ":futures_shared_promise", "//third-party/boost:boost", "//third-party/boost:boost_system", "//xplat/folly:singleton", "//xplat/folly:try", "//xplat/folly/detail:turn_sequencer", "//xplat/folly/executors:executor_with_priority", "//xplat/folly/executors:inline_executor", "//xplat/folly/executors:manual_executor", "//xplat/folly/executors:queued_immediate_executor", "//xplat/folly/executors:timed_drivable_executor", "//xplat/folly/experimental/coro:traits", "//xplat/folly/io/async:async_base", # Deps merged from :extended_light "//xplat/folly:chrono", "//xplat/folly:indexed_mem_pool", "//xplat/folly:memory", "//xplat/folly:mpmc_queue", "//xplat/folly:random", "//xplat/folly:synchronized", "//xplat/folly/concurrency:concurrent_hash_map", "//xplat/folly/concurrency:priority_unbounded_queue_set", "//xplat/folly/concurrency:unbounded_queue", "//xplat/folly/container:bit_iterator", "//xplat/folly/container:evicting_cache_map", "//xplat/folly/detail:socket_fast_open", "//xplat/folly/executors:cpu_thread_pool_executor", "//xplat/folly/executors:global_executor", "//xplat/folly/executors:io_thread_pool_executor", "//xplat/folly/executors:serial_executor", "//xplat/folly/executors/task_queue:priority_lifo_sem_mpmc_queue", "//xplat/folly/executors/task_queue:priority_unbounded_blocking_queue", "//xplat/folly/executors/task_queue:unbounded_blocking_queue", "//xplat/folly/io/async:async_signal_handler", "//xplat/folly/io/async:async_socket_base", "//xplat/folly/io/async:async_socket_exception", "//xplat/folly/io/async:async_udp_server_socket", "//xplat/folly/io/async:async_udp_socket", "//xplat/folly/io/async:destructor_check", "//xplat/folly/io/async/ssl:tls_definitions", "//xplat/folly/portability:event", "//xplat/folly/synchronization:hazptr", "//xplat/folly/synchronization:lifo_sem", "//xplat/folly/tracing:scoped_trace_section", "//xplat/folly/tracing:static_tracepoint", "//xplat/third-party/event:event", ], ) non_fbcode_target( _kind = folly_xplat_library, name = "test_test_utils", raw_headers = [ "test/TestUtils.h", ], exported_deps = [ "fbsource//xplat/folly/portability:gtest", "//xplat/folly:conv", "//xplat/folly:exception_string", "//xplat/folly:fbstring", "//xplat/folly:fixed_string", "//xplat/folly:range", ], ) non_fbcode_target( _kind = folly_xplat_library, name = "test_socket_address_test_helper", srcs = [ "test/SocketAddressTestHelper.cpp", ], raw_headers = [ "test/SocketAddressTestHelper.h", ], deps = [ "fbsource//xplat/folly/portability:sockets", "//xplat/folly/net:tcpinfo_dispatcher", ], ) # Folly test targets non_fbcode_target( _kind = fb_xplat_cxx_library, name = "test-headers", header_namespace = "", apple_sdks = DEFAULT_APPLE_SDKS, force_static = True, platforms = (ANDROID, APPLE, CXX, FBCODE, WINDOWS), public_include_directories = [ "..", ], visibility = ["PUBLIC"], deps = [ ":memory", "//xplat/folly/container/test:f14_test_util", "//xplat/folly/container/test:tracking_types", "//xplat/folly/experimental/flat_combining/test:flat_combining_examples", "//xplat/folly/experimental/flat_combining/test:flat_combining_test_helpers", "//xplat/folly/tracing/test:static_tracepoint_test_module", ], ) non_fbcode_target( _kind = fb_xplat_cxx_library, name = "test-util", header_namespace = "", apple_sdks = DEFAULT_APPLE_SDKS, compiler_flags = CXXFLAGS + [ "-Wno-narrowing", ], fbandroid_compiler_flags = FBANDROID_CXXFLAGS, fbandroid_deps = [ "//xplat/third-party/linker_lib:atomic", ], fbobjc_compiler_flags = FBOBJC_CXXFLAGS, platforms = (ANDROID, APPLE, CXX, FBCODE, WINDOWS), visibility = ["PUBLIC"], deps = [ "//third-party/boost:boost", "//third-party/boost:boost_regex", "//xplat/folly:memory", ], exported_deps = [ "fbsource//third-party/boost:boost", "fbsource//third-party/double-conversion:double-conversion", "fbsource//third-party/glog:glog", "fbsource//xplat/folly:atomic_hash_map", "fbsource//xplat/folly:atomic_linked_list", "fbsource//xplat/folly:c_portability", "fbsource//xplat/folly:chrono", "fbsource//xplat/folly:config", "fbsource//xplat/folly:constexpr_math", "fbsource//xplat/folly:conv", "fbsource//xplat/folly:cpp_attributes", "fbsource//xplat/folly:demangle", "fbsource//xplat/folly:dynamic", "fbsource//xplat/folly:exception", "fbsource//xplat/folly:exception_string", "fbsource//xplat/folly:executor", "fbsource//xplat/folly:expected", "fbsource//xplat/folly:fbstring", "fbsource//xplat/folly:fbvector", "fbsource//xplat/folly:file", "fbsource//xplat/folly:format", "fbsource//xplat/folly:format_traits", "fbsource//xplat/folly:function", "fbsource//xplat/folly:glog", "fbsource//xplat/folly:indestructible", "fbsource//xplat/folly:indexed_mem_pool", "fbsource//xplat/folly:intrusive_list", "fbsource//xplat/folly:likely", "fbsource//xplat/folly:math", "fbsource//xplat/folly:memory", "fbsource//xplat/folly:micro_lock", "fbsource//xplat/folly:move_wrapper", "fbsource//xplat/folly:mpmc_queue", "fbsource//xplat/folly:observer_container", "fbsource//xplat/folly:optional", "fbsource//xplat/folly:overload", "fbsource//xplat/folly:portability", "fbsource//xplat/folly:preprocessor", "fbsource//xplat/folly:random", "fbsource//xplat/folly:range", "fbsource//xplat/folly:scope_guard", "fbsource//xplat/folly:shared_mutex", "fbsource//xplat/folly:singleton", "fbsource//xplat/folly:singleton_thread_local", "fbsource//xplat/folly:small_vector", "fbsource//xplat/folly:sorted_vector_types", "fbsource//xplat/folly:spin_lock", "fbsource//xplat/folly:synchronized", "fbsource//xplat/folly:thread_local", "fbsource//xplat/folly:traits", "fbsource//xplat/folly:unit", "fbsource//xplat/folly/concurrency:cache_locality", "fbsource//xplat/folly/concurrency:concurrent_hash_map", "fbsource//xplat/folly/concurrency:priority_unbounded_queue_set", "fbsource//xplat/folly/concurrency:unbounded_queue", "fbsource//xplat/folly/container:array", "fbsource//xplat/folly/container:bit_iterator", "fbsource//xplat/folly/container:enumerate", "fbsource//xplat/folly/container:evicting_cache_map", "fbsource//xplat/folly/container:f14_hash", "fbsource//xplat/folly/container:foreach", "fbsource//xplat/folly/container:heap_vector_types", "fbsource//xplat/folly/container:iterator", "fbsource//xplat/folly/container:sparse_byte_set", "fbsource//xplat/folly/detail:futex", "fbsource//xplat/folly/detail:socket_fast_open", "fbsource//xplat/folly/detail:turn_sequencer", "fbsource//xplat/folly/executors:cpu_thread_pool_executor", "fbsource//xplat/folly/executors:global_executor", "fbsource//xplat/folly/executors:inline_executor", "fbsource//xplat/folly/executors:io_thread_pool_executor", "fbsource//xplat/folly/executors:serial_executor", "fbsource//xplat/folly/executors/task_queue:priority_lifo_sem_mpmc_queue", "fbsource//xplat/folly/executors/task_queue:priority_unbounded_blocking_queue", "fbsource//xplat/folly/executors/task_queue:unbounded_blocking_queue", "fbsource//xplat/folly/functional:apply_tuple", "fbsource//xplat/folly/functional:invoke", "fbsource//xplat/folly/functional:partial", "fbsource//xplat/folly/io:iobuf", "fbsource//xplat/folly/io:typed_io_buf", "fbsource//xplat/folly/io/async:async_base", "fbsource//xplat/folly/io/async:async_pipe", "fbsource//xplat/folly/io/async:async_signal_handler", "fbsource//xplat/folly/io/async:async_socket", "fbsource//xplat/folly/io/async:async_socket_base", "fbsource//xplat/folly/io/async:async_socket_exception", "fbsource//xplat/folly/io/async:async_ssl_socket", "fbsource//xplat/folly/io/async:async_transport", "fbsource//xplat/folly/io/async:async_transport_certificate", "fbsource//xplat/folly/io/async:async_udp_server_socket", "fbsource//xplat/folly/io/async:async_udp_socket", "fbsource//xplat/folly/io/async:decorated_async_transport_wrapper", "fbsource//xplat/folly/io/async:destructor_check", "fbsource//xplat/folly/io/async:scoped_event_base_thread", "fbsource//xplat/folly/io/async:ssl_context", "fbsource//xplat/folly/io/async:ssl_options", "fbsource//xplat/folly/io/async/ssl:basic_transport_certificate", "fbsource//xplat/folly/io/async/ssl:openssl_utils", "fbsource//xplat/folly/io/async/ssl:ssl_errors", "fbsource//xplat/folly/io/async/ssl:tls_definitions", "fbsource//xplat/folly/lang:bits", "fbsource//xplat/folly/lang:checked_math", "fbsource//xplat/folly/lang:exception", "fbsource//xplat/folly/lang:rvalue_reference_wrapper", "fbsource//xplat/folly/lang:uncaught_exceptions", "fbsource//xplat/folly/logging:init", "fbsource//xplat/folly/logging:init_weak", "fbsource//xplat/folly/logging:log_handler", "fbsource//xplat/folly/logging:log_level", "fbsource//xplat/folly/logging:log_name", "fbsource//xplat/folly/logging:logging", "fbsource//xplat/folly/logging:rate_limiter", "fbsource//xplat/folly/memory:arena", "fbsource//xplat/folly/memory:malloc", "fbsource//xplat/folly/net:tcpinfo", "fbsource//xplat/folly/net:tcpinfo_dispatcher", "fbsource//xplat/folly/portability:asm", "fbsource//xplat/folly/portability:atomic", "fbsource//xplat/folly/portability:builtins", "fbsource//xplat/folly/portability:dirent", "fbsource//xplat/folly/portability:event", "fbsource//xplat/folly/portability:fcntl", "fbsource//xplat/folly/portability:gflags", "fbsource//xplat/folly/portability:gmock", "fbsource//xplat/folly/portability:gtest", "fbsource//xplat/folly/portability:iovec", "fbsource//xplat/folly/portability:math", "fbsource//xplat/folly/portability:openssl", "fbsource//xplat/folly/portability:pthread", "fbsource//xplat/folly/portability:stdlib", "fbsource//xplat/folly/portability:sys_mman", "fbsource//xplat/folly/portability:sys_resource", "fbsource//xplat/folly/portability:sys_stat", "fbsource//xplat/folly/portability:sys_syscall", "fbsource//xplat/folly/portability:sys_time", "fbsource//xplat/folly/portability:syslog", "fbsource//xplat/folly/portability:time", "fbsource//xplat/folly/portability:windows", "fbsource//xplat/folly/ssl:openssl_cert_utils", "fbsource//xplat/folly/ssl:openssl_hash", "fbsource//xplat/folly/ssl:openssl_ptr_types", "fbsource//xplat/folly/ssl:ssl_session", "fbsource//xplat/folly/ssl:ssl_session_manager", "fbsource//xplat/folly/synchronization:call_once", "fbsource//xplat/folly/synchronization:hazptr", "fbsource//xplat/folly/synchronization:lifo_sem", "fbsource//xplat/folly/synchronization:micro_spin_lock", "fbsource//xplat/folly/synchronization:pico_spin_lock", "fbsource//xplat/folly/synchronization:rcu", "fbsource//xplat/folly/synchronization:small_locks", "fbsource//xplat/folly/system:thread_id", "fbsource//xplat/folly/system:thread_name", "fbsource//xplat/folly/tracing:scoped_trace_section", "fbsource//xplat/folly/tracing:static_tracepoint", "fbsource//xplat/third-party/event:event", "fbsource//xplat/third-party/linker_lib:atomic", "fbsource//xplat/third-party/openssl:crypto", "fbsource//xplat/third-party/openssl:ssl", "//third-party/boost:boost_filesystem", "//third-party/boost:boost_system", "//xplat/folly:discriminated_ptr", "//xplat/folly:stop_watch", "//xplat/folly/detail:discriminated_ptr_detail", "//xplat/folly/fibers:core", "//xplat/folly/fibers:event_base_loop_controller", "//xplat/folly/fibers:fiber_manager_map", "//xplat/folly/io/async:server_socket", ], ) FOLLY_TEST_SRCS = [ "test/ApplyTupleTest.cpp", "test/CallOnceTest.cpp", "test/ConvTest.cpp", # "test/CpuIdTest.cpp", "test/DemangleTest.cpp", "test/DynamicConverterTest.cpp", "test/FBStringTest.cpp", "test/FBVectorTestUtil.cpp", "test/FBVectorTest.cpp", "test/ForeachTest.cpp", "test/FormatTest.cpp", "test/FunctionTest.cpp", "test/HashTest.cpp", "test/LoggingTest.cpp", "test/MacAddressTest.cpp", "test/MathTest.cpp", "test/MoveWrapperTest.cpp", "test/OptionalTest.cpp", "test/PortabilityTest.cpp", # "test/RandomTest.cpp", # "test/RangeTest.cpp", # "test/ScopeGuardTest.cpp", "test/SmallLocksTest.cpp", "test/StringTest.cpp", "test/TraitsTest.cpp", ] FOLLY_EXTENDED_TEST_SRCS = [ "test/AtomicStructTest.cpp", "test/CacheLocalityTest.cpp", "test/EvictingCacheMapTest.cpp", # "test/ExceptionWrapperTest.cpp", "test/MemoryIdlerTest.cpp", ] non_fbcode_target( _kind = fb_xplat_cxx_test, name = "folly-test", srcs = glob(FOLLY_TEST_SRCS), compiler_flags = CXXFLAGS + [ "-Wno-unused-private-field", ], contacts = ["oncall+folly@xmail.facebook.com"], fbandroid_compiler_flags = FBANDROID_CXXFLAGS, fbandroid_cpu_suffixes = [ "64bit", None, ], fbobjc_compiler_flags = FBOBJC_CXXFLAGS + [ "-Wno-format", "-Wno-missing-prototypes", "-Wno-shadow", ], platforms = (ANDROID, APPLE, CXX, WINDOWS), preprocessor_flags = CPPFLAGS + [ "-DFOLLY_SKIP_AS_FAILURE=0", ] + select({ "DEFAULT": [], "ovr_config//os:linux": FBANDROID_CPPFLAGS, }), use_instrumentation_test = True, deps = [ "fbsource//third-party/boost:boost", "fbsource//third-party/boost:boost_filesystem", "fbsource//third-party/boost:boost_system", "fbsource//third-party/double-conversion:double-conversion", "fbsource//third-party/glog:glog", "fbsource//xplat/folly:atomic_hash_map", "fbsource//xplat/folly:atomic_linked_list", "fbsource//xplat/folly:c_portability", "fbsource//xplat/folly:chrono", "fbsource//xplat/folly:config", "fbsource//xplat/folly:constexpr_math", "fbsource//xplat/folly:conv", "fbsource//xplat/folly:cpp_attributes", "fbsource//xplat/folly:demangle", "fbsource//xplat/folly:discriminated_ptr", "fbsource//xplat/folly:dynamic", "fbsource//xplat/folly:exception", "fbsource//xplat/folly:exception_string", "fbsource//xplat/folly:executor", "fbsource//xplat/folly:expected", "fbsource//xplat/folly:fbstring", "fbsource//xplat/folly:fbvector", "fbsource//xplat/folly:file", "fbsource//xplat/folly:format", "fbsource//xplat/folly:format_traits", "fbsource//xplat/folly:function", "fbsource//xplat/folly:glog", "fbsource//xplat/folly:indestructible", "fbsource//xplat/folly:indexed_mem_pool", "fbsource//xplat/folly:intrusive_list", "fbsource//xplat/folly:likely", "fbsource//xplat/folly:math", "fbsource//xplat/folly:memory", "fbsource//xplat/folly:micro_lock", "fbsource//xplat/folly:move_wrapper", "fbsource//xplat/folly:mpmc_queue", "fbsource//xplat/folly:observer_container", "fbsource//xplat/folly:optional", "fbsource//xplat/folly:overload", "fbsource//xplat/folly:portability", "fbsource//xplat/folly:preprocessor", "fbsource//xplat/folly:random", "fbsource//xplat/folly:range", "fbsource//xplat/folly:scope_guard", "fbsource//xplat/folly:shared_mutex", "fbsource//xplat/folly:singleton", "fbsource//xplat/folly:singleton_thread_local", "fbsource//xplat/folly:small_vector", "fbsource//xplat/folly:sorted_vector_types", "fbsource//xplat/folly:spin_lock", "fbsource//xplat/folly:stop_watch", "fbsource//xplat/folly:synchronized", "fbsource//xplat/folly:thread_local", "fbsource//xplat/folly:traits", "fbsource//xplat/folly:unit", "fbsource//xplat/folly/concurrency:cache_locality", "fbsource//xplat/folly/concurrency:concurrent_hash_map", "fbsource//xplat/folly/concurrency:priority_unbounded_queue_set", "fbsource//xplat/folly/concurrency:unbounded_queue", "fbsource//xplat/folly/container:array", "fbsource//xplat/folly/container:bit_iterator", "fbsource//xplat/folly/container:enumerate", "fbsource//xplat/folly/container:evicting_cache_map", "fbsource//xplat/folly/container:f14_hash", "fbsource//xplat/folly/container:foreach", "fbsource//xplat/folly/container:heap_vector_types", "fbsource//xplat/folly/container:iterator", "fbsource//xplat/folly/container:sparse_byte_set", "fbsource//xplat/folly/detail:discriminated_ptr_detail", "fbsource//xplat/folly/detail:futex", "fbsource//xplat/folly/detail:socket_fast_open", "fbsource//xplat/folly/detail:turn_sequencer", "fbsource//xplat/folly/executors:cpu_thread_pool_executor", "fbsource//xplat/folly/executors:global_executor", "fbsource//xplat/folly/executors:inline_executor", "fbsource//xplat/folly/executors:io_thread_pool_executor", "fbsource//xplat/folly/executors:serial_executor", "fbsource//xplat/folly/executors/task_queue:priority_lifo_sem_mpmc_queue", "fbsource//xplat/folly/executors/task_queue:priority_unbounded_blocking_queue", "fbsource//xplat/folly/executors/task_queue:unbounded_blocking_queue", "fbsource//xplat/folly/fibers:core", "fbsource//xplat/folly/fibers:event_base_loop_controller", "fbsource//xplat/folly/fibers:fiber_manager_map", "fbsource//xplat/folly/functional:apply_tuple", "fbsource//xplat/folly/functional:invoke", "fbsource//xplat/folly/functional:partial", "fbsource//xplat/folly/io:iobuf", "fbsource//xplat/folly/io:typed_io_buf", "fbsource//xplat/folly/io/async:async_base", "fbsource//xplat/folly/io/async:async_pipe", "fbsource//xplat/folly/io/async:async_signal_handler", "fbsource//xplat/folly/io/async:async_socket", "fbsource//xplat/folly/io/async:async_socket_base", "fbsource//xplat/folly/io/async:async_socket_exception", "fbsource//xplat/folly/io/async:async_ssl_socket", "fbsource//xplat/folly/io/async:async_transport", "fbsource//xplat/folly/io/async:async_transport_certificate", "fbsource//xplat/folly/io/async:async_udp_server_socket", "fbsource//xplat/folly/io/async:async_udp_socket", "fbsource//xplat/folly/io/async:decorated_async_transport_wrapper", "fbsource//xplat/folly/io/async:destructor_check", "fbsource//xplat/folly/io/async:scoped_event_base_thread", "fbsource//xplat/folly/io/async:server_socket", "fbsource//xplat/folly/io/async:ssl_context", "fbsource//xplat/folly/io/async:ssl_options", "fbsource//xplat/folly/io/async/ssl:basic_transport_certificate", "fbsource//xplat/folly/io/async/ssl:openssl_utils", "fbsource//xplat/folly/io/async/ssl:ssl_errors", "fbsource//xplat/folly/io/async/ssl:tls_definitions", "fbsource//xplat/folly/lang:bits", "fbsource//xplat/folly/lang:checked_math", "fbsource//xplat/folly/lang:exception", "fbsource//xplat/folly/lang:rvalue_reference_wrapper", "fbsource//xplat/folly/lang:uncaught_exceptions", "fbsource//xplat/folly/logging:init", "fbsource//xplat/folly/logging:init_weak", "fbsource//xplat/folly/logging:log_handler", "fbsource//xplat/folly/logging:log_level", "fbsource//xplat/folly/logging:log_name", "fbsource//xplat/folly/logging:logging", "fbsource//xplat/folly/logging:rate_limiter", "fbsource//xplat/folly/memory:arena", "fbsource//xplat/folly/memory:malloc", "fbsource//xplat/folly/net:tcpinfo", "fbsource//xplat/folly/net:tcpinfo_dispatcher", "fbsource//xplat/folly/portability:asm", "fbsource//xplat/folly/portability:atomic", "fbsource//xplat/folly/portability:builtins", "fbsource//xplat/folly/portability:dirent", "fbsource//xplat/folly/portability:event", "fbsource//xplat/folly/portability:fcntl", "fbsource//xplat/folly/portability:gflags", "fbsource//xplat/folly/portability:gmock", "fbsource//xplat/folly/portability:gtest", "fbsource//xplat/folly/portability:iovec", "fbsource//xplat/folly/portability:math", "fbsource//xplat/folly/portability:openssl", "fbsource//xplat/folly/portability:pthread", "fbsource//xplat/folly/portability:stdlib", "fbsource//xplat/folly/portability:sys_mman", "fbsource//xplat/folly/portability:sys_resource", "fbsource//xplat/folly/portability:sys_stat", "fbsource//xplat/folly/portability:sys_syscall", "fbsource//xplat/folly/portability:sys_time", "fbsource//xplat/folly/portability:syslog", "fbsource//xplat/folly/portability:time", "fbsource//xplat/folly/portability:windows", "fbsource//xplat/folly/ssl:openssl_cert_utils", "fbsource//xplat/folly/ssl:openssl_hash", "fbsource//xplat/folly/ssl:openssl_ptr_types", "fbsource//xplat/folly/ssl:ssl_session", "fbsource//xplat/folly/ssl:ssl_session_manager", "fbsource//xplat/folly/synchronization:call_once", "fbsource//xplat/folly/synchronization:hazptr", "fbsource//xplat/folly/synchronization:lifo_sem", "fbsource//xplat/folly/synchronization:micro_spin_lock", "fbsource//xplat/folly/synchronization:pico_spin_lock", "fbsource//xplat/folly/synchronization:rcu", "fbsource//xplat/folly/synchronization:small_locks", "fbsource//xplat/folly/system:thread_id", "fbsource//xplat/folly/system:thread_name", "fbsource//xplat/folly/tracing:scoped_trace_section", "fbsource//xplat/folly/tracing:static_tracepoint", "fbsource//xplat/third-party/event:event", "fbsource//xplat/third-party/linker_lib:atomic", "fbsource//xplat/third-party/openssl:crypto", "fbsource//xplat/third-party/openssl:ssl", "//third-party/boost:boost", "//third-party/boost:boost_regex", "//third-party/googletest:gmock_main", "//xplat/folly:expected", "//xplat/folly:fixed_string", "//xplat/folly:memory", "//xplat/folly:optional", "//xplat/folly:test-headers", "//xplat/folly/lang:keep", ], ) non_fbcode_target( _kind = folly_xplat_cxx_test, name = "folly-extended-test", srcs = glob(FOLLY_EXTENDED_TEST_SRCS), compiler_flags = CXXFLAGS, fbandroid_compiler_flags = FBANDROID_CXXFLAGS, fbobjc_compiler_flags = FBOBJC_CXXFLAGS, platforms = (ANDROID, APPLE, CXX, WINDOWS), preprocessor_flags = CPPFLAGS + select({ "DEFAULT": [], "ovr_config//os:linux": FBANDROID_CPPFLAGS, }), use_instrumentation_test = True, deps = [ "fbsource//third-party/boost:boost", "fbsource//third-party/boost:boost_filesystem", "fbsource//third-party/boost:boost_system", "fbsource//third-party/double-conversion:double-conversion", "fbsource//third-party/glog:glog", "fbsource//xplat/folly:atomic_hash_map", "fbsource//xplat/folly:atomic_linked_list", "fbsource//xplat/folly:c_portability", "fbsource//xplat/folly:chrono", "fbsource//xplat/folly:config", "fbsource//xplat/folly:constexpr_math", "fbsource//xplat/folly:conv", "fbsource//xplat/folly:cpp_attributes", "fbsource//xplat/folly:demangle", "fbsource//xplat/folly:discriminated_ptr", "fbsource//xplat/folly:dynamic", "fbsource//xplat/folly:exception", "fbsource//xplat/folly:exception_string", "fbsource//xplat/folly:executor", "fbsource//xplat/folly:expected", "fbsource//xplat/folly:fbstring", "fbsource//xplat/folly:fbvector", "fbsource//xplat/folly:file", "fbsource//xplat/folly:format", "fbsource//xplat/folly:format_traits", "fbsource//xplat/folly:function", "fbsource//xplat/folly:glog", "fbsource//xplat/folly:indestructible", "fbsource//xplat/folly:indexed_mem_pool", "fbsource//xplat/folly:intrusive_list", "fbsource//xplat/folly:likely", "fbsource//xplat/folly:math", "fbsource//xplat/folly:memory", "fbsource//xplat/folly:micro_lock", "fbsource//xplat/folly:move_wrapper", "fbsource//xplat/folly:mpmc_queue", "fbsource//xplat/folly:observer_container", "fbsource//xplat/folly:optional", "fbsource//xplat/folly:overload", "fbsource//xplat/folly:portability", "fbsource//xplat/folly:preprocessor", "fbsource//xplat/folly:random", "fbsource//xplat/folly:range", "fbsource//xplat/folly:scope_guard", "fbsource//xplat/folly:shared_mutex", "fbsource//xplat/folly:singleton", "fbsource//xplat/folly:singleton_thread_local", "fbsource//xplat/folly:small_vector", "fbsource//xplat/folly:sorted_vector_types", "fbsource//xplat/folly:spin_lock", "fbsource//xplat/folly:stop_watch", "fbsource//xplat/folly:synchronized", "fbsource//xplat/folly:thread_local", "fbsource//xplat/folly:traits", "fbsource//xplat/folly:unit", "fbsource//xplat/folly/concurrency:cache_locality", "fbsource//xplat/folly/concurrency:concurrent_hash_map", "fbsource//xplat/folly/concurrency:priority_unbounded_queue_set", "fbsource//xplat/folly/concurrency:unbounded_queue", "fbsource//xplat/folly/container:array", "fbsource//xplat/folly/container:bit_iterator", "fbsource//xplat/folly/container:enumerate", "fbsource//xplat/folly/container:evicting_cache_map", "fbsource//xplat/folly/container:f14_hash", "fbsource//xplat/folly/container:foreach", "fbsource//xplat/folly/container:heap_vector_types", "fbsource//xplat/folly/container:iterator", "fbsource//xplat/folly/container:sparse_byte_set", "fbsource//xplat/folly/detail:discriminated_ptr_detail", "fbsource//xplat/folly/detail:futex", "fbsource//xplat/folly/detail:socket_fast_open", "fbsource//xplat/folly/detail:turn_sequencer", "fbsource//xplat/folly/executors:cpu_thread_pool_executor", "fbsource//xplat/folly/executors:global_executor", "fbsource//xplat/folly/executors:inline_executor", "fbsource//xplat/folly/executors:io_thread_pool_executor", "fbsource//xplat/folly/executors:serial_executor", "fbsource//xplat/folly/executors/task_queue:priority_lifo_sem_mpmc_queue", "fbsource//xplat/folly/executors/task_queue:priority_unbounded_blocking_queue", "fbsource//xplat/folly/executors/task_queue:unbounded_blocking_queue", "fbsource//xplat/folly/fibers:core", "fbsource//xplat/folly/fibers:event_base_loop_controller", "fbsource//xplat/folly/fibers:fiber_manager_map", "fbsource//xplat/folly/functional:apply_tuple", "fbsource//xplat/folly/functional:invoke", "fbsource//xplat/folly/functional:partial", "fbsource//xplat/folly/io:iobuf", "fbsource//xplat/folly/io:typed_io_buf", "fbsource//xplat/folly/io/async:async_base", "fbsource//xplat/folly/io/async:async_pipe", "fbsource//xplat/folly/io/async:async_signal_handler", "fbsource//xplat/folly/io/async:async_socket", "fbsource//xplat/folly/io/async:async_socket_base", "fbsource//xplat/folly/io/async:async_socket_exception", "fbsource//xplat/folly/io/async:async_ssl_socket", "fbsource//xplat/folly/io/async:async_transport", "fbsource//xplat/folly/io/async:async_transport_certificate", "fbsource//xplat/folly/io/async:async_udp_server_socket", "fbsource//xplat/folly/io/async:async_udp_socket", "fbsource//xplat/folly/io/async:decorated_async_transport_wrapper", "fbsource//xplat/folly/io/async:destructor_check", "fbsource//xplat/folly/io/async:scoped_event_base_thread", "fbsource//xplat/folly/io/async:server_socket", "fbsource//xplat/folly/io/async:ssl_context", "fbsource//xplat/folly/io/async:ssl_options", "fbsource//xplat/folly/io/async/ssl:basic_transport_certificate", "fbsource//xplat/folly/io/async/ssl:openssl_utils", "fbsource//xplat/folly/io/async/ssl:ssl_errors", "fbsource//xplat/folly/io/async/ssl:tls_definitions", "fbsource//xplat/folly/lang:bits", "fbsource//xplat/folly/lang:checked_math", "fbsource//xplat/folly/lang:exception", "fbsource//xplat/folly/lang:rvalue_reference_wrapper", "fbsource//xplat/folly/lang:uncaught_exceptions", "fbsource//xplat/folly/logging:init", "fbsource//xplat/folly/logging:init_weak", "fbsource//xplat/folly/logging:log_handler", "fbsource//xplat/folly/logging:log_level", "fbsource//xplat/folly/logging:log_name", "fbsource//xplat/folly/logging:logging", "fbsource//xplat/folly/logging:rate_limiter", "fbsource//xplat/folly/memory:arena", "fbsource//xplat/folly/memory:malloc", "fbsource//xplat/folly/net:tcpinfo", "fbsource//xplat/folly/net:tcpinfo_dispatcher", "fbsource//xplat/folly/portability:asm", "fbsource//xplat/folly/portability:atomic", "fbsource//xplat/folly/portability:builtins", "fbsource//xplat/folly/portability:dirent", "fbsource//xplat/folly/portability:event", "fbsource//xplat/folly/portability:fcntl", "fbsource//xplat/folly/portability:gflags", "fbsource//xplat/folly/portability:gmock", "fbsource//xplat/folly/portability:gtest", "fbsource//xplat/folly/portability:iovec", "fbsource//xplat/folly/portability:math", "fbsource//xplat/folly/portability:openssl", "fbsource//xplat/folly/portability:pthread", "fbsource//xplat/folly/portability:stdlib", "fbsource//xplat/folly/portability:sys_mman", "fbsource//xplat/folly/portability:sys_resource", "fbsource//xplat/folly/portability:sys_stat", "fbsource//xplat/folly/portability:sys_syscall", "fbsource//xplat/folly/portability:sys_time", "fbsource//xplat/folly/portability:syslog", "fbsource//xplat/folly/portability:time", "fbsource//xplat/folly/portability:windows", "fbsource//xplat/folly/ssl:openssl_cert_utils", "fbsource//xplat/folly/ssl:openssl_hash", "fbsource//xplat/folly/ssl:openssl_ptr_types", "fbsource//xplat/folly/ssl:ssl_session", "fbsource//xplat/folly/ssl:ssl_session_manager", "fbsource//xplat/folly/synchronization:call_once", "fbsource//xplat/folly/synchronization:hazptr", "fbsource//xplat/folly/synchronization:lifo_sem", "fbsource//xplat/folly/synchronization:micro_spin_lock", "fbsource//xplat/folly/synchronization:pico_spin_lock", "fbsource//xplat/folly/synchronization:rcu", "fbsource//xplat/folly/synchronization:small_locks", "fbsource//xplat/folly/system:thread_id", "fbsource//xplat/folly/system:thread_name", "fbsource//xplat/folly/tracing:scoped_trace_section", "fbsource//xplat/folly/tracing:static_tracepoint", "fbsource//xplat/third-party/event:event", "fbsource//xplat/third-party/linker_lib:atomic", "fbsource//xplat/third-party/openssl:crypto", "fbsource//xplat/third-party/openssl:ssl", "//third-party/boost:boost", "//third-party/boost:boost_regex", "//third-party/googletest:gmock_main", "//xplat/folly:test-headers", ], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "experimental_symbolizer_stack_trace", exported_deps = ["//xplat/folly/experimental/symbolizer:stack_trace"], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "stats_detail_double_radix_sort", exported_deps = ["//xplat/folly/stats/detail:double_radix_sort"], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "futures_detail_types", exported_deps = ["//xplat/folly/futures/detail:types"], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "stats_detail_bucket", exported_deps = ["//xplat/folly/stats/detail:bucket"], ) non_fbcode_target( _kind = folly_xplat_library, name = "timeout_queue", srcs = [ "TimeoutQueue.cpp", ], apple_sdks = (IOS, MACOSX), raw_headers = [ "TimeoutQueue.h", ], deps = [ "//third-party/boost:boost", "//third-party/boost:boost_multi_index", ], ) fb_dirsync_cpp_library( name = "try", srcs = [ "Try.cpp", ], headers = [ "Try.h", "Try-inl.h", ], use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [ ":exception_wrapper", ":likely", ":memory", ":portability", ":unit", ":utility", "//folly/functional:invoke", "//folly/lang:exception", ], ) fb_dirsync_cpp_library( name = "thread_cached_int", headers = ["ThreadCachedInt.h"], use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [ ":likely", ":thread_local", ], ) fb_dirsync_cpp_library( name = "cancellation_token", srcs = ["CancellationToken.cpp"], headers = [ "CancellationToken.h", "CancellationToken-inl.h", ], use_raw_headers = True, xplat_impl = folly_xplat_library, deps = [ ":optional", "//folly:scope_guard", "//folly/lang:new", "//folly/portability:memory", "//folly/synchronization/detail:sleeper", ], exported_deps = [ "fbsource//third-party/glog:glog", ":cpp_attributes", ":function", ":operation_cancelled", ], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "stats_bucketed_time_series", exported_deps = ["//xplat/folly/stats:bucketed_time_series"], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "stats_histogram", exported_deps = ["//xplat/folly/stats:histogram"], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "stats_streaming_stats", exported_deps = ["//xplat/folly/stats:streaming_stats"], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "gen_base", apple_sdks = DEFAULT_APPLE_SDKS, exported_deps = ["//xplat/folly/gen:base"], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "gen_file", exported_deps = ["//xplat/folly/gen:file"], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "gen_string", exported_deps = ["//xplat/folly/gen:string"], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "gen_core", exported_deps = ["//xplat/folly/gen:core"], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "io_global_shutdown_socket_set", exported_deps = [ "//xplat/folly/io:global_shutdown_socket_set", ], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "experimental_function_scheduler", exported_deps = [ "//xplat/folly/experimental:function_scheduler", ], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "hash_spooky_hash_v1", exported_deps = ["//xplat/folly/hash:spooky_hash_v1"], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "hash_traits", exported_deps = ["//xplat/folly/hash:traits"], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "hash_murmur_hash", exported_deps = ["//xplat/folly/hash:murmur_hash"], ) fb_dirsync_cpp_library( name = "poly_exception", headers = ["PolyException.h"], use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [ "//folly:c_portability", ], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "hash_farm_hash", exported_deps = ["//xplat/folly/hash:farm_hash"], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "logging_init_weak", exported_deps = [ "//xplat/folly/logging:init_weak", ], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "futures_portability", exported_deps = ["//xplat/folly/futures:portability"], ) non_fbcode_target( _kind = folly_xplat_library, name = "clock_gettime_wrappers", srcs = [ "ClockGettimeWrappers.cpp", ], cxx_deps = ["//third-party/toolchains:rt"], raw_headers = [ "ClockGettimeWrappers.h", ], deps = [ ":c_portability", ":likely", "//xplat/folly/portability:time", "//xplat/third-party/linker_lib:dl", ], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "hash_spooky_hash_v2", exported_deps = ["//xplat/folly/hash:spooky_hash_v2"], ) fb_dirsync_cpp_library( name = "concurrent_bit_set", headers = ["ConcurrentBitSet.h"], use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [":portability"], ) fb_dirsync_cpp_library( name = "padded", headers = ["Padded.h"], use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [ ":portability", ":traits", "//folly/functional:invoke", "//folly/portability:sys_types", ], exported_external_deps = [ "boost", ], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "gen_istream", exported_deps = ["//xplat/folly/gen:istream"], ) fb_dirsync_cpp_library( name = "replaceable", headers = [ "Replaceable.h", ], use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [ ":portability", ":traits", ":utility", ], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "logging_rate_limiter", exported_deps = [ "//xplat/folly/logging:rate_limiter", ], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "logging_glog_bridge", exported_deps = [ "//xplat/folly/logging:glog_bridge", ], ) fb_dirsync_cpp_library( name = "rw_spin_lock", headers = ["RWSpinLock.h"], use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = ["//folly/synchronization:rw_spin_lock"], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "stats_detail_sliding_window", exported_deps = ["//xplat/folly/stats/detail:sliding_window"], ) fb_dirsync_cpp_library( name = "fingerprint", srcs = ["Fingerprint.cpp"], headers = ["Fingerprint.h"], use_raw_headers = True, xplat_impl = folly_xplat_library, deps = [ ":portability", "//folly/detail:fingerprint_polynomial", ], exported_deps = [ ":range", ], ) fb_dirsync_cpp_library( name = "micro_spin_lock", headers = ["MicroSpinLock.h"], use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = ["//folly/synchronization:micro_spin_lock"], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "stats_tdigest", exported_deps = ["//xplat/folly/stats:tdigest"], ) fb_dirsync_cpp_library( name = "discriminated_ptr", headers = ["DiscriminatedPtr.h"], use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [ "fbsource//third-party/glog:glog", ":likely", ":portability", "//folly/detail:discriminated_ptr_detail", ], ) fb_dirsync_cpp_library( name = "group_varint", srcs = [ "GroupVarint.cpp", ], headers = ["GroupVarint.h"], use_raw_headers = True, xplat_impl = folly_xplat_library, deps = [ "//folly/container:array", ], exported_deps = [ "fbsource//third-party/glog:glog", ":portability", ":range", "//folly/detail:group_varint_detail", "//folly/lang:bits", "//folly/portability:builtins", ], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "logging_log_name", exported_deps = [ "//xplat/folly/logging:log_name", ], ) fb_dirsync_cpp_library( name = "lazy", headers = ["Lazy.h"], use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [ ":optional", "//folly/functional:invoke", ], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "hash_hash", exported_deps = ["//xplat/folly/hash:hash"], ) fb_dirsync_cpp_library( name = "utf8_string", headers = [ "UTF8String.h", ], use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [ ":range", ], exported_external_deps = [ ("boost", None, "boost_regex"), ], ) non_fbcode_target( _kind = folly_xplat_library, name = "utf8_string_icu", apple_sdks = (IOS, MACOSX), platforms = (CXX, ANDROID, APPLE), raw_headers = [ "UTF8String.h", ], deps = [ ":range", "//third-party/boost:boost_regex_icu", ], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "experimental_threaded_repeating_function_runner", exported_deps = [ "//xplat/folly/experimental:threaded_repeating_function_runner", ], ) fb_dirsync_cpp_library( name = "poly", headers = [ "Poly.h", "Poly-inl.h", ], use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [ ":c_portability", ":cpp_attributes", ":poly_exception", ":traits", "//folly/detail:poly_detail", "//folly/detail:typelist", "//folly/lang:assume", ], ) fb_dirsync_cpp_library( name = "unicode", srcs = ["Unicode.cpp"], headers = ["Unicode.h"], use_raw_headers = True, xplat_impl = folly_xplat_library, deps = [ ":conv", ], exported_deps = [ "//folly/lang:exception", ], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "logging_log_level", exported_deps = [ "//xplat/folly/logging:log_level", ], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "init_init", exported_deps = ["//xplat/folly/init:init"], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "init_phase", exported_deps = ["//xplat/folly/init:phase"], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "logging_init", exported_deps = [ "//xplat/folly/logging:init", ], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "logging_logging", exported_deps = [ "//xplat/folly/logging:logging", ], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "logging_log_handler", exported_deps = [ "//xplat/folly/logging:log_handler", ], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "io_typed_io_buf", exported_deps = [ "//xplat/folly/io:typed_io_buf", ], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "stats_multi_level_time_series", exported_deps = ["//xplat/folly/stats:multi_level_time_series"], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "experimental_symbolizer_detail_debug", exported_deps = ["//xplat/folly/experimental/symbolizer/detail:debug"], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "experimental_symbolizer_line_reader", exported_deps = ["//xplat/folly/experimental/symbolizer:line_reader"], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "stats_timeseries_histogram", exported_deps = ["//xplat/folly/stats:timeseries_histogram"], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "experimental_symbolizer_elf", exported_deps = ["//xplat/folly/experimental/symbolizer:elf"], ) fb_dirsync_cpp_library( name = "token_bucket", headers = ["TokenBucket.h"], use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [ ":constexpr_math", ":likely", ":optional", "//folly/concurrency:cache_locality", ], ) fb_dirsync_cpp_library( name = "producer_consumer_queue", headers = ["ProducerConsumerQueue.h"], use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [ "//folly/concurrency:cache_locality", ], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "experimental_symbolizer_elf_cache", exported_deps = ["//xplat/folly/experimental/symbolizer:elf_cache"], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "experimental_symbolizer_dwarf", exported_deps = ["//xplat/folly/experimental/symbolizer:dwarf"], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "experimental_event_count", exported_deps = [ "//xplat/folly/experimental:event_count", ], ) fb_dirsync_cpp_library( name = "packed_sync_ptr", headers = ["PackedSyncPtr.h"], use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [ "fbsource//third-party/glog:glog", ":portability", "//folly/synchronization:small_locks", ], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "gen_combine", exported_deps = ["//xplat/folly/gen:combine"], ) fb_dirsync_cpp_library( name = "synchronized_ptr", headers = ["SynchronizedPtr.h"], use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [":synchronized"], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "gen_parallel", exported_deps = ["//xplat/folly/gen:parallel"], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "stats_digest_builder", exported_deps = ["//xplat/folly/stats:digest_builder"], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "logging_file_handler_factory", exported_deps = [ "//xplat/folly/logging:file_handler_factory", ], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "logging_example_lib", exported_deps = [ "//xplat/folly/logging/example:lib", ], ) fb_dirsync_cpp_library( name = "mpmc_pipeline", headers = ["MPMCPipeline.h"], use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [ "fbsource//third-party/glog:glog", ":portability", "//folly/detail:mpmc_pipeline_detail", ], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "gen_parallel_map", exported_deps = ["//xplat/folly/gen:parallel_map"], ) fb_dirsync_cpp_library( name = "concurrent_lazy", headers = ["ConcurrentLazy.h"], use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [ "//folly/functional:invoke", "//folly/synchronization:delayed_init", ], ) fb_dirsync_cpp_library( name = "concurrent_skip_list", headers = [ "ConcurrentSkipList.h", "ConcurrentSkipList-inl.h", ], use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [ "fbsource//third-party/glog:glog", ":constexpr_math", ":likely", ":memory", ":thread_local", "//folly/detail:iterators", "//folly/synchronization:micro_spin_lock", ], exported_external_deps = [ ("boost", None, "boost_random"), ], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "stats_detail_buffered_stat", exported_deps = ["//xplat/folly/stats/detail:buffered_stat"], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "experimental_settings_settings", exported_deps = [ "//xplat/folly/experimental/settings:settings", ], ) fb_dirsync_cpp_library( name = "default_keep_alive_executor", headers = [ "DefaultKeepAliveExecutor.h", ], use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [ "fbsource//third-party/glog:glog", ":executor", "//folly/executors:sequenced_executor", "//folly/synchronization:baton", ], ) fb_dirsync_cpp_library( name = "virtual_executor", headers = [ "VirtualExecutor.h", ], use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [ "//folly/executors:virtual_executor", ], ) fb_dirsync_cpp_library( name = "atomic_hash_array", headers = [ "AtomicHashArray.h", "AtomicHashArray-inl.h", ], use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [ ":thread_cached_int", ":utility", "//folly/detail:atomic_hash_utils", "//folly/detail:iterators", "//folly/hash:hash", "//folly/lang:bits", "//folly/lang:exception", ], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "experimental_flat_combining_priority_queue", exported_deps = [ "//xplat/folly/experimental:flat_combining_priority_queue", ], ) fb_dirsync_cpp_library( name = "atomic_unordered_map", headers = ["AtomicUnorderedMap.h"], use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [ ":conv", ":likely", ":random", ":scope_guard", ":traits", "//folly/detail:atomic_unordered_map_utils", "//folly/lang:bits", "//folly/portability:sys_mman", "//folly/portability:unistd", ], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "futures_detail_core", exported_deps = ["//xplat/folly/futures/detail:core"], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "stats_quantile_estimator", exported_deps = ["//xplat/folly/stats:quantile_estimator"], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "io_record_io", exported_deps = [ "//xplat/folly/io:record_io", ], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "futures_core", exported_deps = ["//xplat/folly/futures:core"], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "futures_manual_timekeeper", exported_deps = ["//xplat/folly/futures:manual_timekeeper"], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "futures_barrier", exported_deps = ["//xplat/folly/futures:barrier"], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "futures_shared_promise", exported_deps = ["//xplat/folly/futures:shared_promise"], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "futures_future_splitter", exported_deps = ["//xplat/folly/futures:future_splitter"], ) non_fbcode_target( # @shim _kind = folly_xplat_library, name = "futures_futures", exported_deps = ["//xplat/folly/futures:futures"], ) # This adds the symbol __folly_memcpy through FollyMemcpy.h, but does not # replace the default memcpy. # NOTE: for mobile, this is a no-op. we have this here for symmetry with fbcode non_fbcode_target( _kind = folly_xplat_library, name = "memcpy", srcs = [ "FollyMemcpy.cpp", "memcpy.S", ], fbcode_compiler_flags_override = [], raw_headers = ["FollyMemcpy.h"], ) fb_dirsync_cpp_library( name = "base64", headers = ["base64.h"], use_raw_headers = True, xplat_impl = folly_xplat_library, exported_deps = [ ":c_portability", ":portability", "//folly/detail/base64_detail:base64_api", "//folly/detail/base64_detail:base64_common", "//folly/lang:exception", "//folly/memory:uninitialized_memory_hacks", ], ) fb_dirsync_cpp_library( name = "maybe_managed_ptr", headers = ["MaybeManagedPtr.h"], use_raw_headers = True, xplat_impl = folly_xplat_library, deps = [], exported_deps = [], ) # !!!! fbcode/folly/TARGETS was merged into this file, see https://fburl.com/workplace/xl8l9yuo for more info !!!! ###################################################################### # Libraries fbcode_target( _kind = cpp_library, name = "atomic_hash_map", headers = [ "AtomicHashMap.h", "AtomicHashMap-inl.h", ], exported_deps = [ ":atomic_hash_array", ":c_portability", ":likely", ":thread_cached_int", "//folly/container:foreach", "//folly/detail:atomic_hash_utils", "//folly/detail:iterators", "//folly/hash:hash", ], ) fbcode_target( _kind = cpp_library, name = "atomic_linked_list", headers = [ "AtomicIntrusiveLinkedList.h", "AtomicLinkedList.h", ], exported_deps = [":memory"], ) fbcode_target( _kind = cpp_library, name = "clock_gettime_wrappers", srcs = ["ClockGettimeWrappers.cpp"], headers = ["ClockGettimeWrappers.h"], deps = [ ":likely", ], exported_deps = [ "//folly/portability:time", ], external_deps = [ ("glibc", None, "dl"), ], exported_external_deps = [ ("glibc", None, "rt"), ], ) fbcode_target( _kind = cpp_library, name = "config", # @fb-only[end= ]: headers = ["folly-config.h"], propagated_pp_flags = select({ "DEFAULT": [], # The `liblz4.so` in the default conda channel doesn't *publicly* export # symbols used for fast reset support (although they are availabel as # "internal" symbols and would link statically). "ovr_config//distro:conda": [ "-DFOLLY_USE_LZ4_FAST_RESET=0", ], }), headers = ["//:folly-config.h"], # @oss-only labels = ["oss_dependency"], # @oss-only ) fbcode_target( _kind = cpp_library, name = "conv", srcs = ["Conv.cpp"], headers = ["Conv.h"], undefined_symbols = True, # TODO(T23121628): fix deps and remove deps = [ "fbsource//third-party/fast_float:fast_float", "//folly/lang:safe_assert", ], exported_deps = [ ":c_portability", ":demangle", ":expected", ":fbstring", ":likely", ":portability", ":range", ":traits", ":unit", ":utility", "//folly/lang:exception", "//folly/lang:pretty", "//folly/lang:to_ascii", "//folly/portability:math", ], exported_external_deps = [ "double_conversion", ], ) fbcode_target( _kind = cpp_library, name = "dynamic", headers = [ "DynamicConverter.h", "dynamic.h", "dynamic-inl.h", "json.h", ], exported_deps = [ "//folly/json:dynamic", ], ) fbcode_target( _kind = cpp_library, name = "exception_string", srcs = ["ExceptionString.cpp"], headers = ["ExceptionString.h"], deps = [ ":demangle", "//folly/lang:exception", "//folly/lang:type_info", ], exported_deps = [ ":fbstring", ], ) fbcode_target( _kind = cpp_library, name = "exception_wrapper", srcs = ["ExceptionWrapper.cpp"], headers = [ "ExceptionWrapper.h", "ExceptionWrapper-inl.h", ], undefined_symbols = True, # TODO(T23121628): fix deps and remove exported_deps = [ ":c_portability", ":cpp_attributes", ":demangle", ":exception_string", ":fbstring", ":portability", ":traits", ":utility", "//folly/functional:traits", "//folly/lang:assume", "//folly/lang:exception", ], ) fbcode_target( _kind = cpp_library, name = "executor", srcs = ["Executor.cpp"], headers = [ "Executor.h", ], deps = [ "fbsource//third-party/glog:glog", ":exception_string", ":portability", ], exported_deps = [ ":function", ":optional", ":range", ":utility", "//folly/lang:exception", ], ) fbcode_target( _kind = cpp_library, name = "fmt_utility", srcs = [ "FmtUtility.cpp", ], headers = [ "FmtUtility.h", ], deps = [ ":range", ":string", "//folly/ssl:openssl_hash", ], exported_deps = [ "fbsource//third-party/fmt:fmt", ":cpp_attributes", ], ) fbcode_target( _kind = cpp_library, name = "format_traits", headers = ["FormatTraits.h"], ) fbcode_target( _kind = cpp_library, name = "indestructible", headers = ["Indestructible.h"], exported_deps = [ ":traits", ":utility", ], ) fbcode_target( _kind = cpp_library, name = "indexed_mem_pool", headers = ["IndexedMemPool.h"], exported_deps = [ ":portability", "//folly/concurrency:cache_locality", "//folly/portability:sys_mman", "//folly/portability:unistd", "//folly/synchronization:atomic_struct", ], ) fb_dirsync_cpp_library( # @shim name = "json", xplat_impl = folly_xplat_library, exported_deps = [ ":dynamic", ], ) fb_dirsync_cpp_library( name = "json_pointer", headers = [ "json_pointer.h", ], xplat_impl = folly_xplat_library, exported_deps = [ "//folly/json:json_pointer", ], ) fb_dirsync_cpp_library( name = "json_patch", headers = [ "json_patch.h", ], exported_deps = [ "//folly/json:json_patch", ], ) fbcode_target( _kind = cpp_library, name = "glog", headers = ["GLog.h"], exported_deps = [ "fbsource//third-party/glog:glog", ":likely", ], ) fbcode_target( _kind = cpp_library, name = "memset-impl", srcs = [ "FollyMemset.cpp", ] + select({ "DEFAULT": [], "ovr_config//cpu:x86_64": [ "memset.S", ], "ovr_config//os:linux-arm64": [ "memset_select_aarch64.cpp", ], }), auto_headers = AutoHeaders.NONE, headers = [], modular_headers = False, preprocessor_flags = select({ "DEFAULT": [], "ovr_config//cpu:x86_64": [ "-mavx2", ], }), exported_deps = select({ "DEFAULT": [], "ovr_config//os:linux-arm64": [ "//folly/external/aor:memset_aarch64", # @manual ], }), ) # This exports the symbol __folly_memset to C++ via a header. fbcode_target( _kind = cpp_library, name = "memset", headers = ["FollyMemset.h"], exported_deps = [ ":memset-impl", # @manual ], ) # This overrides the libc memset with __folly_memset. fbcode_target( _kind = cpp_library, name = "memset-use", srcs = [ "FollyMemset.cpp", ] + select({ "DEFAULT": [], "ovr_config//cpu:x86_64": [ "memset.S", ], "ovr_config//os:linux-arm64": [ "memset_select_aarch64.cpp", ], }), auto_headers = AutoHeaders.NONE, headers = [], link_whole = True, # Set link_whole to force linker to use __folly_memset modular_headers = False, preprocessor_flags = select({ "DEFAULT": [], "ovr_config//cpu:arm64": [ "-DFOLLY_MEMSET_IS_MEMSET", ], "ovr_config//cpu:x86_64": [ "-DFOLLY_MEMSET_IS_MEMSET", "-mavx2", ], }), exported_deps = select({ "DEFAULT": [], "ovr_config//os:linux-arm64": [ "//folly/external/aor:memset_aarch64-use", # @manual ], }), ) # This adds the symbol __folly_memcpy but does not replace the default memcpy. fbcode_target( _kind = cpp_library, name = "memcpy-impl", srcs = [ "FollyMemcpy.cpp", ] + select({ "ovr_config//cpu:x86_64": [ "memcpy.S", ], "ovr_config//os:linux-arm64": [ "memcpy_select_aarch64.cpp", ], }), auto_headers = AutoHeaders.NONE, headers = [], modular_headers = False, preprocessor_flags = select({ "DEFAULT": [], "ovr_config//cpu:x86_64": [ "-mavx2", ], }), exported_deps = select({ "DEFAULT": [], "ovr_config//os:linux-arm64": [ "//folly/external/aor:memcpy_aarch64", # @manual ], }), ) # This exports the symbol __folly_memcpy to C++ via a header. fbcode_target( _kind = cpp_library, name = "memcpy", headers = ["FollyMemcpy.h"], exported_deps = [ ":memcpy-impl", # @manual ], ) # This overrides the libc memcpy with __folly_memcpy. fbcode_target( _kind = cpp_library, name = "memcpy-use", srcs = [ "FollyMemcpy.cpp", ] + select({ "DEFAULT": [], "ovr_config//cpu:x86_64": [ "memcpy.S", ], "ovr_config//os:linux-arm64": [ "memcpy_select_aarch64.cpp", ], }), auto_headers = AutoHeaders.NONE, headers = [], link_whole = True, # Set link_whole to force linker to use __folly_memcpy modular_headers = False, preprocessor_flags = select({ "DEFAULT": [], "ovr_config//cpu:arm64": [ "-DFOLLY_MEMCPY_IS_MEMCPY", ], "ovr_config//cpu:x86_64": [ "-DFOLLY_MEMCPY_IS_MEMCPY", "-mavx2", "-march=haswell", ], }), exported_deps = select({ "DEFAULT": [], "ovr_config//os:linux-arm64": [ "//folly/external/aor:memcpy_aarch64-use", # @manual ], }), ) fbcode_target( _kind = cpp_library, name = "micro_lock", srcs = ["MicroLock.cpp"], headers = ["MicroLock.h"], deps = [ "//folly/portability:asm", ], exported_deps = [ ":optional", ":portability", ":utility", "//folly/synchronization:atomic_notification", "//folly/synchronization:atomic_ref", ], ) fbcode_target( _kind = cpp_library, name = "network_address", srcs = [ "IPAddress.cpp", "IPAddressV4.cpp", "IPAddressV6.cpp", "MacAddress.cpp", "SocketAddress.cpp", ], headers = [ "IPAddress.h", "IPAddressException.h", "IPAddressV4.h", "IPAddressV6.h", "MacAddress.h", "SocketAddress.h", ], deps = [ "fbsource//third-party/fmt:fmt", ":exception", ":format", ":scope_guard", ":small_vector", ":string", "//folly/detail:ip_address_source", "//folly/net:net_ops", ], exported_deps = [ ":c_portability", ":constexpr_math", ":conv", ":expected", ":fbstring", ":optional", ":portability", ":range", ":unit", "//folly/detail:ip_address", "//folly/hash:hash", "//folly/lang:bits", "//folly/lang:exception", "//folly/net:network_socket", "//folly/portability:config", "//folly/portability:sockets", ], external_deps = [ "boost", ], ) fbcode_target( _kind = cpp_library, name = "overload", headers = ["Overload.h"], exported_deps = [ ":portability", ":traits", "//folly/functional:invoke", ], ) fbcode_target( _kind = cpp_library, name = "shared_mutex", srcs = ["SharedMutex.cpp"], headers = ["SharedMutex.h"], supports_python_dlopen = True, deps = [ ":indestructible", "//folly/lang:exception", "//folly/portability:sys_resource", ], exported_deps = [ ":c_portability", ":cpp_attributes", ":likely", "//folly/chrono:hardware", "//folly/concurrency:cache_locality", "//folly/detail:futex", "//folly/portability:asm", "//folly/synchronization:lock", "//folly/synchronization:relaxed_atomic", "//folly/synchronization:sanitize_thread", "//folly/system:thread_id", ], ) fbcode_target( _kind = cpp_library, name = "singleton", srcs = [ "Singleton.cpp", ], headers = [ "Singleton.h", "Singleton-inl.h", ], deps = [ "fbsource//third-party/fmt:fmt", ":demangle", ":scope_guard", "//folly/experimental/symbolizer:symbolizer", "//folly/lang:safe_assert", "//folly/portability:config", "//folly/portability:fmt_compile", "//folly/system:at_fork", ], exported_deps = [ "fbsource//third-party/glog:glog", ":cancellation_token", ":exception", ":executor", ":memory", ":synchronized", "//folly/concurrency:core_cached_shared_ptr", "//folly/concurrency/memory:read_mostly_shared_ptr", "//folly/detail:singleton", "//folly/detail:static_singleton_manager", "//folly/hash:hash", "//folly/lang:exception", "//folly/memory:sanitize_leak", "//folly/synchronization:baton", ], external_deps = [ ("glibc", None, "dl"), ("glibc", None, "rt"), ], ) fbcode_target( _kind = cpp_library, name = "spin_lock", headers = [ "SpinLock.h", ], exported_deps = [ ":portability", "//folly/synchronization:small_locks", ], ) fbcode_target( _kind = cpp_library, name = "subprocess", srcs = ["Subprocess.cpp"], headers = ["Subprocess.h"], deps = [ ":conv", ":scope_guard", ":string", "//folly/lang:assume", "//folly/logging:logging", "//folly/portability:dirent", "//folly/portability:fcntl", "//folly/portability:sockets", "//folly/portability:stdlib", "//folly/portability:sys_syscall", "//folly/portability:unistd", "//folly/system:at_fork", "//folly/system:shell", ], exported_deps = [ ":exception", ":file", ":file_util", ":function", ":map_util", ":optional", ":portability", ":range", "//folly/container:span", "//folly/gen:string", "//folly/io:iobuf", "//folly/portability:sys_resource", ], external_deps = [ ("boost", None, "boost_range"), ("glibc", None, "dl"), ], exported_external_deps = [ "boost", ("boost", None, "boost_container"), ], ) fbcode_target( _kind = cpp_library, name = "timeout_queue", srcs = ["TimeoutQueue.cpp"], headers = ["TimeoutQueue.h"], exported_external_deps = [ "boost", ("boost", None, "boost_multi_index"), ], ) fbcode_target( _kind = cpp_library, name = "uri", srcs = [ "Uri.cpp", ], headers = [ "Uri.h", "Uri-inl.h", ], exported_deps = [ ":conv", ":expected", ":string", "//folly/hash:hash", ], external_deps = [ ("boost", None, "boost_regex"), ], ) # For things that would go in c++ stdlib : fb_native.export_file( name = "src-tree", src = ".", mode = "reference", visibility = ["PUBLIC"], ) fb_native.export_file( name = "support/gdb.py", src = "support/gdb.py", visibility = ["PUBLIC"], ) fb_native.export_file( name = "fibers/scripts/gdb.py", src = "fibers/scripts/gdb.py", visibility = ["PUBLIC"], ) fb_native.export_file( name = "coro/scripts/co_bt.py", src = "coro/scripts/co_bt.py", visibility = ["PUBLIC"], ) ================================================ FILE: folly/BUILD_MODE.bzl ================================================ """ build mode definitions for folly """ load("@fbcode//:BUILD_MODE.bzl", get_parent_modes = "get_empty_modes") load("@fbcode_macros//build_defs:create_build_mode.bzl", "extend_build_modes") _extra_cflags = [ "-Wsign-compare", "-Wunused-parameter", ] _extra_cxxflags = [ ] _extra_clang_flags = [ "-Wconditional-uninitialized", "-Wconstant-conversion", "-Wdeprecated-declarations", "-Wextra", "-Wextra-semi", "-Wexceptions", "-Wfloat-conversion", "-Wgnu-conditional-omitted-operand", "-Wheader-hygiene", "-Wimplicit-fallthrough", "-Wmismatched-tags", "-Wmissing-braces", "-Wno-error=missing-noreturn", # Disabled for LLVM-21 migration; tracked in T251075049 "-Wshadow", "-Wshift-sign-overflow", "-Wsometimes-uninitialized", "-Wuninitialized", "-Wuninitialized-const-reference", "-Wunused-const-variable", "-Wunused-exception-parameter", "-Wunused-function", "-Wunused-lambda-capture", "-Wunused-value", "-Wunused-variable", "-Wswitch-enum", ] _extra_gcc_flags = [ "-Wdeprecated-declarations", "-Wmaybe-uninitialized", "-Wmissing-braces", "-Wshadow", "-Wuninitialized", "-Wunused-but-set-variable", ] _extra_asan_options = { "detect_leaks": "1", "detect_odr_violation": "2", "handle_segv": "1", } _modes = extend_build_modes( get_parent_modes(), asan_options = _extra_asan_options, c_flags = _extra_cflags, clang_flags = _extra_clang_flags, cxx_flags = _extra_cxxflags, gcc_flags = _extra_gcc_flags, ) def get_modes(): """ Return modes for this file """ return _modes ================================================ FILE: folly/Benchmark.cpp ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // This needs to be at the end because some versions end up including // Windows.h without defining NOMINMAX, which breaks uses // of `std::numeric_limits::max()`. We explicitly define NOMINMAX here // explicitly instead. #define NOMINMAX 1 #include using namespace std; // === For benchmark+test combos that gate on `FLAGS_benchmark` === FOLLY_GFLAGS_DEFINE_bool(benchmark, false, "Run benchmarks."); // === Mode === FOLLY_GFLAGS_DEFINE_string( bm_mode, "best-of", "'best-of' (default) or 'adaptive'. " "Adaptive interleaves samples across all benchmarks to cancel " "correlated noise, and runs until the target percentile estimate " "is both stable and precise. " "Set --bm_max_secs=20-30 for reliable results. " "See BenchmarkAdaptive.md."); // === Benchmark selection === FOLLY_GFLAGS_DEFINE_string( bm_regex, "", "Only run benchmarks whose names match this regex."); FOLLY_GFLAGS_DEFINE_string( bm_file_regex, "", "Only run benchmarks whose source filenames match this regex."); FOLLY_GFLAGS_DEFINE_bool( bm_list, false, "Print benchmark names and exit without running."); // === Adaptive: convergence target === FOLLY_GFLAGS_DEFINE_double( bm_target_percentile, 33.3, "Adaptive: which percentile of per-slice iteration timings to report. " "Default 33.3 approximates the median of good runs, rejecting transient " "slowdowns. Higher values (e.g. 90) capture tail behavior. " "For comparison, best-of mode always reports the minimum (p0)."); FOLLY_GFLAGS_DEFINE_double( bm_target_precision_pct, 0.4, "Adaptive: measurement precision target. A benchmark converges " "when the 95% confidence interval around the target percentile " "is narrower than this percentage of the estimate. E.g. 0.4 " "means the CI for a 100ns benchmark must be under 0.4ns wide. " "Tighter values (e.g. 0.1) need longer --bm_max_secs or a " "quieter system."); // === How long to measure === FOLLY_GFLAGS_DEFINE_int32( bm_max_secs, 1, "Maximum seconds per benchmark. Adaptive: set to 20-30 for " "robust results on noisy systems. Default 1s is for quick " "iteration."); FOLLY_GFLAGS_DEFINE_double( bm_min_secs, 0.1, "Adaptive: minimum seconds per benchmark before it can converge. " "Ensures enough elapsed time to observe system jitter."); FOLLY_GFLAGS_DEFINE_uint32( bm_min_samples, 20, "Adaptive: minimum samples per benchmark before it can converge. " "Ensures enough data points for stable percentile and CI " "estimates."); // Best-of mode only: FOLLY_GFLAGS_DEFINE_int32( bm_min_iters, 1, "Best-of: minimum iterations per measurement. The inner loop " "doubles from this until duration exceeds --bm_slice_usec."); FOLLY_GFLAGS_DEFINE_int64( bm_max_iters, 1 << 30, "Best-of: maximum iterations per measurement."); FOLLY_GFLAGS_DEFINE_uint32( bm_max_trials, 1000, "Best-of: maximum number of measurement trials (epochs). " "The best (minimum-time) trial is reported."); // === Measurement slice duration === FOLLY_GFLAGS_DEFINE_int64( bm_slice_usec, 1000, "Duration in microseconds of each contiguous measurement slice. " "Values below 1000 risk harness interference affecting results."); FOLLY_GFLAGS_DEFINE_int64( bm_min_usec, 1000, "Deprecated: use --bm_slice_usec (same meaning, same units)."); // === Instrumentation & diagnostics (mode-agnostic) === FOLLY_GFLAGS_DEFINE_bool( bm_warm_up_iteration, false, "Run one iteration of each benchmark before measuring, to warm " "caches and trigger lazy initialization. Automatically enabled " "when --bm_perf_args is set."); #if FOLLY_PERF_IS_SUPPORTED FOLLY_GFLAGS_DEFINE_string( bm_perf_args, "", "Attach `perf` during measurement (skips the first iteration " "for setup). Example: --bm_perf_args=\"record -g\""); #endif FOLLY_GFLAGS_DEFINE_bool( bm_verbose, false, "Log more diagnostic details: convergence progress and baseline " "stats (adaptive), measurement phases (best-of)."); FOLLY_GFLAGS_DEFINE_bool( bm_quiet, false, "Silence non-actionable diagnostics."); // === Output & comparison (mode-agnostic) === FOLLY_GFLAGS_DEFINE_bool(json, false, "Print results in JSON format."); FOLLY_GFLAGS_DEFINE_string( bm_json_verbose, "", "Write verbose JSON to this file (for BenchmarkCompare or " "--bm_relative_to). Written regardless of --json."); FOLLY_GFLAGS_DEFINE_string( bm_relative_to, "", "Print results relative to a previous JSON dump " "(produced by --bm_json_verbose)."); FOLLY_GFLAGS_DEFINE_uint32( bm_result_width_chars, 76, "Width of the results table in characters."); // === Profiling with constant iterations (best-of mode only) === FOLLY_GFLAGS_DEFINE_bool( bm_profile, false, "Best-of: run each benchmark with a fixed iteration count " "(--bm_profile_iters) for external profiling. Results will be " "jittery -- not for measurement."); FOLLY_GFLAGS_DEFINE_int64( bm_profile_iters, 1000, "Best-of: number of iterations when --bm_profile is set."); // === Avoid === FOLLY_GFLAGS_DEFINE_bool( bm_estimate_time, false, "Best-of: alternative measurement strategy that reports the " "geometric mean of p25-p75 latencies. Slower than best-of mode, " "with unclear benefits. " "Prefer --bm_mode=adaptive for noise-robust measurements."); namespace folly { namespace detail { BenchmarkingState& globalBenchmarkState() { static detail::BenchmarkingState state; return state; } } // namespace detail using BenchmarkFun = std::function; #define FB_FOLLY_GLOBAL_BENCHMARK_BASELINE fbFollyGlobalBenchmarkBaseline #define FB_FOLLY_GLOBAL_BENCHMARK_SUSPENDER_BASELINE \ fbFollyGlobalBenchmarkSuspenderBaseline #define FB_STRINGIZE_X2(x) FOLLY_PP_STRINGIZE(x) constexpr const char kGlobalBenchmarkBaseline[] = FB_STRINGIZE_X2(FB_FOLLY_GLOBAL_BENCHMARK_BASELINE); constexpr const char kGlobalBenchmarkSuspenderBaseline[] = FB_STRINGIZE_X2(FB_FOLLY_GLOBAL_BENCHMARK_SUSPENDER_BASELINE); // Add the global baseline BENCHMARK(FB_FOLLY_GLOBAL_BENCHMARK_BASELINE) { #ifdef _MSC_VER _ReadWriteBarrier(); #else asm volatile(""); #endif } // Add the suspender overhead baseline BENCHMARK(FB_FOLLY_GLOBAL_BENCHMARK_SUSPENDER_BASELINE) { BENCHMARK_SUSPEND {} } #undef FB_STRINGIZE_X2 #undef FB_FOLLY_GLOBAL_BENCHMARK_BASELINE static std::pair runBenchmarkGetNSPerIteration( const BenchmarkFun& fun, const double globalBaseline, int64_t sliceUsec) { using std::chrono::duration_cast; using std::chrono::high_resolution_clock; using std::chrono::microseconds; using std::chrono::nanoseconds; using std::chrono::seconds; // They key here is accuracy; too low numbers means the accuracy was // coarse. We up the ante until we get to at least minNanoseconds // timings. static_assert( std::is_same::value, "High resolution clock must be nanosecond resolution."); // We choose a minimum minimum (sic) of 100,000 nanoseconds, but if // the clock resolution is worse than that, it will be larger. In // essence we're aiming at making the quantization noise 0.01%. const auto minNanoseconds = std::max(nanoseconds(100000), microseconds(sliceUsec)); // We establish a total time budget as we don't want a measurement // to take too long. This will curtail the number of actual trials. const auto timeBudget = seconds(FLAGS_bm_max_secs); auto global = high_resolution_clock::now(); std::vector> trialResults( FLAGS_bm_max_trials); size_t actualTrials = 0; // We do measurements in several trials (epochs) and take the minimum, to // account for jitter. for (; actualTrials < FLAGS_bm_max_trials; ++actualTrials) { const auto maxIters = uint32_t(FLAGS_bm_max_iters); for (auto n = uint32_t(FLAGS_bm_min_iters); n < maxIters; n *= 2) { detail::TimeIterData timeIterData = fun(static_cast(n)); if (timeIterData.duration < minNanoseconds) { continue; } // We got an accurate enough timing, done. But only save if // smaller than the current result. auto nsecs = duration_cast(timeIterData.duration); trialResults[actualTrials] = std::make_pair( max(0.0, double(nsecs.count()) / timeIterData.niter - globalBaseline), std::move(timeIterData.userCounters)); // Done with the current trial, we got a meaningful timing. break; } auto now = high_resolution_clock::now(); if (now - global >= timeBudget) { // No more time budget available. ++actualTrials; break; } } // Current state of the art: get the minimum. After some // experimentation, it seems taking the minimum is the best. auto iter = min_element( trialResults.begin(), trialResults.begin() + actualTrials, [](const auto& a, const auto& b) { return a.first < b.first; }); // If the benchmark was basically drowned in baseline noise, it's // possible it became negative. return std::make_pair(max(0.0, iter->first), iter->second); } static std::pair runBenchmarkGetNSPerIterationEstimate( const BenchmarkFun& fun, const double globalBaseline) { using std::chrono::duration_cast; using std::chrono::high_resolution_clock; using std::chrono::microseconds; using std::chrono::nanoseconds; using std::chrono::seconds; using TrialResultType = std::pair; // They key here is accuracy; too low numbers means the accuracy was // coarse. We up the ante until we get to at least minNanoseconds // timings. static_assert( std::is_same::value, "High resolution clock must be nanosecond resolution."); // Estimate single iteration running time for 1 sec double estPerIter = 0.0; // Estimated nanosec per iteration auto estStart = high_resolution_clock::now(); const auto estBudget = seconds(1); for (auto n = 1; n < 1000; n *= 2) { detail::TimeIterData timeIterData = fun(static_cast(n)); auto now = high_resolution_clock::now(); auto nsecs = duration_cast(timeIterData.duration); estPerIter = double(nsecs.count() - globalBaseline) / n; if (now - estStart > estBudget) { break; } } // Can't estimate running time, so make it a baseline if (estPerIter <= 0.0) { estPerIter = globalBaseline; } // We do measurements in several trials (epochs) to account for jitter. size_t actualTrials = 0; const unsigned int estimateCount = to_integral(max(1.0, 5e+7 / estPerIter)); std::vector trialResults(FLAGS_bm_max_trials); const auto maxRunTime = seconds(max(5, FLAGS_bm_max_secs)); auto globalStart = high_resolution_clock::now(); // Run benchmark up to trial times with at least 0.5 sec each // Or until we run out of allowed time (max(5, FLAGS_bm_max_secs)) for (size_t tryId = 0; tryId < FLAGS_bm_max_trials; tryId++) { detail::TimeIterData timeIterData = fun(estimateCount); auto nsecs = duration_cast(timeIterData.duration); if (nsecs.count() > globalBaseline) { auto nsecIter = double(nsecs.count() - globalBaseline) / timeIterData.niter; trialResults[actualTrials++] = std::make_pair(nsecIter, std::move(timeIterData.userCounters)); } // Check if we are out of time quota auto now = high_resolution_clock::now(); if (now - globalStart > maxRunTime) { break; } } // Sort results by running time std::sort( trialResults.begin(), trialResults.begin() + actualTrials, [](const TrialResultType& a, const TrialResultType& b) { return a.first < b.first; }); const auto getPercentile = [](size_t count, double p) -> size_t { return static_cast(count * p); }; const size_t trialP25 = getPercentile(actualTrials, 0.25); const size_t trialP75 = getPercentile(actualTrials, 0.75); if (trialP75 - trialP25 == 0) { // Use first trial results if p75 == p25. return std::make_pair(trialResults[0].first, trialResults[0].second); } double geomeanNsec = 0.0; for (size_t tryId = trialP25; tryId < trialP75; tryId++) { geomeanNsec += std::log(trialResults[tryId].first); } geomeanNsec = std::exp(geomeanNsec / (1.0 * (trialP75 - trialP25))); return std::make_pair( geomeanNsec, trialResults[trialP25 + (trialP75 - trialP25) / 2].second); } static std::pair runProfilingGetNSPerIteration( const BenchmarkFun& fun, const double globalBaseline) { using std::chrono::duration_cast; using std::chrono::high_resolution_clock; using std::chrono::nanoseconds; // They key here is accuracy; too low numbers means the accuracy was // coarse. We up the ante until we get to at least minNanoseconds // timings. static_assert( std::is_same::value, "High resolution clock must be nanosecond resolution."); // This is a very simple measurement with a single epoch // and should be used only for profiling purposes detail::TimeIterData timeIterData = fun(FLAGS_bm_profile_iters); auto nsecs = duration_cast(timeIterData.duration); auto nsecIter = double(nsecs.count()) / timeIterData.niter - globalBaseline; return std::make_pair(nsecIter, std::move(timeIterData.userCounters)); } struct ScaleInfo { double boundary; const char* suffix; }; static const ScaleInfo kTimeSuffixes[]{ {365.25 * 24 * 3600, "years"}, {24 * 3600, "days"}, {3600, "hr"}, {60, "min"}, {1, "s"}, {1E-3, "ms"}, {1E-6, "us"}, {1E-9, "ns"}, {1E-12, "ps"}, {1E-15, "fs"}, {0, nullptr}, }; static const ScaleInfo kMetricSuffixes[]{ {1E24, "Y"}, // yotta {1E21, "Z"}, // zetta {1E18, "X"}, // "exa" written with suffix 'X' so as to not create // confusion with scientific notation {1E15, "P"}, // peta {1E12, "T"}, // terra {1E9, "G"}, // giga {1E6, "M"}, // mega {1E3, "K"}, // kilo {1, ""}, {1E-3, "m"}, // milli {1E-6, "u"}, // micro {1E-9, "n"}, // nano {1E-12, "p"}, // pico {1E-15, "f"}, // femto {1E-18, "a"}, // atto {1E-21, "z"}, // zepto {1E-24, "y"}, // yocto {0, nullptr}, }; static string humanReadable( double n, unsigned int decimals, const ScaleInfo* scales) { if (std::isinf(n) || std::isnan(n)) { return folly::to(n); } const double absValue = fabs(n); const ScaleInfo* scale = scales; while (absValue < scale[0].boundary && scale[1].suffix != nullptr) { ++scale; } const double scaledValue = n / scale->boundary; return stringPrintf("%.*f%s", decimals, scaledValue, scale->suffix); } namespace detail { string readableTime(double n, unsigned int decimals) { return humanReadable(n, decimals, kTimeSuffixes); } } // namespace detail static string metricReadable(double n, unsigned int decimals) { return humanReadable(n, decimals, kMetricSuffixes); } namespace { constexpr std::string_view kUnitHeaders = "relative time/iter iters/s"; constexpr std::string_view kUnitHeadersPadding = " "; std::string headerContents(std::string_view file, size_t columns) { const size_t maxFileNameChars = columns - kUnitHeaders.size() - kUnitHeadersPadding.size(); std::string fname(file); if (fname.size() > maxFileNameChars) { constexpr std::string_view overflowFilePrefix = "[...]"; fname.erase(0, fname.size() - maxFileNameChars); fname.replace(0, overflowFilePrefix.size(), overflowFilePrefix); } return stringPrintf( "%-.*s%*s%*s", static_cast(fname.size()), fname.c_str(), static_cast(kUnitHeadersPadding.size()), kUnitHeadersPadding.data(), static_cast(kUnitHeaders.size()), kUnitHeaders.data()); } class BenchmarkResultsPrinter { public: explicit BenchmarkResultsPrinter( std::set counterNames = {}, std::ostream* os = &std::cout, std::string_view indent = "", size_t columnsAdjust = 0) : counterNames_(std::move(counterNames)), namesLength_{std::accumulate( counterNames_.begin(), counterNames_.end(), size_t{0}, [](size_t acc, auto&& name) { return acc + 2 + name.length(); })}, os_(os), indent_(indent), columns_(FLAGS_bm_result_width_chars + namesLength_ - columnsAdjust) {} void separator(char pad) { line(string(columns_, pad)); } void header(std::string_view file) { separator('='); std::string h = headerContents(file, columns_ - namesLength_); for (auto const& name : counterNames_) { h += " "; h += name; } line(h); separator('='); } void print( const vector& data, const std::vector& annotations = {}) { for (size_t i = 0; i < data.size(); ++i) { auto& datum = data[i]; auto file = datum.file; if (file != lastFile_) { header(file); lastFile_ = file; } string s = datum.name; if (s == "-") { separator('-'); continue; } if (s[0] == '"') { // Strips quote characters from the beginning and end of the name. line(s.substr(1, s.length() - 2)); continue; } bool useBaseline = false; // '%' indicates a relative benchmark. if (s[0] == '%') { s.erase(0, 1); useBaseline = isBaselineSet(); } else { baselineNsPerIter_ = datum.timeInNs; useBaseline = false; } s.resize(columns_ - namesLength_ - kUnitHeaders.size(), ' '); const auto nsPerIter = datum.timeInNs; const auto secPerIter = nsPerIter / 1E9; const auto itersPerSec = (secPerIter == 0) ? std::numeric_limits::infinity() : (1 / secPerIter); std::string row; if (!useBaseline) { row = stringPrintf( "%*s%8.8s %9.9s %8.8s", static_cast(s.size()), s.c_str(), "", // Padding for "relative" header. detail::readableTime(secPerIter, 2).c_str(), metricReadable(itersPerSec, 2).c_str()); } else { row = stringPrintf( "%*s%#7.5g%% %9.9s %8.8s", static_cast(s.size()), s.c_str(), baselineNsPerIter_ / nsPerIter * 100.0, detail::readableTime(secPerIter, 2).c_str(), metricReadable(itersPerSec, 2).c_str()); } for (auto const& name : counterNames_) { if (auto ptr = folly::get_ptr(datum.counters, name)) { switch (ptr->type) { // UserMetrics constructed as precision_values avoid the // implicit cast from long to double when formatting the output case UserMetric::Type::TIME: folly::variant_match(ptr->value, [&](auto value) { row += stringPrintf( " %*s", int(name.length()), detail::readableTime(value, 2).c_str()); }); break; case UserMetric::Type::METRIC: folly::variant_match(ptr->value, [&](auto value) { row += stringPrintf( " %*s", int(name.length()), metricReadable(value, 2).c_str()); }); break; case UserMetric::Type::CUSTOM: default: folly::variant_match(ptr->value, [&](auto value) { row += stringPrintf( " %*" PRId64, int(name.length()), static_cast(value)); }); } } else { row += stringPrintf(" %*s", int(name.length()), "NaN"); } } if (i < annotations.size() && !annotations[i].empty()) { row += indent_; row += detail::kANSIBoldYellow; row += annotations[i]; row += detail::kANSIReset; } line(row); } } private: bool isBaselineSet() { return baselineNsPerIter_ != numeric_limits::max(); } void line(std::string_view s) { *os_ << indent_ << s << std::endl; } std::set counterNames_; size_t namesLength_{0}; std::ostream* os_; std::string indent_; size_t columns_{0}; double baselineNsPerIter_{numeric_limits::max()}; string lastFile_; }; } // namespace static void printBenchmarkResultsAsJson( const vector& data) { dynamic d = dynamic::object; for (auto& datum : data) { d[datum.name] = datum.timeInNs * 1000.; } printf("%s\n", toPrettyJson(d).c_str()); } void benchmarkResultsToDynamic( const vector& data, dynamic& out) { out = dynamic::array; for (auto& datum : data) { if (!datum.counters.empty()) { dynamic obj = dynamic::object; for (auto& counter : datum.counters) { dynamic counterInfo = dynamic::object; folly::variant_match(counter.second.value, [&](auto value) { counterInfo["value"] = value; }); counterInfo["type"] = static_cast(counter.second.type); obj[counter.first] = counterInfo; } out.push_back( dynamic::array(datum.file, datum.name, datum.timeInNs, obj)); } else { out.push_back(dynamic::array(datum.file, datum.name, datum.timeInNs)); } } } void benchmarkResultsFromDynamic( const dynamic& d, vector& results) { for (auto& datum : d) { results.push_back( {datum[0].asString(), datum[1].asString(), datum[2].asDouble(), UserCounters{}}); } } static pair resultKey( const detail::BenchmarkResult& result) { return pair(result.file, result.name); } void printResultComparison( const vector& base, const vector& test) { map, double> baselines; for (auto& baseResult : base) { baselines[resultKey(baseResult)] = baseResult.timeInNs; } // Width available const size_t columns = FLAGS_bm_result_width_chars; auto sep = [&](char pad) { puts(string(columns, pad).c_str()); }; auto header = [&](const string_view& file) { sep('='); printf("%s\n", headerContents(file, columns).c_str()); sep('='); }; string lastFile; for (auto& datum : test) { folly::Optional baseline = folly::get_optional(baselines, resultKey(datum)); auto file = datum.file; if (file != lastFile) { // New file starting header(file); lastFile = file; } string s = datum.name; if (s == "-") { sep('-'); continue; } if (s[0] == '%') { s.erase(0, 1); } s.resize(columns - 29, ' '); auto nsPerIter = datum.timeInNs; auto secPerIter = nsPerIter / 1E9; auto itersPerSec = (secPerIter == 0) ? std::numeric_limits::infinity() : (1 / secPerIter); if (!baseline) { // Print without baseline printf( "%*s %9s %7s\n", static_cast(s.size()), s.c_str(), detail::readableTime(secPerIter, 2).c_str(), metricReadable(itersPerSec, 2).c_str()); } else { // Print with baseline auto rel = *baseline / nsPerIter * 100.0; printf( "%*s %7.2f%% %9s %7s\n", static_cast(s.size()), s.c_str(), rel, detail::readableTime(secPerIter, 2).c_str(), metricReadable(itersPerSec, 2).c_str()); } } sep('='); } void checkRunMode() { if (folly::kIsDebug || folly::kIsSanitize) { std::cerr << detail::kANSIBoldYellow << "WARNING: " << detail::kANSIReset << "Benchmark running " << (folly::kIsDebug ? "in DEBUG mode" : "with SANITIZERS") << std::endl; } } namespace { struct BenchmarksToRun { const detail::BenchmarkRegistration* baseline = nullptr; const detail::BenchmarkRegistration* suspenderBaseline = nullptr; std::vector benchmarks; std::vector separatorsAfter; }; void addSeparator(BenchmarksToRun& res) { size_t separatorAfter = res.benchmarks.size(); if (separatorAfter == 0) { return; } if (res.separatorsAfter.empty() || res.separatorsAfter.back() != (separatorAfter - 1)) { res.separatorsAfter.push_back(res.benchmarks.size() - 1); } } BenchmarksToRun selectBenchmarksToRun( const std::vector& benchmarks) { BenchmarksToRun res; folly::Optional bmRegex; folly::Optional bmFileRegex; res.benchmarks.reserve(benchmarks.size()); if (!FLAGS_bm_regex.empty()) { bmRegex.emplace(FLAGS_bm_regex); } if (!FLAGS_bm_file_regex.empty()) { bmFileRegex.emplace(FLAGS_bm_file_regex); } for (auto& bm : benchmarks) { if (bm.name == "-") { addSeparator(res); continue; } if (bm.name == kGlobalBenchmarkBaseline) { res.baseline = &bm; continue; } if (bm.name == kGlobalBenchmarkSuspenderBaseline) { res.suspenderBaseline = &bm; continue; } bool matchedName = !bmRegex || boost::regex_search(bm.name, *bmRegex); bool matchedFile = !bmFileRegex || boost::regex_search(bm.file, *bmFileRegex); if (matchedName && matchedFile) { res.benchmarks.push_back(&bm); } } if (bmRegex) { res.separatorsAfter.clear(); } CHECK(res.baseline); return res; } void maybeRunWarmUpIteration(const BenchmarksToRun& toRun) { bool shouldRun = FLAGS_bm_warm_up_iteration; #if FOLLY_PERF_IS_SUPPORTED shouldRun = shouldRun || !FLAGS_bm_perf_args.empty(); #endif if (!shouldRun) { return; } if (FLAGS_bm_verbose) { LOG(INFO) << "Running warmup for all benchmarks..."; } for (const auto* bm : toRun.benchmarks) { bm->func(1); } if (FLAGS_bm_verbose) { LOG(INFO) << "Warmup complete"; } } class ShouldDrawLineTracker { public: explicit ShouldDrawLineTracker(const std::vector& separatorsAfter) : separatorsAfter_(&separatorsAfter) {} bool operator()() { std::size_t i = curI_++; if (drawAfterI_ >= separatorsAfter_->size()) { return false; } std::size_t nextToDrawAfter = (*separatorsAfter_)[drawAfterI_]; if (i == nextToDrawAfter) { ++drawAfterI_; return true; } return false; } private: const std::vector* separatorsAfter_; std::size_t curI_ = 0; std::size_t drawAfterI_ = 0; }; // Helper for adaptive mode auto runAdaptiveMode( auto* printer, const BenchmarksToRun& toRun, int64_t sliceUsec) { auto rrResult = detail::runBenchmarksAdaptive( toRun.benchmarks, toRun.baseline->func, toRun.suspenderBaseline->func, {.sliceUsec = sliceUsec, .targetPercentile = FLAGS_bm_target_percentile, .targetPrecisionPct = FLAGS_bm_target_precision_pct, .minSamples = static_cast(FLAGS_bm_min_samples), .minSecs = FLAGS_bm_min_secs, .maxSecs = FLAGS_bm_max_secs, .verbose = FLAGS_bm_verbose, .quiet = FLAGS_bm_quiet}); if (printer != nullptr) { ShouldDrawLineTracker lineTracker(toRun.separatorsAfter); for (const auto& r : rrResult.results) { printer->print({{r.file, r.name, r.timeInNs, r.counters}}); if (lineTracker()) { printer->separator('-'); } } } return std::pair{std::set{}, std::move(rrResult.results)}; } // Returns true when a user overrode a gflag on the command line. bool userSetGflag([[maybe_unused]] const char* name) { #if FOLLY_HAVE_LIBGFLAGS && __has_include() return !gflags::GetCommandLineFlagInfoOrDie(name).is_default; #else // No libgflags means we just have global vars, without CLI args. // Falling back to false is fine since we only use this for warnings. return false; #endif } // Check that no mode-incompatible flags were explicitly set. void validateFlagCombinations() { // Log a user-facing error and exit without a stack trace. auto fatal = [](const std::string& msg) { LOG(ERROR) << detail::kANSIBoldRed << msg << detail::kANSIReset; exit(1); }; if (FLAGS_bm_mode != "best-of" && FLAGS_bm_mode != "adaptive") { fatal( fmt::format( "Unknown --bm_mode='{}'. Must be 'best-of' or 'adaptive'.", FLAGS_bm_mode)); } if (FLAGS_bm_mode == "adaptive") { if (userSetGflag("bm_min_iters")) { fatal("--bm_min_iters is only useful in --bm_mode=best-of."); } if (userSetGflag("bm_max_iters")) { fatal("--bm_max_iters is only useful in --bm_mode=best-of."); } if (userSetGflag("bm_max_trials")) { fatal("--bm_max_trials is only useful in --bm_mode=best-of."); } if (userSetGflag("bm_estimate_time")) { fatal( "--bm_estimate_time is incompatible with adaptive mode. " "Adaptive already targets a configurable percentile " "(--bm_target_percentile)."); } if (userSetGflag("bm_profile")) { fatal( "--bm_profile is not supported in adaptive mode. " "Use --bm_perf_args to attach perf in any mode."); } } else { // Best-of mode if (userSetGflag("bm_target_percentile")) { fatal("--bm_target_percentile requires --bm_mode=adaptive."); } if (userSetGflag("bm_target_precision_pct")) { fatal("--bm_target_precision_pct requires --bm_mode=adaptive."); } if (userSetGflag("bm_min_secs")) { fatal("--bm_min_secs requires --bm_mode=adaptive."); } if (userSetGflag("bm_min_samples")) { fatal("--bm_min_samples requires --bm_mode=adaptive."); } if (FLAGS_bm_estimate_time && !FLAGS_bm_quiet) { LOG(WARNING) << detail::kANSIBoldYellow << "--bm_estimate_time is slow, with odd semantics (geometric mean " << "of p25-p75). Consider --bm_mode=adaptive (BenchmarkAdaptive.md)." << detail::kANSIReset; } } } // Resolve `--bm_slice_usec` / `--bm_min_usec` (deprecated alias). int64_t resolveSliceUsec() { bool minUsecSet = userSetGflag("bm_min_usec"); bool sliceUsecSet = userSetGflag("bm_slice_usec"); if (minUsecSet && sliceUsecSet) { LOG(ERROR) << detail::kANSIBoldRed << "Cannot set both --bm_min_usec and --bm_slice_usec. " << "Use --bm_slice_usec only (--bm_min_usec is deprecated)." << detail::kANSIReset; exit(1); } if (minUsecSet) { LOG(ERROR) << detail::kANSIBoldRed << "--bm_min_usec is deprecated; " << "use --bm_slice_usec instead." << detail::kANSIReset; return FLAGS_bm_min_usec; } return FLAGS_bm_slice_usec; } std::pair, std::vector> runBenchmarksWithPrinterImpl( BenchmarkResultsPrinter* FOLLY_NULLABLE printer, const BenchmarksToRun& toRun) { vector results; results.reserve(toRun.benchmarks.size()); auto const sliceUsec = resolveSliceUsec(); if (sliceUsec < 1000) { LOG(WARNING) << detail::kANSIBoldYellow << "--bm_slice_usec=" << sliceUsec << " is below 1000; benchmark harness overhead may " << "interfere with results." << detail::kANSIReset; } validateFlagCombinations(); // PLEASE KEEP QUIET. MEASUREMENTS IN PROGRESS. // Adaptive mode: interleaved sampling with paired correction for both // baseline and suspender overhead. Does NOT set suspenderOverhead static; // instead uses late correction based on suspensionCount. if (FLAGS_bm_mode == "adaptive") { return runAdaptiveMode(printer, toRun, sliceUsec); } // Encourage users to try `adaptive` when `--bm_mode` was not passed. if (!userSetGflag("bm_mode")) { std::cerr << "NOTE: Default may change to " << detail::kANSIBold << "faster & more robust `--bm_mode=adaptive`." << detail::kANSIReset << "\n" << " Details in BenchmarkAdaptive.md.\n" << "Pass `--bm_mode=best-of` to keep current behavior -- report trial " << "with lowest\n" << "iteration cost, each `1-2 * bm_slice_usec` long, repeated per " << "benchmark up to\n" << "`bm_max_trials` or `bm_max_secs`.\n"; } // Best-of mode: measure suspender overhead upfront and set static for // immediate correction in tally(). if (FLAGS_bm_verbose) { LOG(INFO) << "Measuring suspenderBaseline..."; } auto const globalSuspenderBaseline = runBenchmarkGetNSPerIteration( toRun.suspenderBaseline->func, 0, sliceUsec); BenchmarkSuspender::suspenderOverhead = chrono::nanoseconds( static_cast( globalSuspenderBaseline.first)); if (FLAGS_bm_verbose) { LOG(INFO) << "suspenderOverhead=" << globalSuspenderBaseline.first << " ns/iter"; } if (FLAGS_bm_verbose) { LOG(INFO) << "Measuring globalBaseline..."; } auto const globalBaseline = runBenchmarkGetNSPerIteration(toRun.baseline->func, 0, sliceUsec); if (FLAGS_bm_verbose) { LOG(INFO) << "globalBaseline=" << globalBaseline.first << " ns/iter"; } std::set counterNames; ShouldDrawLineTracker shouldDrawLineTracker(toRun.separatorsAfter); for (std::size_t i = 0; i != toRun.benchmarks.size(); ++i) { std::pair elapsed; const detail::BenchmarkRegistration& bm = *toRun.benchmarks[i]; bool shouldDrawLineAfter = shouldDrawLineTracker(); if (FLAGS_bm_profile) { elapsed = runProfilingGetNSPerIteration(bm.func, globalBaseline.first); } else { elapsed = FLAGS_bm_estimate_time ? runBenchmarkGetNSPerIterationEstimate(bm.func, globalBaseline.first) : runBenchmarkGetNSPerIteration( bm.func, globalBaseline.first, sliceUsec); } // if customized user counters is used, it cannot print the result in real // time as it needs to run all cases first to know the complete set of // counters have been used, then the header can be printed out properly if (printer != nullptr) { printer->print({{bm.file, bm.name, elapsed.first, elapsed.second}}); if (shouldDrawLineAfter) { printer->separator('-'); } } results.push_back({bm.file, bm.name, elapsed.first, elapsed.second}); // get all counter names for (auto const& kv : elapsed.second) { counterNames.insert(kv.first); } } // MEASUREMENTS DONE. return std::make_pair(std::move(counterNames), std::move(results)); } std::vector resultsFromFile( const std::string& filename) { std::string content; readFile(filename.c_str(), content); std::vector ret; if (!content.empty()) { benchmarkResultsFromDynamic(parseJson(content), ret); } return ret; } bool writeResultsToFile( const std::vector& results, const std::string& filename) { dynamic d; benchmarkResultsToDynamic(results, d); return writeFile(toPrettyJson(d), filename.c_str()); } } // namespace namespace detail { std::ostream& operator<<(std::ostream& os, const BenchmarkResult& x) { folly::dynamic r; benchmarkResultsToDynamic({x}, r); return os << r[0]; } bool operator==(const BenchmarkResult& x, const BenchmarkResult& y) { auto xtime = static_cast(x.timeInNs * 1000); auto ytime = static_cast(y.timeInNs * 1000); return x.name == y.name && x.file == y.file && xtime == ytime && x.counters == y.counters; } std::chrono::high_resolution_clock::duration BenchmarkSuspenderBase::timeSpent; std::chrono::high_resolution_clock::duration BenchmarkSuspenderBase::suspenderOverhead; size_t BenchmarkSuspenderBase::suspensionCount; void BenchmarkingStateBase::addBenchmarkImpl( std::string file, std::string name, BenchmarkFun fun, bool useCounter) { std::lock_guard guard(mutex_); benchmarks_.push_back( {std::move(file), std::move(name), std::move(fun), useCounter}); } bool BenchmarkingStateBase::useCounters() const { std::lock_guard guard(mutex_); return std::any_of( benchmarks_.begin(), benchmarks_.end(), [](const auto& bm) { return bm.useCounter; }); } std::vector BenchmarkingStateBase::getBenchmarkList() { std::vector bmNames; auto toRun = selectBenchmarksToRun(benchmarks_); bmNames.reserve(toRun.benchmarks.size()); for (auto benchmarkRegistration : toRun.benchmarks) { bmNames.push_back(benchmarkRegistration->name); } return bmNames; } // static folly::StringPiece BenchmarkingStateBase::getGlobalBaselineNameForTests() { return kGlobalBenchmarkBaseline; } folly::StringPiece BenchmarkingStateBase::getGlobalSuspenderBaselineNameForTests() { return kGlobalBenchmarkSuspenderBaseline; } PerfScoped BenchmarkingStateBase::doSetUpPerfScoped( const std::vector& args) const { return PerfScoped{args}; } PerfScoped BenchmarkingStateBase::setUpPerfScoped() const { std::vector perfArgs; #if FOLLY_PERF_IS_SUPPORTED folly::split(' ', FLAGS_bm_perf_args, perfArgs, true); #endif if (perfArgs.empty()) { return PerfScoped{}; } return doSetUpPerfScoped(perfArgs); } template std::pair, std::vector> BenchmarkingStateBase::runBenchmarksWithPrinter(Printer* printer) const { if (FLAGS_bm_verbose) { LOG(INFO) << "Benchmark run starting..."; } std::lock_guard guard(mutex_); BenchmarksToRun toRun = selectBenchmarksToRun(benchmarks_); maybeRunWarmUpIteration(toRun); detail::PerfScoped perf = setUpPerfScoped(); return runBenchmarksWithPrinterImpl(printer, toRun); } std::vector BenchmarkingStateBase::runBenchmarksWithResults() const { return runBenchmarksWithPrinter( static_cast(nullptr)) .second; } std::vector runBenchmarksWithResults() { return globalBenchmarkState().runBenchmarksWithResults(); } std::string benchmarkResultsToString( const std::vector& results, std::string_view indent, const std::vector& annotations) { size_t maxAnnotationWidth = 0; for (const auto& a : annotations) { if (!a.empty()) { maxAnnotationWidth = std::max(maxAnnotationWidth, indent.size() + a.size()); } } std::set counterNames; for (const auto& r : results) { for (const auto& [key, _] : r.counters) { counterNames.insert(key); } } std::ostringstream oss; BenchmarkResultsPrinter printer( std::move(counterNames), &oss, indent, maxAnnotationWidth); printer.print(results, annotations); printer.separator('='); return oss.str(); } } // namespace detail void runBenchmarks() { auto& state = detail::globalBenchmarkState(); if (FLAGS_bm_list) { auto bmNames = state.getBenchmarkList(); for (auto testName : bmNames) { std::cout << testName << std::endl; } return; } if (FLAGS_bm_profile) { printf( "WARNING: Running with constant number of iterations. Results might be jittery.\n"); } if (FLAGS_bm_min_iters >= FLAGS_bm_max_iters) { std::cerr << "WARNING: bm_min_iters > bm_max_iters; increasing the max" << std::endl; FLAGS_bm_max_iters = FLAGS_bm_min_iters + 1; } checkRunMode(); BenchmarkResultsPrinter printer; bool useCounter = state.useCounters(); // PLEASE KEEP QUIET. MEASUREMENTS IN PROGRESS. const bool shouldPrintInline = FLAGS_bm_relative_to.empty() && !FLAGS_json && !useCounter; auto benchmarkResults = state.runBenchmarksWithPrinter(shouldPrintInline ? &printer : nullptr); // PLEASE MAKE NOISE. MEASUREMENTS DONE. if (FLAGS_json) { printBenchmarkResultsAsJson(benchmarkResults.second); } else if (!FLAGS_bm_relative_to.empty()) { printResultComparison( resultsFromFile(FLAGS_bm_relative_to), benchmarkResults.second); } else if (!shouldPrintInline) { printer = BenchmarkResultsPrinter{std::move(benchmarkResults.first)}; printer.print(benchmarkResults.second); printer.separator('='); } if (!FLAGS_bm_json_verbose.empty()) { writeResultsToFile(benchmarkResults.second, FLAGS_bm_json_verbose); } checkRunMode(); } } // namespace folly ================================================ FILE: folly/Benchmark.h ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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 // for FB_ANONYMOUS_VARIABLE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include FOLLY_GFLAGS_DECLARE_bool(benchmark); FOLLY_GFLAGS_DECLARE_bool(bm_quiet); FOLLY_GFLAGS_DECLARE_uint32(bm_result_width_chars); FOLLY_GFLAGS_DECLARE_int32(bm_min_iters); FOLLY_GFLAGS_DECLARE_int64(bm_max_iters); namespace folly { /** * Runs all benchmarks defined. Usually put in main(). */ void runBenchmarks(); /** * Runs all benchmarks defined if and only if the --benchmark flag has * been passed to the program. Usually put in main(). */ inline bool runBenchmarksOnFlag() { if (FLAGS_benchmark) { runBenchmarks(); } return FLAGS_benchmark; } class UserMetric { public: enum class Type { CUSTOM, TIME, METRIC }; std::variant value; Type type{Type::CUSTOM}; UserMetric() = default; /* implicit */ UserMetric(int64_t val, Type typ = Type::CUSTOM) : value(val), type(typ) {} // Allow users to provide precision values template < typename T, typename = std::enable_if_t>> explicit UserMetric(T precision_val, Type typ = Type::CUSTOM) : value(convert_helper(precision_val)), type(typ) {} friend bool operator==(const UserMetric& x, const UserMetric& y) { return x.value == y.value && x.type == y.type; } friend bool operator!=(const UserMetric& x, const UserMetric& y) { return !(x == y); } private: double convert_helper(double val) { return val; } }; using UserCounters = std::unordered_map; namespace detail { struct TimeIterData { std::chrono::high_resolution_clock::duration duration; unsigned int niter; UserCounters userCounters; size_t suspensionCount = 0; }; using BenchmarkFun = std::function; struct BenchmarkRegistration { std::string file; std::string name; BenchmarkFun func; bool useCounter = false; }; struct BenchmarkResult { std::string file; std::string name; double timeInNs; UserCounters counters; friend std::ostream& operator<<(std::ostream&, const BenchmarkResult&); friend bool operator==(const BenchmarkResult&, const BenchmarkResult&); friend bool operator!=(const BenchmarkResult& x, const BenchmarkResult& y) { return !(x == y); } }; struct BenchmarkSuspenderBase { /** * Accumulates time spent outside benchmark. */ static std::chrono::high_resolution_clock::duration timeSpent; /** * Overhead per suspension (set once at startup for best-of mode, * left at 0 for adaptive mode which does late correction). */ static std::chrono::high_resolution_clock::duration suspenderOverhead; /** * Number of suspensions in the current benchmark invocation. * Reset before each benchmark call, used for late overhead correction. */ static size_t suspensionCount; }; template struct BenchmarkSuspender : BenchmarkSuspenderBase { using TimePoint = std::chrono::high_resolution_clock::time_point; using Duration = std::chrono::high_resolution_clock::duration; struct DismissedTag {}; static inline constexpr DismissedTag Dismissed{}; BenchmarkSuspender() : start(Clock::now()) {} explicit BenchmarkSuspender(DismissedTag) : start(TimePoint{}) {} BenchmarkSuspender(const BenchmarkSuspender&) = delete; BenchmarkSuspender(BenchmarkSuspender&& rhs) noexcept { start = rhs.start; rhs.start = {}; } BenchmarkSuspender& operator=(const BenchmarkSuspender&) = delete; BenchmarkSuspender& operator=(BenchmarkSuspender&& rhs) noexcept { if (start != TimePoint{}) { tally(); } start = rhs.start; rhs.start = {}; return *this; } ~BenchmarkSuspender() { if (start != TimePoint{}) { tally(); } } void dismiss() { assert(start != TimePoint{}); tally(); start = {}; } void rehire() { assert(start == TimePoint{}); start = Clock::now(); } template auto dismissing(F f) -> invoke_result_t { SCOPE_EXIT { rehire(); }; dismiss(); return f(); } /** * This is for use inside of if-conditions, used in BENCHMARK macros. * If-conditions bypass the explicit on operator bool. */ explicit operator bool() const { return false; } private: void tally() { auto end = Clock::now(); timeSpent += (end - start) + suspenderOverhead; ++suspensionCount; start = end; } TimePoint start; }; class PerfScoped; class BenchmarkingStateBase { public: template std::pair, std::vector> runBenchmarksWithPrinter(Printer* printer) const; std::vector runBenchmarksWithResults() const; static folly::StringPiece getGlobalBaselineNameForTests(); static folly::StringPiece getGlobalSuspenderBaselineNameForTests(); bool useCounters() const; void addBenchmarkImpl( std::string file, std::string name, BenchmarkFun, bool useCounter); std::vector getBenchmarkList(); protected: // There is no need for this virtual but we overcome a check virtual ~BenchmarkingStateBase() = default; PerfScoped setUpPerfScoped() const; // virtual for purely testing purposes. virtual PerfScoped doSetUpPerfScoped( const std::vector& args) const; mutable std::mutex mutex_; std::vector benchmarks_; }; template class BenchmarkingState : public BenchmarkingStateBase { public: template typename std::enable_if>::type addBenchmark(std::string file, std::string name, Lambda&& lambda) { auto execute = [=](unsigned int times) { BenchmarkSuspender::timeSpent = {}; BenchmarkSuspender::suspensionCount = 0; unsigned int niter; // CORE MEASUREMENT STARTS auto start = Clock::now(); niter = lambda(times); auto end = Clock::now(); // CORE MEASUREMENT ENDS return detail::TimeIterData{ (end - start) - BenchmarkSuspender::timeSpent, niter, UserCounters{}, BenchmarkSuspender::suspensionCount}; }; this->addBenchmarkImpl( std::move(file), std::move(name), detail::BenchmarkFun(execute), false); } template typename std::enable_if>::type addBenchmark( std::string file, std::string name, Lambda&& lambda) { addBenchmark(std::move(file), std::move(name), [=](unsigned int times) { unsigned int niter = 0; while (times-- > 0) { niter += lambda(); } return niter; }); } template typename std::enable_if< folly::is_invocable_v>::type addBenchmark(std::string file, std::string name, Lambda&& lambda) { auto execute = [=](unsigned int times) { BenchmarkSuspender::timeSpent = {}; BenchmarkSuspender::suspensionCount = 0; unsigned int niter; // CORE MEASUREMENT STARTS auto start = std::chrono::high_resolution_clock::now(); UserCounters counters; niter = lambda(counters, times); auto end = std::chrono::high_resolution_clock::now(); // CORE MEASUREMENT ENDS return detail::TimeIterData{ (end - start) - BenchmarkSuspender::timeSpent, niter, counters, BenchmarkSuspender::suspensionCount}; }; this->addBenchmarkImpl( std::move(file), std::move(name), std::function(execute), true); } template typename std::enable_if>::type addBenchmark(std::string file, std::string name, Lambda&& lambda) { addBenchmark( std::move(file), std::move(name), [=](UserCounters& counters, unsigned int times) { unsigned int niter = 0; while (times-- > 0) { niter += lambda(counters); } return niter; }); } }; BenchmarkingState& globalBenchmarkState(); /** * Runs all benchmarks defined in the program, doesn't print by default. * Usually used when customized printing of results is desired. */ std::vector runBenchmarksWithResults(); // Format benchmark results as a human-readable table string. // Shares formatting logic with the default results printer. // `annotations[i]` is appended to the corresponding row. std::string benchmarkResultsToString( const std::vector& results, std::string_view indent = "", const std::vector& annotations = {}); // Format a time value (in seconds) as a human-readable string, e.g. "8.5us". std::string readableTime(double n, unsigned int decimals); /** * Adds a benchmark wrapped in a std::function. * Was designed to only be used internally but, unfortunately, * is not. */ inline void addBenchmarkImpl( std::string file, std::string name, BenchmarkFun f, bool useCounter) { globalBenchmarkState().addBenchmarkImpl( std::move(file), std::move(name), std::move(f), useCounter); } } // namespace detail /** * Supporting type for BENCHMARK_SUSPEND defined below. */ struct BenchmarkSuspender : detail::BenchmarkSuspender { using Impl = detail::BenchmarkSuspender; using Impl::Impl; }; /** * Adds a benchmark. Usually not called directly but instead through * the macro BENCHMARK defined below. * The lambda function involved can have one of the following forms: * * take zero parameters, and the benchmark calls it repeatedly * * take exactly one parameter of type unsigned, and the benchmark * uses it with counter semantics (iteration occurs inside the * function). * * 2 versions of the above cases but also accept UserCounters& as * as their first parameter. */ template void addBenchmark(std::string file, std::string name, Lambda&& lambda) { detail::globalBenchmarkState().addBenchmark( std::move(file), std::move(name), lambda); } struct dynamic; void benchmarkResultsToDynamic( const std::vector& data, dynamic&); void benchmarkResultsFromDynamic( const dynamic&, std::vector&); void printResultComparison( const std::vector& base, const std::vector& test); } // namespace folly /** * Introduces a benchmark function. Used internally, see BENCHMARK and * friends below. */ FOLLY_PUSH_WARNING FOLLY_CLANG_DISABLE_WARNING("-Wglobal-constructors") #define BENCHMARK_IMPL(funName, stringName, rv, paramType, paramName) \ static void funName(paramType); \ [[maybe_unused]] static const bool FB_ANONYMOUS_VARIABLE( \ follyBenchmarkUnused) = \ (::folly::addBenchmark( \ __FILE__, \ stringName, \ [](paramType paramName) -> unsigned { \ funName(paramName); \ return rv; \ }), \ true); \ static void funName(paramType paramName) #define BENCHMARK_IMPL_COUNTERS( \ funName, stringName, counters, rv, paramType, paramName) \ static void funName( \ ::folly::UserCounters& FOLLY_PP_DETAIL_APPEND_VA_ARG(paramType)); \ [[maybe_unused]] static const bool FB_ANONYMOUS_VARIABLE( \ follyBenchmarkUnused) = \ (::folly::addBenchmark( \ __FILE__, \ stringName, \ [](::folly::UserCounters& counters FOLLY_PP_DETAIL_APPEND_VA_ARG( \ paramType paramName)) -> unsigned { \ funName(counters FOLLY_PP_DETAIL_APPEND_VA_ARG(paramName)); \ return rv; \ }), \ true); \ static void funName( \ [[maybe_unused]] ::folly::UserCounters& counters \ FOLLY_PP_DETAIL_APPEND_VA_ARG(paramType paramName)) /** * Introduces a benchmark function with support for returning the actual * number of iterations. Used internally, see BENCHMARK_MULTI and friends * below. */ #define BENCHMARK_MULTI_IMPL(funName, stringName, paramType, paramName) \ static unsigned funName(paramType); \ [[maybe_unused]] static const bool FB_ANONYMOUS_VARIABLE( \ follyBenchmarkUnused) = \ (::folly::addBenchmark( \ __FILE__, \ stringName, \ [](paramType paramName) { return funName(paramName); }), \ true); \ static unsigned funName(paramType paramName) FOLLY_POP_WARNING /** * Introduces a benchmark function. Use with either one or two arguments. * The first is the name of the benchmark. Use something descriptive, such * as insertVectorBegin. The second argument may be missing, or could be a * symbolic counter. The counter dictates how many internal iteration the * benchmark does. Example: * * BENCHMARK(vectorPushBack) { * vector v; * v.push_back(42); * } * * BENCHMARK(insertVectorBegin, iters) { * vector v; * for (unsigned int i = 0; i < iters; ++i) { * v.insert(v.begin(), 42); * } * } */ #define BENCHMARK(name, ...) \ BENCHMARK_IMPL( \ name, \ FOLLY_PP_STRINGIZE(name), \ FB_ARG_2_OR_1(1, ##__VA_ARGS__), \ FB_ONE_OR_NONE(unsigned, ##__VA_ARGS__), \ __VA_ARGS__) /** * Allow users to record customized counter during benchmarking, * there will be one extra column showing in the output result for each counter * * BENCHMARK_COUNTERS(insertVectorBegin, counters, iters) { * vector v; * for (unsigned int i = 0; i < iters; ++i) { * v.insert(v.begin(), 42); * } * BENCHMARK_SUSPEND { * counters["foo"] = 10; * } * } */ #define BENCHMARK_COUNTERS(name, counters, ...) \ BENCHMARK_IMPL_COUNTERS( \ name, \ FOLLY_PP_STRINGIZE(name), \ counters, \ FB_ARG_2_OR_1(1, ##__VA_ARGS__), \ FB_ONE_OR_NONE(unsigned, ##__VA_ARGS__), \ __VA_ARGS__) /** * Like BENCHMARK above, but allows the user to return the actual * number of iterations executed in the function body. This can be * useful if the benchmark function doesn't know upfront how many * iterations it's going to run or if it runs through a certain * number of test cases, e.g.: * * BENCHMARK_MULTI(benchmarkSomething) { * std::vector testCases { 0, 1, 1, 2, 3, 5 }; * for (int c : testCases) { * doSomething(c); * } * return testCases.size(); * } */ #define BENCHMARK_MULTI(name, ...) \ BENCHMARK_MULTI_IMPL( \ name, \ FOLLY_PP_STRINGIZE(name), \ FB_ONE_OR_NONE(unsigned, ##__VA_ARGS__), \ __VA_ARGS__) /** * Defines a benchmark that passes a parameter to another one. This is * common for benchmarks that need a "problem size" in addition to * "number of iterations". Consider: * * void pushBack(uint32_t n, size_t initialSize) { * vector v; * BENCHMARK_SUSPEND { * v.resize(initialSize); * } * for (uint32_t i = 0; i < n; ++i) { * v.push_back(i); * } * } * BENCHMARK_PARAM(pushBack, 0) * BENCHMARK_PARAM(pushBack, 1000) * BENCHMARK_PARAM(pushBack, 1000000) * * The benchmark above estimates the speed of push_back at different * initial sizes of the vector. The framework will pass 0, 1000, and * 1000000 for initialSize, and the iteration count for n. */ #define BENCHMARK_PARAM(name, param) BENCHMARK_NAMED_PARAM(name, param, param) /** * Like BENCHMARK_PARAM, but passes a UserCounters& parameter. */ #define BENCHMARK_COUNTERS_PARAM(name, param) \ BENCHMARK_COUNTERS_NAMED_PARAM(name, param, param) /** * Same as BENCHMARK_PARAM, but allows one to return the actual number of * iterations that have been run. */ #define BENCHMARK_PARAM_MULTI(name, param) \ BENCHMARK_NAMED_PARAM_MULTI(name, param, param) /* * Like BENCHMARK_PARAM(), but allows a custom name to be specified for each * parameter, rather than using the parameter value. * * Useful when the parameter value is not a valid token for string pasting, * of when you want to specify multiple parameter arguments. * * For example: * * void addValue(uint32_t n, int64_t bucketSize, int64_t min, int64_t max) { * Histogram hist(bucketSize, min, max); * int64_t num = min; * for (uint32_t i = 0; i < n; ++i) { * hist.addValue(num); * ++num; * if (num > max) { num = min; } * } * } * * BENCHMARK_NAMED_PARAM(addValue, 0_to_100, 1, 0, 100) * BENCHMARK_NAMED_PARAM(addValue, 0_to_1000, 10, 0, 1000) * BENCHMARK_NAMED_PARAM(addValue, 5k_to_20k, 250, 5000, 20000) */ #define BENCHMARK_NAMED_PARAM(name, param_name, ...) \ BENCHMARK_IMPL( \ FB_CONCATENATE(name, FB_CONCATENATE(_, param_name)), \ FOLLY_PP_STRINGIZE(name) "(" FOLLY_PP_STRINGIZE(param_name) ")", \ iters, \ unsigned, \ iters) { \ name(iters, ##__VA_ARGS__); \ } /** * Like BENCHMARK_NAMED_PARAM, but passes a UserCounters& parameter to the * benchmark function for recording custom counters. */ #define BENCHMARK_COUNTERS_NAMED_PARAM(name, param_name, ...) \ BENCHMARK_IMPL_COUNTERS( \ FB_CONCATENATE(name, FB_CONCATENATE(_, param_name)), \ FOLLY_PP_STRINGIZE(name) "(" FOLLY_PP_STRINGIZE(param_name) ")", \ counters, \ iters, \ unsigned, \ iters) { \ name(counters, iters, ##__VA_ARGS__); \ } /** * Same as BENCHMARK_NAMED_PARAM, but allows one to return the actual number * of iterations that have been run. */ #define BENCHMARK_NAMED_PARAM_MULTI(name, param_name, ...) \ BENCHMARK_MULTI_IMPL( \ FB_CONCATENATE(name, FB_CONCATENATE(_, param_name)), \ FOLLY_PP_STRINGIZE(name) "(" FOLLY_PP_STRINGIZE(param_name) ")", \ unsigned, \ iters) { \ return name(iters, ##__VA_ARGS__); \ } /** * Just like BENCHMARK, but prints the time relative to a * baseline. The baseline is the most recent BENCHMARK() seen in * the current scope. Example: * * // This is the baseline * BENCHMARK(insertVectorBegin, n) { * vector v; * for (unsigned int i = 0; i < n; ++i) { * v.insert(v.begin(), 42); * } * } * * BENCHMARK_RELATIVE(insertListBegin, n) { * list s; * for (unsigned int i = 0; i < n; ++i) { * s.insert(s.begin(), 42); * } * } * * Any number of relative benchmark can be associated with a * baseline. Another BENCHMARK() occurrence effectively establishes a * new baseline. */ #define BENCHMARK_RELATIVE(name, ...) \ BENCHMARK_IMPL( \ name, \ "%" FOLLY_PP_STRINGIZE(name), \ FB_ARG_2_OR_1(1, ##__VA_ARGS__), \ FB_ONE_OR_NONE(unsigned, ##__VA_ARGS__), \ __VA_ARGS__) #define BENCHMARK_COUNTERS_RELATIVE(name, counters, ...) \ BENCHMARK_IMPL_COUNTERS( \ name, \ "%" FOLLY_PP_STRINGIZE(name), \ counters, \ FB_ARG_2_OR_1(1, ##__VA_ARGS__), \ FB_ONE_OR_NONE(unsigned, ##__VA_ARGS__), \ __VA_ARGS__) /** * Same as BENCHMARK_RELATIVE, but allows one to return the actual number * of iterations that have been run. */ #define BENCHMARK_RELATIVE_MULTI(name, ...) \ BENCHMARK_MULTI_IMPL( \ name, \ "%" FOLLY_PP_STRINGIZE(name), \ FB_ONE_OR_NONE(unsigned, ##__VA_ARGS__), \ __VA_ARGS__) /** * A combination of BENCHMARK_RELATIVE and BENCHMARK_PARAM. */ #define BENCHMARK_RELATIVE_PARAM(name, param) \ BENCHMARK_RELATIVE_NAMED_PARAM(name, param, param) /** * Same as BENCHMARK_RELATIVE_PARAM, but allows one to return the actual * number of iterations that have been run. */ #define BENCHMARK_RELATIVE_PARAM_MULTI(name, param) \ BENCHMARK_RELATIVE_NAMED_PARAM_MULTI(name, param, param) /** * A combination of BENCHMARK_RELATIVE and BENCHMARK_NAMED_PARAM. */ #define BENCHMARK_RELATIVE_NAMED_PARAM(name, param_name, ...) \ BENCHMARK_IMPL( \ FB_CONCATENATE(name, FB_CONCATENATE(_, param_name)), \ "%" FOLLY_PP_STRINGIZE(name) "(" FOLLY_PP_STRINGIZE(param_name) ")", \ iters, \ unsigned, \ iters) { \ name(iters, ##__VA_ARGS__); \ } /** * Same as BENCHMARK_RELATIVE_NAMED_PARAM, but allows one to return the * actual number of iterations that have been run. */ #define BENCHMARK_RELATIVE_NAMED_PARAM_MULTI(name, param_name, ...) \ BENCHMARK_MULTI_IMPL( \ FB_CONCATENATE(name, FB_CONCATENATE(_, param_name)), \ "%" FOLLY_PP_STRINGIZE(name) "(" FOLLY_PP_STRINGIZE(param_name) ")", \ unsigned, \ iters) { \ return name(iters, ##__VA_ARGS__); \ } /** * Draws a line of dashes. */ #define BENCHMARK_DRAW_LINE() \ [[maybe_unused]] static const bool FB_ANONYMOUS_VARIABLE( \ follyBenchmarkUnused) = \ (::folly::addBenchmark(__FILE__, "-", []() -> unsigned { return 0; }), \ true) /** * Prints arbitrary text. */ #define BENCHMARK_DRAW_TEXT(text) \ [[maybe_unused]] static const bool FB_ANONYMOUS_VARIABLE( \ follyBenchmarkUnused) = \ (::folly::addBenchmark(__FILE__, #text, []() -> unsigned { return 0; }), \ true) /** * Allows execution of code that doesn't count toward the benchmark's * time budget. Example: * * BENCHMARK_START_GROUP(insertVectorBegin, n) { * vector v; * BENCHMARK_SUSPEND { * v.reserve(n); * } * for (unsigned int i = 0; i < n; ++i) { * v.insert(v.begin(), 42); * } * } */ #define BENCHMARK_SUSPEND \ if (auto FB_ANONYMOUS_VARIABLE(BENCHMARK_SUSPEND) = \ ::folly::BenchmarkSuspender()) { \ } else ================================================ FILE: folly/BenchmarkUtil.cpp ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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 #include // @manual #include #include #include #include #include #include #include #include #if defined(__APPLE__) || defined(__FreeBSD__) #include #endif namespace folly { namespace detail { size_t bm_llc_size_fallback() { #if defined(__linux__) /// sysctl is deprecated on Linux; walk sysfs int cpu = sched_getcpu(); if (cpu < 0) { return 0; } auto dir = std::filesystem::path( fmt::format("/sys/devices/system/cpu/cpu{}/cache", cpu)); size_t size = 0; for (const auto& entry : std::filesystem::directory_iterator(dir)) { if (entry.path().filename().native().starts_with("index")) { std::ifstream size_ifs(entry.path() / "size"); std::string size_str; size_ifs >> size_str; size_t size_num = std::stoul(size_str); if (size_str.back() == 'K') { size_num *= 1024; } else if (size_str.back() == 'M') { size_num *= 1024 * 1024; } size = std::max(size, size_num); } } return size; #elif defined(__APPLE__) || defined(__FreeBSD__) auto names = std::array{ "hw.l3cachesize", // FreeBSD, Intel Mac "hw.l2cachesize", // Apple Silicon }; for (auto name : names) { uint64_t size = 0; size_t len = sizeof(size); if (sysctlbyname(name, &size, &len, NULL, 0) == 0 && size > 0) { return size; } } return 0; #elif defined(_WIN32) DWORD length = 0; GetLogicalProcessorInformation(nullptr, &length); if (GetLastError() != ERROR_INSUFFICIENT_BUFFER || length == 0) { return 0; } if (length % sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION) != 0) { return 0; } size_t const count = length / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION); std::vector infos(count); if (!GetLogicalProcessorInformation(infos.data(), &length)) { return 0; } size_t size = 0; for (auto const& info : infos) { if (info.Relationship == RelationCache) { size = std::max(size, info.Cache.Size); } } return size; #else return 0; #endif } } // namespace detail size_t bm_llc_size() { return kIsArchX86 || kIsArchAmd64 ? folly::x86_cpuid_get_llc_cache_info().cache_size() : detail::bm_llc_size_fallback(); } void bm_llc_evict(size_t value) { constexpr auto step = folly::hardware_constructive_interference_size / sizeof(size_t); constexpr auto words_per_line = folly::hardware_constructive_interference_size / step; static auto const llc_size = bm_llc_size(); auto const llc_lines = llc_size / folly::hardware_constructive_interference_size; static std::unique_ptr const vec{ new size_t[llc_lines * words_per_line]}; // do N passes over the region, each time touching one word per line for (size_t j = 0; j < step; ++j) { for (size_t i = 0; i < llc_lines; i += step) { vec[i * words_per_line + j] = value; } } } } // namespace folly ================================================ FILE: folly/BenchmarkUtil.h ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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 namespace folly { /** * Call doNotOptimizeAway(var) to ensure that var will be computed even * post-optimization. Use it for variables that are computed during * benchmarking but otherwise are useless. The compiler tends to do a * good job at eliminating unused variables, and this function fools it * into thinking var is in fact needed. * * Call makeUnpredictable(var) when you don't want the optimizer to use * its knowledge of var to shape the following code. This is useful * when constant propagation or power reduction is possible during your * benchmark but not in real use cases. */ template FOLLY_ALWAYS_INLINE void doNotOptimizeAway(const T& datum) { compiler_must_not_elide(datum); } template FOLLY_ALWAYS_INLINE void makeUnpredictable(T& datum) { compiler_must_not_predict(datum); } namespace detail { size_t bm_llc_size_fallback(); } /// bm_llc_size /// /// Calculates the size of the LLC (Last Level Cache, typically L3 on x86-64). size_t bm_llc_size(); /// bm_llc_evict /// /// Write to a large block of memory and evict whatever cache lines might /// currently be resident in any levels of cache. void bm_llc_evict(size_t value); } // namespace folly ================================================ FILE: folly/Bits.h ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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 // @shim ================================================ FILE: folly/CMakeLists.txt ================================================ # Copyright (c) Meta Platforms, Inc. and affiliates. # # 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. # @generated by folly/facebook/generate_cmake.py folly_add_library( NAME atomic_hash_array HEADERS AtomicHashArray-inl.h AtomicHashArray.h EXPORTED_DEPS folly_detail_atomic_hash_utils folly_detail_iterators folly_hash_hash folly_lang_bits folly_lang_exception folly_thread_cached_int folly_utility ) folly_add_library( NAME atomic_hash_map HEADERS AtomicHashMap-inl.h AtomicHashMap.h EXPORTED_DEPS folly_atomic_hash_array folly_c_portability folly_container_foreach folly_detail_atomic_hash_utils folly_detail_iterators folly_hash_hash folly_likely folly_thread_cached_int ) folly_add_library( NAME atomic_linked_list HEADERS AtomicIntrusiveLinkedList.h AtomicLinkedList.h EXPORTED_DEPS folly_memory ) folly_add_library( NAME atomic_unordered_map HEADERS AtomicUnorderedMap.h EXPORTED_DEPS folly_conv folly_detail_atomic_unordered_map_utils folly_lang_bits folly_likely folly_portability_sys_mman folly_portability_unistd folly_random folly_scope_guard folly_traits ) folly_add_library( NAME base64 HEADERS base64.h EXPORTED_DEPS folly_c_portability folly_detail_base64_detail_base64_api folly_detail_base64_detail_base64_common folly_lang_exception folly_memory_uninitialized_memory_hacks folly_portability ) folly_add_library( NAME benchmark TARGET_NAME follybenchmark EXCLUDE_FROM_MONOLITH SRCS Benchmark.cpp detail/BenchmarkAdaptive.cpp HEADERS Benchmark.h detail/BenchmarkAdaptive.h DEPS folly_detail_perf_scoped folly_file_util folly_json_dynamic folly_map_util folly_overload folly_random folly_stats_streaming_stats folly_string EXPORTED_DEPS folly_benchmark_util folly_functional_invoke folly_lang_hint folly_portability folly_portability_gflags folly_preprocessor folly_range folly_scope_guard folly_traits EXTERNAL_DEPS Boost::headers Boost::regex ) folly_add_library( NAME benchmark_util EXCLUDE_FROM_MONOLITH SRCS BenchmarkUtil.cpp HEADERS BenchmarkUtil.h DEPS folly_lang_align folly_portability_sched folly_portability_windows folly_system_arch_x86 EXPORTED_DEPS folly_lang_hint folly_portability ) folly_add_library( NAME bits HEADERS Bits.h EXPORTED_DEPS folly_lang_bits ) folly_add_library( NAME c_portability HEADERS CPortability.h EXPORTED_DEPS folly_portability_config ) folly_add_library( NAME cancellation_token SRCS CancellationToken.cpp HEADERS CancellationToken-inl.h CancellationToken.h DEPS folly_lang_new folly_optional folly_portability_memory folly_scope_guard folly_synchronization_detail_sleeper EXPORTED_DEPS folly_cpp_attributes folly_function folly_operation_cancelled ) folly_add_library( NAME chrono HEADERS Chrono.h EXPORTED_DEPS folly_lang_exception folly_portability folly_portability_time ) folly_add_library( NAME clock_gettime_wrappers SRCS ClockGettimeWrappers.cpp HEADERS ClockGettimeWrappers.h DEPS folly_likely EXPORTED_DEPS folly_portability_time ) folly_add_library( NAME concurrent_bit_set HEADERS ConcurrentBitSet.h EXPORTED_DEPS folly_portability ) folly_add_library( NAME concurrent_lazy HEADERS ConcurrentLazy.h EXPORTED_DEPS folly_functional_invoke folly_synchronization_delayed_init ) folly_add_library( NAME concurrent_skip_list HEADERS ConcurrentSkipList-inl.h ConcurrentSkipList.h EXPORTED_DEPS folly_constexpr_math folly_detail_iterators folly_likely folly_memory folly_synchronization_micro_spin_lock folly_thread_local EXTERNAL_DEPS Boost::headers ) folly_add_library( NAME config HEADERS folly-config.h ) folly_add_library( NAME constexpr_math HEADERS ConstexprMath.h EXPORTED_DEPS folly_lang_checked_math folly_portability folly_portability_constexpr ) folly_add_library( NAME constructor_callback_list HEADERS ConstructorCallbackList.h EXPORTED_DEPS folly_detail_static_singleton_manager folly_format folly_function folly_shared_mutex ) folly_add_library( NAME conv SRCS Conv.cpp HEADERS Conv.h DEPS folly_lang_safe_assert EXPORTED_DEPS folly_c_portability folly_demangle folly_expected folly_fbstring folly_lang_exception folly_lang_pretty folly_lang_to_ascii folly_likely folly_portability folly_portability_math folly_range folly_traits folly_unit folly_utility ) folly_add_library( NAME cpp_attributes HEADERS CppAttributes.h EXPORTED_DEPS folly_portability ) folly_add_library( NAME cpu_id HEADERS CpuId.h EXPORTED_DEPS folly_portability folly_system_arch_x86 ) folly_add_library( NAME default_keep_alive_executor HEADERS DefaultKeepAliveExecutor.h EXPORTED_DEPS folly_executor folly_executors_sequenced_executor folly_synchronization_baton ) folly_add_library( NAME demangle SRCS Demangle.cpp HEADERS Demangle.h DEPS folly_c_portability folly_functional_invoke folly_lang_c_string folly_utility EXPORTED_DEPS folly_fbstring folly_portability_config ) folly_add_library( NAME discriminated_ptr HEADERS DiscriminatedPtr.h EXPORTED_DEPS folly_detail_discriminated_ptr_detail folly_likely folly_portability ) folly_add_library( NAME dynamic HEADERS DynamicConverter.h dynamic-inl.h dynamic.h json.h EXPORTED_DEPS folly_json_dynamic ) folly_add_library( NAME exception HEADERS Exception.h EXPORTED_DEPS folly_conv folly_fbstring folly_likely folly_portability folly_portability_sys_types ) folly_add_library( NAME exception_string SRCS ExceptionString.cpp HEADERS ExceptionString.h DEPS folly_demangle folly_lang_exception folly_lang_type_info EXPORTED_DEPS folly_fbstring ) folly_add_library( NAME exception_wrapper SRCS ExceptionWrapper.cpp HEADERS ExceptionWrapper-inl.h ExceptionWrapper.h EXPORTED_DEPS folly_c_portability folly_cpp_attributes folly_demangle folly_exception_string folly_fbstring folly_functional_traits folly_lang_assume folly_lang_exception folly_portability folly_traits folly_utility ) folly_add_library( NAME executor SRCS Executor.cpp HEADERS Executor.h DEPS folly_exception_string folly_portability EXPORTED_DEPS folly_function folly_lang_exception folly_optional folly_range folly_utility ) folly_add_library( NAME expected HEADERS Expected.h EXPORTED_DEPS folly_c_portability folly_coro_coroutine folly_cpp_attributes folly_lang_exception folly_lang_hint folly_likely folly_portability folly_preprocessor folly_traits folly_unit folly_utility ) folly_add_library( NAME fbstring HEADERS FBString.h EXPORTED_DEPS folly_c_portability folly_cpp_attributes folly_hash_hash folly_lang_assume folly_lang_checked_math folly_lang_exception folly_likely folly_memory_malloc folly_portability folly_traits ) folly_add_library( NAME fbvector HEADERS FBVector.h EXPORTED_DEPS folly_container_fbvector ) folly_add_library( NAME file SRCS File.cpp HEADERS File.h DEPS folly_exception folly_file_util folly_portability_fmt_compile folly_portability_sys_file folly_scope_guard EXPORTED_DEPS folly_exception_wrapper folly_expected folly_file_util folly_portability folly_portability_fcntl folly_portability_unistd folly_range ) folly_add_library( NAME file_util SRCS FileUtil.cpp HEADERS FileUtil.h DEPS folly_detail_file_util_detail folly_detail_file_util_vector_detail folly_net_net_ops folly_portability_sockets folly_portability_stdlib folly_portability_sys_file folly_portability_sys_stat EXPORTED_DEPS folly_net_network_socket folly_portability folly_portability_fcntl folly_portability_sys_uio folly_portability_unistd folly_range folly_scope_guard ) folly_add_library( NAME fingerprint SRCS Fingerprint.cpp HEADERS Fingerprint.h DEPS folly_detail_fingerprint_polynomial folly_portability EXPORTED_DEPS folly_range ) folly_add_library( NAME fixed_string HEADERS FixedString.h EXPORTED_DEPS folly_constexpr_math folly_lang_exception folly_lang_ordering folly_portability folly_portability_constexpr folly_range folly_utility ) folly_add_library( NAME fmt_utility SRCS FmtUtility.cpp HEADERS FmtUtility.h DEPS folly_range folly_ssl_openssl_hash folly_string EXPORTED_DEPS folly_cpp_attributes ) folly_add_library( NAME format SRCS Format.cpp HEADERS Format-inl.h Format.h FormatArg.h DEPS folly_constexpr_math folly_container_array EXPORTED_DEPS folly_c_portability folly_conv folly_exception folly_format_traits folly_lang_exception folly_lang_to_ascii folly_likely folly_map_util folly_portability folly_portability_windows folly_range folly_string folly_traits ) folly_add_library( NAME format_traits HEADERS FormatTraits.h ) folly_add_library( NAME function HEADERS Function.h EXPORTED_DEPS folly_cpp_attributes folly_functional_invoke folly_lang_align folly_lang_exception folly_lang_new folly_portability folly_traits ) folly_add_library( NAME glog HEADERS GLog.h EXPORTED_DEPS folly_likely ) folly_add_library( NAME group_varint SRCS GroupVarint.cpp HEADERS GroupVarint.h DEPS folly_container_array EXPORTED_DEPS folly_detail_group_varint_detail folly_lang_bits folly_portability folly_portability_builtins folly_range ) folly_add_library( NAME hash HEADERS Hash.h EXPORTED_DEPS folly_hash_hash ) folly_add_library( NAME indestructible HEADERS Indestructible.h EXPORTED_DEPS folly_traits folly_utility ) folly_add_library( NAME indexed_mem_pool HEADERS IndexedMemPool.h EXPORTED_DEPS folly_concurrency_cache_locality folly_portability folly_portability_sys_mman folly_portability_unistd folly_synchronization_atomic_struct ) folly_add_library( NAME intrusive_list HEADERS IntrusiveList.h EXPORTED_DEPS folly_container_intrusive_list ) folly_add_library( NAME json EXPORTED_DEPS folly_dynamic ) folly_add_library( NAME json_patch HEADERS json_patch.h EXPORTED_DEPS folly_json_json_patch ) folly_add_library( NAME json_pointer HEADERS json_pointer.h EXPORTED_DEPS folly_json_json_pointer ) folly_add_library( NAME lazy HEADERS Lazy.h EXPORTED_DEPS folly_functional_invoke folly_optional ) folly_add_library( NAME likely HEADERS Likely.h EXPORTED_DEPS folly_lang_builtin ) folly_add_library( NAME map_util HEADERS MapUtil.h EXPORTED_DEPS folly_container_map_util ) folly_add_library( NAME math HEADERS Math.h ) folly_add_library( NAME maybe_managed_ptr HEADERS MaybeManagedPtr.h ) folly_add_library( NAME memcpy HEADERS FollyMemcpy.h EXPORTED_DEPS folly_memcpy-impl ) folly_add_library( NAME memcpy-impl SRCS FollyMemcpy.cpp ) folly_add_library( NAME memcpy-use EXCLUDE_FROM_MONOLITH SRCS FollyMemcpy.cpp ) # x86 assembly memcpy implementation (not supported on MSVC) if (IS_X86_64_ARCH AND NOT MSVC) folly_add_library( NAME memcpy_x86 SRCS memcpy.S ) set_property( TARGET folly_memcpy_x86_obj APPEND PROPERTY COMPILE_OPTIONS "-x" "assembler-with-cpp" ) endif() folly_add_library( NAME memory HEADERS Memory.h EXPORTED_DEPS folly_constexpr_math folly_functional_invoke folly_lang_align folly_lang_exception folly_lang_thunk folly_likely folly_memory_malloc folly_portability folly_portability_config folly_portability_constexpr folly_portability_malloc folly_traits folly_utility ) folly_add_library( NAME memset HEADERS FollyMemset.h EXPORTED_DEPS folly_memset-impl ) folly_add_library( NAME memset-impl SRCS FollyMemset.cpp ) folly_add_library( NAME memset-use EXCLUDE_FROM_MONOLITH SRCS FollyMemset.cpp ) folly_add_library( NAME micro_lock SRCS MicroLock.cpp HEADERS MicroLock.h DEPS folly_portability_asm EXPORTED_DEPS folly_optional folly_portability folly_synchronization_atomic_notification folly_synchronization_atomic_ref folly_utility ) folly_add_library( NAME micro_spin_lock HEADERS MicroSpinLock.h EXPORTED_DEPS folly_synchronization_micro_spin_lock ) folly_add_library( NAME move_wrapper HEADERS MoveWrapper.h ) folly_add_library( NAME mpmc_pipeline HEADERS MPMCPipeline.h EXPORTED_DEPS folly_detail_mpmc_pipeline_detail folly_portability ) folly_add_library( NAME mpmc_queue HEADERS MPMCQueue.h EXPORTED_DEPS folly_concurrency_cache_locality folly_detail_turn_sequencer folly_lang_exception folly_portability_unistd folly_traits ) folly_add_library( NAME network_address SRCS IPAddress.cpp IPAddressV4.cpp IPAddressV6.cpp MacAddress.cpp SocketAddress.cpp HEADERS IPAddress.h IPAddressException.h IPAddressV4.h IPAddressV6.h MacAddress.h SocketAddress.h DEPS folly_detail_ip_address_source folly_exception folly_format folly_net_net_ops folly_scope_guard folly_small_vector folly_string EXPORTED_DEPS folly_c_portability folly_constexpr_math folly_conv folly_detail_ip_address folly_expected folly_fbstring folly_hash_hash folly_lang_bits folly_lang_exception folly_net_network_socket folly_optional folly_portability folly_portability_config folly_portability_sockets folly_range folly_unit EXTERNAL_DEPS Boost::headers ) folly_add_library( NAME observer_container HEADERS ObserverContainer.h EXPORTED_DEPS folly_constructor_callback_list folly_function folly_io_async_destructor_check folly_lang_switch folly_optional folly_scope_guard folly_small_vector ) folly_add_library( NAME operation_cancelled HEADERS OperationCancelled.h ) folly_add_library( NAME optional HEADERS Optional.h EXPORTED_DEPS folly_coro_coroutine folly_hash_traits folly_lang_exception folly_portability folly_traits folly_utility ) folly_add_library( NAME overload HEADERS Overload.h EXPORTED_DEPS folly_functional_invoke folly_portability folly_traits ) folly_add_library( NAME packed_sync_ptr HEADERS PackedSyncPtr.h EXPORTED_DEPS folly_portability folly_synchronization_small_locks ) folly_add_library( NAME padded HEADERS Padded.h EXPORTED_DEPS folly_functional_invoke folly_portability folly_portability_sys_types folly_traits EXTERNAL_DEPS Boost::headers ) folly_add_library( NAME poly HEADERS Poly-inl.h Poly.h EXPORTED_DEPS folly_c_portability folly_cpp_attributes folly_detail_poly_detail folly_detail_typelist folly_lang_assume folly_poly_exception folly_traits ) folly_add_library( NAME poly_exception HEADERS PolyException.h EXPORTED_DEPS folly_c_portability ) folly_add_library( NAME portability HEADERS Portability.h EXPORTED_DEPS folly_c_portability folly_portability_config ) folly_add_library( NAME preprocessor HEADERS Preprocessor.h EXPORTED_DEPS folly_c_portability ) folly_add_library( NAME producer_consumer_queue HEADERS ProducerConsumerQueue.h EXPORTED_DEPS folly_concurrency_cache_locality ) folly_add_library( NAME random SRCS Random.cpp HEADERS Random-inl.h Random.h DEPS folly_cpp_attributes folly_detail_file_util_detail folly_portability_config folly_portability_sys_time folly_portability_unistd folly_singleton_thread_local folly_synchronization_relaxed_atomic folly_thread_local EXPORTED_DEPS folly_functional_invoke folly_lang_bits folly_portability folly_random_xoshiro256pp folly_traits ) folly_add_library( NAME range HEADERS Range.h EXPORTED_DEPS folly_cpu_id folly_detail_range_common folly_detail_range_simd folly_hash_spooky_hash_v2 folly_lang_c_string folly_lang_exception folly_likely folly_portability folly_portability_constexpr folly_traits ) folly_add_library( NAME replaceable HEADERS Replaceable.h EXPORTED_DEPS folly_portability folly_traits folly_utility ) folly_add_library( NAME rw_spin_lock HEADERS RWSpinLock.h EXPORTED_DEPS folly_synchronization_rw_spin_lock ) folly_add_library( NAME scope_guard SRCS ScopeGuard.cpp HEADERS ScopeGuard.h EXPORTED_DEPS folly_lang_exception folly_lang_uncaught_exceptions folly_portability folly_preprocessor folly_utility ) folly_add_library( NAME shared_mutex SRCS SharedMutex.cpp HEADERS SharedMutex.h DEPS folly_indestructible folly_lang_exception folly_portability_sys_resource EXPORTED_DEPS folly_c_portability folly_chrono_hardware folly_concurrency_cache_locality folly_cpp_attributes folly_detail_futex folly_likely folly_portability_asm folly_synchronization_lock folly_synchronization_relaxed_atomic folly_synchronization_sanitize_thread folly_system_thread_id ) folly_add_library( NAME singleton SRCS Singleton.cpp HEADERS Singleton-inl.h Singleton.h DEPS folly_demangle folly_experimental_symbolizer_symbolizer folly_lang_safe_assert folly_portability_config folly_portability_fmt_compile folly_scope_guard folly_system_at_fork EXPORTED_DEPS folly_cancellation_token folly_concurrency_core_cached_shared_ptr folly_concurrency_memory_read_mostly_shared_ptr folly_detail_singleton folly_detail_static_singleton_manager folly_exception folly_executor folly_hash_hash folly_lang_exception folly_memory folly_memory_sanitize_leak folly_synchronization_baton folly_synchronized ) folly_add_library( NAME singleton_thread_local SRCS SingletonThreadLocal.cpp HEADERS SingletonThreadLocal.h EXPORTED_DEPS folly_detail_iterators folly_detail_singleton folly_detail_unique_instance folly_functional_invoke folly_lang_hint folly_scope_guard folly_thread_local ) folly_add_library( NAME small_vector HEADERS small_vector.h EXPORTED_DEPS folly_container_small_vector ) folly_add_library( NAME sorted_vector_types HEADERS sorted_vector_types.h EXPORTED_DEPS folly_container_sorted_vector_types ) folly_add_library( NAME spin_lock HEADERS SpinLock.h EXPORTED_DEPS folly_portability folly_synchronization_small_locks ) folly_add_library( NAME stop_watch HEADERS stop_watch.h EXPORTED_DEPS folly_chrono folly_portability_time folly_utility ) folly_add_library( NAME string SRCS String.cpp HEADERS String-inl.h String.h DEPS folly_container_array EXPORTED_DEPS folly_container_reserve folly_conv folly_cpp_attributes folly_detail_simple_simd_string_utils folly_detail_split_string_simd folly_exception_string folly_optional folly_portability folly_range folly_scope_guard folly_traits folly_unit ) # subprocess is not supported on Windows if(NOT WIN32) folly_add_library( NAME subprocess SRCS Subprocess.cpp HEADERS Subprocess.h DEPS folly_conv folly_lang_assume folly_logging_logging folly_portability_dirent folly_portability_fcntl folly_portability_sockets folly_portability_stdlib folly_portability_sys_syscall folly_portability_unistd folly_scope_guard folly_string folly_system_at_fork folly_system_shell EXPORTED_DEPS folly_container_span folly_exception folly_file folly_file_util folly_function folly_gen_string folly_io_iobuf folly_map_util folly_optional folly_portability folly_portability_sys_resource folly_range EXTERNAL_DEPS Boost::headers ) endif() folly_add_library( NAME synchronized HEADERS Synchronized.h EXPORTED_DEPS folly_container_foreach folly_function folly_functional_apply_tuple folly_likely folly_preprocessor folly_shared_mutex folly_synchronization_lock folly_traits folly_utility ) folly_add_library( NAME synchronized_ptr HEADERS SynchronizedPtr.h EXPORTED_DEPS folly_synchronized ) folly_add_library( NAME thread_cached_int HEADERS ThreadCachedInt.h EXPORTED_DEPS folly_likely folly_thread_local ) folly_add_library( NAME thread_local HEADERS ThreadLocal.h EXPORTED_DEPS folly_detail_thread_local_detail folly_likely folly_portability folly_scope_guard folly_shared_mutex ) folly_add_library( NAME timeout_queue SRCS TimeoutQueue.cpp HEADERS TimeoutQueue.h EXTERNAL_DEPS Boost::headers ) folly_add_library( NAME token_bucket HEADERS TokenBucket.h EXPORTED_DEPS folly_concurrency_cache_locality folly_constexpr_math folly_likely folly_optional ) folly_add_library( NAME traits HEADERS Traits.h EXPORTED_DEPS folly_portability ) folly_add_library( NAME try SRCS Try.cpp HEADERS Try-inl.h Try.h EXPORTED_DEPS folly_exception_wrapper folly_functional_invoke folly_lang_exception folly_likely folly_memory folly_portability folly_unit folly_utility ) folly_add_library( NAME unicode SRCS Unicode.cpp HEADERS Unicode.h DEPS folly_conv EXPORTED_DEPS folly_lang_exception ) folly_add_library( NAME unit HEADERS Unit.h ) folly_add_library( NAME uri SRCS Uri.cpp HEADERS Uri-inl.h Uri.h EXPORTED_DEPS folly_conv folly_expected folly_hash_hash folly_string EXTERNAL_DEPS Boost::regex ) folly_add_library( NAME utf8_string HEADERS UTF8String.h EXPORTED_DEPS folly_range EXTERNAL_DEPS Boost::regex ) folly_add_library( NAME utility HEADERS Utility.h EXPORTED_DEPS folly_c_portability folly_portability folly_traits ) folly_add_library( NAME varint HEADERS Varint.h EXPORTED_DEPS folly_conv folly_expected folly_likely folly_portability folly_range ) folly_add_library( NAME virtual_executor HEADERS VirtualExecutor.h EXPORTED_DEPS folly_executors_virtual_executor ) add_subdirectory(algorithm) add_subdirectory(channels) add_subdirectory(chrono) add_subdirectory(cli) add_subdirectory(codec) add_subdirectory(compression) add_subdirectory(concurrency) add_subdirectory(container) add_subdirectory(coro) add_subdirectory(crypto) add_subdirectory(debugging) add_subdirectory(detail) add_subdirectory(executors) add_subdirectory(experimental) add_subdirectory(ext) add_subdirectory(external) add_subdirectory(fibers) add_subdirectory(functional) add_subdirectory(futures) add_subdirectory(gen) add_subdirectory(hash) add_subdirectory(init) add_subdirectory(io) add_subdirectory(json) add_subdirectory(lang) add_subdirectory(logging) add_subdirectory(memory) add_subdirectory(net) add_subdirectory(observer) add_subdirectory(poly) add_subdirectory(portability) if (PYTHON_EXTENSIONS) add_subdirectory(python) endif() add_subdirectory(random) add_subdirectory(result) add_subdirectory(settings) add_subdirectory(ssl) add_subdirectory(stats) add_subdirectory(synchronization) add_subdirectory(system) add_subdirectory(testing) add_subdirectory(tracing) # Resolve deferred dependencies now that all targets exist folly_resolve_deferred_dependencies() ================================================ FILE: folly/CODING_GUIDELINES.md ================================================ # Folly Coding Guidelines ## The Spirit of Folly Folly is a collection of core libraries, which engineers depend on. Therefore, the primary requirements when writing Folly code are: 1. Create reusable components. Do one job, and do it well. 2. Be complete. Missing APIs create friction for users. 3. Have strong unit tests. Uphold Folly's reputation of reliability. 4. Have documentation. Not everyone's an expert. ## The realities of Folly As an open-source library, there are a few necessary requirements: 1. Define symbols in namespace `folly`. Prefix macros with `FOLLY_`, and C symbols with `folly_`. 2. Put library-private symbols in subnamespace `detail` (e.g. `folly::detail::XYZ`) 3. Restrict dependencies to (a) the standard library, and (b) other Folly libraries. Some exceptions apply, such as select Boost components. 4. Be portable. Notably: - Use `throw_exception` from `folly/lang/Exception.h` instead of `throw` Folly only reimplements existing standard or boost libraries when there is good reason to. When improving upon an already-existing library: 1. Strive to be a drop-in replacement: - Have the same APIs - Have the same guarantees - Have the same naming convention ## Coding style Folly follows Meta's C++ Coding conventions. When changing a file that has non-standard style, consistency takes priority. Some Folly files, based on standard classes, use naming conventions from the C++ standard: `snake_case` instead of `camelCase`. If you are faced with this situation, use your judgement to decide which casing to use. ## Documentation style Follow the [Google developer documentation style guide](https://developers.google.com/style). Highlights: - Use standard American spelling and punctuation. - Use second person: "you" rather than "we." - Use active voice: make clear who's performing the action. - Be conversational and friendly. ================================================ FILE: folly/CPortability.h ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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 /* These definitions are in a separate file so that they * may be included from C- as well as C++-based projects. */ #include /** * Portable version check. */ #ifndef __GNUC_PREREQ #if defined __GNUC__ && defined __GNUC_MINOR__ /* nolint */ #define __GNUC_PREREQ(maj, min) \ ((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min)) #else /* nolint */ #define __GNUC_PREREQ(maj, min) 0 #endif #endif // portable version check for clang #ifndef __CLANG_PREREQ #if defined __clang__ && defined __clang_major__ && defined __clang_minor__ /* nolint */ #define __CLANG_PREREQ(maj, min) \ ((__clang_major__ << 16) + __clang_minor__ >= ((maj) << 16) + (min)) #else /* nolint */ #define __CLANG_PREREQ(maj, min) 0 #endif #endif /// FOLLY_GLIBC_PREREQ #if !defined(__GLIBC__) #define FOLLY_GLIBC_PREREQ(maj, min) 0 #else #define FOLLY_GLIBC_PREREQ(maj, min) \ (__GLIBC__ > (maj)) || (__GLIBC__ == (maj) && __GLIBC_MINOR__ >= (min)) #endif #if defined(__has_builtin) #define FOLLY_HAS_BUILTIN(...) __has_builtin(__VA_ARGS__) #else #define FOLLY_HAS_BUILTIN(...) 0 #endif #if defined(__has_feature) #define FOLLY_HAS_FEATURE(...) __has_feature(__VA_ARGS__) #else #define FOLLY_HAS_FEATURE(...) 0 #endif #if defined(__has_warning) #define FOLLY_HAS_WARNING(...) __has_warning(__VA_ARGS__) #else #define FOLLY_HAS_WARNING(...) 0 #endif /* FOLLY_SANITIZE_ADDRESS is defined to 1 if the current compilation unit * is being compiled with ASAN or HWASAN enabled. * * Beware when using this macro in a header file: this macro may change values * across compilation units if some libraries are built with ASAN/HWASAN enabled * and some built with ASAN/HWSAN disabled. For instance, this may occur, if * folly itself was compiled without ASAN/HWSAN but a downstream project that * uses folly is compiling with ASAN/HWSAN enabled. * * Use FOLLY_LIBRARY_SANITIZE_ADDRESS (defined in folly-config.h) to check if * folly itself was compiled with ASAN enabled. */ #ifndef FOLLY_SANITIZE_ADDRESS #if FOLLY_HAS_FEATURE(address_sanitizer) || defined(__SANITIZE_ADDRESS__) || \ FOLLY_HAS_FEATURE(hwaddress_sanitizer) #define FOLLY_SANITIZE_ADDRESS 1 #endif #endif /* Define attribute wrapper for function attribute used to disable * address sanitizer instrumentation. Unfortunately, this attribute * has issues when inlining is used, so disable that as well. */ #ifdef FOLLY_SANITIZE_ADDRESS #if defined(__clang__) #if __has_attribute(__no_sanitize__) #define FOLLY_DISABLE_ADDRESS_SANITIZER \ __attribute__((__no_sanitize__("address"), __noinline__)) \ __attribute__((__no_sanitize__("hwaddress"), __noinline__)) #elif __has_attribute(__no_address_safety_analysis__) #define FOLLY_DISABLE_ADDRESS_SANITIZER \ __attribute__((__no_address_safety_analysis__, __noinline__)) #elif __has_attribute(__no_sanitize_address__) #define FOLLY_DISABLE_ADDRESS_SANITIZER \ __attribute__((__no_sanitize_address__, __noinline__)) #endif #elif defined(__GNUC__) #define FOLLY_DISABLE_ADDRESS_SANITIZER \ __attribute__((__no_address_safety_analysis__, __noinline__)) #elif defined(_MSC_VER) #define FOLLY_DISABLE_ADDRESS_SANITIZER __declspec(no_sanitize_address) #endif #endif #ifndef FOLLY_DISABLE_ADDRESS_SANITIZER #define FOLLY_DISABLE_ADDRESS_SANITIZER #endif /* Define a convenience macro to test when thread sanitizer is being used * across the different compilers (e.g. clang, gcc) */ #ifndef FOLLY_SANITIZE_THREAD #if FOLLY_HAS_FEATURE(thread_sanitizer) || defined(__SANITIZE_THREAD__) #define FOLLY_SANITIZE_THREAD 1 #endif #endif #ifdef FOLLY_SANITIZE_THREAD #define FOLLY_DISABLE_THREAD_SANITIZER \ __attribute__((no_sanitize_thread, noinline)) #else #define FOLLY_DISABLE_THREAD_SANITIZER #endif /** * Define a convenience macro to test when memory sanitizer is being used * across the different compilers (e.g. clang, gcc) */ #ifndef FOLLY_SANITIZE_MEMORY #if FOLLY_HAS_FEATURE(memory_sanitizer) || defined(__SANITIZE_MEMORY__) #define FOLLY_SANITIZE_MEMORY 1 #endif #endif #ifdef FOLLY_SANITIZE_MEMORY #define FOLLY_DISABLE_MEMORY_SANITIZER \ __attribute__((no_sanitize_memory, noinline)) #else #define FOLLY_DISABLE_MEMORY_SANITIZER #endif /** * Define a convenience macro to test when dataflow sanitizer is being used * across the different compilers (e.g. clang, gcc) */ #ifndef FOLLY_SANITIZE_DATAFLOW #if FOLLY_HAS_FEATURE(dataflow_sanitizer) || defined(__SANITIZE_DATAFLOW__) #define FOLLY_SANITIZE_DATAFLOW 1 #endif #endif #ifdef FOLLY_SANITIZE_DATAFLOW #define FOLLY_DISABLE_DATAFLOW_SANITIZER \ __attribute__((no_sanitize_dataflow, noinline)) #else #define FOLLY_DISABLE_DATAFLOW_SANITIZER #endif /** * Define a convenience macro to test when undefined-behavior sanitizer is being * used across the different compilers (e.g. clang, gcc) */ #ifndef FOLLY_SANITIZE_UNDEFINED_BEHAVIOR #if FOLLY_HAS_FEATURE(undefined_behavior_sanitizer) || \ defined(__SANITIZER_UNDEFINED__) #define FOLLY_SANITIZE_UNDEFINED_BEHAVIOR(...) 1 #endif #endif #ifdef FOLLY_SANITIZE_UNDEFINED_BEHAVIOR #define FOLLY_DISABLE_UNDEFINED_BEHAVIOR_SANITIZER(...) \ __attribute__((no_sanitize(__VA_ARGS__))) #else #define FOLLY_DISABLE_UNDEFINED_BEHAVIOR_SANITIZER(...) #endif /** * Define a convenience macro to test when ASAN, UBSAN, TSAN or MSAN sanitizer * are being used */ #ifndef FOLLY_SANITIZE #if defined(FOLLY_SANITIZE_ADDRESS) || defined(FOLLY_SANITIZE_THREAD) || \ defined(FOLLY_SANITIZE_MEMORY) || defined(FOLLY_SANITIZE_DATAFLOW) || \ defined(FOLLY_SANITIZE_UNDEFINED_BEHAVIOR) #define FOLLY_SANITIZE 1 #endif #endif #define FOLLY_DISABLE_SANITIZERS \ FOLLY_DISABLE_ADDRESS_SANITIZER \ FOLLY_DISABLE_THREAD_SANITIZER \ FOLLY_DISABLE_MEMORY_SANITIZER \ FOLLY_DISABLE_UNDEFINED_BEHAVIOR_SANITIZER("undefined") /** * Macro for marking functions as having public visibility. */ #if defined(__GNUC__) #define FOLLY_EXPORT __attribute__((__visibility__("default"))) #else #define FOLLY_EXPORT #endif // noinline #ifdef _MSC_VER #define FOLLY_NOINLINE __declspec(noinline) #elif defined(__HIP_PLATFORM_HCC__) // HIP software stack defines its own __noinline__ macro. #define FOLLY_NOINLINE __attribute__((noinline)) #elif defined(__GNUC__) #define FOLLY_NOINLINE __attribute__((__noinline__)) #else #define FOLLY_NOINLINE #endif // always inline #ifdef _MSC_VER #define FOLLY_ALWAYS_INLINE __forceinline #elif defined(__GNUC__) #define FOLLY_ALWAYS_INLINE inline __attribute__((__always_inline__)) #else #define FOLLY_ALWAYS_INLINE inline #endif // attribute hidden #if defined(_MSC_VER) #define FOLLY_ATTR_VISIBILITY_HIDDEN #elif defined(__GNUC__) #define FOLLY_ATTR_VISIBILITY_HIDDEN __attribute__((__visibility__("hidden"))) #else #define FOLLY_ATTR_VISIBILITY_HIDDEN #endif // An attribute for marking symbols as weak, if supported #if FOLLY_HAVE_WEAK_SYMBOLS #define FOLLY_ATTR_WEAK __attribute__((__weak__)) #else #define FOLLY_ATTR_WEAK #endif #if defined(__has_attribute) #if __has_attribute(weak) #define FOLLY_ATTR_WEAK_SYMBOLS_COMPILE_TIME __attribute__((__weak__)) #else #define FOLLY_ATTR_WEAK_SYMBOLS_COMPILE_TIME #endif #else #define FOLLY_ATTR_WEAK_SYMBOLS_COMPILE_TIME #endif // Microsoft ABI version (can be overridden manually if necessary) #ifndef FOLLY_MICROSOFT_ABI_VER #ifdef _MSC_VER #define FOLLY_MICROSOFT_ABI_VER _MSC_VER #endif #endif // FOLLY_NAME_RESOLVABLE // // An attribute that marks a function or variable as needing to be resolvable // by name. This generally is needed if inline assembly refers to the variable // by string name. #ifdef __roar__ #define FOLLY_NAME_RESOLVABLE __attribute__((roar_resolvable_by_name)) #else #define FOLLY_NAME_RESOLVABLE #endif // FOLLY_ERASE // // A conceptual attribute/syntax combo for erasing a function from the build // artifacts and forcing all call-sites to inline the callee, at least as far // as each compiler supports. // // Semantically includes the inline specifier. #define FOLLY_ERASE FOLLY_ALWAYS_INLINE FOLLY_ATTR_VISIBILITY_HIDDEN // FOLLY_ERASE_NOINLINE // // Like FOLLY_ERASE, but also noinline. The naming similarity with FOLLY_ERASE // is specifically desirable. #define FOLLY_ERASE_NOINLINE FOLLY_NOINLINE FOLLY_ATTR_VISIBILITY_HIDDEN // FOLLY_ERASE_HACK_GCC // // Equivalent to FOLLY_ERASE, but without hiding under gcc. Useful when applied // to a function which may sometimes be hidden separately, for example by being // declared in an anonymous namespace, since in such cases with -Wattributes // enabled, gcc would emit: 'visibility' attribute ignored. // // Semantically includes the inline specifier. #if defined(__GNUC__) && !defined(__clang__) #define FOLLY_ERASE_HACK_GCC FOLLY_ALWAYS_INLINE #else #define FOLLY_ERASE_HACK_GCC FOLLY_ERASE #endif // FOLLY_ERASE_TRYCATCH // // Equivalent to FOLLY_ERASE, but for code which might contain explicit // exception handling. Has the effect of FOLLY_ERASE, except under MSVC which // warns about __forceinline when functions contain exception handling. // // Semantically includes the inline specifier. #ifdef _MSC_VER #define FOLLY_ERASE_TRYCATCH inline #else #define FOLLY_ERASE_TRYCATCH FOLLY_ERASE #endif // Generalize warning push/pop. #if defined(__GNUC__) || defined(__clang__) // Clang & GCC #define FOLLY_PUSH_WARNING _Pragma("GCC diagnostic push") #define FOLLY_POP_WARNING _Pragma("GCC diagnostic pop") #define FOLLY_GNU_ENABLE_WARNING_INTERNAL2(warningName) #warningName #define FOLLY_GNU_DISABLE_WARNING_INTERNAL2(warningName) #warningName #define FOLLY_GNU_ENABLE_ERROR_INTERNAL2(warningName) #warningName #define FOLLY_GNU_DISABLE_WARNING(warningName) \ _Pragma( \ FOLLY_GNU_DISABLE_WARNING_INTERNAL2(GCC diagnostic ignored warningName)) #define FOLLY_GNU_ENABLE_WARNING(warningName) \ _Pragma( \ FOLLY_GNU_ENABLE_WARNING_INTERNAL2(GCC diagnostic warning warningName)) #define FOLLY_GNU_ENABLE_ERROR(warningName) \ _Pragma(FOLLY_GNU_ENABLE_ERROR_INTERNAL2(GCC diagnostic error warningName)) #ifdef __clang__ #define FOLLY_CLANG_DISABLE_WARNING(warningName) \ FOLLY_GNU_DISABLE_WARNING(warningName) #define FOLLY_GCC_DISABLE_WARNING(warningName) #else #define FOLLY_CLANG_DISABLE_WARNING(warningName) #define FOLLY_GCC_DISABLE_WARNING(warningName) \ FOLLY_GNU_DISABLE_WARNING(warningName) #endif #define FOLLY_MSVC_DISABLE_WARNING(warningNumber) #elif defined(_MSC_VER) #define FOLLY_PUSH_WARNING __pragma(warning(push)) #define FOLLY_POP_WARNING __pragma(warning(pop)) // Disable the GCC warnings. #define FOLLY_GNU_ENABLE_WARNING(warningName) #define FOLLY_GNU_DISABLE_WARNING(warningName) #define FOLLY_GNU_ENABLE_ERROR(warningName) #define FOLLY_GCC_DISABLE_WARNING(warningName) #define FOLLY_CLANG_DISABLE_WARNING(warningName) #define FOLLY_MSVC_DISABLE_WARNING(warningNumber) \ __pragma(warning(disable : warningNumber)) #else #define FOLLY_PUSH_WARNING #define FOLLY_POP_WARNING #define FOLLY_GNU_ENABLE_WARNING(warningName) #define FOLLY_GNU_DISABLE_WARNING(warningName) #define FOLLY_GNU_ENABLE_ERROR(warningName) #define FOLLY_GCC_DISABLE_WARNING(warningName) #define FOLLY_CLANG_DISABLE_WARNING(warningName) #define FOLLY_MSVC_DISABLE_WARNING(warningNumber) #endif #ifdef FOLLY_HAVE_SHADOW_LOCAL_WARNINGS #define FOLLY_GCC_DISABLE_NEW_SHADOW_WARNINGS \ FOLLY_GNU_DISABLE_WARNING("-Wshadow-compatible-local") \ FOLLY_GNU_DISABLE_WARNING("-Wshadow-local") \ FOLLY_GNU_DISABLE_WARNING("-Wshadow") #else #define FOLLY_GCC_DISABLE_NEW_SHADOW_WARNINGS /* empty */ #endif #if defined(_MSC_VER) #define FOLLY_MSVC_DECLSPEC(...) __declspec(__VA_ARGS__) #else #define FOLLY_MSVC_DECLSPEC(...) #endif ================================================ FILE: folly/CancellationToken-inl.h ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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 #include #include #include #include #include #include namespace folly { namespace detail { struct MergingCancellationStateTag {}; // Internal cancellation state object. class CancellationState { public: [[nodiscard]] static CancellationStateSourcePtr create(); protected: // Constructed initially with a CancellationSource reference count of 1. CancellationState() noexcept; // Constructed initially with a CancellationToken reference count of 1. explicit CancellationState(MergingCancellationStateTag) noexcept; virtual ~CancellationState(); friend struct CancellationStateTokenDeleter; friend struct CancellationStateSourceDeleter; void removeTokenReference() noexcept; void removeSourceReference() noexcept; public: [[nodiscard]] CancellationStateTokenPtr addTokenReference() noexcept; [[nodiscard]] CancellationStateSourcePtr addSourceReference() noexcept; bool tryAddCallback( CancellationCallback* callback, bool incrementRefCountIfSuccessful) noexcept; void removeCallback(CancellationCallback* callback) noexcept; bool isCancellationRequested() const noexcept; bool canBeCancelled() const noexcept; // Request cancellation. // Return 'true' if cancellation had already been requested. // Return 'false' if this was the first thread to request // cancellation. bool requestCancellation() noexcept; private: void lock() noexcept; void unlock() noexcept; void unlockAndIncrementTokenCount() noexcept; void unlockAndDecrementTokenCount() noexcept; bool tryLockAndCancelUnlessCancelled() noexcept; template bool tryLock(Predicate predicate) noexcept; static bool canBeCancelled(std::uint64_t state) noexcept; static bool isCancellationRequested(std::uint64_t state) noexcept; static bool isLocked(std::uint64_t state) noexcept; static constexpr std::uint64_t kCancellationRequestedFlag = 1; static constexpr std::uint64_t kLockedFlag = 2; static constexpr std::uint64_t kMergingFlag = 4; static constexpr std::uint64_t kTokenReferenceCountIncrement = 8; static constexpr std::uint64_t kSourceReferenceCountIncrement = std::uint64_t(1) << 34u; static constexpr std::uint64_t kTokenReferenceCountMask = (kSourceReferenceCountIncrement - 1u) - (kTokenReferenceCountIncrement - 1u); static constexpr std::uint64_t kSourceReferenceCountMask = std::numeric_limits::max() - (kSourceReferenceCountIncrement - 1u); // Bit 0 - Cancellation Requested // Bit 1 - Locked Flag // Bit 2 - MergingCancellationState Flag // Bits 3-33 - Token reference count (max ~2 billion) // Bits 34-63 - Source reference count (max ~1 billion) std::atomic state_; CancellationCallback* head_{nullptr}; std::thread::id signallingThreadId_; }; template class CancellationStateWithData : public CancellationState { template CancellationStateWithData(Args&&... data); public: template [[nodiscard]] static std:: pair*> create(Args&&... data); private: std::tuple data_; }; inline void CancellationStateTokenDeleter::operator()( CancellationState* state) noexcept { state->removeTokenReference(); } inline void CancellationStateSourceDeleter::operator()( CancellationState* state) noexcept { state->removeSourceReference(); } } // namespace detail inline CancellationToken::CancellationToken( const CancellationToken& other) noexcept : state_() { if (other.state_) { state_ = other.state_->addTokenReference(); } } inline CancellationToken::CancellationToken(CancellationToken&& other) noexcept : state_(std::move(other.state_)) {} inline CancellationToken& CancellationToken::operator=( const CancellationToken& other) noexcept { if (state_ != other.state_) { CancellationToken temp{other}; swap(temp); } return *this; } inline CancellationToken& CancellationToken::operator=( CancellationToken&& other) noexcept { state_ = std::move(other.state_); return *this; } inline bool CancellationToken::isCancellationRequested() const noexcept { return state_ != nullptr && state_->isCancellationRequested(); } inline bool CancellationToken::canBeCancelled() const noexcept { return state_ != nullptr && state_->canBeCancelled(); } inline void CancellationToken::swap(CancellationToken& other) noexcept { std::swap(state_, other.state_); } inline CancellationToken::CancellationToken( detail::CancellationStateTokenPtr state) noexcept : state_(std::move(state)) {} inline bool operator==( const CancellationToken& a, const CancellationToken& b) noexcept { return a.state_ == b.state_; } inline bool operator!=( const CancellationToken& a, const CancellationToken& b) noexcept { return !(a == b); } inline CancellationSource::CancellationSource() : state_(detail::CancellationState::create()) {} inline CancellationSource::CancellationSource( const CancellationSource& other) noexcept : state_() { if (other.state_) { state_ = other.state_->addSourceReference(); } } inline CancellationSource::CancellationSource( CancellationSource&& other) noexcept : state_(std::move(other.state_)) {} inline CancellationSource& CancellationSource::operator=( const CancellationSource& other) noexcept { if (state_ != other.state_) { CancellationSource temp{other}; swap(temp); } return *this; } inline CancellationSource& CancellationSource::operator=( CancellationSource&& other) noexcept { state_ = std::move(other.state_); return *this; } inline CancellationSource CancellationSource::invalid() noexcept { return CancellationSource{detail::CancellationStateSourcePtr{}}; } inline bool CancellationSource::isCancellationRequested() const noexcept { return state_ != nullptr && state_->isCancellationRequested(); } inline bool CancellationSource::canBeCancelled() const noexcept { return state_ != nullptr; } inline CancellationToken CancellationSource::getToken() const noexcept { if (state_ != nullptr) { return CancellationToken{state_->addTokenReference()}; } return CancellationToken{}; } inline bool CancellationSource::requestCancellation() const noexcept { if (state_ != nullptr) { return state_->requestCancellation(); } return false; } inline void CancellationSource::swap(CancellationSource& other) noexcept { std::swap(state_, other.state_); } inline CancellationSource::CancellationSource( detail::CancellationStateSourcePtr&& state) noexcept : state_(std::move(state)) {} template < typename Callable, std::enable_if_t< std::is_constructible:: value, int>> inline CancellationCallback::CancellationCallback( CancellationToken&& ct, Callable&& callable) : next_(nullptr), prevNext_(nullptr), state_(nullptr), callback_(static_cast(callable)), destructorHasRunInsideCallback_(nullptr), callbackCompleted_(false) { if (ct.state_ != nullptr && ct.state_->tryAddCallback(this, false)) { state_ = ct.state_.release(); } } template < typename Callable, std::enable_if_t< std::is_constructible:: value, int>> inline CancellationCallback::CancellationCallback( const CancellationToken& ct, Callable&& callable) : next_(nullptr), prevNext_(nullptr), state_(nullptr), callback_(static_cast(callable)), destructorHasRunInsideCallback_(nullptr), callbackCompleted_(false) { if (ct.state_ != nullptr && ct.state_->tryAddCallback(this, true)) { state_ = ct.state_.get(); } } inline CancellationCallback::~CancellationCallback() { if (state_ != nullptr) { state_->removeCallback(this); } } inline void CancellationCallback::invokeCallback() noexcept { // Invoke within a noexcept context so that we std::terminate() if it throws. callback_(); } namespace detail { inline CancellationStateSourcePtr CancellationState::create() { return CancellationStateSourcePtr{new CancellationState()}; } inline CancellationState::CancellationState() noexcept : state_(kSourceReferenceCountIncrement) {} inline CancellationState::CancellationState( MergingCancellationStateTag) noexcept : state_(kTokenReferenceCountIncrement | kMergingFlag) {} inline CancellationStateTokenPtr CancellationState::addTokenReference() noexcept { state_.fetch_add(kTokenReferenceCountIncrement, std::memory_order_relaxed); return CancellationStateTokenPtr{this}; } // This `alignas()` is here because "create" will going to allocate this // back-to-back with our `CancellationCallback` array. class alignas(CancellationState) alignas(CancellationCallback) MergingCancellationState : public CancellationState { // There's a noticeable perf gain from specializing the constructors struct CopyTag {}; struct MoveTag {}; struct CopyMoveTag {}; explicit MergingCancellationState(); CancellationCallback* callbackEnd_; // byte after the last callback public: // Ctors are a private implementation detail of the create*() factory funcs explicit MergingCancellationState( CopyTag, size_t nCopy, const CancellationToken** copyToks); explicit MergingCancellationState( MoveTag, size_t nMove, CancellationToken** moveToks); explicit MergingCancellationState( CopyMoveTag, size_t nCopy, const CancellationToken** copyToks, size_t nMove, CancellationToken** moveToks); ~MergingCancellationState() override; static CancellationStateTokenPtr createCopy( size_t nCopy, const CancellationToken** copyToks); static CancellationStateTokenPtr createMove( size_t nMove, CancellationToken** moveToks); static CancellationStateTokenPtr createCopyMove( size_t nCopy, const CancellationToken** copyToks, size_t nMove, CancellationToken** moveToks); void destroy() noexcept; }; inline void CancellationState::removeTokenReference() noexcept { const auto oldState = state_.fetch_sub( kTokenReferenceCountIncrement, std::memory_order_acq_rel); DCHECK( (oldState & kTokenReferenceCountMask) >= kTokenReferenceCountIncrement); if (oldState < (2 * kTokenReferenceCountIncrement)) { if (oldState & kMergingFlag) { static_cast(this)->destroy(); } else { delete this; } } } inline CancellationStateSourcePtr CancellationState::addSourceReference() noexcept { state_.fetch_add(kSourceReferenceCountIncrement, std::memory_order_relaxed); return CancellationStateSourcePtr{this}; } inline void CancellationState::removeSourceReference() noexcept { const auto oldState = state_.fetch_sub( kSourceReferenceCountIncrement, std::memory_order_acq_rel); DCHECK( (oldState & kSourceReferenceCountMask) >= kSourceReferenceCountIncrement); if (oldState < (kSourceReferenceCountIncrement + kTokenReferenceCountIncrement)) { // No "free()" branch because "merging" state has no source pointers. DCHECK(!(oldState & kMergingFlag)); delete this; } } inline bool CancellationState::isCancellationRequested() const noexcept { return isCancellationRequested(state_.load(std::memory_order_acquire)); } inline bool CancellationState::canBeCancelled() const noexcept { return canBeCancelled(state_.load(std::memory_order_acquire)); } inline bool CancellationState::canBeCancelled(std::uint64_t state) noexcept { // Can be cancelled if there is at least one CancellationSource ref-count // or if cancellation has been requested. return (state >= kSourceReferenceCountIncrement) || (state & kMergingFlag) != 0 || isCancellationRequested(state); } inline bool CancellationState::isCancellationRequested( std::uint64_t state) noexcept { return (state & kCancellationRequestedFlag) != 0; } inline bool CancellationState::isLocked(std::uint64_t state) noexcept { return (state & kLockedFlag) != 0; } template struct WithDataTag {}; template template CancellationStateWithData::CancellationStateWithData(Args&&... data) : data_(std::forward(data)...) {} template template std::pair*> CancellationStateWithData::create(Args&&... data) { auto* state = new CancellationStateWithData(std::forward(data)...); return {CancellationStateSourcePtr{state}, &state->data_}; } } // namespace detail template std::pair*> CancellationSource::create( detail::WithDataTag, Args&&... data) { auto cancellationStateWithData = detail::CancellationStateWithData::create( std::forward(data)...); return { CancellationSource{std::move(cancellationStateWithData.first)}, cancellationStateWithData.second}; } template CancellationToken cancellation_token_merge_fn::operator()( Ts&&... tokens) const { if constexpr (sizeof...(Ts) == 0) { return CancellationToken(); } else if constexpr (sizeof...(Ts) == 1) { return (tokens, ...); } else { constexpr size_t N = sizeof...(Ts); constexpr size_t NCopy = ((std::is_reference_v || std::is_const_v)+...); std::array copyToks; std::array moveToks; const detail::CancellationState* prevState = nullptr; size_t copyIdx = 0, moveIdx = 0; ( [&] { constexpr bool mustCopy = std::is_reference_v || std::is_const_v; auto* state = tokens.state_.get(); if (!state) { return; // Omit empties } if (state == prevState) { if constexpr (!mustCopy) { // Move out the input token, although we didn't use it. The // goal is to make deduplication non-observable by the user. std::exchange(tokens, {}); } return; // Omit adjacent duplicates } prevState = state; if constexpr (mustCopy) { copyToks[copyIdx++] = &tokens; } else { moveToks[moveIdx++] = &tokens; } }(), ...); size_t n = copyIdx + moveIdx; if (n == 0) { return CancellationToken(); } else if (n == 1) { if (moveIdx) { // A ternary would have type `const CT*` and NOT move! return std::move(*moveToks[0]); } return *copyToks[0]; } else if constexpr (NCopy == N) { return CancellationToken( detail::MergingCancellationState::createCopy( copyIdx, copyToks.data())); } else if constexpr (NCopy == 0) { return CancellationToken( detail::MergingCancellationState::createMove( moveIdx, moveToks.data())); } else { return CancellationToken( detail::MergingCancellationState::createCopyMove( copyIdx, copyToks.data(), moveIdx, moveToks.data())); } } } } // namespace folly ================================================ FILE: folly/CancellationToken.cpp ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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 #include #include #include #include #include #include #include #include #include namespace folly { namespace detail { CancellationState::~CancellationState() { DCHECK(head_ == nullptr); DCHECK(!isLocked(state_.load(std::memory_order_relaxed))); DCHECK( state_.load(std::memory_order_relaxed) < kTokenReferenceCountIncrement); } bool CancellationState::tryAddCallback( CancellationCallback* callback, bool incrementRefCountIfSuccessful) noexcept { // Try to acquire the lock, but abandon trying to acquire the lock if // cancellation has already been requested (we can just immediately invoke // the callback) or if cancellation can never be requested (we can just // skip registration). if (!tryLock([callback](std::uint64_t oldState) noexcept { if (isCancellationRequested(oldState)) { callback->invokeCallback(); return false; } return canBeCancelled(oldState); })) { return false; } // We've acquired the lock and cancellation has not yet been requested. // Push this callback onto the head of the list. if (head_ != nullptr) { head_->prevNext_ = &callback->next_; } callback->next_ = head_; callback->prevNext_ = &head_; head_ = callback; if (incrementRefCountIfSuccessful) { // Combine multiple atomic operations into a single atomic operation. unlockAndIncrementTokenCount(); } else { unlock(); } // Successfully added the callback. return true; } void CancellationState::removeCallback( CancellationCallback* callback) noexcept { DCHECK(callback != nullptr); lock(); if (callback->prevNext_ != nullptr) { // Still registered in the list => not yet executed. // Just remove it from the list. *callback->prevNext_ = callback->next_; if (callback->next_ != nullptr) { callback->next_->prevNext_ = callback->prevNext_; } unlockAndDecrementTokenCount(); return; } unlock(); // Callback has either already executed or is executing concurrently on // another thread. if (signallingThreadId_ == std::this_thread::get_id()) { // Callback executed on this thread or is still currently executing // and is deregistering itself from within the callback. if (callback->destructorHasRunInsideCallback_ != nullptr) { // Currently inside the callback, let the requestCancellation() method // know the object is about to be destructed and that it should // not try to access the object when the callback returns. *callback->destructorHasRunInsideCallback_ = true; } } else { // Callback is currently executing on another thread, block until it // finishes executing. folly::detail::Sleeper sleeper; while (!callback->callbackCompleted_.load(std::memory_order_acquire)) { sleeper.wait(); } } removeTokenReference(); } bool CancellationState::requestCancellation() noexcept { if (!tryLockAndCancelUnlessCancelled()) { // Was already marked as cancelled return true; } // This thread marked as cancelled and acquired the lock signallingThreadId_ = std::this_thread::get_id(); while (head_ != nullptr) { // Dequeue the first item on the queue. CancellationCallback* callback = head_; head_ = callback->next_; const bool anyMore = head_ != nullptr; if (anyMore) { head_->prevNext_ = &head_; } // Mark this item as removed from the list. callback->prevNext_ = nullptr; // Don't hold the lock while executing the callback // as we don't want to block other threads from // deregistering callbacks. unlock(); // TRICKY: Need to store a flag on the stack here that the callback // can use to signal that the destructor was executed inline // during the call. // If the destructor was executed inline then it's not safe to // dereference 'callback' after 'invokeCallback()' returns. // If the destructor runs on some other thread then the other // thread will block waiting for this thread to signal that the // callback has finished executing. bool destructorHasRunInsideCallback = false; callback->destructorHasRunInsideCallback_ = &destructorHasRunInsideCallback; callback->invokeCallback(); if (!destructorHasRunInsideCallback) { callback->destructorHasRunInsideCallback_ = nullptr; callback->callbackCompleted_.store(true, std::memory_order_release); } if (!anyMore) { // This was the last item in the queue when we dequeued it. // No more items should be added to the queue after we have // marked the state as cancelled, only removed from the queue. // Avoid acquiring/releasing the lock in this case. return false; } lock(); } unlock(); return false; } void CancellationState::lock() noexcept { folly::detail::Sleeper sleeper; std::uint64_t oldState = state_.load(std::memory_order_relaxed); do { while (isLocked(oldState)) { sleeper.wait(); oldState = state_.load(std::memory_order_relaxed); } } while (!state_.compare_exchange_weak( oldState, oldState | kLockedFlag, std::memory_order_acquire, std::memory_order_relaxed)); } void CancellationState::unlock() noexcept { state_.fetch_sub(kLockedFlag, std::memory_order_release); } void CancellationState::unlockAndIncrementTokenCount() noexcept { state_.fetch_sub( kLockedFlag - kTokenReferenceCountIncrement, std::memory_order_release); } void CancellationState::unlockAndDecrementTokenCount() noexcept { auto oldState = state_.fetch_sub( kLockedFlag + kTokenReferenceCountIncrement, std::memory_order_acq_rel); if (oldState < (kLockedFlag + 2 * kTokenReferenceCountIncrement)) { // `MergedTokenDestroyedViaCallback` shows how this is triggered. if (UNLIKELY(oldState & kMergingFlag)) { static_cast(this)->destroy(); } else { delete this; } } } bool CancellationState::tryLockAndCancelUnlessCancelled() noexcept { folly::detail::Sleeper sleeper; std::uint64_t oldState = state_.load(std::memory_order_acquire); while (true) { if (isCancellationRequested(oldState)) { return false; } else if (isLocked(oldState)) { sleeper.wait(); oldState = state_.load(std::memory_order_acquire); } else if (state_.compare_exchange_weak( oldState, oldState | kLockedFlag | kCancellationRequestedFlag, std::memory_order_acq_rel, std::memory_order_acquire)) { return true; } } } template bool CancellationState::tryLock(Predicate predicate) noexcept { folly::detail::Sleeper sleeper; std::uint64_t oldState = state_.load(std::memory_order_acquire); while (true) { if (!predicate(oldState)) { return false; } else if (isLocked(oldState)) { sleeper.wait(); oldState = state_.load(std::memory_order_acquire); } else if (state_.compare_exchange_weak( oldState, oldState | kLockedFlag, std::memory_order_acquire)) { return true; } } } // CTOR EXCEPTION SAFETY: In case the `CancellationCallback` ctors below // throw, we increment `callbackEnd_` as we go. This ensures that the dtor // unwinds only the ctors that succeeded. MergingCancellationState::MergingCancellationState() : CancellationState(MergingCancellationStateTag{}), callbackEnd_(reinterpret_cast(this + 1)) {} MergingCancellationState::MergingCancellationState( CopyTag, size_t nCopy, const CancellationToken** copyToks) : MergingCancellationState() { for (size_t i = 0; i < nCopy; ++i, ++callbackEnd_) { new (callbackEnd_) CancellationCallback(*copyToks[i], [this] { requestCancellation(); }); } } MergingCancellationState::MergingCancellationState( MoveTag, size_t nMove, CancellationToken** moveToks) : MergingCancellationState() { for (size_t i = 0; i < nMove; ++i, ++callbackEnd_) { new (callbackEnd_) CancellationCallback(std::move(*moveToks[i]), [this] { requestCancellation(); }); } } MergingCancellationState::MergingCancellationState( CopyMoveTag, size_t nCopy, const CancellationToken** copyToks, size_t nMove, CancellationToken** moveToks) : MergingCancellationState(CopyTag{}, nCopy, copyToks) { for (size_t i = 0; i < nMove; ++i, ++callbackEnd_) { new (callbackEnd_) CancellationCallback(std::move(*moveToks[i]), [this] { requestCancellation(); }); } } namespace { template auto allocAndConstructMergingState(size_t n, Args&&... ctorArgs) { DCHECK_GE(n, 2); // `unlockAndDecrementTokenCount` assumes this // The merging state uses `alignas` -- this makes the offset math easier. static_assert( alignof(MergingCancellationState) >= alignof(CancellationCallback)); // Future: If either type needs extended alignment, you must (1) use aligned // `folly::operator_new`, (2) update the alignment math here and in `destroy`. static_assert(alignof(MergingCancellationState) <= alignof(std::max_align_t)); static_assert(alignof(CancellationCallback) <= alignof(std::max_align_t)); void* p = operator_new( // fundamental alignment suffices per above sizeof(MergingCancellationState) + n * sizeof(CancellationCallback)); // Free memory if the ctor throws. NB: Sized `delete` isn't worth it here. auto guard = makeGuard(std::bind(operator_delete, p)); auto res = CancellationStateTokenPtr{ new (p) MergingCancellationState(std::forward(ctorArgs)...)}; guard.dismiss(); return res; } } // namespace CancellationStateTokenPtr MergingCancellationState::createCopy( size_t nCopy, const CancellationToken** copyToks) { return allocAndConstructMergingState(nCopy, CopyTag{}, nCopy, copyToks); } CancellationStateTokenPtr MergingCancellationState::createMove( size_t nMove, CancellationToken** moveToks) { return allocAndConstructMergingState(nMove, MoveTag{}, nMove, moveToks); } CancellationStateTokenPtr MergingCancellationState::createCopyMove( size_t nCopy, const CancellationToken** copyToks, size_t nMove, CancellationToken** moveToks) { return allocAndConstructMergingState( nCopy + nMove, CopyMoveTag{}, nCopy, copyToks, nMove, moveToks); } MergingCancellationState::~MergingCancellationState() { // Arrays are expected to be destroyed in reverse order (although today's // `MergeCancellationState` specific ctor does not require it). auto callbackStart = reinterpret_cast(this + 1); while (callbackEnd_ > callbackStart) { (--callbackEnd_)->~CancellationCallback(); } } void MergingCancellationState::destroy() noexcept { // `MergingCancellationState::create` used `operator_new` + in-place `new` auto allocSize = reinterpret_cast(callbackEnd_) - reinterpret_cast(this); this->~MergingCancellationState(); operator_delete(this, allocSize); // Sized `delete` might be 1-2ns faster } } // namespace detail } // namespace folly ================================================ FILE: folly/CancellationToken.h ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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 namespace folly { class CancellationCallback; class CancellationSource; struct cancellation_token_merge_fn; namespace detail { class CancellationState; struct CancellationStateTokenDeleter { void operator()(CancellationState*) noexcept; }; struct CancellationStateSourceDeleter { void operator()(CancellationState*) noexcept; }; using CancellationStateTokenPtr = std::unique_ptr; using CancellationStateSourcePtr = std::unique_ptr; template struct WithDataTag; } // namespace detail /** * A CancellationToken is an object that can be passed into an function or * operation that allows the caller to later request that the operation be * cancelled. * * A CancellationToken object can be obtained by calling the .getToken() * method on a CancellationSource or by copying another CancellationToken * object. All CancellationToken objects obtained from the same original * CancellationSource object all reference the same underlying cancellation * state and will all be cancelled together. * * If your function needs to be cancellable but does not need to request * cancellation then you should take a CancellationToken as a parameter. * If your function needs to be able to request cancellation then you * should instead take a CancellationSource as a parameter. * * @refcode folly/docs/examples/folly/CancellationToken.cpp * @class folly::CancellationToken */ class CancellationToken { public: /** * Constructs to a token that can never be cancelled. * * Pass a default-constructed CancellationToken into an operation that * you never intend to cancel. These objects are very cheap to create. */ CancellationToken() noexcept = default; /// Construct a copy of the token that shares the same underlying state. CancellationToken(const CancellationToken& other) noexcept; /// Construct a token by moving the underlying state CancellationToken(CancellationToken&& other) noexcept; CancellationToken& operator=(const CancellationToken& other) noexcept; CancellationToken& operator=(CancellationToken&& other) noexcept; /** * Query whether someone has called .requestCancellation() on an instance * of CancellationSource object associated with this CancellationToken. */ bool isCancellationRequested() const noexcept; /** * Query whether this CancellationToken can ever have cancellation requested * on it. * * This will return false if the CancellationToken is not associated with a * CancellationSource object. eg. because the CancellationToken was * default-constructed, has been moved-from or because the last * CancellationSource object associated with the underlying cancellation state * has been destroyed and the operation has not yet been cancelled and so * never will be. * * Implementations of operations may be able to take more efficient code-paths * if they know they can never be cancelled. */ bool canBeCancelled() const noexcept; /** * Swaps the underlying state of the cancellation token with the token that is * passed-in. */ void swap(CancellationToken& other) noexcept; friend bool operator==( const CancellationToken& a, const CancellationToken& b) noexcept; private: friend class CancellationCallback; friend class CancellationSource; friend struct cancellation_token_merge_fn; explicit CancellationToken(detail::CancellationStateTokenPtr state) noexcept; detail::CancellationStateTokenPtr state_; }; bool operator==( const CancellationToken& a, const CancellationToken& b) noexcept; bool operator!=( const CancellationToken& a, const CancellationToken& b) noexcept; /** * A CancellationSource object provides the ability to request cancellation of * operations that an associated CancellationToken was passed to. * * @refcode folly/docs/examples/folly/CancellationSource.cpp * @class folly::CancellationSource */ // Example usage: // CancellationSource cs; // Future f = startSomeOperation(cs.getToken()); // // // Later... // cs.requestCancellation(); class CancellationSource { public: /// Construct to a new, independent cancellation source. CancellationSource(); /** * Construct a new reference to the same underlying cancellation state. * * Either the original or the new copy can be used to request cancellation * of associated work. */ CancellationSource(const CancellationSource& other) noexcept; /** * This leaves 'other' in an empty state where 'requestCancellation()' is a * no-op and 'canBeCancelled()' returns false. */ CancellationSource(CancellationSource&& other) noexcept; CancellationSource& operator=(const CancellationSource& other) noexcept; CancellationSource& operator=(CancellationSource&& other) noexcept; /** * Construct a CancellationSource that cannot be cancelled. * * This factory function can be used to obtain a CancellationSource that * is equivalent to a moved-from CancellationSource object without needing * to allocate any shared-state. */ static CancellationSource invalid() noexcept; /** * Query if cancellation has already been requested on this CancellationSource * or any other CancellationSource object copied from the same original * CancellationSource object. */ bool isCancellationRequested() const noexcept; /** * Query if cancellation can be requested through this CancellationSource * object. This will only return false if the CancellationSource object has * been moved-from. */ bool canBeCancelled() const noexcept; /** * Obtain a CancellationToken linked to this CancellationSource. * * This token can be passed into cancellable operations to allow the caller * to later request cancellation of that operation. */ CancellationToken getToken() const noexcept; /** * Request cancellation of work associated with this CancellationSource. * * This will ensure subsequent calls to isCancellationRequested() on any * CancellationSource or CancellationToken object associated with the same * underlying cancellation state to return true. * * If this is the first call to requestCancellation() on any * CancellationSource object with the same underlying state then this call * will also execute the callbacks associated with any CancellationCallback * objects that were constructed with an associated CancellationToken. * * Note that it is possible that another thread may be concurrently * registering a callback with CancellationCallback. This method guarantees * that either this thread will see the callback registration and will * ensure that the callback is called, or the CancellationCallback constructor * will see the cancellation-requested signal and will execute the callback * inline inside the constructor. * * Returns the previous state of 'isCancellationRequested()'. i.e. * - 'true' if cancellation had previously been requested. * - 'false' if this was the first call to request cancellation. */ bool requestCancellation() const noexcept; /** * Swaps the underlying state of the cancellation source with the source that * is passed-in. * * @param other The other cancellation source to copy the underlying state * from. */ void swap(CancellationSource& other) noexcept; friend bool operator==( const CancellationSource& a, const CancellationSource& b) noexcept; /** * Returns a pair of where the underlying state is * created using the arguments that is passed-in. */ template static std::pair*> create( detail::WithDataTag, Args&&...); private: explicit CancellationSource( detail::CancellationStateSourcePtr&& state) noexcept; detail::CancellationStateSourcePtr state_; }; bool operator==( const CancellationSource& a, const CancellationSource& b) noexcept; bool operator!=( const CancellationSource& a, const CancellationSource& b) noexcept; /** * A CancellationCallback object registers the callback with the specified * CancellationToken such that the callback will be * executed if the corresponding CancellationSource object has the * requestCancellation() method called on it. * * If the CancellationToken object already had cancellation requested * then the callback will be executed inline on the current thread before * the constructor returns. Otherwise, the callback will be executed on * in the execution context of the first thread to call requestCancellation() * on a corresponding CancellationSource. * * The callback object must not throw any unhandled exceptions. Doing so * will result in the program terminating via std::terminate(). * * A CancellationCallback object is neither copyable nor movable. * * @refcode folly/docs/examples/folly/CancellationCallback.cpp * @class folly::CancellationCallback */ class CancellationCallback { using VoidFunction = folly::Function; public: template < typename Callable, std::enable_if_t< std::is_constructible::value, int> = 0> CancellationCallback(CancellationToken&& ct, Callable&& callable); template < typename Callable, std::enable_if_t< std::is_constructible::value, int> = 0> CancellationCallback(const CancellationToken& ct, Callable&& callable); /** * Deregisters the callback from the CancellationToken. * * If cancellation has been requested concurrently on another thread and the * callback is currently executing then the destructor will block until after * the callback has returned (otherwise it might be left with a dangling * reference). * * You should generally try to implement your callback functions to be lock * free to avoid deadlocks between the callback executing and the * CancellationCallback destructor trying to deregister the callback. * * If the callback has not started executing yet then the callback will be * deregistered from the CancellationToken before the destructor completes. * * Once the destructor returns you can be guaranteed that the callback will * not be called by a subsequent call to 'requestCancellation()' on a * CancellationSource associated with the CancellationToken passed to the * constructor. */ ~CancellationCallback(); // Not copyable/movable CancellationCallback(const CancellationCallback&) = delete; CancellationCallback(CancellationCallback&&) = delete; CancellationCallback& operator=(const CancellationCallback&) = delete; CancellationCallback& operator=(CancellationCallback&&) = delete; private: friend class detail::CancellationState; void invokeCallback() noexcept; CancellationCallback* next_; // Pointer to the pointer that points to this node in the linked list. // This could be the 'next_' of a previous CancellationCallback or could // be the 'head_' pointer of the CancellationState. // If this node is inserted in the list then this will be non-null. CancellationCallback** prevNext_; detail::CancellationState* state_; VoidFunction callback_; // Pointer to a flag stored on the stack of the caller to invokeCallback() // that is used to indicate to the caller of invokeCallback() that the // destructor has run and it is no longer valid to access the callback // object. bool* destructorHasRunInsideCallback_; // Flag used to signal that the callback has completed executing on another // thread and it is now safe to exit the destructor. std::atomic callbackCompleted_; }; /** * Obtain a CancellationToken linked to any number of other * CancellationTokens. * * This token will have cancellation requested when any of the passed-in * tokens do. * This token is cancellable if any of the passed-in tokens are at the time of * construction. * * Example: * CancellationSource a,b; * auto c = cancellation_token_merge(a.getToken(), b.getToken()); */ struct cancellation_token_merge_fn { template CancellationToken operator()(Ts&&... tokens) const; }; inline constexpr cancellation_token_merge_fn cancellation_token_merge{}; } // namespace folly #include ================================================ FILE: folly/Chrono.h ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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 namespace folly { namespace chrono { /* using override */ using std::chrono::abs; /* using override */ using std::chrono::ceil; /* using override */ using std::chrono::floor; /* using override */ using std::chrono::round; // steady_clock_spec // // All clocks with this spec share epoch and tick rate. struct steady_clock_spec {}; // system_clock_spec // // All clocks with this spec share epoch and tick rate. struct system_clock_spec {}; // clock_traits // // Detects and reexports per-clock traits. // // Specializeable for clocks for which trait detection fails.. template struct clock_traits { private: template using detect_spec_ = typename C::folly_spec; public: using spec = detected_or_t; }; template <> struct clock_traits { using spec = steady_clock_spec; }; template <> struct clock_traits { using spec = system_clock_spec; }; struct coarse_steady_clock { using folly_spec = steady_clock_spec; using duration = std::chrono::steady_clock::duration; using rep = duration::rep; using period = duration::period; using time_point = std::chrono::time_point; constexpr static bool is_steady = true; static time_point now() noexcept { #ifndef CLOCK_MONOTONIC_COARSE auto time = std::chrono::steady_clock::now().time_since_epoch(); #else timespec ts; int ret = clock_gettime(CLOCK_MONOTONIC_COARSE, &ts); if (kIsDebug && (ret != 0)) { throw_exception( "Error using CLOCK_MONOTONIC_COARSE."); } auto time = std::chrono::seconds(ts.tv_sec) + std::chrono::nanoseconds(ts.tv_nsec); #endif return time_point(std::chrono::duration_cast(time)); } }; struct coarse_system_clock { using folly_spec = system_clock_spec; using duration = std::chrono::system_clock::duration; using rep = duration::rep; using period = duration::period; using time_point = std::chrono::time_point; constexpr static bool is_steady = false; static time_point now() noexcept { #ifndef CLOCK_REALTIME_COARSE auto time = std::chrono::system_clock::now().time_since_epoch(); #else timespec ts; int ret = clock_gettime(CLOCK_REALTIME_COARSE, &ts); if (kIsDebug && (ret != 0)) { throw_exception("Error using CLOCK_REALTIME_COARSE."); } auto time = std::chrono::seconds(ts.tv_sec) + std::chrono::nanoseconds(ts.tv_nsec); #endif return time_point(std::chrono::duration_cast(time)); } static std::time_t to_time_t(const time_point& t) noexcept { auto d = t.time_since_epoch(); return std::chrono::duration_cast(d).count(); } static time_point from_time_t(std::time_t t) noexcept { return time_point( std::chrono::duration_cast(std::chrono::seconds(t))); } }; } // namespace chrono } // namespace folly ================================================ FILE: folly/ClockGettimeWrappers.cpp ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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 #include #include #include #include #ifndef _WIN32 #define _GNU_SOURCE 1 #include #endif namespace folly { namespace chrono { static int64_t clock_gettime_ns_fallback(clockid_t clock) { struct timespec ts; int r = clock_gettime(clock, &ts); if (FOLLY_UNLIKELY(r != 0)) { // Mimic what __clock_gettime_ns does (even though this can be a legit // value). return -1; } std::chrono::nanoseconds result = std::chrono::seconds(ts.tv_sec) + std::chrono::nanoseconds(ts.tv_nsec); return result.count(); } // Initialize with default behavior, which we might override on Linux hosts // with VDSO support. int (*clock_gettime)(clockid_t, timespec* ts) = &::clock_gettime; int64_t (*clock_gettime_ns)(clockid_t) = &clock_gettime_ns_fallback; // In MSAN mode use glibc's versions as they are intercepted by the MSAN // runtime which properly tracks memory initialization. #if defined(FOLLY_HAVE_LINUX_VDSO) && !defined(FOLLY_SANITIZE_MEMORY) namespace { struct VdsoInitializer { VdsoInitializer() { m_handle = dlopen("linux-vdso.so.1", RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD); if (!m_handle) { return; } void* p = dlsym(m_handle, "__vdso_clock_gettime"); if (p) { folly::chrono::clock_gettime = (int (*)(clockid_t, timespec*))p; } p = dlsym(m_handle, "__vdso_clock_gettime_ns"); if (p) { folly::chrono::clock_gettime_ns = (int64_t (*)(clockid_t))p; } } ~VdsoInitializer() { if (m_handle) { clock_gettime = &::clock_gettime; clock_gettime_ns = &clock_gettime_ns_fallback; dlclose(m_handle); } } private: void* m_handle; }; const VdsoInitializer vdso_initializer; } // namespace #endif } // namespace chrono } // namespace folly ================================================ FILE: folly/ClockGettimeWrappers.h ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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 namespace folly { namespace chrono { extern int (*clock_gettime)(clockid_t, timespec* ts); extern int64_t (*clock_gettime_ns)(clockid_t); } // namespace chrono } // namespace folly ================================================ FILE: folly/ConcurrentBitSet.h ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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 namespace folly { /** * An atomic bitset of fixed size (specified at compile time). * * Formerly known as AtomicBitSet. It was renamed while fixing a bug * to avoid any silent breakages during run time. */ template class ConcurrentBitSet { public: /** * Construct a ConcurrentBitSet; all bits are initially false. */ ConcurrentBitSet(); ConcurrentBitSet(const ConcurrentBitSet&) = delete; ConcurrentBitSet& operator=(const ConcurrentBitSet&) = delete; /** * Set bit idx to true, using the given memory order. Returns the * previous value of the bit. * * Note that the operation is a read-modify-write operation due to the use * of fetch_or. */ bool set(size_t idx, std::memory_order order = std::memory_order_seq_cst); /** * Set bit idx to false, using the given memory order. Returns the * previous value of the bit. * * Note that the operation is a read-modify-write operation due to the use * of fetch_and. */ bool reset(size_t idx, std::memory_order order = std::memory_order_seq_cst); /** * Set bit idx to the given value, using the given memory order. Returns * the previous value of the bit. * * Note that the operation is a read-modify-write operation due to the use * of fetch_and or fetch_or. * * Yes, this is an overload of set(), to keep as close to std::bitset's * interface as possible. */ bool set( size_t idx, bool value, std::memory_order order = std::memory_order_seq_cst); /** * Read bit idx. */ bool test( size_t idx, std::memory_order order = std::memory_order_seq_cst) const; /** * Same as test() with the default memory order. */ bool operator[](size_t idx) const; /** * Return the size of the bitset. */ constexpr size_t size() const { return N; } private: // Pick the largest lock-free type available #if (ATOMIC_LLONG_LOCK_FREE == 2) using BlockType = unsigned long long; #elif (ATOMIC_LONG_LOCK_FREE == 2) typedef unsigned long BlockType; #else // Even if not lock free, what can we do? typedef unsigned int BlockType; #endif using AtomicBlockType = std::atomic; static constexpr size_t kBitsPerBlock = std::numeric_limits::digits; static constexpr size_t blockIndex(size_t bit) { return bit / kBitsPerBlock; } static constexpr size_t bitOffset(size_t bit) { return bit % kBitsPerBlock; } // avoid casts static constexpr BlockType kOne = 1; static constexpr size_t kNumBlocks = (N + kBitsPerBlock - 1) / kBitsPerBlock; std::array data_; }; // value-initialize to zero template inline ConcurrentBitSet::ConcurrentBitSet() : data_() {} template inline bool ConcurrentBitSet::set(size_t idx, std::memory_order order) { assert(idx < N); BlockType mask = kOne << bitOffset(idx); return data_[blockIndex(idx)].fetch_or(mask, order) & mask; } template inline bool ConcurrentBitSet::reset(size_t idx, std::memory_order order) { assert(idx < N); BlockType mask = kOne << bitOffset(idx); return data_[blockIndex(idx)].fetch_and(~mask, order) & mask; } template inline bool ConcurrentBitSet::set( size_t idx, bool value, std::memory_order order) { return value ? set(idx, order) : reset(idx, order); } template inline bool ConcurrentBitSet::test( size_t idx, std::memory_order order) const { assert(idx < N); BlockType mask = kOne << bitOffset(idx); return data_[blockIndex(idx)].load(order) & mask; } template inline bool ConcurrentBitSet::operator[](size_t idx) const { return test(idx); } } // namespace folly ================================================ FILE: folly/ConcurrentLazy.h ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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 namespace folly { /* * ConcurrentLazy is for thread-safe, delayed initialization of a value. This * combines the benefits of both `folly::Lazy` and `folly::DelayedInit` to * compute the value, once, at access time. * * There are a few differences between the non-concurrent Lazy, most notably: * * - this only safely initializes the value; thread-safety of the underlying * value is left up to the caller. * - the underlying types are not copyable or moveable, which means that this * type is also not copyable or moveable. * * Otherwise, all design considerations from `folly::Lazy` are reflected here. */ template struct ConcurrentLazy { using result_type = invoke_result_t; static_assert( !std::is_const::value, "Func should not be a const-qualified type"); static_assert( !std::is_reference::value, "Func should not be a reference type"); template < typename F, std::enable_if_t, int> = 0> explicit ConcurrentLazy(F&& f) noexcept( std::is_nothrow_constructible_v) : ctor_(static_cast(f)) {} const result_type& operator()() const { return value_.try_emplace_with(std::ref(ctor_)); } result_type& operator()() { return value_.try_emplace_with(std::ref(ctor_)); } private: mutable folly::DelayedInit value_; mutable Ctor ctor_; }; template ConcurrentLazy> concurrent_lazy(Func&& func) { return ConcurrentLazy>(static_cast(func)); } } // namespace folly ================================================ FILE: folly/ConcurrentSkipList-inl.h ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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 #include #include #include #include #include #include namespace folly { namespace detail { template class csl_iterator; template class SkipListNode { enum : uint16_t { IS_HEAD_NODE = 1, MARKED_FOR_REMOVAL = (1 << 1), FULLY_LINKED = (1 << 2), }; public: using value_type = T; SkipListNode(const SkipListNode&) = delete; SkipListNode& operator=(const SkipListNode&) = delete; template < typename NodeAlloc, typename U, typename = typename std::enable_if::value>::type> static SkipListNode* create( NodeAlloc& alloc, int height, U&& data, bool isHead = false) { DCHECK(height >= 1 && height < 64) << height; size_t size = sizeof(SkipListNode) + height * sizeof(std::atomic); auto storage = std::allocator_traits::allocate(alloc, size); // do placement new return new (storage) SkipListNode(uint8_t(height), std::forward(data), isHead); } template static void destroy(NodeAlloc& alloc, SkipListNode* node) { size_t size = sizeof(SkipListNode) + node->height_ * sizeof(std::atomic); node->~SkipListNode(); std::allocator_traits::deallocate( alloc, typename std::allocator_traits::pointer(node), size); } template struct DestroyIsNoOp : StrictConjunction< AllocatorHasTrivialDeallocate, std::is_trivially_destructible> {}; // copy the head node to a new head node assuming lock acquired SkipListNode* copyHead(SkipListNode* node) { DCHECK(node != nullptr && height_ > node->height_); setFlags(node->getFlags()); for (uint8_t i = 0; i < node->height_; ++i) { setSkip(i, node->skip(i)); } return this; } inline SkipListNode* skip(int layer) const { DCHECK_LT(layer, height_); return skip_[layer].load(std::memory_order_acquire); } // next valid node as in the linked list SkipListNode* next() { SkipListNode* node; for (node = skip(0); (node != nullptr && node->markedForRemoval()); node = node->skip(0)) { } return node; } void setSkip(uint8_t h, SkipListNode* next) { DCHECK_LT(h, height_); skip_[h].store(next, std::memory_order_release); } value_type& data() { return data_; } const value_type& data() const { return data_; } int maxLayer() const { return height_ - 1; } int height() const { return height_; } std::unique_lock acquireGuard() { return std::unique_lock(spinLock_); } bool fullyLinked() const { return getFlags() & FULLY_LINKED; } bool markedForRemoval() const { return getFlags() & MARKED_FOR_REMOVAL; } bool isHeadNode() const { return getFlags() & IS_HEAD_NODE; } void setIsHeadNode() { setFlags(uint16_t(getFlags() | IS_HEAD_NODE)); } void setFullyLinked() { setFlags(uint16_t(getFlags() | FULLY_LINKED)); } void setMarkedForRemoval() { setFlags(uint16_t(getFlags() | MARKED_FOR_REMOVAL)); } private: // Note! this can only be called from create() as a placement new. template SkipListNode(uint8_t height, U&& data, bool isHead) : height_(height), data_(std::forward(data)) { spinLock_.init(); setFlags(0); if (isHead) { setIsHeadNode(); } // need to explicitly init the dynamic atomic pointer array for (uint8_t i = 0; i < height_; ++i) { new (&skip_[i]) std::atomic(nullptr); } } ~SkipListNode() { for (uint8_t i = 0; i < height_; ++i) { skip_[i].~atomic(); } } uint16_t getFlags() const { return flags_.load(std::memory_order_acquire); } void setFlags(uint16_t flags) { flags_.store(flags, std::memory_order_release); } // TODO(xliu): on x86_64, it's possible to squeeze these into // skip_[0] to maybe save 8 bytes depending on the data alignments. // NOTE: currently this is x86_64 only anyway, due to the // MicroSpinLock. std::atomic flags_; const uint8_t height_; MicroSpinLock spinLock_; value_type data_; std::atomic skip_[0]; }; class SkipListRandomHeight { enum { kMaxHeight = 64 }; public: // make it a singleton. static SkipListRandomHeight* instance() { static SkipListRandomHeight instance_; return &instance_; } int getHeight(int maxHeight) const { DCHECK_LE(maxHeight, kMaxHeight) << "max height too big!"; double p = randomProb(); for (int i = 0; i < maxHeight; ++i) { if (p < lookupTable_[i]) { return i + 1; } } return maxHeight; } size_t getSizeLimit(int height) const { DCHECK_LT(height, kMaxHeight); return sizeLimitTable_[height]; } private: SkipListRandomHeight() { initLookupTable(); } void initLookupTable() { // set skip prob = 1/E static const double kProbInv = exp(1); static const double kProb = 1.0 / kProbInv; static const size_t kMaxSizeLimit = std::numeric_limits::max(); double sizeLimit = 1; double p = lookupTable_[0] = (1 - kProb); sizeLimitTable_[0] = 1; for (int i = 1; i < kMaxHeight - 1; ++i) { p *= kProb; sizeLimit *= kProbInv; lookupTable_[i] = lookupTable_[i - 1] + p; sizeLimitTable_[i] = folly::constexpr_clamp_cast(sizeLimit); } lookupTable_[kMaxHeight - 1] = 1; sizeLimitTable_[kMaxHeight - 1] = kMaxSizeLimit; } static double randomProb() { static ThreadLocal rng_; return (*rng_)(); } double lookupTable_[kMaxHeight]; size_t sizeLimitTable_[kMaxHeight]; }; template class NodeRecycler; template class NodeRecycler< NodeType, NodeAlloc, typename std::enable_if< !NodeType::template DestroyIsNoOp::value>::type> { public: explicit NodeRecycler(const NodeAlloc& alloc) : refs_(0), dirty_(false), alloc_(alloc) { lock_.init(); } explicit NodeRecycler() : refs_(0), dirty_(false) { lock_.init(); } ~NodeRecycler() { CHECK_EQ(refs(), 0); if (nodes_) { for (auto& node : *nodes_) { NodeType::destroy(alloc_, node); } } } void add(NodeType* node) { std::lock_guard g(lock_); if (nodes_.get() == nullptr) { nodes_ = std::make_unique>(1, node); } else { nodes_->push_back(node); } DCHECK_GT(refs(), 0); dirty_.store(true, std::memory_order_relaxed); } int addRef() { return refs_.fetch_add(1, std::memory_order_acq_rel); } int releaseRef() { // This if statement is purely an optimization. It's possible that this // misses an opportunity to delete, but that's OK, we'll try again at // the next opportunity. It does not harm the thread safety. For this // reason, we can use relaxed loads to make the decision. if (!dirty_.load(std::memory_order_relaxed) || refs() > 1) { return refs_.fetch_add(-1, std::memory_order_acq_rel); } std::unique_ptr> newNodes; int ret; { // The order at which we lock, add, swap, is very important for // correctness. std::lock_guard g(lock_); ret = refs_.fetch_add(-1, std::memory_order_acq_rel); if (ret == 1) { // When releasing the last reference, it is safe to remove all the // current nodes in the recycler, as we already acquired the lock here // so no more new nodes can be added, even though new accessors may be // added after this. newNodes.swap(nodes_); dirty_.store(false, std::memory_order_relaxed); } } // TODO(xliu) should we spawn a thread to do this when there are large // number of nodes in the recycler? if (newNodes) { for (auto& node : *newNodes) { NodeType::destroy(alloc_, node); } } return ret; } NodeAlloc& alloc() { return alloc_; } private: int refs() const { return refs_.load(std::memory_order_relaxed); } std::unique_ptr> nodes_; std::atomic refs_; // current number of visitors to the list std::atomic dirty_; // whether *nodes_ is non-empty MicroSpinLock lock_; // protects access to *nodes_ [[FOLLY_ATTR_NO_UNIQUE_ADDRESS]] NodeAlloc alloc_; }; // In case of arena allocator, no recycling is necessary, and it's possible // to save on ConcurrentSkipList size. template class NodeRecycler< NodeType, NodeAlloc, typename std::enable_if< NodeType::template DestroyIsNoOp::value>::type> { public: explicit NodeRecycler(const NodeAlloc& alloc) : alloc_(alloc) {} void addRef() {} void releaseRef() {} void add(NodeType* /* node */) {} NodeAlloc& alloc() { return alloc_; } private: [[FOLLY_ATTR_NO_UNIQUE_ADDRESS]] NodeAlloc alloc_; }; } // namespace detail } // namespace folly ================================================ FILE: folly/ConcurrentSkipList.h ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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. */ // A concurrent skip list (CSL) implementation. // Ref: http://www.cs.tau.ac.il/~shanir/nir-pubs-web/Papers/OPODIS2006-BA.pdf /* This implements a sorted associative container that supports only unique keys. (Similar to std::set.) Features: 1. Small memory overhead: ~40% less memory overhead compared with std::set (1.6 words per node versus 3). It has an minimum of 4 words (7 words if there nodes got deleted) per-list overhead though. 2. Read accesses (count, find iterator, skipper) are lock-free and mostly wait-free (the only wait a reader may need to do is when the node it is visiting is in a pending stage, i.e. deleting, adding and not fully linked). Write accesses (remove, add) need to acquire locks, but locks are local to the predecessor nodes and/or successor nodes. 3. Good high contention performance, comparable single-thread performance. In the multithreaded case (12 workers), CSL tested 10x faster than a RWSpinLocked std::set for an averaged sized list (1K - 1M nodes). Comparable read performance to std::set when single threaded, especially when the list size is large, and scales better to larger lists: when the size is small, CSL can be 20-50% slower on find()/contains(). As the size gets large (> 1M elements), find()/contains() can be 30% faster. Iterating through a skiplist is similar to iterating through a linked list, thus is much (2-6x) faster than on a std::set (tree-based). This is especially true for short lists due to better cache locality. Based on that, it's also faster to intersect two skiplists. 4. Lazy removal with GC support. The removed nodes get deleted when the last Accessor to the skiplist is destroyed. Caveats: 1. Write operations are usually 30% slower than std::set in a single threaded environment. 2. Need to have a head node for each list, which has a 4 word overhead. 3. When the list is quite small (< 1000 elements), single threaded benchmarks show CSL can be 10x slower than std:set. 4. The interface requires using an Accessor to access the skiplist. (See below.) 5. Currently x64 only, due to use of MicroSpinLock. 6. Freed nodes will not be reclaimed as long as there are ongoing uses of the list. Sample usage: typedef ConcurrentSkipList SkipListT; shared_ptr sl(SkipListT::createInstance(init_head_height); { // It's usually good practice to hold an accessor only during // its necessary life cycle (but not in a tight loop as // Accessor creation incurs ref-counting overhead). // // Holding it longer delays garbage-collecting the deleted // nodes in the list. SkipListT::Accessor accessor(sl); accessor.insert(23); accessor.erase(2); for (auto &elem : accessor) { // use elem to access data } ... ... } Another useful type is the Skipper accessor. This is useful if you want to skip to locations in the way std::lower_bound() works, i.e. it can be used for going through the list by skipping to the node no less than a specified key. The Skipper keeps its location as state, which makes it convenient for things like implementing intersection of two sets efficiently, as it can start from the last visited position. { SkipListT::Accessor accessor(sl); SkipListT::Skipper skipper(accessor); skipper.to(30); if (skipper) { CHECK_LE(30, *skipper); } ... ... // GC may happen when the accessor gets destructed. } */ #pragma once #include #include #include #include #include #include #include #include #include #include #include namespace folly { template < typename T, typename Comp = std::less, // All nodes are allocated using provided SysAllocator, // it should be thread-safe. typename NodeAlloc = SysAllocator, int MAX_HEIGHT = 24> class ConcurrentSkipList { // MAX_HEIGHT needs to be at least 2 to suppress compiler // warnings/errors (Werror=uninitialized triggered due to preds_[1] // being treated as a scalar in the compiler). static_assert( MAX_HEIGHT >= 2 && MAX_HEIGHT < 64, "MAX_HEIGHT can only be in the range of [2, 64)"); using ScopedLocker = std::unique_lock; using SkipListType = ConcurrentSkipList; public: using NodeType = detail::SkipListNode; using value_type = T; using key_type = T; using iterator = detail::csl_iterator; using const_iterator = detail::csl_iterator; class Accessor; class Skipper; explicit ConcurrentSkipList(int height, const NodeAlloc& alloc) : recycler_(alloc), head_(NodeType::create(recycler_.alloc(), height, value_type(), true)) { } explicit ConcurrentSkipList(int height) : recycler_(), head_(NodeType::create(recycler_.alloc(), height, value_type(), true)) { } // Convenience function to get an Accessor to a new instance. static Accessor create(int height, const NodeAlloc& alloc) { return Accessor(createInstance(height, alloc)); } static Accessor create(int height = 1) { return Accessor(createInstance(height)); } // Create a shared_ptr skiplist object with initial head height. static std::shared_ptr createInstance( int height, const NodeAlloc& alloc) { return std::make_shared(height, alloc); } static std::shared_ptr createInstance(int height = 1) { return std::make_shared(height); } size_t size() const { return size_.load(std::memory_order_relaxed); } bool empty() const { return size() == 0; } //=================================================================== // Below are implementation details. // Please see ConcurrentSkipList::Accessor for stdlib-like APIs. //=================================================================== ~ConcurrentSkipList() { if constexpr (NodeType::template DestroyIsNoOp::value) { // Avoid traversing the list if using arena allocator. return; } for (NodeType* current = head_.load(std::memory_order_relaxed); current;) { NodeType* tmp = current->skip(0); NodeType::destroy(recycler_.alloc(), current); current = tmp; } } private: static bool greater(const value_type& data, const NodeType* node) { return node && Comp()(node->data(), data); } static bool less(const value_type& data, const NodeType* node) { return (node == nullptr) || Comp()(data, node->data()); } static int findInsertionPoint( NodeType* cur, int cur_layer, const value_type& data, NodeType* preds[], NodeType* succs[]) { int foundLayer = -1; NodeType* pred = cur; NodeType* foundNode = nullptr; for (int layer = cur_layer; layer >= 0; --layer) { NodeType* node = pred->skip(layer); while (greater(data, node)) { pred = node; node = node->skip(layer); } if (foundLayer == -1 && !less(data, node)) { // the two keys equal foundLayer = layer; foundNode = node; } preds[layer] = pred; // if found, succs[0..foundLayer] need to point to the cached foundNode, // as foundNode might be deleted at the same time thus pred->skip() can // return nullptr or another node. succs[layer] = foundNode ? foundNode : node; } return foundLayer; } int height() const { return head_.load(std::memory_order_acquire)->height(); } int maxLayer() const { return height() - 1; } size_t incrementSize(int delta) { return size_.fetch_add(delta, std::memory_order_relaxed) + delta; } // Returns the node if found, nullptr otherwise. NodeType* find(const value_type& data) { auto ret = findNode(data); if (ret.second && !ret.first->markedForRemoval()) { return ret.first; } return nullptr; } // lock all the necessary nodes for changing (adding or removing) the list. // returns true if all the lock acquired successfully and the related nodes // are all validate (not in certain pending states), false otherwise. bool lockNodesForChange( int nodeHeight, ScopedLocker guards[MAX_HEIGHT], NodeType* preds[MAX_HEIGHT], NodeType* succs[MAX_HEIGHT], bool adding = true) { NodeType *pred, *succ, *prevPred = nullptr; bool valid = true; for (int layer = 0; valid && layer < nodeHeight; ++layer) { pred = preds[layer]; DCHECK(pred != nullptr) << "layer=" << layer << " height=" << height() << " nodeheight=" << nodeHeight; succ = succs[layer]; if (pred != prevPred) { guards[layer] = pred->acquireGuard(); prevPred = pred; } valid = !pred->markedForRemoval() && pred->skip(layer) == succ; // check again after locking if (adding) { // when adding a node, the succ shouldn't be going away valid = valid && (succ == nullptr || !succ->markedForRemoval()); } } return valid; } // Returns a paired value: // pair.first always stores the pointer to the node with the same input key. // It could be either the newly added data, or the existed data in the // list with the same key. // pair.second stores whether the data is added successfully: // 0 means not added, otherwise returns the new size. template std::pair addOrGetData(U&& data) { NodeType *preds[MAX_HEIGHT], *succs[MAX_HEIGHT]; NodeType* newNode; size_t newSize; while (true) { int max_layer = 0; int layer = findInsertionPointGetMaxLayer(data, preds, succs, &max_layer); if (layer >= 0) { NodeType* nodeFound = succs[layer]; DCHECK(nodeFound != nullptr); if (nodeFound->markedForRemoval()) { continue; // if it's getting deleted retry finding node. } // wait until fully linked. while (FOLLY_UNLIKELY(!nodeFound->fullyLinked())) { } return std::make_pair(nodeFound, 0); } // need to capped at the original height -- the real height may have grown int nodeHeight = detail::SkipListRandomHeight::instance()->getHeight(max_layer + 1); ScopedLocker guards[MAX_HEIGHT]; if (!lockNodesForChange(nodeHeight, guards, preds, succs)) { continue; // give up the locks and retry until all valid } // locks acquired and all valid, need to modify the links under the locks. newNode = NodeType::create( recycler_.alloc(), nodeHeight, std::forward(data)); for (int k = 0; k < nodeHeight; ++k) { newNode->setSkip(k, succs[k]); preds[k]->setSkip(k, newNode); } newNode->setFullyLinked(); newSize = incrementSize(1); break; } int hgt = height(); size_t sizeLimit = detail::SkipListRandomHeight::instance()->getSizeLimit(hgt); if (hgt < MAX_HEIGHT && newSize > sizeLimit) { growHeight(hgt + 1); } CHECK_GT(newSize, 0); return std::make_pair(newNode, newSize); } bool remove(const value_type& data) { NodeType* nodeToDelete = nullptr; ScopedLocker nodeGuard; bool isMarked = false; int nodeHeight = 0; NodeType *preds[MAX_HEIGHT], *succs[MAX_HEIGHT]; while (true) { int max_layer = 0; int layer = findInsertionPointGetMaxLayer(data, preds, succs, &max_layer); if (!isMarked && (layer < 0 || !okToDelete(succs[layer], layer))) { return false; } if (!isMarked) { nodeToDelete = succs[layer]; nodeHeight = nodeToDelete->height(); nodeGuard = nodeToDelete->acquireGuard(); if (nodeToDelete->markedForRemoval()) { return false; } nodeToDelete->setMarkedForRemoval(); isMarked = true; } // acquire pred locks from bottom layer up ScopedLocker guards[MAX_HEIGHT]; if (!lockNodesForChange(nodeHeight, guards, preds, succs, false)) { continue; // this will unlock all the locks } for (int k = nodeHeight - 1; k >= 0; --k) { preds[k]->setSkip(k, nodeToDelete->skip(k)); } incrementSize(-1); break; } recycle(nodeToDelete); return true; } const value_type* first() const { auto node = head_.load(std::memory_order_acquire)->skip(0); return node ? &node->data() : nullptr; } const value_type* last() const { NodeType* pred = head_.load(std::memory_order_acquire); NodeType* node = nullptr; for (int layer = maxLayer(); layer >= 0; --layer) { do { node = pred->skip(layer); if (node) { pred = node; } } while (node != nullptr); } return pred == head_.load(std::memory_order_relaxed) ? nullptr : &pred->data(); } static bool okToDelete(NodeType* candidate, int layer) { DCHECK(candidate != nullptr); return candidate->fullyLinked() && candidate->maxLayer() == layer && !candidate->markedForRemoval(); } // find node for insertion/deleting int findInsertionPointGetMaxLayer( const value_type& data, NodeType* preds[], NodeType* succs[], int* max_layer) const { *max_layer = maxLayer(); return findInsertionPoint( head_.load(std::memory_order_acquire), *max_layer, data, preds, succs); } // Find node for access. Returns a paired values: // pair.first = the first node that no-less than data value // pair.second = 1 when the data value is founded, or 0 otherwise. // This is like lower_bound, but not exact: we could have the node marked for // removal so still need to check that. std::pair findNode(const value_type& data) const { return findNodeDownRight(data); } // Find node by first stepping down then stepping right. Based on benchmark // results, this is slightly faster than findNodeRightDown for better // locality on the skipping pointers. std::pair findNodeDownRight(const value_type& data) const { NodeType* pred = head_.load(std::memory_order_acquire); int ht = pred->height(); NodeType* node = nullptr; bool found = false; while (!found) { // stepping down for (; ht > 0 && less(data, node = pred->skip(ht - 1)); --ht) { } if (ht == 0) { return std::make_pair(node, 0); // not found } // node <= data now, but we need to fix up ht --ht; // stepping right while (greater(data, node)) { pred = node; node = node->skip(ht); } found = !less(data, node); } return std::make_pair(node, found); } // find node by first stepping right then stepping down. // We still keep this for reference purposes. std::pair findNodeRightDown(const value_type& data) const { NodeType* pred = head_.load(std::memory_order_acquire); NodeType* node = nullptr; auto top = maxLayer(); int found = 0; for (int layer = top; !found && layer >= 0; --layer) { node = pred->skip(layer); while (greater(data, node)) { pred = node; node = node->skip(layer); } found = !less(data, node); } return std::make_pair(node, found); } NodeType* lower_bound(const value_type& data) const { auto node = findNode(data).first; while (node != nullptr && node->markedForRemoval()) { node = node->skip(0); } return node; } void growHeight(int height) { NodeType* oldHead = head_.load(std::memory_order_acquire); if (oldHead->height() >= height) { // someone else already did this return; } NodeType* newHead = NodeType::create(recycler_.alloc(), height, value_type(), true); { // need to guard the head node in case others are adding/removing // nodes linked to the head. ScopedLocker g = oldHead->acquireGuard(); newHead->copyHead(oldHead); NodeType* expected = oldHead; if (!head_.compare_exchange_strong( expected, newHead, std::memory_order_release)) { // if someone has already done the swap, just return. NodeType::destroy(recycler_.alloc(), newHead); return; } oldHead->setMarkedForRemoval(); } recycle(oldHead); } void recycle(NodeType* node) { recycler_.add(node); } detail::NodeRecycler recycler_; std::atomic head_; std::atomic size_{0}; }; template class ConcurrentSkipList::Accessor { using NodeType = detail::SkipListNode; using SkipListType = ConcurrentSkipList; public: using value_type = T; using key_type = T; using reference = T&; using pointer = T*; using const_reference = const T&; using const_pointer = const T*; using size_type = size_t; using key_compare = Comp; using value_compare = Comp; using iterator = typename SkipListType::iterator; using const_iterator = typename SkipListType::const_iterator; using Skipper = typename SkipListType::Skipper; explicit Accessor(std::shared_ptr skip_list) : slHolder_(std::move(skip_list)) { sl_ = slHolder_.get(); DCHECK(sl_ != nullptr); sl_->recycler_.addRef(); } // Unsafe initializer: the caller assumes the responsibility to keep // skip_list valid during the whole life cycle of the Accessor. explicit Accessor(ConcurrentSkipList* skip_list) : sl_(skip_list) { DCHECK(sl_ != nullptr); sl_->recycler_.addRef(); } Accessor(const Accessor& accessor) : sl_(accessor.sl_), slHolder_(accessor.slHolder_) { sl_->recycler_.addRef(); } Accessor& operator=(const Accessor& accessor) { if (this != &accessor) { slHolder_ = accessor.slHolder_; sl_->recycler_.releaseRef(); sl_ = accessor.sl_; sl_->recycler_.addRef(); } return *this; } ~Accessor() { sl_->recycler_.releaseRef(); } bool empty() const { return sl_->size() == 0; } size_t size() const { return sl_->size(); } size_type max_size() const { return std::numeric_limits::max(); } // returns end() if the value is not in the list, otherwise returns an // iterator pointing to the data, and it's guaranteed that the data is valid // as far as the Accessor is hold. iterator find(const key_type& value) { return iterator(sl_->find(value)); } const_iterator find(const key_type& value) const { return iterator(sl_->find(value)); } size_type count(const key_type& data) const { return contains(data); } iterator begin() const { NodeType* head = sl_->head_.load(std::memory_order_acquire); return iterator(head->next()); } iterator end() const { return iterator(nullptr); } const_iterator cbegin() const { return begin(); } const_iterator cend() const { return end(); } template < typename U, typename = typename std::enable_if::value>::type> std::pair insert(U&& data) { auto ret = sl_->addOrGetData(std::forward(data)); return std::make_pair(iterator(ret.first), ret.second); } size_t erase(const key_type& data) { return remove(data); } iterator lower_bound(const key_type& data) const { return iterator(sl_->lower_bound(data)); } size_t height() const { return sl_->height(); } // first() returns pointer to the first element in the skiplist, or // nullptr if empty. // // last() returns the pointer to the last element in the skiplist, // nullptr if list is empty. // // Note: As concurrent writing can happen, first() is not // guaranteed to be the min_element() in the list. Similarly // last() is not guaranteed to be the max_element(), and both of them can // be invalid (i.e. nullptr), so we name them differently from front() and // tail() here. const key_type* first() const { return sl_->first(); } const key_type* last() const { return sl_->last(); } // Try to remove the last element in the skip list. // // Returns true if we removed it, false if either the list is empty // or a race condition happened (i.e. the used-to-be last element // was already removed by another thread). bool pop_back() { auto last = sl_->last(); return last ? sl_->remove(*last) : false; } std::pair addOrGetData(const key_type& data) { auto ret = sl_->addOrGetData(data); return std::make_pair(&ret.first->data(), ret.second); } SkipListType* skiplist() const { return sl_; } // legacy interfaces // TODO:(xliu) remove these. // Returns true if the node is added successfully, false if not, i.e. the // node with the same key already existed in the list. bool contains(const key_type& data) const { return sl_->find(data); } bool add(const key_type& data) { return sl_->addOrGetData(data).second; } bool remove(const key_type& data) { return sl_->remove(data); } private: SkipListType* sl_; std::shared_ptr slHolder_; }; // implements forward iterator concept. template class detail::csl_iterator : public detail::IteratorFacade< csl_iterator, ValT, std::forward_iterator_tag> { public: using value_type = ValT; using reference = value_type&; using pointer = value_type*; using difference_type = ptrdiff_t; explicit csl_iterator(NodeT* node = nullptr) : node_(node) {} template csl_iterator( const csl_iterator& other, typename std::enable_if< std::is_convertible::value>::type* = nullptr) : node_(other.node_) {} size_t nodeSize() const { return node_ == nullptr ? 0 : node_->height() * sizeof(NodeT*) + sizeof(*this); } bool good() const { return node_ != nullptr; } private: template friend class csl_iterator; friend class detail:: IteratorFacade; void increment() { node_ = node_->next(); } bool equal(const csl_iterator& other) const { return node_ == other.node_; } value_type& dereference() const { return node_->data(); } NodeT* node_; }; // Skipper interface template class ConcurrentSkipList::Skipper { using NodeType = detail::SkipListNode; using SkipListType = ConcurrentSkipList; using Accessor = typename SkipListType::Accessor; public: using value_type = T; using reference = T&; using pointer = T*; using difference_type = ptrdiff_t; Skipper(std::shared_ptr skipList) : accessor_(std::move(skipList)) { init(); } Skipper(const Accessor& accessor) : accessor_(accessor) { init(); } void init() { // need to cache the head node NodeType* head_node = head(); headHeight_ = head_node->height(); for (int i = 0; i < headHeight_; ++i) { preds_[i] = head_node; succs_[i] = head_node->skip(i); } int max_layer = maxLayer(); for (int i = 0; i < max_layer; ++i) { hints_[i] = uint8_t(i + 1); } hints_[max_layer] = max_layer; } // advance to the next node in the list. Skipper& operator++() { preds_[0] = succs_[0]; succs_[0] = preds_[0]->skip(0); int height = curHeight(); for (int i = 1; i < height && preds_[0] == succs_[i]; ++i) { preds_[i] = succs_[i]; succs_[i] = preds_[i]->skip(i); } return *this; } Accessor& accessor() { return accessor_; } const Accessor& accessor() const { return accessor_; } bool good() const { return succs_[0] != nullptr; } int maxLayer() const { return headHeight_ - 1; } int curHeight() const { // need to cap the height to the cached head height, as the current node // might be some newly inserted node and also during the time period the // head height may have grown. return succs_[0] ? std::min(headHeight_, succs_[0]->height()) : 0; } const value_type& data() const { DCHECK(succs_[0] != nullptr); return succs_[0]->data(); } value_type& operator*() const { DCHECK(succs_[0] != nullptr); return succs_[0]->data(); } value_type* operator->() const { DCHECK(succs_[0] != nullptr); return &succs_[0]->data(); } /* * Skip to the position whose data is no less than the parameter. * (I.e. the lower_bound). * * Returns true if the data is found, false otherwise. */ bool to(const value_type& data) { int layer = curHeight() - 1; if (layer < 0) { return false; // reaches the end of the list } int lyr = hints_[layer]; int max_layer = maxLayer(); while (SkipListType::greater(data, succs_[lyr]) && lyr < max_layer) { ++lyr; } hints_[layer] = lyr; // update the hint int foundLayer = SkipListType::findInsertionPoint( preds_[lyr], lyr, data, preds_, succs_); if (foundLayer < 0) { return false; } DCHECK(succs_[0] != nullptr) << "lyr=" << lyr << "; max_layer=" << max_layer; return !succs_[0]->markedForRemoval(); } private: NodeType* head() const { return accessor_.skiplist()->head_.load(std::memory_order_acquire); } Accessor accessor_; int headHeight_; NodeType *succs_[MAX_HEIGHT], *preds_[MAX_HEIGHT]; uint8_t hints_[MAX_HEIGHT]; }; } // namespace folly ================================================ FILE: folly/ConstexprMath.h ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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 namespace folly { /// numbers /// /// mimic: std::numbers, C++20 (partial) namespace numbers { namespace detail { template using enable_if_floating_t = std::enable_if_t::value, T>; } /// e_v /// /// mimic: std::numbers::e_v, C++20 template inline constexpr T e_v = detail::enable_if_floating_t( 2.71828182845904523536028747135266249775724709369995L); /// ln2_v /// /// mimic: std::numbers::ln2_v, C++20 template inline constexpr T ln2_v = detail::enable_if_floating_t( 0.69314718055994530941723212145817656807550013436025L); /// e /// /// mimic: std::numbers::e, C++20 inline constexpr double e = e_v; /// ln2 /// /// mimic: std::numbers::ln2, C++20 inline constexpr double ln2 = ln2_v; } // namespace numbers /// floating_point_integral_constant /// /// Like std::integral_constant but for floating-point types holding integral /// values representable in an integral type. template struct floating_point_integral_constant { using value_type = T; static constexpr value_type value = static_cast(Value); constexpr operator value_type() const noexcept { return value; } constexpr value_type operator()() const noexcept { return value; } }; // ---- namespace detail { template constexpr size_t constexpr_iterated_squares_desc_size_(T const base) { using lim = std::numeric_limits; size_t s = 1; auto r = base; while (r <= lim::max() / r) { ++s; r *= r; } return s; } } // namespace detail /// constexpr_iterated_squares_desc_size_v /// /// Effectively calculates: floor(log(max_exponent)/log(base)) /// /// For use with constexpr_iterated_squares_desc below. template inline constexpr size_t constexpr_iterated_squares_desc_size_v = detail::constexpr_iterated_squares_desc_size_(Base::value); /// constexpr_iterated_squares_desc /// /// A constexpr scaling array of integer powers-of-powers-of-two, descending, /// with the associated powers-of-two. /// /// scaling = [..., {8, b^8}, {4, b^4}, {2, b^2}, {1, b^1}] for b = base /// /// Includes select constexpr scaling algorithms based on the scaling array. /// /// The scaling array and the scaling algorithms are general-purpose, if niche. /// They may be used by other constexpr math functions (floating-point) either /// to improve runtime performance or to improve numerical approximations. /// /// Some compilers fail to support passing some types as non-type template /// params. In particular, long double is not universally supported. Therefore, /// this utility takes its base as a type rather than as a value. For floating- /// point integral bases, that is, bases of floating-point type but of integral /// value, floating_point_integral_constant is the easiest parameterization. template struct constexpr_iterated_squares_desc { static_assert(Size > 0, "requires non-zero size"); using size_type = decltype(Size); using base_type = T; struct item_type { size_type power; base_type scale; }; static constexpr size_type size = Size; base_type base; item_type scaling[size]; private: using lim = std::numeric_limits; static_assert( lim::max_exponent < std::numeric_limits::max(), "size_type too small for base_type"); public: explicit constexpr constexpr_iterated_squares_desc(base_type r) noexcept : base{r}, scaling{} { assert(size <= detail::constexpr_iterated_squares_desc_size_(base)); size_type i = 0; size_type p = 1; while (true) { // a for-loop might cause multiplication overflow below scaling[size - 1 - i] = {p, r}; if (++i == size) { break; } p *= 2; r *= r; } } /// shrink /// /// Returns scaling params of the form: /// item_type{power, scale} with scale = base ^ power /// With power the smallest nonnegative integer such that: /// abs(num) / scale <= max constexpr item_type shrink(base_type const num, base_type const max) const { assert(max > base_type(0)); auto const rmax = max / base; auto const snum = num < base_type(0) ? -num : num; auto power = size_type(0); auto scale = base_type(1); if (!(snum / scale <= max)) { for (auto const& i : scaling) { auto const next = scale * i.scale; auto const div = snum / next; if (div <= rmax) { continue; } power += i.power; scale = next; if (div <= max) { break; } } } assert(snum / scale <= max); return {power, scale}; } /// growth /// /// Returns scaling params of the form: /// item_type{power, scale} with scale = base ^ power /// With power the smallest nonnegative integer such that: /// abs(num) * scale >= min constexpr item_type growth(base_type const num, base_type const min) const { assert(min > base_type(0)); auto const rmin = min * base; auto const snum = num < base_type(0) ? -num : num; auto power = size_type(0); auto scale = base_type(1); if (!(snum * scale >= min)) { for (auto const& i : scaling) { auto const next = scale * i.scale; auto const mul = snum * next; if (mul >= rmin) { continue; } power += i.power; scale = next; if (mul >= min) { break; } } } assert(snum * scale >= min); return {power, scale}; } }; /// constexpr_iterated_squares_desc_v /// /// An instance of constexpr_iterated_squares_desc of max size with the given /// base. template inline constexpr auto constexpr_iterated_squares_desc_v = constexpr_iterated_squares_desc< typename Base::value_type, constexpr_iterated_squares_desc_size_v>{Base::value}; /// constexpr_iterated_squares_desc_2_v /// /// An alias for constexpr_iterated_squares_desc_v with base 2, which is the /// most common base to use with iterated-squares. template constexpr auto& constexpr_iterated_squares_desc_2_v = constexpr_iterated_squares_desc_v< floating_point_integral_constant>; // TLDR: Prefer using operator< for ordering. And when // a and b are equivalent objects, we return b to make // sorting stable. // See http://stepanovpapers.com/notes.pdf for details. template constexpr T constexpr_max(T a, Ts... ts) { T list[] = {ts..., a}; // 0-length arrays are illegal for (auto i = 0u; i < sizeof...(Ts); ++i) { a = list[i] < a ? a : list[i]; } return a; } // When a and b are equivalent objects, we return a to // make sorting stable. template constexpr T constexpr_min(T a, Ts... ts) { T list[] = {ts..., a}; // 0-length arrays are illegal for (auto i = 0u; i < sizeof...(Ts); ++i) { a = list[i] < a ? list[i] : a; } return a; } template constexpr T const& constexpr_clamp( T const& v, T const& lo, T const& hi, Less less) { T const& a = less(v, lo) ? lo : v; T const& b = less(hi, a) ? hi : a; return b; } template constexpr T const& constexpr_clamp(T const& v, T const& lo, T const& hi) { return constexpr_clamp(v, lo, hi, std::less{}); } template constexpr bool constexpr_isnan(T const t) { return t != t; // NOLINT } namespace detail { template struct constexpr_abs_helper {}; template struct constexpr_abs_helper< T, typename std::enable_if::value>::type> { static constexpr T go(T t) { return t < static_cast(0) ? -t : t; } }; template struct constexpr_abs_helper< T, typename std::enable_if< std::is_integral::value && !std::is_same::value && std::is_unsigned::value>::type> { static constexpr T go(T t) { return t; } }; template struct constexpr_abs_helper< T, typename std::enable_if< std::is_integral::value && !std::is_same::value && std::is_signed::value>::type> { static constexpr typename std::make_unsigned::type go(T t) { return typename std::make_unsigned::type(t < static_cast(0) ? -t : t); } }; } // namespace detail template constexpr auto constexpr_abs(T t) -> decltype(detail::constexpr_abs_helper::go(t)) { return detail::constexpr_abs_helper::go(t); } namespace detail { template constexpr T constexpr_log2_(T a, T e) { return e == T(1) ? a : constexpr_log2_(a + T(1), e / T(2)); } template constexpr T constexpr_log2_ceil_(T l2, T t) { return l2 + T(T(1) << l2 < t ? 1 : 0); } } // namespace detail template constexpr T constexpr_log2(T t) { return detail::constexpr_log2_(T(0), t); } template constexpr T constexpr_log2_ceil(T t) { return detail::constexpr_log2_ceil_(constexpr_log2(t), t); } /// constexpr_trunc /// /// mimic: std::trunc (C++23) template < typename T, std::enable_if_t::value, int> = 0> constexpr T constexpr_trunc(T const t) { using lim = std::numeric_limits; using int_type = std::uintmax_t; using int_lim = std::numeric_limits; static_assert(lim::radix == 2, "non-binary radix"); static_assert(lim::digits <= int_lim::digits, "overwide mantissa"); constexpr auto bound = static_cast(std::uintmax_t(1) << (lim::digits - 1)); auto const neg = !constexpr_isnan(t) && t < T(0); auto const s = neg ? -t : t; if (constexpr_isnan(t) || t == T(0) || !(s < bound)) { return t; } if (s < T(1)) { return neg ? -T(0) : T(0); } auto const r = static_cast(static_cast(s)); return neg ? -r : r; } template ::value, int> = 0> constexpr T constexpr_trunc(T const t) { return t; } /// constexpr_round /// /// mimic: std::round (C++23) template constexpr T constexpr_round(T const t) { constexpr auto half = T(1) / T(2); auto const same = constexpr_isnan(t) || t == T(0); return same ? t : constexpr_trunc(t < T(0) ? t - half : t + half); } /// constexpr_floor /// /// mimic: std::floor (C++23) template constexpr T constexpr_floor(T const t) { auto const s = constexpr_trunc(t); return t < s ? s - T(1) : s; } /// constexpr_ceil /// /// mimic: std::ceil (C++23) template constexpr T constexpr_ceil(T const t) { auto const s = constexpr_trunc(t); return s < t ? s + T(1) : s; } /// constexpr_ceil /// /// The least integer at least t that round divides. template constexpr T constexpr_ceil(T t, T round) { return round == T(0) ? t : ((t + (t <= T(0) ? T(0) : round - T(1))) / round) * round; } /// constexpr_mult /// /// Multiply two values, allowing for constexpr floating-point overflow to /// infinity. template constexpr T constexpr_mult(T const a, T const b) { using lim = std::numeric_limits; if (constexpr_isnan(a) || constexpr_isnan(b)) { return constexpr_isnan(a) ? a : b; } if (std::is_floating_point::value) { constexpr auto inf = lim::infinity(); auto const ax = constexpr_abs(a); auto const bx = constexpr_abs(b); if ((ax == T(0) && bx == inf) || (bx == T(0) && ax == inf)) { return lim::quiet_NaN(); } // floating-point multiplication overflow, ie where multiplication of two // finite values overflows to infinity of either sign, is not constexpr per // gcc // floating-point division overflow, ie where division of two finite values // overflows to infinity of either sign, is not constexpr per gcc // floating-point division by zero is not constexpr per any compiler, but we // use it in the checks for the other two conditions if (ax != inf && bx != inf && T(1) < bx && lim::max() / bx < ax) { auto const a_neg = static_cast(a < T(0)); auto const b_neg = static_cast(b < T(0)); auto const sign = a_neg == b_neg ? T(1) : T(-1); return sign * inf; } } return a * b; } namespace detail { template < typename T, typename E, std::enable_if_t::value, int> = 1> constexpr T constexpr_ipow(T const base, E const exp) { if (std::is_floating_point::value) { if (exp < E(0)) { return T(1) / constexpr_ipow(base, -exp); } if (exp == E(0)) { return T(1); } if (constexpr_isnan(base)) { return base; } } assert(!(exp < E(0)) && "negative exponent with integral base"); if (exp == E(0)) { return T(1); } if (exp == E(1)) { return base; } auto const hexp = constexpr_trunc(exp / E(2)); auto const div = constexpr_ipow(base, hexp); auto const rem = hexp * E(2) == exp ? T(1) : base; return constexpr_mult(constexpr_mult(div, div), rem); } template < typename T, typename E, std::enable_if_t::value, int> = 1> constexpr T constexpr_ipow(T const base, E const exp) { if (std::is_floating_point::value) { if (exp == E(0)) { return T(1); } if (constexpr_isnan(base)) { return base; } } if (exp == E(0)) { return T(1); } if (exp == E(1)) { return base; } auto const hexp = constexpr_trunc(exp / E(2)); auto const div = constexpr_ipow(base, hexp); auto const rem = hexp * E(2) == exp ? T(1) : base; return constexpr_mult(constexpr_mult(div, div), rem); } } // namespace detail /// constexpr_exp /// /// Calculates an approximation of the mathematical function exp(num). Usable in /// constant evaluations. Like std::exp, which becomes constexpr in C++26. /// /// The integer overload uses iterated squaring and multiplication. The /// floating-point overload naively evaluates the taylor series of exp(num) /// until approximate convergence. /// /// mimic: std::exp (C++23, C++26) template < typename T, typename N, std::enable_if_t< std::is_floating_point::value && std::is_integral::value && !std::is_same::value, int> = 0> constexpr T constexpr_exp(N const power) { auto const npower = constexpr_abs(power); auto const result = detail::constexpr_ipow(numbers::e_v, npower); return power < N(0) ? T(1) / result : result; } template < typename N, std::enable_if_t< std::is_integral::value && !std::is_same::value, int> = 0> constexpr double constexpr_exp(N const power) { return constexpr_exp(power); } template < typename T, std::enable_if_t::value, int> = 0> constexpr T constexpr_exp(T const power) { using lim = std::numeric_limits; // edge cases if (constexpr_isnan(power)) { return power; } if (power == -lim::infinity()) { return +T(0); } if (power == +lim::infinity()) { return power; } // convergence works better with positive powers since signs do not alternate auto const abspower = constexpr_abs(power); // convergence must short-circuit when terms grow to floating-point infinity auto const bound = T(1) < abspower ? lim::max() / abspower : lim::infinity(); // term #index = power * coeff auto index = size_t(0); auto term = T(1); // result = sum of terms auto result = T(1); // sum the terms until ~convergence while (!(constexpr_abs(term) < lim::epsilon())) { if (bound < term) { return power < T(0) ? T(0) : lim::infinity(); } index += 1; term = term * abspower / index; result += term; } return power < T(0) ? T(1) / result : result; } /// constexpr_log /// /// Calculates an approximation of the natural logarithm ln(num). /// /// The implementation uses a quickly-converging, high-precision iterative /// technique as described in: /// https://en.wikipedia.org/wiki/Natural_logarithm#High_precision /// /// The technique works best with numbers that are close enough to 1, so the /// implementation uses a quick shrink/growth technique as described in: /// https://en.wikipedia.org/wiki/Natural_logarithm#Efficient_computation template < typename T, std::enable_if_t::value, int> = 0> constexpr T constexpr_log(T const num) { using lim = std::numeric_limits; constexpr auto& isq = constexpr_iterated_squares_desc_2_v; // edge cases if (constexpr_isnan(num)) { return num; } if (num < T(0)) { return lim::quiet_NaN(); } if (num == T(0)) { return -lim::infinity(); } if (num == lim::infinity()) { return num; } // compression auto const shrink = isq.shrink(num, isq.base); auto const growth = isq.growth(num, T(1)); auto const scaled = num * growth.scale / shrink.scale; assert(scaled <= isq.base); assert(scaled >= T(1)); auto sum = T(0); auto delta = T(2); while (constexpr_abs(delta) >= lim::epsilon()) { auto expterm = constexpr_exp(sum); delta = T(2) * (scaled - expterm) / (scaled + expterm); sum += delta; } auto const ln2 = numbers::ln2_v; return sum - growth.power * ln2 + shrink.power * ln2; } /// constexpr_pow /// /// Calculates an approximation of the value of base raised to the exponent exp. /// /// The implementation uses iterated squaring and multiplication for the integer /// part of the exponent and uses the identity x^y = exp(y * log(x)) for the /// fractional part of the exponent. /// /// Notes: /// * Forbids base of +0 or -0 with finite non-positive exponent: in part since /// the plausible infinite result would be sensitive to the sign of the zero; /// and in part since std::pow would be required or permitted to raise error /// div-by-zero. /// * Forbids finite negative base with finite non-integer exponent: in part /// since std::pow would be required to raise error invalid. /// /// mimic: std::pow (C++26) template < typename T, typename E, std::enable_if_t< std::is_integral::value && !std::is_same::value, int> = 0> constexpr T constexpr_pow(T const base, E const exp) { return detail::constexpr_ipow(base, exp); } template < typename T, std::enable_if_t::value, int> = 0> constexpr T constexpr_pow(T const base, T const exp) { using lim = std::numeric_limits; // edge cases if (exp == T(0)) { return T(1); } if (constexpr_isnan(base)) { return base; } if (exp == lim::infinity() || exp == -lim::infinity()) { auto const abase = constexpr_abs(base); if (abase < T(1)) { return exp == lim::infinity() ? T(0) : lim::infinity(); } if (T(1) < abase) { return exp == lim::infinity() ? lim::infinity() : T(0); } return T(1); } if (base == T(1)) { return base; } if (constexpr_isnan(exp)) { return exp; } assert(base != T(0) || exp > T(0)); // error div-by-zero if (base == lim::infinity()) { return exp < T(0) ? T(0) : lim::infinity(); } if (base == -lim::infinity()) { auto const oddi = // exp == constexpr_trunc(exp) && exp != constexpr_trunc(exp / T(2)) * T(2); return (oddi ? -T(1) : T(1)) * (exp < T(0) ? T(0) : lim::infinity()); } if (base == T(0)) { auto const oddi = // exp == constexpr_trunc(exp) && exp != constexpr_trunc(exp / T(2)) * T(2); return oddi ? base : T(0); } if (exp < T(0)) { return T(1) / constexpr_pow(base, -exp); } // as an identity: x^y = exp(y * log(x)); but calculation is imprecise ... so, // for better precision, split the calculation into its integral-power and its // fractional-power components // as a cost, the complexity of constexpr_ipow here is logarithmic in y, i.e., // linear in the logarithm of y, which can be prohibitive auto const exp_trunc = constexpr_trunc(exp); assert(T(0) < base || exp == exp_trunc); // error invalid auto const exp_fract = exp - exp_trunc; auto const anyi = exp_fract == T(0); return constexpr_mult( detail::constexpr_ipow(base, exp_trunc), anyi ? T(1) : constexpr_exp(exp_fract * constexpr_log(base))); } /// constexpr_find_last_set /// /// Return the 1-based index of the most significant bit which is set. /// For x > 0, constexpr_find_last_set(x) == 1 + floor(log2(x)). template constexpr std::size_t constexpr_find_last_set(T const t) { using U = std::make_unsigned_t; return t == T(0) ? 0 : 1 + constexpr_log2(static_cast(t)); } namespace detail { template constexpr std::size_t constexpr_find_first_set_( std::size_t s, std::size_t a, U const u) { return s == 0 ? a : constexpr_find_first_set_( s / 2, a + s * bool((u >> a) % (U(1) << s) == U(0)), u); } } // namespace detail /// constexpr_find_first_set /// /// Return the 1-based index of the least significant bit which is set. /// For x > 0, the exponent in the largest power of two which does not divide x. template constexpr std::size_t constexpr_find_first_set(T t) { using U = std::make_unsigned_t; using size = std::integral_constant; return t == T(0) ? 0 : 1 + detail::constexpr_find_first_set_(size{}, 0, static_cast(t)); } template constexpr T constexpr_add_overflow_clamped(T a, T b) { using L = std::numeric_limits; using M = std::intmax_t; static_assert( !std::is_integral::value || sizeof(T) <= sizeof(M), "Integral type too large!"); if (!folly::is_constant_evaluated_or(true)) { if constexpr (std::is_integral_v) { T ret{}; if (FOLLY_UNLIKELY(!checked_add(&ret, a, b))) { if constexpr (std::is_signed_v) { // Could be either overflow or underflow for signed types. // Can only be underflow if both inputs are negative. if (a < 0 && b < 0) { return L::min(); } } return L::max(); } return ret; } } // clang-format off return // don't do anything special for non-integral types. !std::is_integral::value ? a + b : // for narrow integral types, just convert to intmax_t. sizeof(T) < sizeof(M) ? T(constexpr_clamp( static_cast(a) + static_cast(b), static_cast(L::min()), static_cast(L::max()))) : // when a >= 0, cannot add more than `MAX - a` onto a. !(a < 0) ? a + constexpr_min(b, T(L::max() - a)) : // a < 0 && b >= 0, `a + b` will always be in valid range of type T. !(b < 0) ? a + b : // a < 0 && b < 0, keep the result >= MIN. a + constexpr_max(b, T(L::min() - a)); // clang-format on } template constexpr T constexpr_sub_overflow_clamped(T a, T b) { using L = std::numeric_limits; using M = std::intmax_t; static_assert( !std::is_integral::value || sizeof(T) <= sizeof(M), "Integral type too large!"); // clang-format off return // don't do anything special for non-integral types. !std::is_integral::value ? a - b : // for unsigned type, keep result >= 0. std::is_unsigned::value ? (a < b ? 0 : a - b) : // for narrow signed integral types, just convert to intmax_t. sizeof(T) < sizeof(M) ? T(constexpr_clamp( static_cast(a) - static_cast(b), static_cast(L::min()), static_cast(L::max()))) : // (a >= 0 && b >= 0) || (a < 0 && b < 0), `a - b` will always be valid. (a < 0) == (b < 0) ? a - b : // MIN < b, so `-b` should be in valid range (-MAX <= -b <= MAX), // convert subtraction to addition. L::min() < b ? constexpr_add_overflow_clamped(a, T(-b)) : // -b = -MIN = (MAX + 1) and a <= -1, result is in valid range. a < 0 ? a - b : // -b = -MIN = (MAX + 1) and a >= 0, result > MAX. L::max(); // clang-format on } // clamp_cast<> provides sane numeric conversions from float point numbers to // integral numbers, and between different types of integral numbers. It helps // to avoid unexpected bugs introduced by bad conversion, and undefined behavior // like overflow when casting float point numbers to integral numbers. // // When doing clamp_cast(value), if `value` is in valid range of Dst, // it will give correct result in Dst, equal to `value`. // // If `value` is outside the representable range of Dst, it will be clamped to // MAX or MIN in Dst, instead of being undefined behavior. // // Float NaNs are converted to 0 in integral type. // // Here's some comparison with static_cast<>: // (with FB-internal gcc-5-glibc-2.23 toolchain) // // static_cast(NaN) = 6 // clamp_cast(NaN) = 0 // // static_cast(9999999999.0f) = -348639895 // clamp_cast(9999999999.0f) = 2147483647 // // static_cast(2147483647.0f) = -348639895 // clamp_cast(2147483647.0f) = 2147483647 // // static_cast(4294967295.0f) = 0 // clamp_cast(4294967295.0f) = 4294967295 // // static_cast(-1) = 4294967295 // clamp_cast(-1) = 0 // // static_cast(32768u) = -32768 // clamp_cast(32768u) = 32767 template constexpr typename std::enable_if::value, Dst>::type constexpr_clamp_cast(Src src) { static_assert( std::is_integral::value && sizeof(Dst) <= sizeof(int64_t), "constexpr_clamp_cast can only cast into integral type (up to 64bit)"); using L = std::numeric_limits; // clang-format off return // Check if Src and Dst have same signedness. std::is_signed::value == std::is_signed::value ? ( // Src and Dst have same signedness. If sizeof(Src) <= sizeof(Dst), // we can safely convert Src to Dst without any loss of accuracy. sizeof(Src) <= sizeof(Dst) ? Dst(src) : // If Src is larger in size, we need to clamp it to valid range in Dst. Dst(constexpr_clamp(src, Src(L::min()), Src(L::max())))) // Src and Dst have different signedness. // Check if it's signed -> unsigned cast. : std::is_signed::value && std::is_unsigned::value ? ( // If src < 0, the result should be 0. src < 0 ? Dst(0) : // Otherwise, src >= 0. If src can fit into Dst, we can safely cast it // without loss of accuracy. sizeof(Src) <= sizeof(Dst) ? Dst(src) : // If Src is larger in size than Dst, we need to ensure the result is // at most Dst MAX. Dst(constexpr_min(src, Src(L::max())))) // It's unsigned -> signed cast. : ( // Since Src is unsigned, and Dst is signed, Src can fit into Dst only // when sizeof(Src) < sizeof(Dst). sizeof(Src) < sizeof(Dst) ? Dst(src) : // If Src does not fit into Dst, we need to ensure the result is at most // Dst MAX. Dst(constexpr_min(src, Src(L::max())))); // clang-format on } namespace detail { // Upper/lower bound values that could be accurately represented in both // integral and float point types. constexpr double kClampCastLowerBoundDoubleToInt64F = -9223372036854774784.0; constexpr double kClampCastUpperBoundDoubleToInt64F = 9223372036854774784.0; constexpr double kClampCastUpperBoundDoubleToUInt64F = 18446744073709549568.0; constexpr float kClampCastLowerBoundFloatToInt32F = -2147483520.0f; constexpr float kClampCastUpperBoundFloatToInt32F = 2147483520.0f; constexpr float kClampCastUpperBoundFloatToUInt32F = 4294967040.0f; // This works the same as constexpr_clamp, but the comparison are done in Src // to prevent any implicit promotions. template constexpr D constexpr_clamp_cast_helper(S src, S sl, S su, D dl, D du) { return src < sl ? dl : (src > su ? du : D(src)); } } // namespace detail template constexpr typename std::enable_if::value, Dst>::type constexpr_clamp_cast(Src src) { static_assert( std::is_integral::value && sizeof(Dst) <= sizeof(int64_t), "constexpr_clamp_cast can only cast into integral type (up to 64bit)"); using L = std::numeric_limits; // clang-format off return // Special case: cast NaN into 0. constexpr_isnan(src) ? Dst(0) : // using `sizeof(Src) > sizeof(Dst)` as a heuristic that Dst can be // represented in Src without loss of accuracy. // see: https://en.wikipedia.org/wiki/Floating-point_arithmetic sizeof(Src) > sizeof(Dst) ? detail::constexpr_clamp_cast_helper( src, Src(L::min()), Src(L::max()), L::min(), L::max()) : // sizeof(Src) < sizeof(Dst) only happens when doing cast of // 32bit float -> u/int64_t. // Losslessly promote float into double, change into double -> u/int64_t. sizeof(Src) < sizeof(Dst) ? ( src >= 0.0 ? constexpr_clamp_cast( constexpr_clamp_cast(double(src))) : constexpr_clamp_cast( constexpr_clamp_cast(double(src)))) : // The following are for sizeof(Src) == sizeof(Dst). std::is_same::value && std::is_same::value ? detail::constexpr_clamp_cast_helper( double(src), detail::kClampCastLowerBoundDoubleToInt64F, detail::kClampCastUpperBoundDoubleToInt64F, L::min(), L::max()) : std::is_same::value && std::is_same::value ? detail::constexpr_clamp_cast_helper( double(src), 0.0, detail::kClampCastUpperBoundDoubleToUInt64F, L::min(), L::max()) : std::is_same::value && std::is_same::value ? detail::constexpr_clamp_cast_helper( float(src), detail::kClampCastLowerBoundFloatToInt32F, detail::kClampCastUpperBoundFloatToInt32F, L::min(), L::max()) : detail::constexpr_clamp_cast_helper( float(src), 0.0f, detail::kClampCastUpperBoundFloatToUInt32F, L::min(), L::max()); // clang-format on } } // namespace folly ================================================ FILE: folly/ConstructorCallbackList.h ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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 #include namespace folly { // A mixin to register and issue callbacks every time a class constructor is // invoked // // For example: // #include // // class Foo { // ... // private: // ... // // add this member last to minimize partially constructed errors // ConstructorCallbackList constructorCB_{this}; // } // // int main() { // auto cb = [](Foo * f) { // std::cout << "New Foo" << f << std::endl; // }; // ConstructorCallbackList::addCallback(cb); // Foo f{}; // will call callback, print to stdout // } // // This code is designed to be light weight so as to mixin to many // places with low overhead. // // NOTE: The callback is triggered with a *partially* constructed object. // This implies that that callback code can only access members that are // constructed *before* the ConstructorCallbackList object. Also, at the time // of the callback, none of the Foo() constructor code will have run. // Per the example above, // the best practice is to place the ConstructorCallbackList declaration last // in the parent class. This will minimize the amount of uninitialized // data in the Foo instance, but will not eliminate it unless it has a trivial // constructor. // // Implementation/Overhead Notes: // // By design, adding ConstructorCallbackList to an object should be very // light weight. From a memory context, this adds 1 byte of memory to the // parent class. From a CPU/performance perspective, the constructor does a load // of an atomic int and the cost of the actual callbacks themselves. So if this // is put in place and only used infrequently, e.g., during debugging, // this cost should be quite small. // // A compile-time static array is used intentionally over a dynamic one for // two reasons: (1) a dynamic array seems to require a proper lock in // the constructor which would exceed our perf target, and (2) having a // finite array provides some sanity checking on the number of callbacks // that can be registered. template class ConstructorCallbackList { public: static constexpr std::size_t kMaxCallbacks = MaxCallbacks; using This = ConstructorCallbackList; using Callback = folly::Function; using CallbackArray = std::array; explicit ConstructorCallbackList(T* t) { // This code depends on the C++ standard where values that are // initialized to zero ("Zero Initiation") are initialized before any more // complex static pre-main() dynamic initialization - see // https://en.cppreference.com/w/cpp/language/initialization) for // more details. // // This assumption prevents a subtle initialization race condition // where something could call this code pre-main() before // numCallbacks_ was set to zero, and thus prevents issuing // callbacks on garbage data. auto nCBs = This::global().numCallbacks_.load(std::memory_order_acquire); // fire callbacks to inform listeners about the new constructor /**** * We don't need the full lock here, just the atomic int to tell us * how far into the array to go/how many callbacks are registered * * NOTE that nCBs > 0 will always imply that callbacks_ is non-nullptr */ for (size_t i = 0; i < nCBs; i++) { (This::global().callbacks_)[i](t); } } /** * Add a callback to the static class that will fire every time * someone creates a new one. * * Implement this as a static array of callbacks rather than a dynamic * vector to avoid nasty race conditions on resize, startup and shutdown. * * Implement this with functions rather than an observer pattern classes * to avoid race conditions on shutdown * * Intentionally don't implement removeConstructorCallbackList to simplify * implementation (e.g., just the counter is atomic rather than the whole * array) and thus reduce computational cost. * * @throw std::length_error() if this callback would exceed our max */ static void addCallback(Callback cb) { // Ensure that a single callback is added at a time std::lock_guard g(This::global().mutex_); auto idx = This::global().numCallbacks_.load(std::memory_order_acquire); if (idx >= (This::global().callbacks_).size()) { throw std::length_error( fmt::format("Too many callbacks - max {}", MaxCallbacks)); } (This::global().callbacks_)[idx] = std::move(cb); // Only increment numCallbacks_ after fully initializing the array // entry. This step makes the new array entry visible to other threads. This::global().numCallbacks_.store(idx + 1, std::memory_order_release); } private: // use createGlobal to avoid races on shutdown struct GlobalStorage { mutable folly::SharedMutex mutex_; This::CallbackArray callbacks_{}; std::atomic numCallbacks_{0}; }; static auto& global() { return folly::detail::createGlobal(); } }; } // namespace folly ================================================ FILE: folly/Conv.cpp ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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 #include #include #include #include namespace folly { namespace detail { namespace { /** * Finds the first non-digit in a string. The number of digits * searched depends on the precision of the Tgt integral. Assumes the * string starts with NO whitespace and NO sign. * * The semantics of the routine is: * for (;; ++b) { * if (b >= e || !isdigit(*b)) return b; * } * * Complete unrolling marks bottom-line (i.e. entire conversion) * improvements of 20%. */ inline const char* findFirstNonDigit(const char* b, const char* e) { for (; b < e; ++b) { auto const c = static_cast(*b) - '0'; if (c >= 10) { break; } } return b; } // Maximum value of number when represented as a string template struct MaxString { static const char* const value; }; template <> const char* const MaxString::value = "255"; template <> const char* const MaxString::value = "65535"; template <> const char* const MaxString::value = "4294967295"; #if __SIZEOF_LONG__ == 4 template <> const char* const MaxString::value = "4294967295"; #else template <> const char* const MaxString::value = "18446744073709551615"; #endif static_assert( sizeof(unsigned long) >= 4, "Wrong value for MaxString::value," " please update."); template <> const char* const MaxString::value = "18446744073709551615"; static_assert( sizeof(unsigned long long) >= 8, "Wrong value for MaxString::value" ", please update."); #if FOLLY_HAVE_INT128_T template <> const char* const MaxString<__uint128_t>::value = "340282366920938463463374607431768211455"; #endif /* * Lookup tables that converts from a decimal character value to an integral * binary value, shifted by a decimal "shift" multiplier. * For all character values in the range '0'..'9', the table at those * index locations returns the actual decimal value shifted by the multiplier. * For all other values, the lookup table returns an invalid OOR value. */ // Out-of-range flag value, larger than the largest value that can fit in // four decimal bytes (9999), but four of these added up together should // still not overflow uint16_t. constexpr int32_t OOR = 10000; alignas(16) constexpr uint16_t shift1[] = { OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 0-9 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 10 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 20 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 30 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, 0, 1, // 40 2, 3, 4, 5, 6, 7, 8, 9, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 60 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 70 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 80 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 90 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 100 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 110 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 120 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 130 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 140 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 150 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 160 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 170 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 180 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 190 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 200 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 210 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 220 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 230 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 240 OOR, OOR, OOR, OOR, OOR, OOR // 250 }; alignas(16) constexpr uint16_t shift10[] = { OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 0-9 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 10 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 20 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 30 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, 0, 10, // 40 20, 30, 40, 50, 60, 70, 80, 90, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 60 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 70 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 80 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 90 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 100 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 110 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 120 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 130 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 140 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 150 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 160 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 170 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 180 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 190 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 200 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 210 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 220 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 230 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 240 OOR, OOR, OOR, OOR, OOR, OOR // 250 }; alignas(16) constexpr uint16_t shift100[] = { OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 0-9 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 10 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 20 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 30 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, 0, 100, // 40 200, 300, 400, 500, 600, 700, 800, 900, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 60 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 70 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 80 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 90 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 100 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 110 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 120 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 130 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 140 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 150 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 160 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 170 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 180 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 190 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 200 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 210 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 220 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 230 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 240 OOR, OOR, OOR, OOR, OOR, OOR // 250 }; alignas(16) constexpr uint16_t shift1000[] = { OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 0-9 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 10 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 20 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 30 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, 0, 1000, // 40 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 60 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 70 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 80 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 90 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 100 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 110 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 120 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 130 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 140 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 150 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 160 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 170 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 180 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 190 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 200 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 210 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 220 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 230 OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, OOR, // 240 OOR, OOR, OOR, OOR, OOR, OOR // 250 }; struct ErrorString { const char* string; bool quote; }; // Keep this in sync with ConversionCode in Conv.h constexpr const std::array< ErrorString, static_cast(ConversionCode::NUM_ERROR_CODES)> kErrorStrings{{ // SUCCESS {"Success", true}, // EMPTY_INPUT_STRING {"Empty input string", true}, // NO_DIGITS {"No digits found in input string", true}, // BOOL_OVERFLOW {"Integer overflow when parsing bool (must be 0 or 1)", true}, // BOOL_INVALID_VALUE {"Invalid value for bool", true}, // NON_DIGIT_CHAR {"Non-digit character found", true}, // INVALID_LEADING_CHAR {"Invalid leading character", true}, // POSITIVE_OVERFLOW {"Overflow during conversion", true}, // NEGATIVE_OVERFLOW {"Negative overflow during conversion", true}, // STRING_TO_FLOAT_ERROR {"Unable to convert string to floating point value", true}, // NON_WHITESPACE_AFTER_END {"Non-whitespace character found after end of conversion", true}, // ARITH_POSITIVE_OVERFLOW {"Overflow during arithmetic conversion", false}, // ARITH_NEGATIVE_OVERFLOW {"Negative overflow during arithmetic conversion", false}, // ARITH_LOSS_OF_PRECISION {"Loss of precision during arithmetic conversion", false}, // SPLIT_ERROR, {"Unexpected number of fields resulting from a split", true}, // CUSTOM, {"Custom conversion failed", true}, }}; // Check if ASCII is really ASCII using IsAscii = std::bool_constant<'A' == 65 && 'Z' == 90 && 'a' == 97 && 'z' == 122>; // The code in this file that uses tolower() really only cares about // 7-bit ASCII characters, so we can take a nice shortcut here. inline char tolower_ascii(char in) { return IsAscii::value ? in | 0x20 : char(std::tolower(in)); } inline bool bool_str_cmp(const char** b, size_t len, const char* value) { // Can't use strncasecmp, since we want to ensure that the full value matches const char* p = *b; const char* e = *b + len; const char* v = value; while (*v != '\0') { if (p == e || tolower_ascii(*p) != *v) { // value is already lowercase return false; } ++p; ++v; } *b = p; return true; } } // namespace Expected str_to_bool(StringPiece* src) noexcept { auto b = src->begin(), e = src->end(); for (;; ++b) { if (b >= e) { return makeUnexpected(ConversionCode::EMPTY_INPUT_STRING); } if ((*b < '\t' || *b > '\r') && *b != ' ') { break; } } bool result; auto len = size_t(e - b); switch (*b) { case '0': case '1': { result = false; for (; b < e && isdigit(*b); ++b) { if (result || (*b != '0' && *b != '1')) { return makeUnexpected(ConversionCode::BOOL_OVERFLOW); } result = (*b == '1'); } break; } case 'y': case 'Y': result = true; if (!bool_str_cmp(&b, len, "yes")) { ++b; // accept the single 'y' character } break; case 'n': case 'N': result = false; if (!bool_str_cmp(&b, len, "no")) { ++b; } break; case 't': case 'T': result = true; if (!bool_str_cmp(&b, len, "true")) { ++b; } break; case 'f': case 'F': result = false; if (!bool_str_cmp(&b, len, "false")) { ++b; } break; case 'o': case 'O': if (bool_str_cmp(&b, len, "on")) { result = true; } else if (bool_str_cmp(&b, len, "off")) { result = false; } else { return makeUnexpected(ConversionCode::BOOL_INVALID_VALUE); } break; default: return makeUnexpected(ConversionCode::BOOL_INVALID_VALUE); } src->assign(b, e); return result; } /// Uses `fast_float::from_chars` to convert from string to an integer. template Expected str_to_floating_fast_float_from_chars( StringPiece* src) noexcept { if (src->empty()) { return makeUnexpected(ConversionCode::EMPTY_INPUT_STRING); } // move through leading whitespace characters auto* e = src->end(); auto* b = std::find_if_not(src->begin(), e, [](char c) { return (c >= '\t' && c <= '\r') || c == ' '; }); if (b == e) { return makeUnexpected(ConversionCode::EMPTY_INPUT_STRING); } Tgt result; fast_float::parse_options options{ fast_float::chars_format::general | fast_float::chars_format::allow_leading_plus}; auto [ptr, ec] = fast_float::from_chars_advanced(b, e, result, options); bool isOutOfRange{ec == std::errc::result_out_of_range}; bool isOk{ec == std::errc()}; if (!isOk && !isOutOfRange) { return makeUnexpected(ConversionCode::STRING_TO_FLOAT_ERROR); } auto numMatchedChars = ptr - src->data(); src->advance(numMatchedChars); return result; } template Expected str_to_floating_fast_float_from_chars(StringPiece* src) noexcept; template Expected str_to_floating_fast_float_from_chars(StringPiece* src) noexcept; /** * StringPiece to double, with progress information. Alters the * StringPiece parameter to munch the already-parsed characters. */ template Expected str_to_floating(StringPiece* src) noexcept { return detail::str_to_floating_fast_float_from_chars(src); } template Expected str_to_floating( StringPiece* src) noexcept; template Expected str_to_floating( StringPiece* src) noexcept; namespace { /** * This class takes care of additional processing needed for signed values, * like leading sign character and overflow checks. */ template > class SignedValueHandler; template class SignedValueHandler { public: ConversionCode init(const char*& b) { negative_ = false; if (!std::isdigit(*b)) { if (*b == '-') { negative_ = true; } else if (FOLLY_UNLIKELY(*b != '+')) { return ConversionCode::INVALID_LEADING_CHAR; } ++b; } return ConversionCode::SUCCESS; } ConversionCode overflow() { return negative_ ? ConversionCode::NEGATIVE_OVERFLOW : ConversionCode::POSITIVE_OVERFLOW; } template Expected finalize(U value) { T rv; if (negative_) { FOLLY_PUSH_WARNING FOLLY_MSVC_DISABLE_WARNING(4146) // unary minus operator applied to unsigned type, result still unsigned rv = T(-value); FOLLY_POP_WARNING if (FOLLY_UNLIKELY(rv > 0)) { return makeUnexpected(ConversionCode::NEGATIVE_OVERFLOW); } } else { rv = T(value); if (FOLLY_UNLIKELY(rv < 0)) { return makeUnexpected(ConversionCode::POSITIVE_OVERFLOW); } } return rv; } private: bool negative_; }; // For unsigned types, we don't need any extra processing template class SignedValueHandler { public: ConversionCode init(const char*&) { return ConversionCode::SUCCESS; } ConversionCode overflow() { return ConversionCode::POSITIVE_OVERFLOW; } Expected finalize(T value) { return value; } }; } // namespace /** * String represented as a pair of pointers to char to signed/unsigned * integrals. Assumes NO whitespace before or after, and also that the * string is composed entirely of digits (and an optional sign only for * signed types). String may be empty, in which case digits_to returns * an appropriate error. */ template inline Expected digits_to( const char* b, const char* const e) noexcept { using UT = make_unsigned_t; assert(b <= e); SignedValueHandler sgn; auto err = sgn.init(b); if (FOLLY_UNLIKELY(err != ConversionCode::SUCCESS)) { return makeUnexpected(err); } auto size = size_t(e - b); /* Although the string is entirely made of digits, we still need to * check for overflow. */ if (size > std::numeric_limits::digits10) { // Leading zeros? if (b < e && *b == '0') { for (++b;; ++b) { if (b == e) { return Tgt(0); // just zeros, e.g. "0000" } if (*b != '0') { size = size_t(e - b); break; } } } if (size > std::numeric_limits::digits10 && (size != std::numeric_limits::digits10 + 1 || strncmp(b, MaxString::value, size) > 0)) { return makeUnexpected(sgn.overflow()); } } // Here we know that the number won't overflow when // converted. Proceed without checks. UT result = 0; for (; e - b >= 4; b += 4) { result *= UT(10000); const int32_t r0 = shift1000[static_cast(b[0])]; const int32_t r1 = shift100[static_cast(b[1])]; const int32_t r2 = shift10[static_cast(b[2])]; const int32_t r3 = shift1[static_cast(b[3])]; const auto sum = r0 + r1 + r2 + r3; if (sum >= OOR) { goto outOfRange; } result += UT(sum); } switch (e - b) { case 3: { const int32_t r0 = shift100[static_cast(b[0])]; const int32_t r1 = shift10[static_cast(b[1])]; const int32_t r2 = shift1[static_cast(b[2])]; const auto sum = r0 + r1 + r2; if (sum >= OOR) { goto outOfRange; } result = UT(1000 * result + sum); break; } case 2: { const int32_t r0 = shift10[static_cast(b[0])]; const int32_t r1 = shift1[static_cast(b[1])]; const auto sum = r0 + r1; if (sum >= OOR) { goto outOfRange; } result = UT(100 * result + sum); break; } case 1: { const int32_t sum = shift1[static_cast(b[0])]; if (sum >= OOR) { goto outOfRange; } result = UT(10 * result + sum); break; } default: assert(b == e); if (size == 0) { return makeUnexpected(ConversionCode::NO_DIGITS); } break; } return sgn.finalize(result); outOfRange: return makeUnexpected(ConversionCode::NON_DIGIT_CHAR); } template Expected digits_to( const char*, const char*) noexcept; template Expected digits_to( const char*, const char*) noexcept; template Expected digits_to( const char*, const char*) noexcept; template Expected digits_to( const char*, const char*) noexcept; template Expected digits_to( const char*, const char*) noexcept; template Expected digits_to( const char*, const char*) noexcept; template Expected digits_to( const char*, const char*) noexcept; template Expected digits_to( const char*, const char*) noexcept; template Expected digits_to( const char*, const char*) noexcept; template Expected digits_to( const char*, const char*) noexcept; template Expected digits_to(const char*, const char*) noexcept; #if FOLLY_HAVE_INT128_T template Expected<__int128, ConversionCode> digits_to<__int128>( const char*, const char*) noexcept; template Expected digits_to(const char*, const char*) noexcept; #endif /** * StringPiece to integrals, with progress information. Alters the * StringPiece parameter to munch the already-parsed characters. */ template Expected str_to_integral(StringPiece* src) noexcept { using UT = make_unsigned_t; auto b = src->data(), past = src->data() + src->size(); for (;; ++b) { if (FOLLY_UNLIKELY(b >= past)) { return makeUnexpected(ConversionCode::EMPTY_INPUT_STRING); } if ((*b < '\t' || *b > '\r') && *b != ' ') { break; } } SignedValueHandler sgn; auto err = sgn.init(b); if (FOLLY_UNLIKELY(err != ConversionCode::SUCCESS)) { return makeUnexpected(err); } if (is_signed_v && FOLLY_UNLIKELY(b >= past)) { return makeUnexpected(ConversionCode::NO_DIGITS); } if (FOLLY_UNLIKELY(!isdigit(*b))) { return makeUnexpected(ConversionCode::NON_DIGIT_CHAR); } auto m = findFirstNonDigit(b + 1, past); auto tmp = digits_to(b, m); if (FOLLY_UNLIKELY(!tmp.hasValue())) { return makeUnexpected( tmp.error() == ConversionCode::POSITIVE_OVERFLOW ? sgn.overflow() : tmp.error()); } auto res = sgn.finalize(tmp.value()); if (res.hasValue()) { src->advance(size_t(m - src->data())); } return res; } template Expected str_to_integral( StringPiece* src) noexcept; template Expected str_to_integral( StringPiece* src) noexcept; template Expected str_to_integral( StringPiece* src) noexcept; template Expected str_to_integral( StringPiece* src) noexcept; template Expected str_to_integral(StringPiece* src) noexcept; template Expected str_to_integral( StringPiece* src) noexcept; template Expected str_to_integral( StringPiece* src) noexcept; template Expected str_to_integral( StringPiece* src) noexcept; template Expected str_to_integral( StringPiece* src) noexcept; template Expected str_to_integral( StringPiece* src) noexcept; template Expected str_to_integral(StringPiece* src) noexcept; #if FOLLY_HAVE_INT128_T template Expected<__int128, ConversionCode> str_to_integral<__int128>( StringPiece* src) noexcept; template Expected str_to_integral(StringPiece* src) noexcept; #endif } // namespace detail ConversionError makeConversionError(ConversionCode code, StringPiece input) { using namespace detail; static_assert( std::is_unsigned::type>::value, "ConversionCode should be unsigned"); auto index = static_cast(code); FOLLY_SAFE_CHECK(index < kErrorStrings.size(), "code=", uint64_t(index)); const ErrorString& err = kErrorStrings[index]; if (code == ConversionCode::EMPTY_INPUT_STRING && input.empty()) { return {err.string, code}; } std::string tmp(err.string); tmp.append(": "); if (err.quote) { tmp.append(1, '"'); } if (!input.empty()) { tmp.append(input.data(), input.size()); } if (err.quote) { tmp.append(1, '"'); } return {tmp, code}; } } // namespace folly ================================================ FILE: folly/Conv.h ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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. */ // // Docs: https://fburl.com/fbcref_conv // /** * Conv provides the ubiquitous method `to(source)`, along with * a few other generic interfaces for converting objects to and from * string-like types (std::string, fbstring, StringPiece), as well as * range-checked conversions between numeric and enum types. The mechanisms are * extensible, so that user-specified types can add folly::to support. * * folly::to(123) * // "123" * ******************************************************************************* * ## TYPE -> STRING CONVERSIONS ******************************************************************************* * You can call the `to` or `to`. These are variadic * functions that convert their arguments to strings, and concatenate them to * form a result. So, for example, * * auto str = to(123, "456", 789); * * Sets str to `"123456789"`. * * In addition to just concatenating the arguments, related functions can * delimit them with some string: `toDelim(",", "123", 456, "789")` * will return the string `"123,456,789"`. * * toAppend does not return a string; instead, it takes a pointer to a string as * its last argument, and appends the result of the concatenation into it: * std::string str = "123"; * toAppend(456, "789", &str); // Now str is "123456789". * * The toAppendFit function acts like toAppend, but it precalculates the size * required to perform the append operation, and reserves that space in the * output string before actually inserting its arguments. This can sometimes * save on string expansion, but beware: appending to the same string many times * with toAppendFit is likely a pessimization, since it will resize the string * once per append. * * The combination of the append and delim variants also exist: toAppendDelim * and toAppendDelimFit are defined, with the obvious semantics. * ******************************************************************************* * ## STRING -> TYPE CONVERSIONS ******************************************************************************* * Going in the other direction, and parsing a string into a C++ type, is also * supported: * to("123"); // Returns 123. * * Out of range (e.g. `to("1000")`), or invalidly formatted (e.g. * `to("four")`) inputs will throw. If throw-on-error is undesirable (for * instance: you're dealing with untrusted input, and want to protect yourself * from users sending you down a very slow exception-throwing path), you can use * `tryTo`, which will return an `Expected`. * * There are overloads of to() and tryTo() that take a `StringPiece*`. These * parse out a type from the beginning of a string, and modify the passed-in * StringPiece to indicate the portion of the string not consumed. * ******************************************************************************* * ## NUMERIC / ENUM CONVERSIONS ******************************************************************************* * Conv also supports a `to(S)` overload, where T and S are numeric or enum * types, that checks to see that the target type can represent its argument, * and will throw if it cannot. This includes cases where a floating point to * integral conversion is attempted on a value with a non-zero fractional * component, and integral to floating point conversions that would lose * precision. Enum conversions are range-checked for the underlying type of the * enum, but there is no check that the input value is a valid choice of enum * value. * ******************************************************************************* * ## CUSTOM TYPE CONVERSIONS ******************************************************************************* * Users may customize the string conversion functionality for their own data * types. The key functions you should implement are: * // Two functions to allow conversion to your type from a string. * Expected parseTo(folly::StringPiece in, * YourType& out); * YourErrorType makeConversionError(YourErrorType in, StringPiece in); * // Two functions to allow conversion from your type to a string. * template * void toAppend(const YourType& in, String* out); * size_t estimateSpaceNeeded(const YourType& in); * * These are documented below, inline. * * @file Conv.h */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if __has_include() #include #endif #include // V8 JavaScript implementation #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace folly { // Keep this in sync with kErrorStrings in Conv.cpp enum class ConversionCode : unsigned char { SUCCESS, EMPTY_INPUT_STRING, NO_DIGITS, BOOL_OVERFLOW, BOOL_INVALID_VALUE, NON_DIGIT_CHAR, INVALID_LEADING_CHAR, POSITIVE_OVERFLOW, NEGATIVE_OVERFLOW, STRING_TO_FLOAT_ERROR, NON_WHITESPACE_AFTER_END, ARITH_POSITIVE_OVERFLOW, ARITH_NEGATIVE_OVERFLOW, ARITH_LOSS_OF_PRECISION, SPLIT_ERROR, CUSTOM, NUM_ERROR_CODES, // has to be the last entry }; struct FOLLY_EXPORT ConversionErrorBase : std::range_error { using std::range_error::range_error; }; class FOLLY_EXPORT ConversionError : public ConversionErrorBase { public: ConversionError(const std::string& str, ConversionCode code) : ConversionErrorBase(str), code_(code) {} ConversionError(const char* str, ConversionCode code) : ConversionErrorBase(str), code_(code) {} ConversionCode errorCode() const { return code_; } private: ConversionCode code_; }; /** * Custom Error Translation * * Your overloaded parseTo() function can return a custom error code on failure. * ::folly::to() will call makeConversionError to translate that error code into * an object to throw. makeConversionError is found by argument-dependent * lookup. It should have this signature: * * namespace other_namespace { * enum YourErrorCode { BAD_ERROR, WORSE_ERROR }; * * struct YourConversionError : ConversionErrorBase { * YourConversionError(const char* what) : ConversionErrorBase(what) {} * }; * * YourConversionError * makeConversionError(YourErrorCode code, ::folly::StringPiece sp) { * ... * return YourConversionError(messageString); * } */ ConversionError makeConversionError(ConversionCode code, StringPiece input); namespace detail { /** * Enforce that the suffix following a number is made up only of whitespace. */ inline ConversionCode enforceWhitespaceErr(StringPiece sp) { for (auto c : sp) { if (FOLLY_UNLIKELY(!std::isspace(c))) { return ConversionCode::NON_WHITESPACE_AFTER_END; } } return ConversionCode::SUCCESS; } /** * Keep this implementation around for prettyToDouble(). */ inline void enforceWhitespace(StringPiece sp) { auto err = enforceWhitespaceErr(sp); if (err != ConversionCode::SUCCESS) { throw_exception(makeConversionError(err, sp)); } } } // namespace detail /** * @overloadbrief to, but return an Expected * * The identity conversion function. * tryTo(T) returns itself for all types T. */ template typename std::enable_if< std::is_same::type>::value, Expected>::type tryTo(Src&& value) noexcept { return static_cast(value); } /** * @overloadbrief Convert from one type to another. */ template typename std::enable_if< std::is_same::type>::value, Tgt>::type to(Src&& value) { return static_cast(value); } /** * Arithmetic to boolean */ /** * Unchecked conversion from arithmetic to boolean. This is different from the * other arithmetic conversions because we use the C convention of treating any * non-zero value as true, instead of range checking. */ template typename std::enable_if< is_arithmetic_v && !std::is_same::value && std::is_same::value, Expected>::type tryTo(const Src& value) noexcept { return value != Src(); } template typename std::enable_if< is_arithmetic_v && !std::is_same::value && std::is_same::value, Tgt>::type to(const Src& value) { return value != Src(); } /** * Anything to string */ namespace detail { template using LastElement = type_pack_element_t; #ifdef _MSC_VER // MSVC can't quite figure out the LastElementImpl::call() stuff // in the base implementation, so we have to use tuples instead, // which result in significantly more templates being compiled, // though the runtime performance is the same. template > const R& getLastElement(const Ts&... ts) { return std::get(std::forward_as_tuple(ts...)); } inline void getLastElement() {} #else template struct LastElementImpl; template <> struct LastElementImpl<> { static void call() {} }; template struct LastElementImpl { template static const Last& call(Igns..., const Last& last) { return last; } }; template > const R& getLastElement(const Ts&... ts) { return LastElementImpl...>::call(ts...); } #endif } // namespace detail /** * Conversions from integral types to string types. */ #if FOLLY_HAVE_INT128_T namespace detail { template constexpr unsigned int digitsEnough() { // digits10 returns the number of decimal digits that this type can represent, // not the number of characters required for the max value, so we need to add // one. ex: char digits10 returns 2, because 256-999 cannot be represented, // but we need 3. auto const digits10 = std::numeric_limits::digits10; return static_cast(digits10) + 1; } inline size_t unsafeTelescope128(char* outb, char* oute, unsigned __int128 x) { using Usrc = unsigned __int128; // Decompose the input into at most 3 components using the largest power-of-10 // base that fits in a 64-bit unsigned integer, and then convert the // components using 64-bit arithmetic and concatenate them. constexpr static auto kBase = UINT64_C(10'000'000'000'000'000'000); constexpr static size_t kBaseDigits = 19; size_t p = 0; const auto leading = [&](Usrc v) { assert(v >> 64 == 0); p = detail::to_ascii_with_route<10, to_ascii_alphabet_lower>( outb, oute, static_cast(v)); }; const auto append = [&](uint64_t v) { assert(v < kBase); assert(outb + p + kBaseDigits <= oute); auto v64 = static_cast(v); detail::to_ascii_with_route<10, to_ascii_alphabet_lower>( outb + p, kBaseDigits, v64); p += kBaseDigits; }; if (x >> 64 > 0) { const auto rem = static_cast(x % kBase); x /= kBase; if (x >> 64 > 0) { const auto rem2 = static_cast(x % kBase); x /= kBase; leading(x); append(rem2); append(rem); return p; } leading(x); append(rem); return p; } leading(x); return p; } } // namespace detail #endif /** * @overloadbrief Appends conversion to string. * * A single char gets appended. */ template void toAppend(char value, Tgt* result) { *result += value; } /** * @overloadbrief Estimates the number of characters in a value's string * representation. */ template constexpr typename std::enable_if::value, size_t>::type estimateSpaceNeeded(T) { return 1; } template constexpr size_t estimateSpaceNeeded(const char (&)[N]) { return N; } /** * Everything implicitly convertible to const char* gets appended. */ template typename std::enable_if< std::is_convertible::value && IsSomeString::value>::type toAppend(Src value, Tgt* result) { // Treat null pointers like an empty string, as in: // operator<<(std::ostream&, const char*). if (const char* c = value) { result->append(c); } } template typename std::enable_if::value, size_t>:: type estimateSpaceNeeded(Src value) { const char* c = value; return c ? std::strlen(c) : 0; } template typename std::enable_if::value, size_t>::type estimateSpaceNeeded(Src const& value) { return value.size(); } template typename std::enable_if< std::is_convertible::value && !IsSomeString::value && !std::is_convertible::value, size_t>::type estimateSpaceNeeded(Src value) { return folly::StringPiece(value).size(); } template <> inline size_t estimateSpaceNeeded(std::nullptr_t /* value */) { return 0; } template typename std::enable_if< std::is_pointer::value && IsSomeString>::value, size_t>::type estimateSpaceNeeded(Src value) { return value->size(); } /** * Strings get appended, too. */ template typename std::enable_if< IsSomeString::value && IsSomeString::value>::type toAppend(const Src& value, Tgt* result) { result->append(value); } /** * and StringPiece objects too */ template typename std::enable_if::value>::type toAppend( StringPiece value, Tgt* result) { result->append(value.data(), value.size()); } /** * There's no implicit conversion from fbstring to other string types, * so make a specialization. */ template typename std::enable_if::value>::type toAppend( const fbstring& value, Tgt* result) { result->append(value.data(), value.size()); } #if FOLLY_HAVE_INT128_T /** * Special handling for 128 bit integers. */ template void toAppend(__int128 value, Tgt* result) { using Usrc = unsigned __int128; char buffer[detail::digitsEnough() + 1]; const auto oute = buffer + sizeof(buffer); size_t p; if (value < 0) { buffer[0] = '-'; p = 1 + detail::unsafeTelescope128(buffer + 1, oute, -Usrc(value)); } else { p = detail::unsafeTelescope128(buffer, oute, value); } result->append(buffer, p); } template void toAppend(unsigned __int128 value, Tgt* result) { char buffer[detail::digitsEnough()]; size_t p = detail::unsafeTelescope128(buffer, buffer + sizeof(buffer), value); result->append(buffer, p); } template constexpr typename std::enable_if::value, size_t>::type estimateSpaceNeeded(T) { return detail::digitsEnough<__int128>(); } template constexpr typename std:: enable_if::value, size_t>::type estimateSpaceNeeded(T) { return detail::digitsEnough(); } #endif /** * int32_t and int64_t to string (by appending) go through here. The * result is APPENDED to a preexisting string passed as the second * parameter. This should be efficient with fbstring because fbstring * incurs no dynamic allocation below 23 bytes and no number has more * than 22 bytes in its textual representation (20 for digits, one for * sign, one for the terminating 0). */ template typename std::enable_if< is_integral_v && is_signed_v && IsSomeString::value && sizeof(Src) >= 4>::type toAppend(Src value, Tgt* result) { char buffer[to_ascii_size_max_decimal]; auto uvalue = value < 0 ? ~static_cast(value) + 1 : static_cast(value); if (value < 0) { result->push_back('-'); } result->append(buffer, to_ascii_decimal(buffer, uvalue)); } template typename std::enable_if< is_integral_v && is_signed_v && sizeof(Src) >= 4 && sizeof(Src) < 16, size_t>::type estimateSpaceNeeded(Src value) { auto uvalue = value < 0 ? ~static_cast(value) + 1 : static_cast(value); return size_t(value < 0) + to_ascii_size_decimal(uvalue); } /** * As above, but for uint32_t and uint64_t. */ template typename std::enable_if< is_integral_v && !is_signed_v && IsSomeString::value && sizeof(Src) >= 4>::type toAppend(Src value, Tgt* result) { char buffer[to_ascii_size_max_decimal]; result->append(buffer, to_ascii_decimal(buffer, value)); } template typename std::enable_if< is_integral_v && !is_signed_v && sizeof(Src) >= 4 && sizeof(Src) < 16, size_t>::type estimateSpaceNeeded(Src value) { return to_ascii_size_decimal(value); } /** * All small signed and unsigned integers to string go through 32-bit * types int32_t and uint32_t, respectively. */ template typename std::enable_if< is_integral_v && IsSomeString::value && sizeof(Src) < 4>::type toAppend(Src value, Tgt* result) { using Intermediate = typename std::conditional, int64_t, uint64_t>::type; toAppend(static_cast(value), result); } template typename std::enable_if< is_integral_v && sizeof(Src) < 4 && !std::is_same::value, size_t>::type estimateSpaceNeeded(Src value) { using Intermediate = typename std::conditional, int64_t, uint64_t>::type; return estimateSpaceNeeded(static_cast(value)); } /** * Enumerated values get appended as integers. */ template typename std::enable_if< std::is_enum::value && IsSomeString::value>::type toAppend(Src value, Tgt* result) { toAppend(to_underlying(value), result); } template typename std::enable_if::value, size_t>::type estimateSpaceNeeded(Src value) { return estimateSpaceNeeded(to_underlying(value)); } /** * Conversions from floating-point types to string types. */ /// Operating mode for the floating point type version of /// `folly::ToAppend`. This is modeled after /// `double_conversion::DoubleToStringConverter::DtoaMode`. /// Dtoa is an acronym for Double to ASCII. enum class DtoaMode { /// Outputs the shortest representation of a `double`. /// The output is either in decimal or exponential notation; which ever is /// shortest. SHORTEST, /// Outputs the shortest representation of a `float`. /// This outputs in either decimal or exponential notation, which ever is /// shortest. SHORTEST_SINGLE, /// Outputs fixed precision after the decimal point. Similar to /// `printf`'s %f. /// The output is in decimal notation. /// Use the `numDigits` parameter to specify the precision. FIXED, /// Outputs with a precision that is independent of the decimal point. /// The outputs is either decimal or exponential notation, depending on the /// value and the precision. /// Similar to `printf`'s %g formatting. /// Use the `numDigits` parameter to specify the precision. PRECISION, }; /// Flags for the floating point type version of `folly::ToAppend`. /// This is modeled after `double_conversion::DoubleToStringConverter::Flags`. /// Dtoa is an acronym for Double to ASCII. /// This enum is used to store bit wise flags, so a variable of this type may be /// a bitwise combination of these definitions. enum class DtoaFlags { NO_FLAGS = 0, /// Emits a plus sign for positive exponents. e.g., 1.2e+3 EMIT_POSITIVE_EXPONENT_SIGN = 1, /// Emits a trailing decimal point. e.g., 123. EMIT_TRAILING_DECIMAL_POINT = 2, /// Emits a trailing decimal point. e.g., 123.0 /// Requires `EMIT_TRAILING_DECIMAL_POINT` to be set. EMIT_TRAILING_ZERO_AFTER_POINT = 4, /// -0.0 outputs as 0.0 UNIQUE_ZERO = 8, /// Trailing zeros are removed from the fractional portion /// of the result in precision mode. Matches `printf`'s %g. /// When `EMIT_TRAILING_ZERO_AFTER_POINT` is also given, one trailing zero is /// preserved. NO_TRAILING_ZERO = 16, }; constexpr DtoaFlags operator|(DtoaFlags a, DtoaFlags b) { return static_cast(to_underlying(a) | to_underlying(b)); } constexpr DtoaFlags operator&(DtoaFlags a, DtoaFlags b) { return static_cast(to_underlying(a) & to_underlying(b)); } namespace detail { constexpr int kConvMaxDecimalInShortestLow = -6; /// 10^kConvMaxDecimalInShortestLow. Replace with constexpr std::pow in C++26. constexpr double kConvMaxDecimalInShortestLowValue = 0.000001; constexpr int kConvMaxDecimalInShortestHigh = 21; /// 10^kConvMaxDecimalInShortestHigh. Replace with constexpr std::pow in C++26. constexpr double kConvMaxDecimalInShortestHighValue = 1'000'000'000'000'000'000'000.0; constexpr int kBase10MaximalLength = 17; constexpr int kConvMaxFixedDigitsAfterPoint = double_conversion::DoubleToStringConverter::kMaxFixedDigitsAfterPoint; constexpr int kConvMaxPrecisionDigits = double_conversion::DoubleToStringConverter::kMaxPrecisionDigits; /// Converts `DtoaMode` to /// `double_conversion::DoubleToStringConverter::DtoaMode`. /// This is temporary until /// `double_conversion::DoubleToStringConverter::DtoaMode` is removed. constexpr double_conversion::DoubleToStringConverter::DtoaMode convert( DtoaMode mode) { switch (mode) { case DtoaMode::SHORTEST: return double_conversion::DoubleToStringConverter::SHORTEST; case DtoaMode::SHORTEST_SINGLE: return double_conversion::DoubleToStringConverter::SHORTEST_SINGLE; case DtoaMode::FIXED: return double_conversion::DoubleToStringConverter::FIXED; case DtoaMode::PRECISION: return double_conversion::DoubleToStringConverter::PRECISION; default: /* unexpected */ assert(false); // Default to PRECISION per existing behavior. return double_conversion::DoubleToStringConverter::PRECISION; } } /// Converts `DtoaFlags` to /// `double_conversion::DoubleToStringConverter::DtoaFlags`. /// This is temporary until /// `double_conversion::DoubleToStringConverter::DtoaFlags` is removed. constexpr double_conversion::DoubleToStringConverter::Flags convert( DtoaFlags flags) { return static_cast(flags); } } // namespace detail /** * `numDigits` is only used with `FIXED` && `PRECISION`. */ template typename std::enable_if< std::is_floating_point::value && IsSomeString::value>::type toAppend( Src value, Tgt* result, DtoaMode mode, unsigned int numDigits, DtoaFlags flags = DtoaFlags::NO_FLAGS) { double_conversion::DoubleToStringConverter::Flags dcFlags = detail::convert(flags); double_conversion::DoubleToStringConverter conv( dcFlags, "Infinity", "NaN", 'E', detail::kConvMaxDecimalInShortestLow, detail::kConvMaxDecimalInShortestHigh, 6, // max leading padding zeros 1); // max trailing padding zeros char buffer[256]; double_conversion::StringBuilder builder(buffer, sizeof(buffer)); double_conversion::DoubleToStringConverter::DtoaMode dcMode = detail::convert(mode); FOLLY_PUSH_WARNING FOLLY_CLANG_DISABLE_WARNING("-Wcovered-switch-default") switch (dcMode) { case double_conversion::DoubleToStringConverter::SHORTEST: conv.ToShortest(value, &builder); break; case double_conversion::DoubleToStringConverter::SHORTEST_SINGLE: conv.ToShortestSingle(static_cast(value), &builder); break; case double_conversion::DoubleToStringConverter::FIXED: conv.ToFixed(value, int(numDigits), &builder); break; case double_conversion::DoubleToStringConverter::PRECISION: default: assert(dcMode == double_conversion::DoubleToStringConverter::PRECISION); conv.ToPrecision(value, int(numDigits), &builder); break; } FOLLY_POP_WARNING const size_t length = size_t(builder.position()); builder.Finalize(); result->append(buffer, length); } /** * As above, but for floating point */ template typename std::enable_if< std::is_floating_point::value && IsSomeString::value>::type toAppend(Src value, Tgt* result) { toAppend(value, result, DtoaMode::SHORTEST, 0); } /** * Upper bound of the length of the output from * DoubleToStringConverter::ToShortest(double, StringBuilder*), * as used in toAppend(double, string*). */ template typename std::enable_if::value, size_t>::type estimateSpaceNeeded(Src value) { // kBase10MaximalLength is 17. We add 1 for decimal point, // e.g. 10.0/9 is 17 digits and 18 characters, including the decimal point. constexpr int kMaxMantissaSpace = detail::kBase10MaximalLength + 1; // strlen("E-") + digits10(numeric_limits::max_exponent10) constexpr int kMaxExponentSpace = 2 + 3; static const int kMaxPositiveSpace = std::max({ // E.g. 1.1111111111111111E-100. kMaxMantissaSpace + kMaxExponentSpace, // E.g. 0.000001.1111111111111111, if kConvMaxDecimalInShortestLow is -6. kMaxMantissaSpace - detail::kConvMaxDecimalInShortestLow, // If kConvMaxDecimalInShortestHigh is 21, then 1e21 is the smallest // number > 1 which ToShortest outputs in exponential notation, // so 21 is the longest non-exponential number > 1. detail::kConvMaxDecimalInShortestHigh, }); return size_t( kMaxPositiveSpace + (value < 0 ? 1 : 0)); // +1 for minus sign, if negative } template constexpr typename std::enable_if< !std::is_fundamental::value && #if FOLLY_HAVE_INT128_T // On OSX 10.10, is_fundamental<__int128> is false :-O !std::is_same<__int128, Src>::value && !std::is_same::value && #endif !IsSomeString::value && !std::is_convertible::value && !std::is_convertible::value && !std::is_enum::value, size_t>::type estimateSpaceNeeded(const Src&) { return sizeof(Src) + 1; // dumbest best effort ever? } #ifndef DOXYGEN_SHOULD_SKIP_THIS namespace detail { template struct EstimateSpaceToReserveAll; template struct EstimateSpaceToReserveAll> { template FOLLY_ERASE static constexpr size_t one(const T& v) { if constexpr (!Tag) { return 0; } else { return estimateSpaceNeeded(v); } } template static size_t call(const T&... v) { const size_t sizes[] = {one<(I + 1 < sizeof...(I))>(v)...}; size_t size = 0; for (const auto s : sizes) { size += s; } return size; } }; template void reserveInTarget(const O& o) { (void)o; } template void reserveInTarget(const T& v, const O& o) { o->reserve(estimateSpaceNeeded(v)); } template void reserveInTarget(const T0& v0, const T1& v1, const Ts&... vs) { using seq = std::index_sequence_for; getLastElement(vs...)->reserve( EstimateSpaceToReserveAll::call(v0, v1, vs...)); } template void reserveInTargetDelim(const Delimiter& d, const Ts&... vs) { static_assert(sizeof...(vs) >= 2, "Needs at least 2 args"); using seq = std::index_sequence_for; size_t fordelim = (sizeof...(vs) - 2) * estimateSpaceNeeded(d); getLastElement(vs...)->reserve( fordelim + EstimateSpaceToReserveAll::call(vs...)); } template struct ToAppendStrImplAll; template struct ToAppendStrImplAll> { template FOLLY_ERASE static void one(const T& v, Tgt* result) { if constexpr (Tag) { toAppend(v, result); } } template static void call(const T&... v) { auto r = getLastElement(v...); ((one(v, r)), ...); } }; template struct ToAppendDelimStrImplAll; template struct ToAppendDelimStrImplAll> { template FOLLY_ERASE static void one(const Delimiter& d, const T& v, Tgt* result) { if constexpr (Tag >= 1) { toAppend(v, result); } if constexpr (Tag >= 2) { toAppend(d, result); } } template static void call(const Delimiter& d, const T&... v) { static_assert(sizeof...(I) > 0); constexpr size_t N = sizeof...(I) - 1; auto r = detail::getLastElement(v...); ((one<(N - I < 2 ? N - I : 2)>(d, v, r)), ...); } }; template < class Delimiter, class T, class... Ts, std::enable_if_t< sizeof...(Ts) >= 2 && IsSomeString>::type>::value, int> = 0> void toAppendDelimStrImpl(const Delimiter& delim, const T& v, const Ts&... vs) { using seq = std::index_sequence_for; ToAppendDelimStrImplAll::call(delim, v, vs...); } } // namespace detail #endif /** * Variadic conversion to string. Appends each element in turn. * If we have two or more things to append, we will not reserve * the space for them and will depend on strings exponential growth. * If you just append once consider using toAppendFit which reserves * the space needed (but does not have exponential as a result). * * Custom implementations of toAppend() can be provided in the same namespace as * the type to customize printing. estimateSpaceNeed() may also be provided to * avoid reallocations in toAppendFit(): * * namespace other_namespace { * * template * void toAppend(const OtherType&, String* out); * * // optional * size_t estimateSpaceNeeded(const OtherType&); * * } */ template < class... Ts, std::enable_if_t< sizeof...(Ts) >= 3 && IsSomeString>::type>::value, int> = 0> void toAppend(const Ts&... vs) { using seq = std::index_sequence_for; detail::ToAppendStrImplAll::call(vs...); } /** * @overloadbrief toAppend, but pre-allocate the exact amount of space required. * * Special version of the call that preallocates exactly as much memory * as need for arguments to be stored in target. This means we are * not doing exponential growth when we append. If you are using it * in a loop you are aiming at your foot with a big perf-destroying * bazooka. * On the other hand if you are appending to a string once, this * will probably save a few calls to malloc. */ template < class... Ts, std::enable_if_t< IsSomeString>::type>::value, int> = 0> void toAppendFit(const Ts&... vs) { ::folly::detail::reserveInTarget(vs...); toAppend(vs...); } template void toAppendFit(const Ts&) {} /** * Variadic base case: do nothing. */ template typename std::enable_if::value>::type toAppend( Tgt* /* result */) {} /** * @overloadbrief Use a specified delimiter between appendees. * * Variadic base case: do nothing. */ template typename std::enable_if::value>::type toAppendDelim( const Delimiter& /* delim */, Tgt* /* result */) {} /** * 1 element: same as toAppend. */ template typename std::enable_if::value>::type toAppendDelim( const Delimiter& /* delim */, const T& v, Tgt* tgt) { toAppend(v, tgt); } /** * Append to string with a delimiter in between elements. Check out * comments for toAppend for details about memory allocation. */ template < class Delimiter, class... Ts, std::enable_if_t< sizeof...(Ts) >= 3 && IsSomeString>::type>::value, int> = 0> void toAppendDelim(const Delimiter& delim, const Ts&... vs) { detail::toAppendDelimStrImpl(delim, vs...); } /** * @overloadbrief toAppend with custom delimiter and exact pre-allocation. * * Detail in comment for toAppendFit */ template < class Delimiter, class... Ts, std::enable_if_t< IsSomeString>::type>::value, int> = 0> void toAppendDelimFit(const Delimiter& delim, const Ts&... vs) { detail::reserveInTargetDelim(delim, vs...); toAppendDelim(delim, vs...); } template void toAppendDelimFit(const De&, const Ts&) {} /** * to(v1, v2, ...) uses toAppend() (see below) as back-end * for all types. */ template < class Tgt, class... Ts, std::enable_if_t< IsSomeString::value && (sizeof...(Ts) != 1 || !std::is_same>::value), int> = 0> Tgt to(const Ts&... vs) { Tgt result; toAppendFit(vs..., &result); return result; } /** * Special version of to for floating point. When calling * folly::to(double), generic implementation above will * firstly reserve 24 (or 25 when negative value) bytes. This will * introduce a malloc call for most mainstream string implementations. * * But for most cases, a floating point doesn't need 24 (or 25) bytes to * be converted as a string. * * This special version will not do string reserve. */ template typename std::enable_if< IsSomeString::value && std::is_floating_point::value, Tgt>::type to(Src value) { Tgt result; toAppend(value, &result); return result; } /** * @overloadbrief Like `to`, but uses a custom delimiter. * * toDelim(SomeString str) returns itself. */ template typename std::enable_if< IsSomeString::value && std::is_same::type>::value, Tgt>::type toDelim(const Delim& /* delim */, Src&& value) { return static_cast(value); } /** * toDelim(delim, v1, v2, ...) uses toAppendDelim() as * back-end for all types. */ template < class Tgt, class Delim, class... Ts, std::enable_if_t< IsSomeString::value && (sizeof...(Ts) != 1 || !std::is_same>::value), int> = 0> Tgt toDelim(const Delim& delim, const Ts&... vs) { Tgt result; toAppendDelimFit(delim, vs..., &result); return result; } /** * Conversions from string types to integral types. */ namespace detail { Expected str_to_bool(StringPiece* src) noexcept; template Expected str_to_floating(StringPiece* src) noexcept; extern template Expected str_to_floating( StringPiece* src) noexcept; extern template Expected str_to_floating( StringPiece* src) noexcept; template Expected str_to_floating_fast_float_from_chars( StringPiece* src) noexcept; extern template Expected str_to_floating_fast_float_from_chars(StringPiece* src) noexcept; extern template Expected str_to_floating_fast_float_from_chars(StringPiece* src) noexcept; template Expected digits_to(const char* b, const char* e) noexcept; extern template Expected digits_to( const char*, const char*) noexcept; extern template Expected digits_to( const char*, const char*) noexcept; extern template Expected digits_to(const char*, const char*) noexcept; extern template Expected digits_to( const char*, const char*) noexcept; extern template Expected digits_to(const char*, const char*) noexcept; extern template Expected digits_to( const char*, const char*) noexcept; extern template Expected digits_to( const char*, const char*) noexcept; extern template Expected digits_to( const char*, const char*) noexcept; extern template Expected digits_to(const char*, const char*) noexcept; extern template Expected digits_to( const char*, const char*) noexcept; extern template Expected digits_to(const char*, const char*) noexcept; #if FOLLY_HAVE_INT128_T extern template Expected<__int128, ConversionCode> digits_to<__int128>( const char*, const char*) noexcept; extern template Expected digits_to(const char*, const char*) noexcept; #endif template Expected str_to_integral(StringPiece* src) noexcept; extern template Expected str_to_integral( StringPiece* src) noexcept; extern template Expected str_to_integral(StringPiece* src) noexcept; extern template Expected str_to_integral(StringPiece* src) noexcept; extern template Expected str_to_integral( StringPiece* src) noexcept; extern template Expected str_to_integral(StringPiece* src) noexcept; extern template Expected str_to_integral( StringPiece* src) noexcept; extern template Expected str_to_integral(StringPiece* src) noexcept; extern template Expected str_to_integral( StringPiece* src) noexcept; extern template Expected str_to_integral(StringPiece* src) noexcept; extern template Expected str_to_integral( StringPiece* src) noexcept; extern template Expected str_to_integral(StringPiece* src) noexcept; #if FOLLY_HAVE_INT128_T extern template Expected<__int128, ConversionCode> str_to_integral<__int128>( StringPiece* src) noexcept; extern template Expected str_to_integral(StringPiece* src) noexcept; #endif template typename std::enable_if< std::is_same::value, Expected>::type convertTo(StringPiece* src) noexcept { return str_to_bool(src); } template typename std::enable_if< std::is_floating_point::value, Expected>::type convertTo(StringPiece* src) noexcept { return str_to_floating(src); } template typename std::enable_if< is_integral_v && !std::is_same::value, Expected>::type convertTo(StringPiece* src) noexcept { return str_to_integral(src); } } // namespace detail /** * String represented as a pair of pointers to char to unsigned * integrals. Assumes NO whitespace before or after. */ template typename std::enable_if< is_integral_v && !std::is_same::value, Expected>::type tryTo(const char* b, const char* e) noexcept { return detail::digits_to(b, e); } template typename std::enable_if< // is_integral_v && !std::is_same::value, Tgt>::type to(const char* b, const char* e) { return tryTo(b, e).thenOrThrow(identity, [=](ConversionCode code) { return makeConversionError(code, StringPiece(b, e)); }); } /** * Conversions from string types to arithmetic types. */ /** * Parsing strings to numeric types. */ template [[nodiscard]] inline typename std::enable_if< // is_arithmetic_v, Expected>::type parseTo(StringPiece src, Tgt& out) { return detail::convertTo(&src).then([&](Tgt res) { return void(out = res), src; }); } /** * Integral / Floating Point to integral / Floating Point */ namespace detail { /** * Bool to integral/float doesn't need any special checks, and this * overload means we aren't trying to see if a bool is less than * an integer. */ template typename std::enable_if< !std::is_same::value && (is_integral_v || std::is_floating_point::value), Expected>::type convertTo(const bool& value) noexcept { return static_cast(value ? 1 : 0); } /** * Checked conversion from integral to integral. The checks are only * performed when meaningful, e.g. conversion from int to long goes * unchecked. */ template typename std::enable_if< is_integral_v && !std::is_same::value && !std::is_same::value && is_integral_v, Expected>::type convertTo(const Src& value) noexcept { if constexpr ( make_unsigned_t(std::numeric_limits::max()) < make_unsigned_t(std::numeric_limits::max())) { if (greater_than::max()>(value)) { return makeUnexpected(ConversionCode::ARITH_POSITIVE_OVERFLOW); } } if constexpr ( is_signed_v && (!is_signed_v || sizeof(Src) > sizeof(Tgt))) { if (less_than::min()>(value)) { return makeUnexpected(ConversionCode::ARITH_NEGATIVE_OVERFLOW); } } return static_cast(value); } /** * Checked conversion from floating to floating. The checks are only * performed when meaningful, e.g. conversion from float to double goes * unchecked. */ template typename std::enable_if< std::is_floating_point::value && std::is_floating_point::value && !std::is_same::value, Expected>::type convertTo(const Src& value) noexcept { if (FOLLY_UNLIKELY(std::isinf(value))) { return static_cast(value); } if constexpr ( std::numeric_limits::max() < std::numeric_limits::max()) { if (value > std::numeric_limits::max()) { return makeUnexpected(ConversionCode::ARITH_POSITIVE_OVERFLOW); } if (value < std::numeric_limits::lowest()) { return makeUnexpected(ConversionCode::ARITH_NEGATIVE_OVERFLOW); } } return static_cast(value); } /** * Check if a floating point value can safely be converted to an * integer value without triggering undefined behaviour. */ template inline typename std::enable_if< std::is_floating_point::value && is_integral_v && !std::is_same::value, bool>::type checkConversion(const Src& value) { constexpr Src tgtMaxAsSrc = static_cast(std::numeric_limits::max()); constexpr Src tgtMinAsSrc = static_cast(std::numeric_limits::min()); // NOTE: The following two comparisons also handle the case where value is // NaN, as all comparisons with NaN are false. if (!(value < tgtMaxAsSrc)) { if (!(value <= tgtMaxAsSrc)) { return false; } const Src mmax = folly::nextafter(tgtMaxAsSrc, Src()); if (static_cast(value - mmax) > std::numeric_limits::max() - static_cast(mmax)) { return false; } } else if (value <= tgtMinAsSrc) { if (value < tgtMinAsSrc) { return false; } const Src mmin = folly::nextafter(tgtMinAsSrc, Src()); if (static_cast(value - mmin) < std::numeric_limits::min() - static_cast(mmin)) { return false; } } return true; } // Integers can always safely be converted to floating point values template constexpr typename std::enable_if< is_integral_v && std::is_floating_point::value, bool>::type checkConversion(const Src&) { return true; } // Also, floating point values can always be safely converted to bool // Per the standard, any floating point value that is not zero will yield true template constexpr typename std::enable_if< std::is_floating_point::value && std::is_same::value, bool>::type checkConversion(const Src&) { return true; } /** * Checked conversion from integral to floating point and back. The * result must be convertible back to the source type without loss of * precision. This seems Draconian but sometimes is what's needed, and * complements existing routines nicely. For various rounding * routines, see . */ template typename std::enable_if< (is_integral_v && std::is_floating_point::value) || (std::is_floating_point::value && is_integral_v), Expected>::type convertTo(const Src& value) noexcept { if (FOLLY_LIKELY(checkConversion(value))) { Tgt result = static_cast(value); if (FOLLY_LIKELY(checkConversion(result))) { Src witness = static_cast(result); if (FOLLY_LIKELY(value == witness)) { return result; } } } return makeUnexpected(ConversionCode::ARITH_LOSS_OF_PRECISION); } template inline std::string errorValue(const Src& value) { return to("(", pretty_name(), ") ", value); } template using IsArithToArith = std::bool_constant< !std::is_same::value && !std::is_same::value && is_arithmetic_v && is_arithmetic_v>; } // namespace detail template typename std::enable_if< detail::IsArithToArith::value, Expected>::type tryTo(const Src& value) noexcept { return detail::convertTo(value); } template typename std::enable_if::value, Tgt>::type to( const Src& value) { return tryTo(value).thenOrThrow(identity, [&](ConversionCode e) { return makeConversionError(e, detail::errorValue(value)); }); } /** * Custom Conversions * * Any type can be used with folly::to by implementing parseTo. The * implementation should be provided in the namespace of the type to facilitate * argument-dependent lookup: * * namespace other_namespace { * ::folly::Expected<::folly::StringPiece, SomeErrorCode> * parseTo(::folly::StringPiece, OtherType&) noexcept; * } */ template [[nodiscard]] typename std::enable_if< std::is_enum::value, Expected>::type parseTo(StringPiece in, T& out) noexcept { typename std::underlying_type::type tmp{}; auto restOrError = parseTo(in, tmp); out = static_cast(tmp); // Harmless if parseTo fails return restOrError; } [[nodiscard]] inline Expected parseTo( StringPiece in, StringPiece& out) noexcept { out = in; return StringPiece{in.end(), in.end()}; } namespace detail { template FOLLY_ERASE Expected parseToStr( StringPiece in, Str& out) { out.clear(); out.append(in.data(), in.size()); // TODO try/catch? return StringPiece{in.end(), in.end()}; } } // namespace detail [[nodiscard]] inline Expected parseTo( StringPiece in, std::string& out) { return detail::parseToStr(in, out); } [[nodiscard]] inline Expected parseTo( StringPiece in, std::string_view& out) { out = std::string_view(in.data(), in.size()); return StringPiece{in.end(), in.end()}; } [[nodiscard]] inline Expected parseTo( StringPiece in, fbstring& out) { return detail::parseToStr(in, out); } template [[nodiscard]] inline typename std::enable_if< IsSomeString::value, Expected>::type parseTo(StringPiece in, Str& out) { return detail::parseToStr(in, out); } namespace detail { template using ParseToResult = decltype(parseTo(StringPiece{}, std::declval())); struct CheckTrailingSpace { Expected operator()(StringPiece sp) const { auto e = enforceWhitespaceErr(sp); if (FOLLY_UNLIKELY(e != ConversionCode::SUCCESS)) { return makeUnexpected(e); } return unit; } }; template struct ReturnUnit { template constexpr Expected operator()(T&&) const { return unit; } }; // Older versions of the parseTo customization point threw on error and // returned void. Handle that. template inline typename std::enable_if< std::is_void>::value, Expected>::type parseToWrap(StringPiece sp, Tgt& out) { parseTo(sp, out); return StringPiece(sp.end(), sp.end()); } template inline typename std::enable_if< !std::is_void>::value, ParseToResult>::type parseToWrap(StringPiece sp, Tgt& out) { return parseTo(sp, out); } template using ParseToError = ExpectedErrorType()))>; } // namespace detail /** * String or StringPiece to target conversion. Accepts leading and trailing * whitespace, but no non-space trailing characters. */ template inline typename std::enable_if< !std::is_same::value, Expected>>::type tryTo(StringPiece src) noexcept { Tgt result{}; using Error = detail::ParseToError; using Check = typename std::conditional< is_arithmetic_v, detail::CheckTrailingSpace, detail::ReturnUnit>::type; return parseTo(src, result).then(Check(), [&](Unit) { return std::move(result); }); } template inline typename std::enable_if< IsSomeString::value && !std::is_same::value, Tgt>::type to(Src const& src) { return to(StringPiece(src.data(), src.size())); } template inline typename std::enable_if::value, Tgt>::type to(StringPiece src) { Tgt result{}; using Error = detail::ParseToError; using Check = typename std::conditional< is_arithmetic_v, detail::CheckTrailingSpace, detail::ReturnUnit>::type; auto tmp = detail::parseToWrap(src, result); return tmp .thenOrThrow( Check(), [&](Error e) { throw_exception(makeConversionError(e, src)); }) .thenOrThrow( [&](Unit) { return std::move(result); }, [&](Error e) { throw_exception(makeConversionError(e, tmp.value())); }); } /** * tryTo/to that take the strings by pointer so the caller gets information * about how much of the string was consumed by the conversion. These do not * check for trailing whitespace. */ template Expected> tryTo(StringPiece* src) noexcept { Tgt result; return parseTo(*src, result).then([&, src](StringPiece sp) -> Tgt { *src = sp; return std::move(result); }); } template Tgt to(StringPiece* src) { Tgt result{}; using Error = detail::ParseToError; return parseTo(*src, result) .thenOrThrow( [&, src](StringPiece sp) -> Tgt { *src = sp; return std::move(result); }, [=](Error e) { return makeConversionError(e, *src); }); } /** * Enum to anything and back */ template typename std::enable_if< std::is_enum::value && !std::is_same::value && !std::is_convertible::value, Expected>::type tryTo(const Src& value) noexcept { return tryTo(to_underlying(value)); } template typename std::enable_if< !std::is_convertible::value && std::is_enum::value && !std::is_same::value, Expected>::type tryTo(const Src& value) noexcept { using I = typename std::underlying_type::type; return tryTo(value).then([](I i) { return static_cast(i); }); } template typename std::enable_if< std::is_enum::value && !std::is_same::value && !std::is_convertible::value, Tgt>::type to(const Src& value) { return to(to_underlying(value)); } template typename std::enable_if< !std::is_convertible::value && std::is_enum::value && !std::is_same::value, Tgt>::type to(const Src& value) { return static_cast(to::type>(value)); } } // namespace folly ================================================ FILE: folly/CppAttributes.h ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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. */ /** * GCC compatible wrappers around clang attributes. */ #pragma once #include #ifndef __has_attribute #define FOLLY_HAS_ATTRIBUTE(x) 0 #else #define FOLLY_HAS_ATTRIBUTE(x) __has_attribute(x) #endif #ifndef __has_cpp_attribute #define FOLLY_HAS_CPP_ATTRIBUTE(x) 0 #else #define FOLLY_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x) #endif #ifndef __has_extension #define FOLLY_HAS_EXTENSION(x) 0 #else #define FOLLY_HAS_EXTENSION(x) __has_extension(x) #endif /** * Nullable indicates that a return value or a parameter may be a `nullptr`, * e.g. * * int* FOLLY_NULLABLE foo(int* a, int* FOLLY_NULLABLE b) { * if (*a > 0) { // safe dereference * return nullptr; * } * if (*b < 0) { // unsafe dereference * return *a; * } * if (b != nullptr && *b == 1) { // safe checked dereference * return new int(1); * } * return nullptr; * } * * Ignores Clang's -Wnullability-extension since it correctly handles the case * where the extension is not present. */ #if FOLLY_HAS_EXTENSION(nullability) #define FOLLY_NULLABLE \ FOLLY_PUSH_WARNING \ FOLLY_CLANG_DISABLE_WARNING("-Wnullability-extension") \ _Nullable FOLLY_POP_WARNING #define FOLLY_NONNULL \ FOLLY_PUSH_WARNING \ FOLLY_CLANG_DISABLE_WARNING("-Wnullability-extension") \ _Nonnull FOLLY_POP_WARNING #else #define FOLLY_NULLABLE #define FOLLY_NONNULL #endif /** * "Cold" indicates to the compiler that a function is only expected to be * called from unlikely code paths. It can affect decisions made by the * optimizer both when processing the function body and when analyzing * call-sites. */ #if FOLLY_HAS_CPP_ATTRIBUTE(gnu::cold) #define FOLLY_ATTR_GNU_COLD gnu::cold #else #define FOLLY_ATTR_GNU_COLD #endif /// FOLLY_ATTR_MAYBE_UNUSED_IF_NDEBUG /// /// When defined(NDEBUG), expands to maybe_unused; otherwise, expands to empty. /// Useful for marking variables that are used, in the sense checked for by the /// attribute maybe_unused, only in debug builds. #if defined(NDEBUG) #define FOLLY_ATTR_MAYBE_UNUSED_IF_NDEBUG maybe_unused #else #define FOLLY_ATTR_MAYBE_UNUSED_IF_NDEBUG #endif /** * no_unique_address indicates that a member variable can be optimized to * occupy no space, rather than the minimum 1-byte used by default. * * class Empty {}; * * class NonEmpty1 { * [[FOLLY_ATTR_NO_UNIQUE_ADDRESS]] Empty e; * int f; * }; * * class NonEmpty2 { * Empty e; * int f; * }; * * sizeof(NonEmpty1); // may be == sizeof(int) * sizeof(NonEmpty2); // must be > sizeof(int) */ #if FOLLY_HAS_CPP_ATTRIBUTE(no_unique_address) #define FOLLY_ATTR_NO_UNIQUE_ADDRESS no_unique_address #elif FOLLY_HAS_CPP_ATTRIBUTE(msvc::no_unique_address) #define FOLLY_ATTR_NO_UNIQUE_ADDRESS msvc::no_unique_address #else #define FOLLY_ATTR_NO_UNIQUE_ADDRESS #endif #if FOLLY_HAS_CPP_ATTRIBUTE(clang::no_destroy) #define FOLLY_ATTR_CLANG_NO_DESTROY clang::no_destroy #else #define FOLLY_ATTR_CLANG_NO_DESTROY #endif #if FOLLY_HAS_CPP_ATTRIBUTE(clang::uninitialized) #define FOLLY_ATTR_CLANG_UNINITIALIZED clang::uninitialized #else #define FOLLY_ATTR_CLANG_UNINITIALIZED #endif /** * Accesses to objects with types with this attribute are not subjected to * type-based alias analysis, but are instead assumed to be able to alias any * other type of objects, just like the char type. */ #if FOLLY_HAS_CPP_ATTRIBUTE(gnu::may_alias) #define FOLLY_ATTR_GNU_MAY_ALIAS gnu::may_alias #else #define FOLLY_ATTR_GNU_MAY_ALIAS #endif #if FOLLY_HAS_CPP_ATTRIBUTE(gnu::pure) #define FOLLY_ATTR_GNU_PURE gnu::pure #else #define FOLLY_ATTR_GNU_PURE #endif #if FOLLY_HAS_CPP_ATTRIBUTE(clang::preserve_most) #define FOLLY_ATTR_CLANG_PRESERVE_MOST clang::preserve_most #else #define FOLLY_ATTR_CLANG_PRESERVE_MOST #endif #if FOLLY_HAS_CPP_ATTRIBUTE(clang::preserve_all) #define FOLLY_ATTR_CLANG_PRESERVE_ALL clang::preserve_all #else #define FOLLY_ATTR_CLANG_PRESERVE_ALL #endif #if FOLLY_HAS_CPP_ATTRIBUTE(gnu::used) #define FOLLY_ATTR_GNU_USED gnu::used #else #define FOLLY_ATTR_GNU_USED #endif #if FOLLY_HAS_CPP_ATTRIBUTE(gnu::retain) #define FOLLY_ATTR_GNU_RETAIN gnu::retain #else #define FOLLY_ATTR_GNU_RETAIN #endif #if FOLLY_HAS_CPP_ATTRIBUTE(gnu::noclone) #define FOLLY_ATTR_GNU_NOCLONE gnu::noclone #else #define FOLLY_ATTR_GNU_NOCLONE #endif #if FOLLY_HAS_CPP_ATTRIBUTE(gnu::flatten) #define FOLLY_ATTR_GNU_FLATTEN gnu::flatten #else #define FOLLY_ATTR_GNU_FLATTEN #endif #if FOLLY_HAS_CPP_ATTRIBUTE(clang::lifetimebound) #define FOLLY_ATTR_CLANG_LIFETIMEBOUND clang::lifetimebound #else #define FOLLY_ATTR_CLANG_LIFETIMEBOUND #endif #if FOLLY_HAS_CPP_ATTRIBUTE(clang::coro_await_elidable) #define FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE clang::coro_await_elidable #else #define FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE #endif #if FOLLY_HAS_CPP_ATTRIBUTE(clang::coro_await_elidable_argument) #define FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE_ARGUMENT \ clang::coro_await_elidable_argument #else #define FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE_ARGUMENT #endif #if FOLLY_HAS_CPP_ATTRIBUTE(clang::no_thread_safety_analysis) #define FOLLY_ATTR_CLANG_NO_THREAD_SAFETY_ANALYSIS \ clang::no_thread_safety_analysis #else #define FOLLY_ATTR_CLANG_NO_THREAD_SAFETY_ANALYSIS #endif ================================================ FILE: folly/CpuId.h ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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 namespace folly { /** * Identification of an Intel CPU. * Supports CPUID feature flags (EAX=1) and extended features (EAX=7, ECX=0). * Values from * http://www.intel.com/content/www/us/en/processors/processor-identification-cpuid-instruction-note.html */ class CpuId { public: // Always inline in order for this to be usable from a __ifunc__. // In shared library mode, a __ifunc__ runs at relocation time, while the // PLT hasn't been fully populated yet; thus, ifuncs cannot use symbols // with potentially external linkage. (This issue is less likely in opt // mode since inlining happens more likely, and it doesn't happen for // statically linked binaries which don't depend on the PLT) FOLLY_ALWAYS_INLINE CpuId() { unsigned int reg[4]; x86_cpuid(reg, 0); vendor_[0] = reg[1]; vendor_[1] = reg[3]; vendor_[2] = reg[2]; const int n = reg[0]; if (n >= 1) { x86_cpuid(reg, 1); f1c_ = reg[2]; f1d_ = reg[3]; } if (n >= 7) { x86_cpuid(reg, 7); f7b_ = reg[1]; f7c_ = reg[2]; f7d_ = reg[3]; } } #define FOLLY_DETAIL_CPUID_X(name, r, bit) \ FOLLY_ALWAYS_INLINE bool name() const { return ((r) & (1U << bit)) != 0; } // cpuid(1): Processor Info and Feature Bits. #define FOLLY_DETAIL_CPUID_C(name, bit) FOLLY_DETAIL_CPUID_X(name, f1c_, bit) FOLLY_DETAIL_CPUID_C(sse3, 0) FOLLY_DETAIL_CPUID_C(pclmuldq, 1) FOLLY_DETAIL_CPUID_C(dtes64, 2) FOLLY_DETAIL_CPUID_C(monitor, 3) FOLLY_DETAIL_CPUID_C(dscpl, 4) FOLLY_DETAIL_CPUID_C(vmx, 5) FOLLY_DETAIL_CPUID_C(smx, 6) FOLLY_DETAIL_CPUID_C(eist, 7) FOLLY_DETAIL_CPUID_C(tm2, 8) FOLLY_DETAIL_CPUID_C(ssse3, 9) FOLLY_DETAIL_CPUID_C(cnxtid, 10) FOLLY_DETAIL_CPUID_C(fma, 12) FOLLY_DETAIL_CPUID_C(cx16, 13) FOLLY_DETAIL_CPUID_C(xtpr, 14) FOLLY_DETAIL_CPUID_C(pdcm, 15) FOLLY_DETAIL_CPUID_C(pcid, 17) FOLLY_DETAIL_CPUID_C(dca, 18) FOLLY_DETAIL_CPUID_C(sse41, 19) FOLLY_DETAIL_CPUID_C(sse42, 20) FOLLY_DETAIL_CPUID_C(x2apic, 21) FOLLY_DETAIL_CPUID_C(movbe, 22) FOLLY_DETAIL_CPUID_C(popcnt, 23) FOLLY_DETAIL_CPUID_C(tscdeadline, 24) FOLLY_DETAIL_CPUID_C(aes, 25) FOLLY_DETAIL_CPUID_C(xsave, 26) FOLLY_DETAIL_CPUID_C(osxsave, 27) FOLLY_DETAIL_CPUID_C(avx, 28) FOLLY_DETAIL_CPUID_C(f16c, 29) FOLLY_DETAIL_CPUID_C(rdrand, 30) #undef FOLLY_DETAIL_CPUID_C #define FOLLY_DETAIL_CPUID_D(name, bit) FOLLY_DETAIL_CPUID_X(name, f1d_, bit) FOLLY_DETAIL_CPUID_D(fpu, 0) FOLLY_DETAIL_CPUID_D(vme, 1) FOLLY_DETAIL_CPUID_D(de, 2) FOLLY_DETAIL_CPUID_D(pse, 3) FOLLY_DETAIL_CPUID_D(tsc, 4) FOLLY_DETAIL_CPUID_D(msr, 5) FOLLY_DETAIL_CPUID_D(pae, 6) FOLLY_DETAIL_CPUID_D(mce, 7) FOLLY_DETAIL_CPUID_D(cx8, 8) FOLLY_DETAIL_CPUID_D(apic, 9) FOLLY_DETAIL_CPUID_D(sep, 11) FOLLY_DETAIL_CPUID_D(mtrr, 12) FOLLY_DETAIL_CPUID_D(pge, 13) FOLLY_DETAIL_CPUID_D(mca, 14) FOLLY_DETAIL_CPUID_D(cmov, 15) FOLLY_DETAIL_CPUID_D(pat, 16) FOLLY_DETAIL_CPUID_D(pse36, 17) FOLLY_DETAIL_CPUID_D(psn, 18) FOLLY_DETAIL_CPUID_D(clfsh, 19) FOLLY_DETAIL_CPUID_D(ds, 21) FOLLY_DETAIL_CPUID_D(acpi, 22) FOLLY_DETAIL_CPUID_D(mmx, 23) FOLLY_DETAIL_CPUID_D(fxsr, 24) FOLLY_DETAIL_CPUID_D(sse, 25) FOLLY_DETAIL_CPUID_D(sse2, 26) FOLLY_DETAIL_CPUID_D(ss, 27) FOLLY_DETAIL_CPUID_D(htt, 28) FOLLY_DETAIL_CPUID_D(tm, 29) FOLLY_DETAIL_CPUID_D(pbe, 31) #undef FOLLY_DETAIL_CPUID_D // cpuid(7): Extended Features. #define FOLLY_DETAIL_CPUID_B(name, bit) FOLLY_DETAIL_CPUID_X(name, f7b_, bit) FOLLY_DETAIL_CPUID_B(bmi1, 3) FOLLY_DETAIL_CPUID_B(hle, 4) FOLLY_DETAIL_CPUID_B(avx2, 5) FOLLY_DETAIL_CPUID_B(smep, 7) FOLLY_DETAIL_CPUID_B(bmi2, 8) FOLLY_DETAIL_CPUID_B(erms, 9) FOLLY_DETAIL_CPUID_B(invpcid, 10) FOLLY_DETAIL_CPUID_B(rtm, 11) FOLLY_DETAIL_CPUID_B(mpx, 14) FOLLY_DETAIL_CPUID_B(avx512f, 16) FOLLY_DETAIL_CPUID_B(avx512dq, 17) FOLLY_DETAIL_CPUID_B(rdseed, 18) FOLLY_DETAIL_CPUID_B(adx, 19) FOLLY_DETAIL_CPUID_B(smap, 20) FOLLY_DETAIL_CPUID_B(avx512ifma, 21) FOLLY_DETAIL_CPUID_B(pcommit, 22) FOLLY_DETAIL_CPUID_B(clflushopt, 23) FOLLY_DETAIL_CPUID_B(clwb, 24) FOLLY_DETAIL_CPUID_B(avx512pf, 26) FOLLY_DETAIL_CPUID_B(avx512er, 27) FOLLY_DETAIL_CPUID_B(avx512cd, 28) FOLLY_DETAIL_CPUID_B(sha, 29) FOLLY_DETAIL_CPUID_B(avx512bw, 30) FOLLY_DETAIL_CPUID_B(avx512vl, 31) #undef FOLLY_DETAIL_CPUID_B #define FOLLY_DETAIL_CPUID_C(name, bit) FOLLY_DETAIL_CPUID_X(name, f7c_, bit) FOLLY_DETAIL_CPUID_C(prefetchwt1, 0) FOLLY_DETAIL_CPUID_C(avx512vbmi, 1) FOLLY_DETAIL_CPUID_C(avx512vbmi2, 6) FOLLY_DETAIL_CPUID_C(vaes, 9) FOLLY_DETAIL_CPUID_C(vpclmulqdq, 10) FOLLY_DETAIL_CPUID_C(avx512vnni, 11) FOLLY_DETAIL_CPUID_C(avx512bitalg, 12) FOLLY_DETAIL_CPUID_C(avx512vpopcntdq, 14) FOLLY_DETAIL_CPUID_C(rdpid, 22) #undef FOLLY_DETAIL_CPUID_C #define FOLLY_DETAIL_CPUID_D(name, bit) FOLLY_DETAIL_CPUID_X(name, f7d_, bit) FOLLY_DETAIL_CPUID_D(avx5124vnniw, 2) FOLLY_DETAIL_CPUID_D(avx5124fmaps, 3) FOLLY_DETAIL_CPUID_D(avx512vp2intersect, 8) FOLLY_DETAIL_CPUID_D(amxbf16, 22) FOLLY_DETAIL_CPUID_D(avx512fp16, 23) FOLLY_DETAIL_CPUID_D(amxtile, 24) FOLLY_DETAIL_CPUID_D(amxint8, 25) #undef FOLLY_DETAIL_CPUID_D #undef FOLLY_DETAIL_CPUID_X #define FOLLY_DETAIL_VENDOR(name, str) \ FOLLY_ALWAYS_INLINE bool vendor_##name() const { \ /* Size of str should be 12 + NUL terminator. */ \ static_assert(sizeof(str) == 13, "Bad CPU Vendor string"); \ /* Just as with the main CpuId call above, this can also \ still be in an __ifunc__, so no function calls :( */ \ return memcmp(&vendor_[0], &str[0], 12) == 0; \ } FOLLY_DETAIL_VENDOR(intel, "GenuineIntel") FOLLY_DETAIL_VENDOR(amd, "AuthenticAMD") #undef FOLLY_DETAIL_VENDOR private: uint32_t vendor_[3] = {0}; uint32_t f1c_ = 0; uint32_t f1d_ = 0; uint32_t f7b_ = 0; uint32_t f7c_ = 0; uint32_t f7d_ = 0; }; } // namespace folly ================================================ FILE: folly/DefaultKeepAliveExecutor.h ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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 namespace folly { /// An Executor accepts units of work with add(), which should be /// threadsafe. class DefaultKeepAliveExecutor : public virtual Executor { public: virtual ~DefaultKeepAliveExecutor() override { DCHECK(!keepAlive_); } template static auto getWeakRef(ExecutorT& executor) { static_assert( std::is_base_of::value, "getWeakRef only works for folly::DefaultKeepAliveExecutor implementations."); using WeakRefExecutorType = std::conditional_t< std::is_base_of::value, SequencedExecutor, Executor>; return WeakRef::create( executor.controlBlock_, &executor); } folly::Executor::KeepAlive<> weakRef() { return getWeakRef(*this); } class WeakRefExecutor : public virtual Executor {}; protected: void joinKeepAlive() { DCHECK(keepAlive_); keepAlive_.reset(); keepAliveReleaseBaton_.wait(); } void joinAndResetKeepAlive() { joinKeepAlive(); auto keepAliveCount = controlBlock_->keepAliveCount_.exchange(1, std::memory_order_relaxed); DCHECK_EQ(keepAliveCount, 0); keepAliveReleaseBaton_.reset(); keepAlive_ = makeKeepAlive(this); } private: struct ControlBlock { std::atomic keepAliveCount_{1}; }; template class WeakRef : public virtual ExecutorT, public virtual WeakRefExecutor { public: static folly::Executor::KeepAlive create( std::shared_ptr controlBlock, ExecutorT* executor) { return makeKeepAlive(new WeakRef(std::move(controlBlock), executor)); } void add(Func f) override { if (auto executor = lock()) { executor->add(std::move(f)); } } void addWithPriority(Func f, int8_t priority) override { if (auto executor = lock()) { executor->addWithPriority(std::move(f), priority); } } virtual uint8_t getNumPriorities() const override { return numPriorities_; } private: WeakRef(std::shared_ptr controlBlock, ExecutorT* executor) : controlBlock_(std::move(controlBlock)), executor_(executor), numPriorities_(executor->getNumPriorities()) {} bool keepAliveAcquire() noexcept override { auto keepAliveCount = keepAliveCount_.fetch_add(1, std::memory_order_relaxed); // We should never increment from 0 DCHECK(keepAliveCount > 0); return true; } void keepAliveRelease() noexcept override { auto keepAliveCount = keepAliveCount_.fetch_sub(1, std::memory_order_acq_rel); DCHECK(keepAliveCount >= 1); if (keepAliveCount == 1) { delete this; } } folly::Executor::KeepAlive lock() { auto controlBlock = controlBlock_->keepAliveCount_.load(std::memory_order_relaxed); do { if (controlBlock == 0) { return {}; } } while (!controlBlock_->keepAliveCount_.compare_exchange_weak( controlBlock, controlBlock + 1, std::memory_order_release, std::memory_order_relaxed)); return makeKeepAlive(executor_); } std::atomic keepAliveCount_{1}; std::shared_ptr controlBlock_; ExecutorT* executor_; uint8_t numPriorities_; }; bool keepAliveAcquire() noexcept override { auto keepAliveCount = controlBlock_->keepAliveCount_.fetch_add(1, std::memory_order_relaxed); // We should never increment from 0 DCHECK(keepAliveCount > 0); return true; } void keepAliveRelease() noexcept override { auto keepAliveCount = controlBlock_->keepAliveCount_.fetch_sub(1, std::memory_order_acq_rel); DCHECK(keepAliveCount >= 1); if (keepAliveCount == 1) { keepAliveReleaseBaton_.post(); // std::memory_order_release } } std::shared_ptr controlBlock_{std::make_shared()}; Baton<> keepAliveReleaseBaton_; KeepAlive keepAlive_{makeKeepAlive(this)}; }; template auto getWeakRef(ExecutorT& executor) { static_assert( std::is_base_of::value, "getWeakRef only works for folly::DefaultKeepAliveExecutor implementations."); return DefaultKeepAliveExecutor::getWeakRef(executor); } } // namespace folly ================================================ FILE: folly/Demangle.cpp ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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 #include #include #include #include #include #include #if __has_include() #include // @donotremove #endif // The headers (binutils) and (glibc) both declare the // symbol basename. Unfortunately, the declarations are different. So including // both headers in the same translation unit fails due to the two conflicting // declarations. Since includes we must be careful. #if __has_include() #pragma push_macro("HAVE_DECL_BASENAME") #define HAVE_DECL_BASENAME 1 #include // @manual #pragma pop_macro("HAVE_DECL_BASENAME") #endif // try to find cxxabi demangle // // prefer using a weakref #if __has_include() [[gnu::weakref("__cxa_demangle")]] static char* cxxabi_demangle( char const*, char*, size_t*, int*); #else // __has_include() static constexpr auto cxxabi_demangle = static_cast(nullptr); #endif // __has_include() // try to find liberty demangle // // cannot use a weak symbol since it may be the only referenced symbol in // liberty // // in contrast with cxxabi, where there are certainly other referenced symbols // // for rust_demangle_callback, detect its declaration in the header #if __has_include() namespace { struct poison {}; FOLLY_PUSH_WARNING FOLLY_GCC_DISABLE_WARNING("-Wunused-function") [[maybe_unused]] void rust_demangle_callback(poison); FOLLY_POP_WARNING [[maybe_unused]] FOLLY_ERASE int rust_demangle_callback_fallback( const char*, int, demangle_callbackref, void*) { return 0; } FOLLY_CREATE_QUAL_INVOKER( invoke_rust_demangle_primary, ::rust_demangle_callback); FOLLY_CREATE_QUAL_INVOKER( invoke_rust_demangle_fallback, rust_demangle_callback_fallback); using invoke_rust_demangle_fn = folly::invoke_first_match< invoke_rust_demangle_primary, invoke_rust_demangle_fallback>; constexpr invoke_rust_demangle_fn invoke_rust_demangle; int call_rust_demangle_callback( const char* mangled, int options, demangle_callbackref cb, void* opaque) { return invoke_rust_demangle(mangled, options, cb, opaque); } } // namespace using liberty_demangle_t = int(const char*, int, demangle_callbackref, void*); static constexpr liberty_demangle_t* liberty_cplus_demangle = cplus_demangle_v3_callback; static constexpr liberty_demangle_t* liberty_rust_demangle = call_rust_demangle_callback; #if defined(DMGL_NO_RECURSE_LIMIT) static constexpr auto liberty_demangle_options_no_recurse_limit = DMGL_NO_RECURSE_LIMIT; #else static constexpr auto liberty_demangle_options_no_recurse_limit = 0; #endif static constexpr auto liberty_demangle_options = // DMGL_PARAMS | DMGL_ANSI | DMGL_TYPES | // liberty_demangle_options_no_recurse_limit; #else // __has_include() using liberty_demangle_t = int(...); static constexpr liberty_demangle_t* liberty_cplus_demangle = nullptr; static constexpr liberty_demangle_t* liberty_rust_demangle = nullptr; static constexpr auto liberty_demangle_options = 0; #endif // __has_include() // implementations namespace folly { bool demangle_build_has_cxxabi() noexcept { return to_bool(cxxabi_demangle); } bool demangle_build_has_liberty() noexcept { bool vals[] = { to_bool(liberty_cplus_demangle), to_bool(liberty_rust_demangle), }; return std::all_of(std::begin(vals), std::end(vals), folly::identity); } namespace { void demangleStringCallback(const char* str, size_t size, void* p) { fbstring* demangle = static_cast(p); demangle->append(str, size); } } // namespace fbstring demangle(const char* name) { if (!name) { return fbstring(); } if (demangle_max_symbol_size) { // GCC's __cxa_demangle() uses on-stack data structures for the // parser state which are linear in the number of components of the // symbol. For extremely long symbols, this can cause a stack // overflow. We set an arbitrary symbol length limit above which we // just return the mangled name. size_t mangledLen = strlen(name); if (mangledLen > demangle_max_symbol_size) { return fbstring(name, mangledLen); } } if (folly::demangle_build_has_liberty()) { liberty_demangle_t* funcs[] = { liberty_rust_demangle, liberty_cplus_demangle, }; for (auto func : funcs) { fbstring demangled; // Unlike most library functions, this returns 1 on success and 0 on // failure int success = func( name, liberty_demangle_options, demangleStringCallback, &demangled); if (success && !demangled.empty()) { return demangled; } } } if (cxxabi_demangle) { int status; size_t len = 0; // malloc() memory for the demangled type name char* demangled = cxxabi_demangle(name, nullptr, &len, &status); if (status != 0) { return name; } // len is the length of the buffer (including NUL terminator and maybe // other junk) return fbstring( demangled, strlen(demangled), len, AcquireMallocatedString()); } else { return fbstring(name); } } namespace { struct DemangleBuf { char* dest; size_t remaining; size_t total; }; void demangleBufCallback(const char* str, size_t size, void* p) { DemangleBuf* buf = static_cast(p); size_t n = std::min(buf->remaining, size); memcpy(buf->dest, str, n); buf->dest += n; buf->remaining -= n; buf->total += size; } } // namespace size_t demangle(const char* name, char* out, size_t outSize) { if (demangle_max_symbol_size) { size_t mangledLen = strlen(name); if (mangledLen > demangle_max_symbol_size) { if (outSize) { size_t n = std::min(mangledLen, outSize - 1); memcpy(out, name, n); out[n] = '\0'; } return mangledLen; } } if (folly::demangle_build_has_liberty()) { liberty_demangle_t* funcs[] = { liberty_rust_demangle, liberty_cplus_demangle, }; for (auto func : funcs) { DemangleBuf dbuf; dbuf.dest = out; dbuf.remaining = outSize ? outSize - 1 : 0; // leave room for null term dbuf.total = 0; // Unlike most library functions, this returns 1 on success and 0 on // failure int success = func(name, liberty_demangle_options, demangleBufCallback, &dbuf); if (success) { if (outSize != 0) { *dbuf.dest = '\0'; } return dbuf.total; } } } // fallback - just return original return folly::strlcpy(out, name, outSize); } } // namespace folly ================================================ FILE: folly/Demangle.h ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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 namespace folly { inline constexpr size_t demangle_max_symbol_size = #if defined(FOLLY_DEMANGLE_MAX_SYMBOL_SIZE) FOLLY_DEMANGLE_MAX_SYMBOL_SIZE; #else 0; #endif bool demangle_build_has_cxxabi() noexcept; bool demangle_build_has_liberty() noexcept; /** * Return the demangled (prettified) version of a C++ type. * * This function tries to produce a human-readable type, but the type name will * be returned unchanged in case of error or if demangling isn't supported on * your system. * * Use for debugging -- do not rely on demangle() returning anything useful. * * This function may allocate memory (and therefore throw std::bad_alloc). */ fbstring demangle(const char* name); inline fbstring demangle(const std::type_info& type) { return demangle(type.name()); } /** * Return the demangled (prettified) version of a C++ type in a user-provided * buffer. * * The semantics are the same as for snprintf or strlcpy: bufSize is the size * of the buffer, the string is always null-terminated, and the return value is * the number of characters (not including the null terminator) that would have * been written if the buffer was big enough. (So a return value >= bufSize * indicates that the output was truncated) * * This function does not allocate memory and is async-signal-safe. * * Note that the underlying function for the fbstring-returning demangle is * somewhat standard (abi::__cxa_demangle, which uses malloc), the underlying * function for this version is less so (cplus_demangle_v3_callback from * libiberty), so it is possible for the fbstring version to work, while this * version returns the original, mangled name. */ size_t demangle(const char* name, char* out, size_t outSize); inline size_t demangle(const std::type_info& type, char* buf, size_t bufSize) { return demangle(type.name(), buf, bufSize); } } // namespace folly ================================================ FILE: folly/DiscriminatedPtr.h ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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. */ /** * Discriminated pointer: Type-safe pointer to one of several types. * * Similar to std::variant, but has no space overhead over a raw pointer, as * it relies on the fact that (on x86_64) there are 16 unused bits in a * pointer. */ #pragma once #include #include #include #include #include #include #if !FOLLY_X64 && !FOLLY_AARCH64 && !FOLLY_PPC64 && !FOLLY_RISCV64 #error "DiscriminatedPtr is x64, arm64, ppc64 and riscv64 specific code." #endif namespace folly { /** * Discriminated pointer. * * Given a list of types, a DiscriminatedPtr may point to an object * of one of the given types, or may be empty. DiscriminatedPtr is type-safe: * you may only get a pointer to the type that you put in, otherwise get * throws an exception (and get_nothrow returns nullptr) * * This pointer does not do any kind of lifetime management -- it's not a * "smart" pointer. You are responsible for deallocating any memory used * to hold pointees, if necessary. */ template class DiscriminatedPtr { // <, not <=, as our indexes are 1-based (0 means "empty") static_assert( sizeof...(Types) < std::numeric_limits::max(), "too many types"); public: /** * Create an empty DiscriminatedPtr. */ DiscriminatedPtr() : data_(0) {} /** * Create a DiscriminatedPtr that points to an object of type T. * Fails at compile time if T is not a valid type (listed in Types) */ template explicit DiscriminatedPtr(T* ptr) { set(ptr, typeIndex()); } /** * Set this DiscriminatedPtr to point to an object of type T. * Fails at compile time if T is not a valid type (listed in Types) */ template void set(T* ptr) { set(ptr, typeIndex()); } /** * Get a pointer to the object that this DiscriminatedPtr points to, if it is * of type T. Fails at compile time if T is not a valid type (listed in * Types), and returns nullptr if this DiscriminatedPtr is empty or points to * an object of a different type. */ template T* get_nothrow() noexcept { void* p = FOLLY_LIKELY(hasType()) ? ptr() : nullptr; return static_cast(p); } template const T* get_nothrow() const noexcept { const void* p = FOLLY_LIKELY(hasType()) ? ptr() : nullptr; return static_cast(p); } /** * Get a pointer to the object that this DiscriminatedPtr points to, if it is * of type T. Fails at compile time if T is not a valid type (listed in * Types), and throws std::invalid_argument if this DiscriminatedPtr is empty * or points to an object of a different type. */ template T* get() { if (FOLLY_UNLIKELY(!hasType())) { throw std::invalid_argument("Invalid type"); } return static_cast(ptr()); } template const T* get() const { if (FOLLY_UNLIKELY(!hasType())) { throw std::invalid_argument("Invalid type"); } return static_cast(ptr()); } /** * Return true iff this DiscriminatedPtr is empty. */ bool empty() const { return index() == 0; } /** * Return true iff the object pointed by this DiscriminatedPtr has type T, * false otherwise. Fails at compile time if T is not a valid type (listed * in Types...) */ template bool hasType() const { return index() == typeIndex(); } /** * Clear this DiscriminatedPtr, making it empty. */ void clear() { data_ = 0; } /** * Assignment operator from a pointer of type T. */ template DiscriminatedPtr& operator=(T* ptr) { set(ptr); return *this; } /** * Apply a visitor to this object, calling the appropriate overload for * the type currently stored in DiscriminatedPtr. Throws invalid_argument * if the DiscriminatedPtr is empty. * * The visitor must meet the following requirements: * * - The visitor must allow invocation as a function by overloading * operator(), unambiguously accepting all values of type T* (or const T*) * for all T in Types... * - All operations of the function object on T* (or const T*) must * return the same type (or a static_assert will fire). */ template _t> apply(V&& visitor) { size_t n = index(); if (n == 0) { throw std::invalid_argument("Empty DiscriminatedPtr"); } constexpr dptr_detail::ApplyVisitor call; return call(n, visitor, ptr()); } template _t> apply(V&& visitor) const { size_t n = index(); if (n == 0) { throw std::invalid_argument("Empty DiscriminatedPtr"); } constexpr dptr_detail::ApplyConstVisitor call; return call(n, visitor, ptr()); } /** * Get the 1-based type index of the type currently stored in this pointer. * Returns 0 if the pointer is empty. */ size_t index() const { return data_ >> 48; } private: /** * Get the 1-based type index of T in Types. */ template uint16_t typeIndex() const { constexpr auto idx = type_pack_find_v; static_assert(idx < sizeof...(Types)); return uint16_t(idx + 1); } void* ptr() const { return reinterpret_cast(data_ & ((1ULL << 48) - 1)); } void set(void* p, uint16_t v) { uintptr_t ip = reinterpret_cast(p); CHECK(!(ip >> 48)); ip |= static_cast(v) << 48; data_ = ip; } /** * We store a pointer in the least significant 48 bits of data_, and a type * index (0 = empty, or 1-based index in Types) in the most significant 16 * bits. We rely on the fact that pointers have their most significant 16 * bits clear on x86_64. */ uintptr_t data_; }; template decltype(auto) apply_visitor( Visitor&& visitor, const DiscriminatedPtr& variant) { return variant.apply(std::forward(visitor)); } template decltype(auto) apply_visitor( Visitor&& visitor, DiscriminatedPtr& variant) { return variant.apply(std::forward(visitor)); } template decltype(auto) apply_visitor( Visitor&& visitor, DiscriminatedPtr&& variant) { return variant.apply(std::forward(visitor)); } } // namespace folly ================================================ FILE: folly/DynamicConverter.h ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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 // @shim ================================================ FILE: folly/Exception.h ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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 namespace folly { // Various helpers to throw appropriate std::system_error exceptions from C // library errors (returned in errno, as positive return values (many POSIX // functions), or as negative return values (Linux syscalls)) // // The *Explicit functions take an explicit value for errno. // On linux and similar platforms the value of `errno` is a mixture of // POSIX-`errno`-domain error codes and per-OS extended error codes. So the // most appropriate category to use is `system_category`. // // On Windows `system_category` means codes that can be returned by Win32 API // `GetLastError` and codes from the `errno`-domain must be reported as // `generic_category`. inline const std::error_category& errorCategoryForErrnoDomain() noexcept { if (kIsWindows) { return std::generic_category(); } return std::system_category(); } inline std::system_error makeSystemErrorExplicit(int err, const char* msg) { return std::system_error(err, errorCategoryForErrnoDomain(), msg); } template std::system_error makeSystemErrorExplicit(int err, Args&&... args) { return makeSystemErrorExplicit( err, to(std::forward(args)...).c_str()); } inline std::system_error makeSystemError(const char* msg) { return makeSystemErrorExplicit(errno, msg); } template std::system_error makeSystemError(Args&&... args) { return makeSystemErrorExplicit(errno, std::forward(args)...); } // Helper to throw std::system_error [[noreturn]] inline void throwSystemErrorExplicit(int err, const char* msg) { throw_exception(makeSystemErrorExplicit(err, msg)); } template [[noreturn]] void throwSystemErrorExplicit(int err, Args&&... args) { throw_exception(makeSystemErrorExplicit(err, std::forward(args)...)); } // Helper to throw std::system_error from errno and components of a string template [[noreturn]] void throwSystemError(Args&&... args) { throwSystemErrorExplicit(errno, std::forward(args)...); } // Check a Posix return code (0 on success, error number on error), throw // on error. template void checkPosixError(int err, Args&&... args) { if (FOLLY_UNLIKELY(err != 0)) { throwSystemErrorExplicit(err, std::forward(args)...); } } // Check a Linux kernel-style return code (>= 0 on success, negative error // number on error), throw on error. template void checkKernelError(ssize_t ret, Args&&... args) { if (FOLLY_UNLIKELY(ret < 0)) { throwSystemErrorExplicit(int(-ret), std::forward(args)...); } } // Check a traditional Unix return code (-1 and sets errno on error), throw // on error. template void checkUnixError(ssize_t ret, Args&&... args) { if (FOLLY_UNLIKELY(ret == -1)) { throwSystemError(std::forward(args)...); } } template void checkUnixErrorExplicit(ssize_t ret, int savedErrno, Args&&... args) { if (FOLLY_UNLIKELY(ret == -1)) { throwSystemErrorExplicit(savedErrno, std::forward(args)...); } } // Check the return code from a fopen-style function (returns a non-nullptr // FILE* on success, nullptr on error, sets errno). Works with fopen, fdopen, // freopen, tmpfile, etc. template void checkFopenError(FILE* fp, Args&&... args) { if (FOLLY_UNLIKELY(!fp)) { throwSystemError(std::forward(args)...); } } template void checkFopenErrorExplicit(FILE* fp, int savedErrno, Args&&... args) { if (FOLLY_UNLIKELY(!fp)) { throwSystemErrorExplicit(savedErrno, std::forward(args)...); } } /** * If cond is not true, raise an exception of type E. E must have a ctor that * works with const char* (a description of the failure). */ #define CHECK_THROW(cond, E) \ do { \ if (!(cond)) { \ folly::throw_exception( \ "Check failed: " #cond ", in " __FILE__ \ ":" FOLLY_PP_STRINGIZE_MACRO(__LINE__)); \ } \ } while (0) } // namespace folly ================================================ FILE: folly/ExceptionString.cpp ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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 #include #include #include #include namespace folly { namespace { fbstring exception_string_type(std::type_info const* ti) { return ti ? demangle(*ti) : ""; } } // namespace /** * Debug string for an exception: include type and what(), if * defined. */ fbstring exceptionStr(std::exception const& e) { auto prefix = exception_string_type(folly::type_info_of(e)); return std::move(prefix) + ": " + e.what(); } fbstring exceptionStr(std::exception_ptr const& ep) { if (auto ex = exception_ptr_get_object(ep)) { return exceptionStr(*ex); } return exception_string_type(exception_ptr_get_type(ep)); } } // namespace folly ================================================ FILE: folly/ExceptionString.h ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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 namespace folly { /** * Debug string for an exception: include type and what(), if * defined. */ fbstring exceptionStr(std::exception const& e); fbstring exceptionStr(std::exception_ptr const& ep); } // namespace folly ================================================ FILE: folly/ExceptionWrapper-inl.h ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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 namespace folly { struct exception_wrapper::with_exception_from_fn_ { struct impl_var_ { template using apply = void; }; struct impl_arg_ { template using apply = function_arguments_element_t<0, F>; }; struct impl_bye_; template > using impl_ = conditional_t< function_is_variadic_v, impl_var_, conditional_t>; template using member_ = typename member_pointer_traits::member_type; template struct arg_type_; template struct arg_type_::value>, Sig> { using type = typename impl_::template apply; }; template struct arg_type_::value>, Ptr> : arg_type_> {}; template struct arg_type_, Obj> : arg_type_> {}; // void if Fn is a variadic callable; otherwise the first arg type template using apply = _t>; }; struct exception_wrapper::with_exception_from_ex_ { template using apply = Ex; }; inline exception_wrapper::exception_wrapper(exception_wrapper&& that) noexcept : ptr_{detail::extract_exception_ptr(std::move(that.ptr_))} {} inline exception_wrapper::exception_wrapper( std::exception_ptr const& ptr) noexcept : ptr_{ptr} {} inline exception_wrapper::exception_wrapper(std::exception_ptr&& ptr) noexcept : ptr_{detail::extract_exception_ptr(std::move(ptr))} {} template < class Ex, class Ex_, FOLLY_REQUIRES_DEF( Conjunction< exception_wrapper::IsStdException, exception_wrapper::IsRegularExceptionType>::value)> inline exception_wrapper::exception_wrapper(Ex&& ex) : ptr_{make_exception_ptr_with(std::in_place, std::forward(ex))} {} template < class Ex, class Ex_, FOLLY_REQUIRES_DEF(exception_wrapper::IsRegularExceptionType::value)> inline exception_wrapper::exception_wrapper(std::in_place_t, Ex&& ex) : ptr_{make_exception_ptr_with(std::in_place, std::forward(ex))} {} template < class Ex, typename... As, FOLLY_REQUIRES_DEF(exception_wrapper::IsRegularExceptionType::value)> inline exception_wrapper::exception_wrapper( std::in_place_type_t, As&&... as) : ptr_{make_exception_ptr_with( std::in_place_type, std::forward(as)...)} {} inline exception_wrapper& exception_wrapper::operator=( exception_wrapper&& that) noexcept { // assume relocatability on all platforms constexpr auto sz = sizeof(std::exception_ptr); std::exception_ptr tmp; std::memcpy(static_cast(&tmp), &ptr_, sz); std::memcpy(static_cast(&ptr_), &that.ptr_, sz); std::memset(static_cast(&that.ptr_), 0, sz); return *this; } inline void exception_wrapper::swap(exception_wrapper& that) noexcept { // assume relocatability on all platforms constexpr auto sz = sizeof(std::exception_ptr); aligned_storage_for_t storage; std::memcpy(&storage, &ptr_, sz); std::memcpy(static_cast(&ptr_), &that.ptr_, sz); std::memcpy(static_cast(&that.ptr_), &storage, sz); } inline exception_wrapper::operator bool() const noexcept { return !!ptr_; } inline bool exception_wrapper::operator!() const noexcept { return !ptr_; } inline void exception_wrapper::reset() { ptr_ = {}; } inline bool exception_wrapper::has_exception_ptr() const noexcept { return !!ptr_; } inline std::exception* exception_wrapper::get_mutable_exception() const noexcept { return exception_ptr_get_object(ptr_); } inline std::exception const* exception_wrapper::get_exception() const noexcept { return exception_ptr_get_object(ptr_); } template inline Ex* exception_wrapper::get_mutable_exception() const noexcept { return exception_ptr_get_object_hint(ptr_); } template inline Ex const* exception_wrapper::get_exception() const noexcept { return exception_ptr_get_object_hint(ptr_); } inline std::exception_ptr exception_wrapper::to_exception_ptr() const& noexcept { return ptr_; } inline std::exception_ptr& exception_wrapper::exception_ptr() & noexcept { return ptr_; } inline std::exception_ptr const& exception_wrapper::exception_ptr() const& noexcept { return ptr_; } inline std::exception_ptr&& exception_wrapper::exception_ptr() && noexcept { return std::move(ptr_); } inline std::exception_ptr const&& exception_wrapper::exception_ptr() const&& noexcept { return std::move(ptr_); } inline std::type_info const* exception_wrapper::type() const noexcept { return exception_ptr_get_type(ptr_); } inline folly::fbstring exception_wrapper::what() const { if (auto e = get_exception()) { return class_name() + ": " + e->what(); } return class_name(); } inline folly::fbstring exception_wrapper::class_name() const { auto const* const ti = type(); return !*this ? "" : !ti ? "" : folly::demangle(*ti); } template inline bool exception_wrapper::is_compatible_with() const noexcept { return exception_ptr_get_object(ptr_); } [[noreturn]] inline void exception_wrapper::throw_exception() const { ptr_ ? std::rethrow_exception(ptr_) : onNoExceptionError(__func__); } template [[noreturn]] inline void exception_wrapper::throw_with_nested(Ex&& ex) const { try { throw_exception(); } catch (...) { std::throw_with_nested(std::forward(ex)); } } template inline bool exception_wrapper::with_exception_(This&, Fn fn_, tag_t) { return void(fn_()), true; } template inline bool exception_wrapper::with_exception_(This& this_, Fn fn_, tag_t) { if constexpr (std::is_const_v) { auto ptr = this_.template get_exception>(); return ptr && (void(fn_(static_cast(*ptr))), true); } else { auto ptr = this_.template get_mutable_exception>(); return ptr && (void(fn_(static_cast(*ptr))), true); } } template inline bool exception_wrapper::with_exception_(This& this_, Fn fn_) { using from_fn = with_exception_from_fn_; using from_ex = with_exception_from_ex_; using from = conditional_t::value, from_fn, from_ex>; using type = typename from::template apply; return with_exception_(this_, std::move(fn_), tag); } template inline void exception_wrapper::handle_( This& this_, char const* name, CatchFns&... fns) { if (!this_) { onNoExceptionError(name); } if (!(with_exception_(this_, fns) || ...)) { this_.throw_exception(); } } template inline bool exception_wrapper::with_exception(Fn fn) { return with_exception_(*this, std::move(fn)); } template inline bool exception_wrapper::with_exception(Fn fn) const { return with_exception_(*this, std::move(fn)); } template inline void exception_wrapper::handle(CatchFns... fns) { handle_(*this, __func__, fns...); } template inline void exception_wrapper::handle(CatchFns... fns) const { handle_(*this, __func__, fns...); } } // namespace folly ================================================ FILE: folly/ExceptionWrapper.cpp ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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 #include namespace folly { [[noreturn]] void exception_wrapper::onNoExceptionError( char const* const name) { std::ios_base::Init ioinit_; // ensure std::cerr is alive std::cerr << "Cannot use `" << name << "` with an empty folly::exception_wrapper" << std::endl; std::terminate(); } fbstring exceptionStr(exception_wrapper const& ew) { return ew.what(); } } // namespace folly ================================================ FILE: folly/ExceptionWrapper.h ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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 #include #include #include #include #include #include #include #include #include #include #include #include namespace folly { #define FOLLY_REQUIRES_DEF(...) \ std::enable_if_t(__VA_ARGS__), long> #define FOLLY_REQUIRES(...) FOLLY_REQUIRES_DEF(__VA_ARGS__) = __LINE__ //! Throwing exceptions can be a convenient way to handle errors. Storing //! exceptions in an `exception_ptr` makes it easy to handle exceptions in a //! different thread or at a later time. `exception_ptr` can also be used in a //! very generic result/exception wrapper. //! //! However, inspecting exceptions through the `exception_ptr` interface, namely //! through `rethrow_exception`, is expensive. This is a wrapper interface which //! offers faster inspection. //! //! \par Example usage: //! \code //! exception_wrapper globalExceptionWrapper; //! //! // Thread1 //! void doSomethingCrazy() { //! int rc = doSomethingCrazyWithLameReturnCodes(); //! if (rc == NAILED_IT) { //! globalExceptionWrapper = exception_wrapper(); //! } else if (rc == FACE_PLANT) { //! globalExceptionWrapper = make_exception_wrapper(); //! } else if (rc == FAIL_WHALE) { //! globalExceptionWrapper = make_exception_wrapper(); //! } //! } //! //! // Thread2: Exceptions are ok! //! void processResult() { //! try { //! globalExceptionWrapper.throw_exception(); //! } catch (const FacePlantException& e) { //! LOG(ERROR) << "FACEPLANT!"; //! } catch (const FailWhaleException& e) { //! LOG(ERROR) << "FAILWHALE!"; //! } //! } //! //! // Thread2: Exceptions are bad! //! void processResult() { //! globalExceptionWrapper.handle( //! [&](FacePlantException& faceplant) { //! LOG(ERROR) << "FACEPLANT"; //! }, //! [&](FailWhaleException& failwhale) { //! LOG(ERROR) << "FAILWHALE!"; //! }, //! [](...) { //! LOG(FATAL) << "Unrecognized exception"; //! }); //! } //! \endcode class exception_wrapper final { private: struct with_exception_from_fn_; struct with_exception_from_ex_; [[noreturn]] static void onNoExceptionError(char const* name); template using IsStdException = std::is_base_of>; std::exception_ptr ptr_; template struct IsRegularExceptionType : StrictConjunction< std::is_copy_constructible, Negation>, Negation>> {}; template static bool with_exception_(This& this_, Fn fn_, tag_t); template static bool with_exception_(This& this_, Fn fn_, tag_t); template static bool with_exception_(This& this_, Fn fn_); template static void handle_(This& this_, char const* name, CatchFns&... fns); public: //! Default-constructs an empty `exception_wrapper` //! \post `type() == nullptr` exception_wrapper() noexcept {} //! Move-constructs an `exception_wrapper` //! \post `*this` contains the value of `that` prior to the move //! \post `that.type() == nullptr` exception_wrapper(exception_wrapper&& that) noexcept; //! Copy-constructs an `exception_wrapper` //! \post `*this` contains a copy of `that`, and `that` is unmodified //! \post `type() == that.type()` exception_wrapper(exception_wrapper const& that) = default; //! Move-assigns an `exception_wrapper` //! \pre `this != &that` //! \post `*this` contains the value of `that` prior to the move //! \post `that.type() == nullptr` exception_wrapper& operator=(exception_wrapper&& that) noexcept; //! Copy-assigns an `exception_wrapper` //! \post `*this` contains a copy of `that`, and `that` is unmodified //! \post `type() == that.type()` exception_wrapper& operator=(exception_wrapper const& that) = default; //! \post `!ptr || bool(*this)` explicit exception_wrapper(std::exception_ptr const& ptr) noexcept; explicit exception_wrapper(std::exception_ptr&& ptr) noexcept; //! \pre `typeid(ex) == typeid(typename decay::type)` //! \post `bool(*this)` //! \post `type() == &typeid(ex)` //! \note Exceptions of types derived from `std::exception` can be implicitly //! converted to an `exception_wrapper`. template < class Ex, class Ex_ = std::decay_t, FOLLY_REQUIRES( Conjunction, IsRegularExceptionType>::value)> /* implicit */ exception_wrapper(Ex&& ex); //! \pre `typeid(ex) == typeid(typename decay::type)` //! \post `bool(*this)` //! \post `type() == &typeid(ex)` //! \note Exceptions of types not derived from `std::exception` can still be //! used to construct an `exception_wrapper`, but you must specify //! `std::in_place` as the first parameter. template < class Ex, class Ex_ = std::decay_t, FOLLY_REQUIRES(IsRegularExceptionType::value)> exception_wrapper(std::in_place_t, Ex&& ex); template < class Ex, typename... As, FOLLY_REQUIRES(IsRegularExceptionType::value)> exception_wrapper(std::in_place_type_t, As&&... as); //! Swaps the value of `*this` with the value of `that` void swap(exception_wrapper& that) noexcept; //! \return `true` if `*this` is holding an exception. explicit operator bool() const noexcept; //! \return `!bool(*this)` bool operator!() const noexcept; //! Make this `exception_wrapper` empty //! \post `!*this` void reset(); //! \return `true` if this `exception_wrapper` holds an exception. bool has_exception_ptr() const noexcept; //! \return a pointer to the `std::exception` held by `*this`, if it holds //! one; otherwise, returns `nullptr`. std::exception* get_mutable_exception() const noexcept; std::exception const* get_exception() const noexcept; //! \returns a pointer to the `Ex` held by `*this`, if it holds an object //! whose type `From` permits `std::is_convertible`; //! otherwise, returns `nullptr`. //! //! `folly::get_exception(ew)` is identical, but avoids the "dependent //! template" wart, and supports inspecting other types. //! //! This is most efficient when `Ex` matches the exact stored type, or when //! the type alias `Ex::folly_get_exception_hint_types` has a good hint. //! \overload template Ex* get_mutable_exception() const noexcept; template Ex const* get_exception() const noexcept; //! \return A `std::exception_ptr` that references the exception held by //! `*this`. std::exception_ptr to_exception_ptr() const& noexcept; // NB: Can add this back, if a good use-case arises. std::exception_ptr to_exception_ptr() && = delete; std::exception_ptr& exception_ptr() & noexcept; std::exception_ptr const& exception_ptr() const& noexcept; std::exception_ptr&& exception_ptr() && noexcept; std::exception_ptr const&& exception_ptr() const&& noexcept; //! \return `true` if the wrappers point to the same exception object friend inline bool operator==( exception_wrapper const& lhs, exception_wrapper const& rhs) noexcept { return lhs.ptr_ == rhs.ptr_; } //! Returns the `typeid` of the wrapped exception object. If there is no //! wrapped exception object, returns `nullptr`. std::type_info const* type() const noexcept; //! \return If `get_exception() != nullptr`, `class_name() + ": " + //! get_exception()->what()`; otherwise, `class_name()`. folly::fbstring what() const; //! \return If `!*this`, the empty string; otherwise, if `!type()`, text that //! is not a class name; otherwise, the demangling of `type()->name()`. folly::fbstring class_name() const; //! \tparam Ex The expression type to check for compatibility with. //! \return `true` if and only if `*this` wraps an exception that would be //! caught with a `catch(Ex const&)` clause. //! \note If `*this` is empty, this function returns `false`. template bool is_compatible_with() const noexcept; //! Throws the wrapped expression. //! \pre `bool(*this)` [[noreturn]] void throw_exception() const; //! Terminates the process with the wrapped expression. [[noreturn]] void terminate_with() const noexcept { throw_exception(); } //! Throws the wrapped expression nested into another exception. //! \pre `bool(*this)` //! \param ex Exception in *this will be thrown nested into ex; // see std::throw_with_nested() for details on this semantic. template [[noreturn]] void throw_with_nested(Ex&& ex) const; //! Call `fn` with the wrapped exception (if any), if `fn` can accept it. //! \par Example //! \code //! exception_wrapper ew{std::runtime_error("goodbye cruel world")}; //! //! assert( ew.with_exception([](std::runtime_error& e){/*...*/}) ); //! //! assert( !ew.with_exception([](int& e){/*...*/}) ); //! //! assert( !exception_wrapper{}.with_exception([](int& e){/*...*/}) ); //! \endcode //! \tparam Ex Optionally, the type of the exception that `fn` accepts. //! \tparam Fn The type of a monomorphic function object. //! \param fn A function object to call with the wrapped exception //! \return `true` if and only if `fn` was called. //! \note Optionally, you may explicitly specify the type of the exception //! that `fn` expects, as in //! \code //! ew.with_exception([](auto&& e) { /*...*/; }); //! \endcode //! \note The handler is not invoked with an active exception. //! **Do not try to rethrow the exception with `throw;` from within your //! handler -- that is, a throw expression with no operand.** This may //! cause your process to terminate. (It is perfectly ok to throw from //! a handler so long as you specify the exception to throw, as in //! `throw e;`.) template bool with_exception(Fn fn); //! \overload template bool with_exception(Fn fn) const; //! Handle the wrapped expression as if with a series of `catch` clauses, //! propagating the exception if no handler matches. //! \par Example //! \code //! exception_wrapper ew{std::runtime_error("goodbye cruel world")}; //! //! ew.handle( //! [&](std::logic_error const& e) { //! LOG(DFATAL) << "ruh roh"; //! ew.throw_exception(); // rethrow the active exception without //! // slicing it. Will not be caught by other //! // handlers in this call. //! }, //! [&](std::exception const& e) { //! LOG(ERROR) << ew.what(); //! }); //! \endcode //! In the above example, any exception _not_ derived from `std::exception` //! will be propagated. To specify a catch-all clause, pass a lambda that //! takes a C-style ellipses, as in: //! \code //! ew.handle(/*...* /, [](...) { /* handle unknown exception */ } ) //! \endcode //! \pre `!*this` //! \tparam CatchFns A pack of unary monomorphic function object types. //! \param fns A pack of unary monomorphic function objects to be treated as //! an ordered list of potential exception handlers. //! \note The handlers are not invoked with an active exception. //! **Do not try to rethrow the exception with `throw;` from within your //! handler -- that is, a throw expression with no operand.** This may //! cause your process to terminate. (It is perfectly ok to throw from //! a handler so long as you specify the exception to throw, as in //! `throw e;`.) template void handle(CatchFns... fns); //! \overload template void handle(CatchFns... fns) const; // Implement the `folly::get_exception(ew)` protocol template Ex const* get_exception(get_exception_tag_t) const noexcept { return get_exception(); } template Ex* get_mutable_exception(get_exception_tag_t) const noexcept { return get_mutable_exception(); } }; /** * \return An `exception_wrapper` that wraps an instance of type `Ex` * that has been constructed with arguments `std::forward(as)...`. */ template exception_wrapper make_exception_wrapper(As&&... as) { return exception_wrapper{std::in_place_type, std::forward(as)...}; } /** * Inserts `ew.what()` into the ostream `sout`. * \return `sout` */ template std::basic_ostream& operator<<( std::basic_ostream& sout, exception_wrapper const& ew) { sout << ew.class_name(); if (auto e = ew.get_exception()) { sout << ": " << e->what(); } return sout; } /** * Swaps the value of `a` with the value of `b`. */ inline void swap(exception_wrapper& a, exception_wrapper& b) noexcept { a.swap(b); } // For consistency with exceptionStr() functions in ExceptionString.h fbstring exceptionStr(exception_wrapper const& ew); //! `try_and_catch` is a convenience for `try {} catch(...) {}`` that returns an //! `exception_wrapper` with the thrown exception, if any. template exception_wrapper try_and_catch(F&& fn) noexcept { auto x = [&] { return void(static_cast(fn)()), std::exception_ptr{}; }; return exception_wrapper{catch_exception(x, current_exception)}; } /** * A convenience shorthand for `exception_wrapper(current_exception())`. */ inline exception_wrapper current_exception_wrapper() noexcept { return exception_wrapper{current_exception()}; } } // namespace folly template <> struct fmt::formatter : formatter { template auto format(const folly::exception_wrapper& ew, Context& ctx) const { return formatter::format(ew.what(), ctx); } }; #include #undef FOLLY_REQUIRES #undef FOLLY_REQUIRES_DEF ================================================ FILE: folly/Executor.cpp ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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 #include #include #include #include #include namespace folly { void Executor::invokeCatchingExnsLog(char const* const prefix) noexcept { auto ep = current_exception(); LOG(ERROR) << prefix << " threw unhandled " << exceptionStr(ep); } void Executor::addWithPriority(Func, int8_t /* priority */) { throw std::runtime_error( "addWithPriority() is not implemented for this Executor"); } bool Executor::keepAliveAcquire() noexcept { return false; } void Executor::keepAliveRelease() noexcept { LOG(FATAL) << __func__ << "() should not be called for folly::Executor types " << "which do not override keepAliveAcquire()"; } // Base case of permitting with no termination to avoid nullptr tests static ExecutorBlockingList emptyList{nullptr, {false, false, nullptr, {}}}; thread_local ExecutorBlockingList* executor_blocking_list = &emptyList; Optional getExecutorBlockingContext() noexcept { return // kIsMobile || !executor_blocking_list->curr.forbid ? none : // make_optional(executor_blocking_list->curr); } ExecutorBlockingGuard::ExecutorBlockingGuard(PermitTag) noexcept { if (!kIsMobile) { list_ = *executor_blocking_list; list_.prev = executor_blocking_list; list_.curr.forbid = false; // Do not overwrite tag or executor pointer executor_blocking_list = &list_; } } ExecutorBlockingGuard::ExecutorBlockingGuard( TrackTag, Executor* ex, StringPiece tag) noexcept { if (!kIsMobile) { list_ = *executor_blocking_list; list_.prev = executor_blocking_list; list_.curr.forbid = true; list_.curr.ex = ex; // If no string was provided, maintain the parent string to keep some // information if (!tag.empty()) { list_.curr.tag = tag; } executor_blocking_list = &list_; } } ExecutorBlockingGuard::ExecutorBlockingGuard( ProhibitTag, Executor* ex, StringPiece tag) noexcept { if (!kIsMobile) { list_ = *executor_blocking_list; list_.prev = executor_blocking_list; list_.curr.forbid = true; list_.curr.ex = ex; list_.curr.allowTerminationOnBlocking = true; // If no string was provided, maintain the parent string to keep some // information if (!tag.empty()) { list_.curr.tag = tag; } executor_blocking_list = &list_; } } ExecutorBlockingGuard::~ExecutorBlockingGuard() { if (!kIsMobile) { if (executor_blocking_list != &list_) { terminate_with("dtor mismatch"); } executor_blocking_list = list_.prev; } } } // namespace folly ================================================ FILE: folly/Executor.h ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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 namespace folly { using Func = Function; namespace detail { class ExecutorKeepAliveBase { public: // A dummy keep-alive is a keep-alive to an executor which does not support // the keep-alive mechanism. static constexpr uintptr_t kDummyFlag = uintptr_t(1) << 0; // An alias keep-alive is a keep-alive to an executor to which there is // known to be another keep-alive whose lifetime surrounds the lifetime of // the alias. static constexpr uintptr_t kAliasFlag = uintptr_t(1) << 1; static constexpr uintptr_t kFlagMask = kDummyFlag | kAliasFlag; static constexpr uintptr_t kExecutorMask = ~kFlagMask; }; } // namespace detail class Executor; /** * `ExecutorKeepAlive` is a safe pointer to an `Executor`. * * For any `Executor` that supports keep-alive functionality, its destructor * will block until all the `ExecutorKeepAlive` objects associated with that * executor are destroyed. For executors that don't support the keep-alive * functionality, `ExecutorKeepAlive` doesn't provide such protection. * * `ExecutorKeepAlive` should *always* be used instead of `Executor*`. * `ExecutorKeepAlive` can be implicitly constructed from `Executor*`. * * The `getKeepAliveToken()` helper can be used to construct a keep-alive in * templated code if you need to preserve the original executor type. */ template class ExecutorKeepAlive : private detail::ExecutorKeepAliveBase { public: using KeepAliveFunc = Function; ExecutorKeepAlive() = default; ~ExecutorKeepAlive() { static_assert( std::is_standard_layout::value, "standard-layout"); static_assert(sizeof(ExecutorKeepAlive) == sizeof(void*), "pointer size"); static_assert( alignof(ExecutorKeepAlive) == alignof(void*), "pointer align"); reset(); } ExecutorKeepAlive(ExecutorKeepAlive&& other) noexcept : storage_(std::exchange(other.storage_, 0)) {} ExecutorKeepAlive(const ExecutorKeepAlive& other) noexcept; template < typename OtherExecutor, typename = typename std::enable_if< std::is_convertible::value>::type> /* implicit */ ExecutorKeepAlive( ExecutorKeepAlive&& other) noexcept : ExecutorKeepAlive(other.get(), other.storage_ & kFlagMask) { other.storage_ = 0; } template < typename OtherExecutor, typename = typename std::enable_if< std::is_convertible::value>::type> /* implicit */ ExecutorKeepAlive( const ExecutorKeepAlive& other) noexcept; /* implicit */ ExecutorKeepAlive(ExecutorT* executor); ExecutorKeepAlive& operator=(ExecutorKeepAlive&& other) noexcept { reset(); storage_ = std::exchange(other.storage_, 0); return *this; } ExecutorKeepAlive& operator=(ExecutorKeepAlive const& other) { return operator=(folly::copy(other)); } template < typename OtherExecutor, typename = typename std::enable_if< std::is_convertible::value>::type> ExecutorKeepAlive& operator=( ExecutorKeepAlive&& other) noexcept { return *this = ExecutorKeepAlive(std::move(other)); } template < typename OtherExecutor, typename = typename std::enable_if< std::is_convertible::value>::type> ExecutorKeepAlive& operator=(const ExecutorKeepAlive& other) { return *this = ExecutorKeepAlive(other); } void reset() noexcept; explicit operator bool() const { return storage_; } ExecutorT* get() const { return reinterpret_cast(storage_ & kExecutorMask); } ExecutorT& operator*() const { return *get(); } ExecutorT* operator->() const { return get(); } ExecutorKeepAlive copy() const; ExecutorKeepAlive get_alias() const { return ExecutorKeepAlive(storage_ | kAliasFlag); } template void add(KAF&& f) && { static_assert( is_invocable::value, "Parameter to add must be void(ExecutorKeepAlive&&)>"); auto ex = get(); ex->add([ka = std::move(*this), f_2 = std::forward(f)]() mutable { f_2(std::move(ka)); }); } private: friend class Executor; template friend class ExecutorKeepAlive; ExecutorKeepAlive(ExecutorT* executor, uintptr_t flags) noexcept : storage_(reinterpret_cast(executor) | flags) { assert(executor); assert(!(reinterpret_cast(executor) & ~kExecutorMask)); assert(!(flags & kExecutorMask)); } explicit ExecutorKeepAlive(uintptr_t storage) noexcept : storage_(storage) {} // Combined storage for the executor pointer and for all flags. uintptr_t storage_{reinterpret_cast(nullptr)}; }; /// An Executor accepts units of work with add(), which should be /// threadsafe. class Executor { public: virtual ~Executor() = default; /// Enqueue a function to be executed by this executor. This and all /// variants must be threadsafe. virtual void add(Func) = 0; /// Enqueue a function with a given priority, where 0 is the medium priority /// This is up to the implementation to enforce virtual void addWithPriority(Func, int8_t priority); virtual uint8_t getNumPriorities() const { return 1; } static constexpr int8_t LO_PRI = SCHAR_MIN; static constexpr int8_t MID_PRI = 0; static constexpr int8_t HI_PRI = SCHAR_MAX; // Compatibility shim. Cannot be forward-declared, unlike // `ExecutorKeepAlive`. template using KeepAlive = ExecutorKeepAlive; template static KeepAlive getKeepAliveToken(ExecutorT* executor) { static_assert( std::is_base_of::value, "getKeepAliveToken only works for folly::Executor implementations."); if (!executor) { return {}; } folly::Executor* executorPtr = executor; if (executorPtr->keepAliveAcquire()) { return makeKeepAlive(executor); } return makeKeepAliveDummy(executor); } template static KeepAlive getKeepAliveToken(ExecutorT& executor) { static_assert( std::is_base_of::value, "getKeepAliveToken only works for folly::Executor implementations."); return getKeepAliveToken(&executor); } template FOLLY_ERASE static void invokeCatchingExns(char const* p, F f) noexcept { catch_exception(f, invokeCatchingExnsLog, p); } protected: template friend class ExecutorKeepAlive; /** * Returns true if the KeepAlive is constructed from an executor that does * not support the keep alive ref-counting functionality */ template static bool isKeepAliveDummy(const KeepAlive& keepAlive) { return keepAlive.storage_ & KeepAlive::kDummyFlag; } static bool keepAliveAcquire(Executor* executor) { return executor->keepAliveAcquire(); } static void keepAliveRelease(Executor* executor) { return executor->keepAliveRelease(); } // Acquire a keep alive token. Should return false if keep-alive mechanism // is not supported. virtual bool keepAliveAcquire() noexcept; // Release a keep alive token previously acquired by keepAliveAcquire(). // Will never be called if keepAliveAcquire() returns false. virtual void keepAliveRelease() noexcept; template static KeepAlive makeKeepAlive(ExecutorT* executor) { static_assert( std::is_base_of::value, "makeKeepAlive only works for folly::Executor implementations."); return KeepAlive{executor, uintptr_t(0)}; } private: static void invokeCatchingExnsLog(char const* prefix) noexcept; template static KeepAlive makeKeepAliveDummy(ExecutorT* executor) { static_assert( std::is_base_of::value, "makeKeepAliveDummy only works for folly::Executor implementations."); return KeepAlive{executor, KeepAlive::kDummyFlag}; } }; template ExecutorKeepAlive::ExecutorKeepAlive( const ExecutorKeepAlive& other) noexcept : ExecutorKeepAlive(Executor::getKeepAliveToken(other.get())) {} template template ExecutorKeepAlive::ExecutorKeepAlive( const ExecutorKeepAlive& other) noexcept : ExecutorKeepAlive(Executor::getKeepAliveToken(other.get())) {} template ExecutorKeepAlive::ExecutorKeepAlive(ExecutorT* executor) { *this = Executor::getKeepAliveToken(executor); } template void ExecutorKeepAlive::reset() noexcept { if (Executor* executor = get()) { auto const flags = std::exchange(storage_, 0) & kFlagMask; if (!(flags & (kDummyFlag | kAliasFlag))) { executor->keepAliveRelease(); } } } template ExecutorKeepAlive ExecutorKeepAlive::copy() const { return Executor::isKeepAliveDummy(*this) // ? Executor::makeKeepAliveDummy(get()) : Executor::getKeepAliveToken(get()); } /// Returns a keep-alive token which guarantees that Executor will keep /// processing tasks until the token is released (if supported by Executor). /// KeepAlive always contains a valid pointer to an Executor. template Executor::KeepAlive getKeepAliveToken(ExecutorT* executor) { static_assert( std::is_base_of::value, "getKeepAliveToken only works for folly::Executor implementations."); return Executor::getKeepAliveToken(executor); } template Executor::KeepAlive getKeepAliveToken(ExecutorT& executor) { static_assert( std::is_base_of::value, "getKeepAliveToken only works for folly::Executor implementations."); return getKeepAliveToken(&executor); } template Executor::KeepAlive getKeepAliveToken( Executor::KeepAlive& ka) { return ka.copy(); } struct ExecutorBlockingContext { bool forbid; bool allowTerminationOnBlocking; Executor* ex = nullptr; StringPiece tag; }; static_assert( std::is_standard_layout::value, "non-standard layout"); struct ExecutorBlockingList { ExecutorBlockingList* prev; ExecutorBlockingContext curr; }; static_assert( std::is_standard_layout::value, "non-standard layout"); class ExecutorBlockingGuard { public: struct PermitTag {}; struct TrackTag {}; struct ProhibitTag {}; ~ExecutorBlockingGuard(); ExecutorBlockingGuard() = delete; explicit ExecutorBlockingGuard(PermitTag) noexcept; explicit ExecutorBlockingGuard( TrackTag, Executor* ex, StringPiece tag) noexcept; explicit ExecutorBlockingGuard( ProhibitTag, Executor* ex, StringPiece tag) noexcept; ExecutorBlockingGuard(ExecutorBlockingGuard&&) = delete; ExecutorBlockingGuard(ExecutorBlockingGuard const&) = delete; ExecutorBlockingGuard& operator=(ExecutorBlockingGuard const&) = delete; ExecutorBlockingGuard& operator=(ExecutorBlockingGuard&&) = delete; private: ExecutorBlockingList list_; }; Optional getExecutorBlockingContext() noexcept; } // namespace folly ================================================ FILE: folly/Expected.h ================================================ /* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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. */ /** * Like folly::Optional, but can store a value *or* an error. */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define FOLLY_EXPECTED_ID(X) FB_CONCATENATE(FB_CONCATENATE(Folly, X), __LINE__) #define FOLLY_REQUIRES_IMPL(...) \ bool FOLLY_EXPECTED_ID(Requires) = false, \ typename std::enable_if< \ (FOLLY_EXPECTED_ID(Requires) || static_cast(__VA_ARGS__)), \ int>::type = 0 #define FOLLY_REQUIRES_TRAILING(...) , FOLLY_REQUIRES_IMPL(__VA_ARGS__) #define FOLLY_REQUIRES(...) template namespace folly { namespace expected_detail { namespace expected_detail_ExpectedHelper { struct ExpectedHelper; } /* using override */ using expected_detail_ExpectedHelper::ExpectedHelper; } // namespace expected_detail /** * Unexpected - a helper type used to disambiguate the construction of * Expected objects in the error state. */ template class [[nodiscard]] Unexpected final { template friend class Unexpected; template friend class Expected; friend struct expected_detail::ExpectedHelper; public: /** * Constructors */ Unexpected() = default; Unexpected(const Unexpected&) = default; Unexpected(Unexpected&&) = default; Unexpected& operator=(const Unexpected&) = default; Unexpected& operator=(Unexpected&&) = default; [[FOLLY_ATTR_GNU_COLD]] constexpr /* implicit */ Unexpected(const Error& err) : error_(err) {} [[FOLLY_ATTR_GNU_COLD]] constexpr /* implicit */ Unexpected(Error&& err) : error_(std::move(err)) {} template ::value)> constexpr /* implicit */ Unexpected(Unexpected that) : error_(std::move(that.error())) {} /** * Assignment */ template ::value)> Unexpected& operator=(Unexpected that) { error_ = std::move(that.error()); } /** * Observers */ Error& error() & noexcept { return error_; } const Error& error() const& noexcept { return error_; } Error&& error() && noexcept { return std::move(error_); } const Error&& error() const&& noexcept { return std::move(error_); } private: Error error_; }; template < class Error FOLLY_REQUIRES_TRAILING(IsEqualityComparable::value)> inline bool operator==( const Unexpected& lhs, const Unexpected& rhs) { return lhs.error() == rhs.error(); } template < class Error FOLLY_REQUIRES_TRAILING(IsEqualityComparable::value)> inline bool operator!=( const Unexpected& lhs, const Unexpected& rhs) { return !(lhs == rhs); } /** * For constructing an Unexpected object from an error code. Unexpected objects * are implicitly convertible to Expected object in the error state. Usage is * as follows: * * enum class MyErrorCode { BAD_ERROR, WORSE_ERROR }; * Expected myAPI() { * int i = // ...; * return i ? makeExpected(i) * : makeUnexpected(MyErrorCode::BAD_ERROR); * } */ template constexpr Unexpected::type> makeUnexpected( Error&& err) { return Unexpected::type>{ static_cast(err)}; } template class FOLLY_EXPORT BadExpectedAccess; /** * A common base type for exceptions thrown by Expected when the caller * erroneously requests a value. */ template <> class FOLLY_EXPORT BadExpectedAccess : public std::exception { public: explicit BadExpectedAccess() noexcept = default; BadExpectedAccess(BadExpectedAccess const&) {} BadExpectedAccess& operator=(BadExpectedAccess const&) { return *this; } char const* what() const noexcept override { return "bad expected access"; } }; /** * An exception type thrown by Expected on catastrophic logic errors, i.e., when * the caller tries to access the value within an Expected but when the Expected * instead contains an error. */ template class FOLLY_EXPORT BadExpectedAccess : public BadExpectedAccess { public: explicit BadExpectedAccess(Error error) : error_{static_cast(error)} {} /** * The error code that was held by the Expected object when the caller * erroneously requested the value. */ Error& error() & { return error_; } Error const& error() const& { return error_; } Error&& error() && { return static_cast(error_); } Error const&& error() const&& { return static_cast(error_); } private: Error error_; }; /** * Forward declarations */ template class Expected; template [[nodiscard]] constexpr Expected::type, Error> makeExpected(Value&&); /** * Alias for an Expected type's associated value_type */ template using ExpectedValueType = typename std::remove_reference::type::value_type; /** * Alias for an Expected type's associated error_type */ template using ExpectedErrorType = typename std::remove_reference::type::error_type; // Details... namespace expected_detail { template struct Promise; template struct PromiseReturn; template