Make `gn args --list` deterministic This fixes Args::GetAllArguments() to return deterministic results, even when declare_args() are used in args.gn or some of its imported files. While this is ugly, this is used by the Fuchsia build in order to override variables in third-party .gni files. This also creates two Settings instance that return `is_default() == true`: - The first is just the regular default toolchain. - The second one has an empty label, and is used by the scope created when parsing the args.gn file. The std::sort() comparator assumed there was only one default toolchain, this resulted in practice in non-deterministic outputs for the list of arguments. This CL fixes the issue by ensuring the comparator places the empty toolchain first, then the default one second, followed by all others sorted by their label. Bug: 327 Change-Id: I342d285a15339572adf72c2ddb29bbd81b814d10 Reviewed-on: https://gn-review.googlesource.com/c/gn/+/15160 Commit-Queue: David Turner <digit@google.com> Reviewed-by: Brett Wilson <brettw@chromium.org>
diff --git a/src/gn/args.cc b/src/gn/args.cc index fbc0ee4..3b765f8 100644 --- a/src/gn/args.cc +++ b/src/gn/args.cc
@@ -268,25 +268,31 @@ std::lock_guard<std::mutex> lock(lock_); - // Sort the keys from declared_arguments_per_toolchain_ so + // Sort the toolchains from declared_arguments_per_toolchain_ so // the return value will be deterministic. Always prioritize // the default toolchain. - std::vector<const Settings*> keys; - keys.reserve(declared_arguments_per_toolchain_.size()); + std::vector<const Settings*> toolchains; + toolchains.reserve(declared_arguments_per_toolchain_.size()); for (const auto& map_pair : declared_arguments_per_toolchain_) { - keys.push_back(map_pair.first); + toolchains.push_back(map_pair.first); } - std::sort(keys.begin(), keys.end(), + std::sort(toolchains.begin(), toolchains.end(), [](const Settings* a, const Settings* b) -> bool { - return a->is_default() || - a->toolchain_label() < b->toolchain_label(); + // NOTE: There can be multiple default toolchains in the map! + // which happens when declare_args() blocks are found in args.gn + // or some of its imports. This uses a Settings instance with + // an empty label, where `is_default()` returns true. + if (a->is_default() != b->is_default()) + return a->is_default(); + return a->toolchain_label() < b->toolchain_label(); }); // Default values. - for (const auto& key : keys) { - const auto& value = declared_arguments_per_toolchain_[key]; - for (const auto& arg : value) + for (const auto& toolchain : toolchains) { + const auto& value_map = declared_arguments_per_toolchain_[toolchain]; + for (const auto& arg : value_map) { result.insert(std::make_pair(arg.first, ValueWithOverride(arg.second))); + } } // Merge in overrides.